diff --git a/ROADMAP.md b/ROADMAP.md index 939b069d7..e5f7db9db 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **Stage: alpha** · next: beta, ~26 points and ~162–292 h away (one Opus 5.5 agent; ~33–67 h wall-clock with 5 agents) -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (in the browser an internal error that stops the page keeps its unsaved files, which open again on reload (#1123); boolean operations refuse input past their caps, and a boolean group past them draws without its result (#1104); figcraft-cli parses its arguments with clap, following RFC-0012 (#1396); the selection and hover outlines of rounded rectangles and frames follow their corners (#1506); component properties keep the order they were made in when one is renamed, and Make slot asks for the slot's name first (#1536, #1537); the browser build publishes files to a server, so Share ▸ Online and Publish to server work there (#917); the browser build uses the fonts installed on the computer once asked, each read when text uses it (#1051); SVG import reads `color()` and `color-mix()` colours, with a note and a grey for those it can't (#1550); the portable Windows zip keeps its settings, recovery copies and plugins in `config\` beside the executable, starting with an installed copy's preferences; the window starts on other graphics when its first ones fail before the first frame: Direct3D 12 compiles with FXC and no device compiles wgpu's indirect-validation shaders, then Vulkan, OpenGL and lower requirements on the power-saving GPU (#965, #1438); Thai, Lao, Khmer and Burmese lines break between words (#1219); an AI agent attached over MCP shows where it works (#1416); a manual overlay's position can be dragged on the canvas (#405); one name for live sessions and comments: Preferences ▸ Your name, in the Join dialog too, kept in the browser, and a rename shows at once in the sessions you are in (#1380); requests that change a server's files have a budget per address, the server key's too, and join codes are found by their hash (#1075); the Join dialog shows a join from the click until its file is ready, with Cancel, and a failed join with Try again (#1381); requests that change a server's files have a budget per address, the server key's too, and join codes are found by their hash (#1075); SVG import draws each pattern tile once per scale, within the layer budget (#1080); Vietnamese letters typed with combining marks (decomposed) take one font that has every mark, instead of missing-glyph boxes beside the letter (#1478); comment.list reports each message's @mentions (#1415); mask groups draw only over the area they can paint, and nested isolated layers share one memory budget (#1061); components can't contain their own instances, directly or through other components; `edit.duplicate` returns an id map with `map: true` (#995); the batches kept for reconnecting apps are bounded in bytes by default, and `figcraft-server` takes `max_log_mb` (#1069); MCP and control calls name their document (#998); sibling placement in live sessions searches instead of counting (#1065); an AI agent attached over MCP comments under its own name, with an Agent label (#1414); the instance-swap menus have a search field (#1338); a refused batch's fixes take time linear in the batch (#1063); the connector tool shows the connector it will make while dragging, ends snapping to attach points (#1387); large images on the canvas draw from a smaller copy, so two 16k images no longer lag (#1483); a file's JPEG thumbnail can't crash the home screen (#1491's progressive JPEG check, now shared); `.fig` import: library text styles linked by asset key, and override rows restating a nested level (#1234); `.fig` import keeps the file's saved line breaks while the text and its box are unedited (#1236); Download a missing free font from the Missing fonts window (`font.download`, #754); the web build's wasm back under its 24 MiB gate (#1472); shadows of images with transparent pixels follow their alpha (#1289); Crop image: Object ▸ Crop image, the right-click menu and the image fill row enter crop mode (`image.crop`, #1464); offline Penpot 2.x import/export (#1143); optional batch continue-on-error with per-step outcomes (#1386); the Design panel's sections on one grid (#858); FigJam outer shape outlines and sticky body fallback beside author text (#1341, #1397); images placed at their own size, 2x screenshots in points (#1360); the toolbar on one 32 pt grid, without its Resources button (#1210); M4: zero-length round-capped dashes draw dots; dash expansion uses the existing safety budget; extracted from #1143; Arabic draft catalog; scaled instances keep their scale when their main is rebuilt (#922); .fig embedded images are copied once per blob, not once per hash (#1059); page ruler guides survive save and reopen (#1476); colour picker LCH and OKLCH models; non-ASCII layer names transliterate in code export (#1235); branches in live sessions and branch history entries, gap 17; plugin widgets take text input and a property menu (plugin API 4); boards: templates and tables; boards: a shared timer and voting sessions; boards: the dot grid, connector ends dragged to re-attach, curved connectors; Installed weights of an embedded font family (#1246); `.sketch` import, best effort (Sketch 48 and newer); CJK line breaking: JIS X 4051's line-start and line-end sets completed with the halfwidth small kana, the halfwidth and spacing (semi-)voiced sound marks, the iteration marks 〱–〵 and the halfwidth parentheses, with behaviour cases for each; Adobe Fonts found on macOS (#298); egui 0.37 hidden-capture deadline contract; Dev Mode measurements kept on the canvas and a read-only layers panel; boards: a board area row and progress entry; community fixes in #507, #510, #529 and #530: auto-save, a text box by dragging, batch step references, removing someone from a live session, Russian and Turkish catalogs reviewed, Czech and Indonesian drafts, the Japanese draft completed, zoom previews, the web clipboard, Alt-drag snapping and Cmd+D repeat, recovery copies, crash and rollback fixes, colour emoji, `.fig` import fixes; earlier changes are in the revision history below) · **Target:** Figma Design (desktop app 126.10.7) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (`comment.list {mentions}` lists only the threads that @mention a name (#1415); in the browser an internal error that stops the page keeps its unsaved files, which open again on reload (#1123); boolean operations refuse input past their caps, and a boolean group past them draws without its result (#1104); figcraft-cli parses its arguments with clap, following RFC-0012 (#1396); the selection and hover outlines of rounded rectangles and frames follow their corners (#1506); component properties keep the order they were made in when one is renamed, and Make slot asks for the slot's name first (#1536, #1537); the browser build publishes files to a server, so Share ▸ Online and Publish to server work there (#917); the browser build uses the fonts installed on the computer once asked, each read when text uses it (#1051); SVG import reads `color()` and `color-mix()` colours, with a note and a grey for those it can't (#1550); the portable Windows zip keeps its settings, recovery copies and plugins in `config\` beside the executable, starting with an installed copy's preferences; the window starts on other graphics when its first ones fail before the first frame: Direct3D 12 compiles with FXC and no device compiles wgpu's indirect-validation shaders, then Vulkan, OpenGL and lower requirements on the power-saving GPU (#965, #1438); Thai, Lao, Khmer and Burmese lines break between words (#1219); an AI agent attached over MCP shows where it works (#1416); a manual overlay's position can be dragged on the canvas (#405); one name for live sessions and comments: Preferences ▸ Your name, in the Join dialog too, kept in the browser, and a rename shows at once in the sessions you are in (#1380); requests that change a server's files have a budget per address, the server key's too, and join codes are found by their hash (#1075); the Join dialog shows a join from the click until its file is ready, with Cancel, and a failed join with Try again (#1381); requests that change a server's files have a budget per address, the server key's too, and join codes are found by their hash (#1075); SVG import draws each pattern tile once per scale, within the layer budget (#1080); Vietnamese letters typed with combining marks (decomposed) take one font that has every mark, instead of missing-glyph boxes beside the letter (#1478); comment.list reports each message's @mentions (#1415); mask groups draw only over the area they can paint, and nested isolated layers share one memory budget (#1061); components can't contain their own instances, directly or through other components; `edit.duplicate` returns an id map with `map: true` (#995); the batches kept for reconnecting apps are bounded in bytes by default, and `figcraft-server` takes `max_log_mb` (#1069); MCP and control calls name their document (#998); sibling placement in live sessions searches instead of counting (#1065); an AI agent attached over MCP comments under its own name, with an Agent label (#1414); the instance-swap menus have a search field (#1338); a refused batch's fixes take time linear in the batch (#1063); the connector tool shows the connector it will make while dragging, ends snapping to attach points (#1387); large images on the canvas draw from a smaller copy, so two 16k images no longer lag (#1483); a file's JPEG thumbnail can't crash the home screen (#1491's progressive JPEG check, now shared); `.fig` import: library text styles linked by asset key, and override rows restating a nested level (#1234); `.fig` import keeps the file's saved line breaks while the text and its box are unedited (#1236); Download a missing free font from the Missing fonts window (`font.download`, #754); the web build's wasm back under its 24 MiB gate (#1472); shadows of images with transparent pixels follow their alpha (#1289); Crop image: Object ▸ Crop image, the right-click menu and the image fill row enter crop mode (`image.crop`, #1464); offline Penpot 2.x import/export (#1143); optional batch continue-on-error with per-step outcomes (#1386); the Design panel's sections on one grid (#858); FigJam outer shape outlines and sticky body fallback beside author text (#1341, #1397); images placed at their own size, 2x screenshots in points (#1360); the toolbar on one 32 pt grid, without its Resources button (#1210); M4: zero-length round-capped dashes draw dots; dash expansion uses the existing safety budget; extracted from #1143; Arabic draft catalog; scaled instances keep their scale when their main is rebuilt (#922); .fig embedded images are copied once per blob, not once per hash (#1059); page ruler guides survive save and reopen (#1476); colour picker LCH and OKLCH models; non-ASCII layer names transliterate in code export (#1235); branches in live sessions and branch history entries, gap 17; plugin widgets take text input and a property menu (plugin API 4); boards: templates and tables; boards: a shared timer and voting sessions; boards: the dot grid, connector ends dragged to re-attach, curved connectors; Installed weights of an embedded font family (#1246); `.sketch` import, best effort (Sketch 48 and newer); CJK line breaking: JIS X 4051's line-start and line-end sets completed with the halfwidth small kana, the halfwidth and spacing (semi-)voiced sound marks, the iteration marks 〱–〵 and the halfwidth parentheses, with behaviour cases for each; Adobe Fonts found on macOS (#298); egui 0.37 hidden-capture deadline contract; Dev Mode measurements kept on the canvas and a read-only layers panel; boards: a board area row and progress entry; community fixes in #507, #510, #529 and #530: auto-save, a text box by dragging, batch step references, removing someone from a live session, Russian and Turkish catalogs reviewed, Czech and Indonesian drafts, the Japanese draft completed, zoom previews, the web clipboard, Alt-drag snapping and Cmd+D repeat, recovery copies, crash and rollback fixes, colour emoji, `.fig` import fixes; earlier changes are in the revision history below) · **Target:** Figma Design (desktop app 126.10.7) FigCraft is a clean-room, open-source, pure-Rust reimplementation of the Figma design workflow, native on macOS, Windows, Linux and FreeBSD and in the browser, fully drivable by agents through @@ -355,6 +355,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md). | Date | Change | Summary | |---|---|---| +| 2026-10-30 | minor | M9, part of #1415: `comment.list` takes `mentions` (a name, any case, a leading `@` allowed) and lists only the threads where a message, the first or a reply, @mentions it, combined with the other filters (`{"mentions": "Claude", "filter": "unread"}`). It matches the names each message's `mentions` reports, by the same parser the thread view highlights with (`figcraft_doc::mentions`), so `@Ada Lovelace` is Ada Lovelace, not Ada, and an email address mentions nobody; the name counts as one of the file's people, so an unknown name can be several words. Numbers unchanged. Tests: `comment_mentions_tests`. | | 2026-10-30 | minor | M8, #1123: a safety net for internal errors in the browser. wasm32 can't catch a panic (stable Rust has no unwinding std for it), so there the command guard and the frame guard do nothing and a panic stopped the page, losing every unsaved change. The page now follows its files with unsaved changes after every frame (shared copies, taken again only when a file changed); its panic hook writes them to the page's local storage as `.figcraft` copies, as many as fit (browsers give a page about 5 MB), and says on the page that it stopped and what reloading brings back; the next page opens them again as unsaved files (`file.recover` also takes the copy as base64 `data`). Numbers unchanged. Tests: `recovery_tests` (figcraft-web). | | 2026-10-30 | minor | M8, #1104: boolean operations had no input cap, so a crafted file could keep the sweep busy for unbounded time and memory (any number of segments, crossing as often as they like, and a fold re-sweeping its growing result for every operand). Each sweep now takes at most 250 000 segments and 2 million possible crossings (pairs of segments whose bounds overlap, counted exactly in O(n log n)), and a fold at most 4 million segments swept over all its steps; past them the operation is refused with `TooComplex` ("the shapes are too complex to combine") before it runs. The example files' boolean groups sweep at most 55 segments and 155 possible crossings per step. A boolean group past the caps draws without its result, with a warning, once, like layers past the render's memory budget; the refusal is remembered, so redrawing it costs nothing; Flatten refuses it instead of dropping it. Numbers unchanged. Tests: `budget_tests` (pathops), `boolean_budget_tests` (render), `boolean_cap_tests` (engine). | | 2026-10-30 | minor | M0.9, #1396: `figcraft-cli` parses its arguments with clap, following RFC-0012 (craft-contrib), so help comes from the same definitions the parser uses and bad arguments fail before any work starts. Changed syntax: an option given twice is a usage error (it used to take the last value; the automation roots still repeat), a value starting with `-` needs `=` (`--filter=--json`), `--o` for `-o` and the `version` subcommand are gone (`--version` stays), `--format` is checked before the file is opened, help and usage errors are clap's text (`Usage: figcraft-cli …`, `error: …` with a hint instead of the whole usage). Every subcommand, flag, value form and exit code is otherwise unchanged. Tests: `args::tests` (each documented form, every usage error, every subcommand's help) and the CLI integration tests. Numbers unchanged | diff --git a/crates/engine/src/cmd/comment.rs b/crates/engine/src/cmd/comment.rs index ea3d07562..6dab7e8ef 100644 --- a/crates/engine/src/cmd/comment.rs +++ b/crates/engine/src/cmd/comment.rs @@ -332,13 +332,37 @@ fn mentionable<'a>(people: &'a [String], user: &'a str) -> Vec<&'a str> { people.iter().map(String::as_str).chain([user]).collect() } +/// The names `text` @mentions by the rule the thread view highlights them with +/// ([`figcraft_doc::mentions`]), and whether each is one of `people` (else it is the letters and +/// digits after the `@`). +fn mentioned<'a>(text: &'a str, people: &[&'a str]) -> impl Iterator { + mentions(text, people).into_iter().map(move |(r, who)| (who.or_else(|| text.get(r.start + 1..r.end)).unwrap_or_default(), who.is_some())) +} + +/// Whether a message of `c` @mentions `name` (any case), as `comment.list` reports mentions; +/// `name` resolves like one of `people`, so a name nobody in the file has can be several words. +fn thread_mentions(c: &Comment, people: &[&str], name: &str) -> bool { + let people: Vec<&str> = people.iter().copied().chain([name]).collect(); + c.messages().any(|m| mentioned(&m.text, &people).any(|(n, _)| n.eq_ignore_ascii_case(name))) +} + +/// `comment.list`'s `mentions` filter: a name (a leading `@` is fine), or none. +fn mentions_param(p: &Value) -> crate::Result> { + match get(p, "mentions") { + None | Some(Value::Null) => Ok(None), + Some(Value::String(s)) if !s.trim().trim_start_matches('@').trim().is_empty() => { + Ok(Some(s.trim().trim_start_matches('@').trim().to_string())) + } + Some(_) => Err(bad("comment.list", "`mentions` must be a name")), + } +} + /// A message as JSON, with the names it @mentions: `{name, known, you}` each, resolved against /// `people` (`mentionable`); `known: false` is a name nobody in the file has. fn message_json(m: &Message, people: &[&str], user: &str) -> Value { let mut named: Vec = Vec::new(); - for (r, who) in mentions(&m.text, people) { - let name = who.or_else(|| m.text.get(r.start + 1..r.end)).unwrap_or_default(); - let v = json!({"name": name, "known": who.is_some(), "you": who.is_some_and(|w| w.eq_ignore_ascii_case(user))}); + for (name, known) in mentioned(&m.text, people) { + let v = json!({"name": name, "known": known, "you": known && name.eq_ignore_ascii_case(user)}); if !named.contains(&v) { named.push(v); } @@ -807,16 +831,21 @@ pub fn specs() -> Vec { spec!( "comment.list", "List comments", - "{page? (default: current; \"all\" for every page), filter? (all|open|resolved|mine|unread; default all), sort? (date|page; default date), query? (search text), frame? (a top-level frame id), resolved? (false hides resolved ones), author? (whose threads and read state)}", + "{page? (default: current; \"all\" for every page), filter? (all|open|resolved|mine|unread; default all), sort? (date|page; default date), query? (search text), frame? (a top-level frame id), resolved? (false hides resolved ones), author? (whose threads and read state), mentions? (a name, any case: only threads where a message @mentions it, as each message's `mentions` reports)}", false, |s, p| { let doc = s.doc()?; let (page, filter, sort, text) = list_params("comment.list", s, p)?; + let want = mentions_param(p)?; let who = author(s, p); let q = Query { page, frame: id(p, "frame"), filter, sort, text: &text }; let people = doc.comment_people(); let people = mentionable(&people, &who); - let list: Vec = query(doc, &q, &who).into_iter().map(|c| comment_json(doc, c, &who, &people)).collect(); + let list: Vec = query(doc, &q, &who) + .into_iter() + .filter(|c| want.as_deref().is_none_or(|w| thread_mentions(c, &people, w))) + .map(|c| comment_json(doc, c, &who, &people)) + .collect(); Ok(json!({"comments": list})) } ), diff --git a/crates/engine/src/comment_mentions_tests.rs b/crates/engine/src/comment_mentions_tests.rs index b25a7934e..945810292 100644 --- a/crates/engine/src/comment_mentions_tests.rs +++ b/crates/engine/src/comment_mentions_tests.rs @@ -1,5 +1,6 @@ //! `comment.list` reports the names each message @mentions, by the rule the thread view highlights //! them with: resolved against the file's people where it can be, and whether the name is you. +//! Its `mentions` param lists only the threads where a message mentions a name. use serde_json::{Value, json}; @@ -42,3 +43,37 @@ fn the_mention_rule_matches_the_highlighting_rule() { assert_eq!(figcraft_doc::mention_ranges("@Sam Lee, @sam, x@Sam, @Zoe!", &people), vec![0..8, 10..14]); assert!(figcraft_doc::mentions("@ and a@b.com", &people).is_empty()); } + +/// The ids of the threads `comment.list` returns for `p`. +fn listed(s: &mut Session, p: Value) -> Vec { + let l = s.execute("comment.list", &p).unwrap(); + l["comments"].as_array().unwrap().iter().map(|c| c["id"].as_u64().unwrap()).collect() +} + +fn add(s: &mut Session, text: &str, author: &str) -> u64 { + s.execute("comment.add", &json!({"x": 0, "y": 0, "text": text, "author": author})).unwrap()["id"].as_u64().unwrap() +} + +#[test] +fn the_mentions_param_lists_only_the_threads_that_mention_a_name() { + let mut s = Session::new(); + let claude = add(&mut s, "@Claude make this bigger", "Sam"); + let in_reply = add(&mut s, "Spacing looks off", "Sam"); + s.execute("comment.reply", &json!({"id": in_reply, "text": "agreed, @claude: tighten it", "author": "Ada Lovelace"})).unwrap(); + let other = add(&mut s, "@Ada Lovelace and @Zoe Smith, mail claude@example.com", "Sam"); + add(&mut s, "No mentions here", "Sam"); + let mut both = listed(&mut s, json!({"mentions": "CLAUDE"})); + both.sort_unstable(); + assert_eq!(both, vec![claude, in_reply], "any case, in a reply too, never an email address"); + assert_eq!(listed(&mut s, json!({"mentions": " @Claude "})), listed(&mut s, json!({"mentions": "claude"})), "a leading @ is fine"); + assert_eq!(listed(&mut s, json!({"mentions": "ada lovelace"})), vec![other]); + assert!(listed(&mut s, json!({"mentions": "Ada"})).is_empty(), "@Ada Lovelace names Ada Lovelace, not Ada"); + assert_eq!(listed(&mut s, json!({"mentions": "Zoe Smith"})), vec![other], "a name nobody in the file has, several words"); + assert_eq!(listed(&mut s, json!({"mentions": null})).len(), 4); + // Combined with the other filters: resolved threads drop out. + s.execute("comment.resolve", &json!({"id": claude})).unwrap(); + assert_eq!(listed(&mut s, json!({"mentions": "Claude", "filter": "open"})), vec![in_reply]); + for junk in [json!(""), json!("@"), json!(5), json!(["Claude"])] { + assert!(s.execute("comment.list", &json!({"mentions": junk})).is_err(), "{junk}"); + } +} diff --git a/docs/collaboration-parity.md b/docs/collaboration-parity.md index 32755cdc4..3694eabf0 100644 --- a/docs/collaboration-parity.md +++ b/docs/collaboration-parity.md @@ -1,6 +1,6 @@ # Collaboration parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (the browser build publishes files to a server, so Share ▸ Online and Publish to server work there (#917); an AI agent attached over MCP shows where it works (#1416); one name for live sessions and comments: Preferences ▸ Your name, in the Join dialog too, kept in the browser, and a rename shows at once in the sessions you are in (#1380); the Join dialog shows a join from the click until its file is ready, with Cancel, and a failed join with Try again (#1381); requests that change a server's files have a budget per address, the server key's too, and join codes are found by their hash (#1075); comment.list reports each message's @mentions (#1415); the batches kept for reconnecting apps are bounded in bytes by default, and `figcraft-server` takes `max_log_mb` (#1069); sibling placement in live sessions searches instead of counting (#1065); an AI agent attached over MCP comments under its own name, with an Agent label (#1414); a refused batch's fixes take time linear in the batch (#1063); image entries are checked as they apply; branches in live sessions and branch history entries; server message limits follow `max_file_mb`; a damaged log generation field keeps flushed edits; the web build joins from a `#join=` link; removing someone from a live session; live-session revert guard; live sessions per document; a public figcraft-server: open hosting, owner tokens, per-address limits, idle expiry, moderation, install.sh; cursor chat and spotlight in live sessions; branch version history and finer list merges; comments and version history behaviour suite and fixes; hosting the server: `wss://` in the desktop app, server releases, deployment guide; team libraries on the server and swap library; branches: create, update from main, review and merge; wording: the standalone server with share links to edit or view; numbers unchanged) · **Target:** Figma Design (desktop app 126.10.7) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (`comment.list {mentions}` lists only the threads that @mention a name (#1415); the browser build publishes files to a server, so Share ▸ Online and Publish to server work there (#917); an AI agent attached over MCP shows where it works (#1416); one name for live sessions and comments: Preferences ▸ Your name, in the Join dialog too, kept in the browser, and a rename shows at once in the sessions you are in (#1380); the Join dialog shows a join from the click until its file is ready, with Cancel, and a failed join with Try again (#1381); requests that change a server's files have a budget per address, the server key's too, and join codes are found by their hash (#1075); comment.list reports each message's @mentions (#1415); the batches kept for reconnecting apps are bounded in bytes by default, and `figcraft-server` takes `max_log_mb` (#1069); sibling placement in live sessions searches instead of counting (#1065); an AI agent attached over MCP comments under its own name, with an Agent label (#1414); a refused batch's fixes take time linear in the batch (#1063); image entries are checked as they apply; branches in live sessions and branch history entries; server message limits follow `max_file_mb`; a damaged log generation field keeps flushed edits; the web build joins from a `#join=` link; removing someone from a live session; live-session revert guard; live sessions per document; a public figcraft-server: open hosting, owner tokens, per-address limits, idle expiry, moderation, install.sh; cursor chat and spotlight in live sessions; branch version history and finer list merges; comments and version history behaviour suite and fixes; hosting the server: `wss://` in the desktop app, server releases, deployment guide; team libraries on the server and swap library; branches: create, update from main, review and merge; wording: the standalone server with share links to edit or view; numbers unchanged) · **Target:** Figma Design (desktop app 126.10.7) Figma is multiplayer first; FigCraft is local first. **Ready ~13% (estimated):** comments, version history and branches (review and merge) work inside one file; the real-time collaboration core landed in #103 (sync model, @@ -29,6 +29,7 @@ cursors, selections, text carets, cursor chat and spotlights shown live ([`colla | Date | Change | Summary | |---|---|---| +| 2026-10-30 | minor | M9, part of #1415: `comment.list {mentions}` lists only the threads where a message @mentions a name, combined with the other filters, so an agent tagged in comments reads just its threads. Numbers unchanged. | | 2026-10-30 | minor | M9, #917: the browser build publishes to a `figcraft-server`. Share ▸ Online (the server in Preferences) and Share ▸ Publish to server… upload the open file over the page's own WebSocket, make the links and join codes others join with, and keep the tab in sync with the new file, as the desktop app does; asking a server about its hosting, stopping a share and sharing again go out the same way. The Share popover offers only online sharing there (a page can't listen, so the local network stays the desktop app's); without a server in Preferences it says so and offers joining. A failed publish says why and leaves the file as it was. The browser keeps only your name across reloads, so the owner token of a file it put on an open server lasts as long as the page (`crates/collab-net/src/exchange.rs`, run by the browser client's driver; the requests and what their answers mean are shared with the desktop transport in `requests.rs`). Tests: `exchange_tests` (scripted server: with and without the key, sharing or not, refusals, an old server without join codes, time-outs, leaving while publishing, the background requests), `live_join_tests`; checked in a browser against a local server. Percentages and hours unchanged | | 2026-10-30 | minor | Where an AI agent works (M8, #1416): an agent attached over MCP (`figcraft-cli mcp --attach`) changed the file with no sign of where or whether it was still at work. Now, while it runs commands, the people row at the top of the right panel shows its avatar, ringed in its own colour and named as its comments are signed ("Claude (agent)"), and the canvas outlines the layers its last edit changed (the innermost ones), or that it last selected, in that colour with its name. Both go 8 seconds after its last command, and only the file it works in shows them. Display only: no undo step, never saved, never sent to a live session's other people, and the user's own commands never make one (`agent_presence_tests` in the engine, `live::agent` tests in the UI). Ready unchanged | | 2026-10-29 | minor | M9, #1380: your name is one setting, shown where you join. Preferences calls it "Your name" (it signs your comments and is what others see in live sessions). The Join dialog shows it, editable, beside the code, as the Share popover does, and the field is empty while it is the default. A new name is kept at once, and the browser build keeps it in the page's local storage, so a reload joins as you (the other preferences still start afresh there). Changing it while in live sessions renames you in each of them: a `name` signal, which the server cleans and answers with your `PeerJoined` to everyone, and newcomers' welcomes list the new name. A rename made while joining, or before a reconnect, goes out once the host reads signals. An older server ignores it, and clearing the name keeps the session's name until you join again. Tests: `collab/src/rename_tests.rs`, `collab-net/src/rename_tests.rs` (two apps over loopback), `ui-egui/src/display_name_tests.rs`, and `figcraft-web`'s `kept` round-trip. Numbers unchanged | diff --git a/docs/mcp.md b/docs/mcp.md index 81b13c65e..c11eda31f 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1186,13 +1186,18 @@ someone (`comment.people` lists the names used in the file and, as `you`, who is Read state is kept per name: a new message is unread for everyone but its author; `comment.markRead {id? | link?, read?}` marks one thread (or, without `id`, all of them) read or unread (not an undo step). `comment.list {page? | "all", filter? (all|open|resolved|mine|unread), -sort? (date|page), query?, frame?}` returns the threads with their position, `frame`/`frameName`, +sort? (date|page), query?, frame?, mentions?}` returns the threads with their position, `frame`/`frameName`, `pageName`, `unread`, `link` and replies (with `edited` times and `reactions`). Each message, the first and every reply, has `mentions`: the names it @mentions by the rule the app highlights them with, `{name, known, you}` each. `known` means the file's people (`comment.people`, or you) have the name, spelt as they spell it; `you` means it names you (or `author`). `@Name` counts at the start of the text or after a character that isn't a letter or digit, so `a@b.com` mentions nobody; -an unknown name is the letters and digits after the `@`. Links are +an unknown name is the letters and digits after the `@`. `mentions: ""` (any case; a leading +`@` is fine) lists only the threads where a message, the first or a reply, mentions that name as +those `mentions` report it, combined with the other filters: `{"mentions": "Claude", "filter": +"unread"}` is the unread threads that tag Claude. `@Ada Lovelace` mentions "Ada Lovelace", not +"Ada", when the file knows Ada Lovelace; a name nobody in the file has can still be several words +(`"Zoe Smith"` finds `@Zoe Smith`). Links are `figcraft://comment/`: `comment.link {id}` returns one, `comment.open {id | link, view?}` goes to its page, zooms to its frame (`fit`, the default; `center` keeps the zoom; `keep` doesn't move) and opens it in the app, and `comment.next {id?, dir?, …list params}` steps to the next or previous diff --git a/docs/roadmap.md b/docs/roadmap.md index 5d420a02f..6a064ae37 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # FigCraft roadmap: milestones and what's next -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (in the browser an internal error that stops the page keeps its unsaved files, which open again on reload (#1123); boolean operations refuse input past their caps, and a boolean group past them draws without its result (#1104); figcraft-cli parses its arguments with clap, following RFC-0012 (#1396); the selection and hover outlines of rounded rectangles and frames follow their corners (#1506); component properties keep the order they were made in when one is renamed, and Make slot asks for the slot's name first (#1536, #1537); the browser build publishes files to a server, so Share ▸ Online and Publish to server work there (#917); the browser build uses the fonts installed on the computer once asked, each read when text uses it (#1051); SVG import reads `color()` and `color-mix()` colours, with a note and a grey for those it can't (#1550); the portable Windows zip keeps its settings, recovery copies and plugins in `config\` beside the executable, starting with an installed copy's preferences; the window starts on other graphics when its first ones fail before the first frame: Direct3D 12 compiles with FXC and no device compiles wgpu's indirect-validation shaders, then Vulkan, OpenGL and lower requirements on the power-saving GPU (#965, #1438); Thai, Lao, Khmer and Burmese lines break between words (#1219); an AI agent attached over MCP shows where it works (#1416); a manual overlay's position can be dragged on the canvas (#405); one name for live sessions and comments: Preferences ▸ Your name, in the Join dialog too, kept in the browser, and a rename shows at once in the sessions you are in (#1380); requests that change a server's files have a budget per address, the server key's too, and join codes are found by their hash (#1075); the Join dialog shows a join from the click until its file is ready, with Cancel, and a failed join with Try again (#1381); requests that change a server's files have a budget per address, the server key's too, and join codes are found by their hash (#1075); SVG import draws each pattern tile once per scale, within the layer budget (#1080); Vietnamese letters typed with combining marks (decomposed) take one font that has every mark, instead of missing-glyph boxes beside the letter (#1478); comment.list reports each message's @mentions (#1415); M8: mask groups draw only over the area they can paint, and nested isolated layers share one memory budget (#1061); M2: components can't contain their own instances, directly or through others; `edit.duplicate` returns an id map with `map: true` (#995); the batches kept for reconnecting apps are bounded in bytes by default, and `figcraft-server` takes `max_log_mb` (#1069); MCP and control calls name their document (#998); sibling placement in live sessions searches instead of counting (#1065); an AI agent attached over MCP comments under its own name, with an Agent label (#1414); the instance-swap menus have a search field (#1338); a refused batch's fixes take time linear in the batch (#1063); the connector tool shows the connector it will make while dragging, ends snapping to attach points (#1387); large images on the canvas draw from a smaller copy, so two 16k images no longer lag (#1483); a file's JPEG thumbnail can't crash the home screen (#1491's progressive JPEG check, now shared); `.fig` import: library text styles linked by asset key, and override rows restating a nested level (#1234); `.fig` import keeps the file's saved line breaks while the text and its box are unedited (#1236); Download a missing free font from the Missing fonts window (`font.download`, #754); the web build's wasm back under its 24 MiB gate (#1472); shadows of images with transparent pixels follow their alpha (#1289); Crop image: Object ▸ Crop image, the right-click menu and the image fill row enter crop mode (`image.crop`, #1464); M0.9: optional batch continue-on-error with per-step outcomes (#1386); Alt pressed during a drag duplicates (#1385); variables in paint props (M3, #1000); finding layers without selecting them (M0.9, part of #996); the attached MCP server reads the app's replies with a 512 MiB bound (#1113); a failed batch keeps the undo steps it undid (#1410); Arabic draft catalog; Android Vector Drawable export; a selected unfilled shape drags over a filled layer below it (#1370); text truncation fills the last line, survives resizing, and auto height takes Max lines (#1375, #1376, #1377); M11 widget fields and property menus; M12 Boards: timer and voting; Installed weights of an embedded font family (#1246); `.sketch` import, best effort; Adobe Fonts found on macOS (#298); M12 Boards; community fixes in #507, #510, #529 and #530: auto-save, a text box by dragging, batch step references, removing someone from a live session, Russian and Turkish catalogs reviewed, Czech and Indonesian drafts, the Japanese draft completed, zoom previews, the web clipboard, Alt-drag snapping and Cmd+D repeat, recovery copies, crash and rollback fixes, colour emoji, `.fig` import fixes; earlier changes are in the revision history below) · **Target:** Figma Design (desktop app 126.10.7) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (`comment.list {mentions}` lists only the threads that @mention a name (#1415); in the browser an internal error that stops the page keeps its unsaved files, which open again on reload (#1123); boolean operations refuse input past their caps, and a boolean group past them draws without its result (#1104); figcraft-cli parses its arguments with clap, following RFC-0012 (#1396); the selection and hover outlines of rounded rectangles and frames follow their corners (#1506); component properties keep the order they were made in when one is renamed, and Make slot asks for the slot's name first (#1536, #1537); the browser build publishes files to a server, so Share ▸ Online and Publish to server work there (#917); the browser build uses the fonts installed on the computer once asked, each read when text uses it (#1051); SVG import reads `color()` and `color-mix()` colours, with a note and a grey for those it can't (#1550); the portable Windows zip keeps its settings, recovery copies and plugins in `config\` beside the executable, starting with an installed copy's preferences; the window starts on other graphics when its first ones fail before the first frame: Direct3D 12 compiles with FXC and no device compiles wgpu's indirect-validation shaders, then Vulkan, OpenGL and lower requirements on the power-saving GPU (#965, #1438); Thai, Lao, Khmer and Burmese lines break between words (#1219); an AI agent attached over MCP shows where it works (#1416); a manual overlay's position can be dragged on the canvas (#405); one name for live sessions and comments: Preferences ▸ Your name, in the Join dialog too, kept in the browser, and a rename shows at once in the sessions you are in (#1380); requests that change a server's files have a budget per address, the server key's too, and join codes are found by their hash (#1075); the Join dialog shows a join from the click until its file is ready, with Cancel, and a failed join with Try again (#1381); requests that change a server's files have a budget per address, the server key's too, and join codes are found by their hash (#1075); SVG import draws each pattern tile once per scale, within the layer budget (#1080); Vietnamese letters typed with combining marks (decomposed) take one font that has every mark, instead of missing-glyph boxes beside the letter (#1478); comment.list reports each message's @mentions (#1415); M8: mask groups draw only over the area they can paint, and nested isolated layers share one memory budget (#1061); M2: components can't contain their own instances, directly or through others; `edit.duplicate` returns an id map with `map: true` (#995); the batches kept for reconnecting apps are bounded in bytes by default, and `figcraft-server` takes `max_log_mb` (#1069); MCP and control calls name their document (#998); sibling placement in live sessions searches instead of counting (#1065); an AI agent attached over MCP comments under its own name, with an Agent label (#1414); the instance-swap menus have a search field (#1338); a refused batch's fixes take time linear in the batch (#1063); the connector tool shows the connector it will make while dragging, ends snapping to attach points (#1387); large images on the canvas draw from a smaller copy, so two 16k images no longer lag (#1483); a file's JPEG thumbnail can't crash the home screen (#1491's progressive JPEG check, now shared); `.fig` import: library text styles linked by asset key, and override rows restating a nested level (#1234); `.fig` import keeps the file's saved line breaks while the text and its box are unedited (#1236); Download a missing free font from the Missing fonts window (`font.download`, #754); the web build's wasm back under its 24 MiB gate (#1472); shadows of images with transparent pixels follow their alpha (#1289); Crop image: Object ▸ Crop image, the right-click menu and the image fill row enter crop mode (`image.crop`, #1464); M0.9: optional batch continue-on-error with per-step outcomes (#1386); Alt pressed during a drag duplicates (#1385); variables in paint props (M3, #1000); finding layers without selecting them (M0.9, part of #996); the attached MCP server reads the app's replies with a 512 MiB bound (#1113); a failed batch keeps the undo steps it undid (#1410); Arabic draft catalog; Android Vector Drawable export; a selected unfilled shape drags over a filled layer below it (#1370); text truncation fills the last line, survives resizing, and auto height takes Max lines (#1375, #1376, #1377); M11 widget fields and property menus; M12 Boards: timer and voting; Installed weights of an embedded font family (#1246); `.sketch` import, best effort; Adobe Fonts found on macOS (#298); M12 Boards; community fixes in #507, #510, #529 and #530: auto-save, a text box by dragging, batch step references, removing someone from a live session, Russian and Turkish catalogs reviewed, Czech and Indonesian drafts, the Japanese draft completed, zoom previews, the web clipboard, Alt-drag snapping and Cmd+D repeat, recovery copies, crash and rollback fixes, colour emoji, `.fig` import fixes; earlier changes are in the revision history below) · **Target:** Figma Design (desktop app 126.10.7) Forward-looking plan. Where FigCraft stands today: [`ROADMAP.md`](../ROADMAP.md) and [`target-app-parity.md`](target-app-parity.md); the full ranked list: [`gaps.md`](gaps.md). Hours @@ -75,6 +75,7 @@ blockers. | Date | Change | Summary | |---|---|---| +| 2026-10-30 | minor | M9, part of #1415: `comment.list {mentions}` lists only the threads where a message @mentions a name (any case, by the same parser as the highlighting), combined with the other filters. Tests: `comment_mentions_tests` | | 2026-10-30 | minor | M8, #1123: in the browser, where a panic can't be caught, the page's panic hook keeps its files with unsaved changes in local storage (as many as fit) and the next page opens them again as unsaved files; `file.recover` takes the copy as base64 `data`. Tests: `recovery_tests` (figcraft-web) | | 2026-10-30 | minor | M8, #1104: boolean operations refuse input past 250 000 segments, 2 million possible crossings or 4 million segments swept over a fold (`TooComplex`, before the sweep runs); a boolean group past them draws without its result, with a warning, and Flatten refuses it. Tests: `budget_tests`, `boolean_budget_tests`, `boolean_cap_tests` | | 2026-10-30 | minor | M0.9, #1396: `figcraft-cli` parses its arguments with clap, following RFC-0012 (craft-contrib), so help comes from the same definitions the parser uses and bad arguments fail before any work starts. Changed syntax: an option given twice is a usage error (it used to take the last value; the automation roots still repeat), a value starting with `-` needs `=` (`--filter=--json`), `--o` for `-o` and the `version` subcommand are gone (`--version` stays), `--format` is checked before the file is opened, help and usage errors are clap's text (`Usage: figcraft-cli …`, `error: …` with a hint instead of the whole usage). Every subcommand, flag, value form and exit code is otherwise unchanged. Tests: `args::tests` (each documented form, every usage error, every subcommand's help) and the CLI integration tests. Numbers unchanged |