diff --git a/README.md b/README.md index 012ca857a..7bbc4d524 100644 --- a/README.md +++ b/README.md @@ -103,8 +103,8 @@ - Dev Mode on the Ridgeline home screen: the frame carries a Ready for dev badge, a Development annotation callout points at the featured trail card, a trail row instance is selected and the Inspect panel shows a Playground preview of it and generated React code with a TrailRow props interface and the instance's title and details values -

Dev Mode: ready-for-dev status, annotations and React code where component properties become props · examples/ridgeline-app.figcraft

+ Developer Mode on the Ridgeline home screen: the frame carries a Ready for dev badge, a Development annotation callout points at the featured trail card, a trail row instance is selected and the Inspect panel shows a Playground preview of it and generated React code with a TrailRow props interface and the instance's title and details values +

Developer Mode: ready-for-dev status, annotations and React code where component properties become props · examples/ridgeline-app.figcraft

Vector edit mode on a wave path with a gradient, tapered variable-width stroke: its anchor points, Bezier handles and the transform box of the selected points are visible, with the vector edit toolbar at the bottom; above it are a boolean-built mountain mark and an angular-gradient knot of excluded rings @@ -127,8 +127,8 @@

Boards: a whiteboard file kind with sticky notes, templates, connectors, a shared timer and voting, in the same live sessions

- Dev Mode on the Ridgeline home screen with the featured trail card selected: red redlines kept on the canvas show 118.3 from the card to the search field, 62.1 from the card to the first trail row and 256.3 along the card's left side, and the Inspect panel's Measurements section lists the three with their values under an Add measurement menu -

Dev Mode measurements: redlines placed between layers or along a side, saved in the file and following the layers · examples/ridgeline-app.figcraft

+ Developer Mode on the Ridgeline home screen with the featured trail card selected: red redlines kept on the canvas show 118.3 from the card to the search field, 62.1 from the card to the first trail row and 256.3 along the card's left side, and the Inspect panel's Measurements section lists the three with their values under an Add measurement menu +

Developer Mode measurements: redlines placed between layers or along a side, saved in the file and following the layers · examples/ridgeline-app.figcraft

diff --git a/ROADMAP.md b/ROADMAP.md index a574499c0..651f3ab2e 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-11-06 · **Change:** minor (the left rail's and tab strip's icons are larger, 20 pt (#553); Suggest auto layout adds nested auto layout frames by rows and columns and keeps the placement of the design, as the help center describes (#1823); the Local variables window selects several variables (click, Shift+click for a range, Cmd/Ctrl+click to toggle, Escape to clear) and delete, move to a group and scope act on the selected local variables in one undo step (#2002); the desktop UI takes a system face for Ethiopic, Devanagari, Gurmukhi, Gujarati, Tamil, Telugu, Kannada and Malayalam, Sinhala, Lao, Myanmar, Khmer, Armenian and Georgian as it did for Hebrew and Arabic, so a String variable's Amharic value draws in the panels instead of boxes (#2014); a layer's effect binding takes only a field its effect has; ; the boolean operation menu leads each entry with an original icon (#2003); glass takes one sample instead of its dispersion window where the colours split under 1/256 px; glass bends towards its core, and rounded rectangles work it out in closed form; glass's light is a line on its bevel and splay turns the bend towards the centre; glass bends with a thick-lens profile and splits its colours over 8 steps; glass effects take variables and keep their frost within 0–100; web host bridge: `?host=parent` or `data-host="page"` hands a page the app's files, documented in the hosting guide; a connector end moved with Cmd/Ctrl held goes on any part of an object, and stays there as the object moves or resizes; a tap on a plugin panel's button keeps what was typed in its fields; a Dash cap setting (none, round or square) on Dash and Custom strokes, drawn on both ends of every dash; library analytics list how many layers and files use each style and variable; the Controller menu names each button as PlayStation and Switch controllers do; widgets draw lines and SVG pictures (plugin API 6); a connector end dropped along a side of a shape attaches to that side (#1708); regression tests pin a rounded shape's one-sided stroke and SVG ids and font families without XML-forbidden characters (#1351, #1124); a damaged image is refused with a reason instead of showing as an empty box (#1474); an image copied in another app pastes again on the desktop, and in the browser for the first time (#492); SwiftUI and Compose code names a bound variable by its iOS or Android code syntax; the colour picker's Image tab shows a placeholder and Upload from computer, an asset preview, and a file dropped on it replaces the image, keeping the fill mode, crop and position (#1682); the Frame tool's Phone presets have the iPhone 17 family (#1691); Show in folder works in the Flatpak build and selects the file on Linux (#1701); a dashed line starts and ends with a half-length dash, on the canvas and in exports; the Pages, Layers and Assets search fields keep their text in the middle of the field (#1703); a board connector's path can be reshaped by dragging its path handles (#1710); boards have 16 more shapes (arrows, polygons, star, plus and flowchart symbols), in pickers that lay them out in a grid; library analytics count insertions and show insertions and detachments over the past 30 days, 90 days or year; a stroke's Custom style takes a dash pattern of any length as a comma-separated list (#1681); a control request can set its own timeout, up to 10 minutes (`timeoutMs`; `figcraft-cli mcp --attach PORT --timeout SECONDS`) (#1696); gap 6: a widget opens its plugin's panel (plugin API 5), whose callbacks redraw it; the keyboard's Menu key opens the context menu, as Shift+F10 does; an Open link action can open in place of the prototype, with "Open in new tab" off (gap 15); a collection of this file can be extended (Extend collection on its row, `variable.extendCollection`), its extension following its edits at once, and extended collections extend again in the file; the eyedropper with Cmd/Ctrl+Shift makes a colour style or variable of what it picks; a live session's welcome sends the document compressed to clients that read it, about seven times smaller (#1665); a control request that times out says truthfully whether it was dropped or is still running and will take effect, and only screenshots and input blame the window (#1673); a board keeps earlier votes' results, listed newest first in Results and deleted one at a time; the object a stamp will stick to is outlined while the stamp tool is over it or a stamp is dragged over it, and board objects take no drops (gap 24); a server can list the files shared from it now for everyone who reaches it, Home ▸ Online shows them and their links carry no secret (`team_files`, part of #742); team library analytics count detached instances per component and file (gap 10); skipping the Open link notice by command is saved with the preferences at once (gap 15); a selected connector's bar on a board sets its start and end points, solid or dashed, its weight and any colour (#1596); cells copied from a spreadsheet paste onto a board as a table; a long layer name stays on one line in the Export button and the Dev Mode header, ending with an ellipsis, and no longer covers the Design panel (#1650); with hundreds of layers selected, the Design panel and the selection overlays stop going over the whole selection on every frame, so dragging 648 layers averages about 1.1 ms of UI per frame (#1571); plugin panels show pictures: an `image` control, base64 within 256 KB and 4,096 px a side, named by its label (gap 6); `.fig` connector labels without a saved snapshot are kept, centred on their line (gap 1); a drag moving many layers re-runs only what sits around them, not each layer's bindings, sizing, text and instance checks, on every move (#1571); damaged font tracking table offsets are checked instead of wrapping on 32-bit targets, and font indexing makes its existing offset bounds explicit (#1125); Bosnian (`bs`) draft catalog; text style dedupe is O(spans) via a last-two cache and fingerprint buckets (#1102); pasted layers whose image the clipboard lacks are named and show a placeholder that reads as missing (#1646); paint-style colours on layers pasted from another design app keep their style instead of turning black (#1638); progress docs cleaned of what merges left twice: rows merged into one, repeated status items removed, and `cargo xtask docs` (in `ci`) fails on a repeated table row or status item; a name typed for a variable or collection finds the one with that exact id before one named like it, and stored mode ids, font family variables and the panels' labels read exact ids (#1613); plugin widgets have hover styles: a layer's `hover` fill, stroke, opacity or text colour shows while the pointer is over it (gap 6); `.fig` elbowed and curved connectors without a saved path are routed like board connectors instead of drawn straight (gap 1); every file a command reads whole by path is a regular file within a cap that fits it: documents 16 GiB, placed images 256 MiB, CLI scripts 1 GiB; `figcraft-cli connect publish` publishes a code repository's code connect mappings to a design file (gap 16); while a drag only moves a large selection, its outlines, box, slots, spacing and Design panel values are worked out once and shifted, not rebuilt on every move (#1571); Shift+Cmd/Ctrl+Enter on a selected board shape adds the next shape already joined to it, and goes on typing (#1597); an imported or pasted file finds an installed family under the other names its fonts give it, and with other case and spacing, instead of reporting it missing (#1292); a development example generates figcraft-cli's shell completions and man pages from its clap definitions (part of #1396); `.fig` ZIP image reads share a total byte budget and count skipped images in the import warnings (#1076); German terminology corrections (M10): gradient stops, OpenType stylistic sets and the Home-screen preference; a component set's property rows fit the 240 px right panel: variant values wrap two to a line there and every + and − stays in view; the server key is checked by comparing SHA-256 hashes of the presented and the kept key, as the deployment guide says (#1130); opening a file contacts no team library server it names until you check it (#1097); a hostile server can no longer grow a client's memory with made-up people, and a session remembers a bounded number of deleted layer ids (#1121); `doc.list` gives AI agents and plugins the open files' names, not their paths (#1117); with a large selection, frames where nothing changed reuse the selection's outlines, box and slots and the Design panel's instance and position values instead of working them out again (#1571); team libraries and plugins larger than the 16 MiB download burst can be downloaded again, more slowly (#1089); a batch keeps one undo snapshot, not one per step (#1099); files nested 256 levels deep import on the browser's 1 MB stack (#1101); SVG and PDF exports count pattern sources and the renders they embed from the layer's depth, so one cap bounds a whole export (#1082); an explicit variables file path (`variable.import`, the `variables` of `file.open` and `file.import`) is refused past 256 MiB or when it isn't a regular file, as the sibling `variables.json` already was (part of #1114); joining and leaving a file nobody is editing reads and writes nothing on the server: it stays loaded a minute after everyone leaves (#1074); opening a `.figcraft` file unpacks at most 6 GiB in all (2 GiB in the browser), every area together (#1079); hovering the canvas hit-tests once per pointer move, not on every frame, and moving an auto layout frame no longer lays out its contents again on every move (#1571); `.fig` instance expansion no longer walks variable nodes for every instance or copies every outer override row into each nested instance (#1077); `comment.people` lists names that were only @mentioned, and `comment.next` and `comment.prompt` carry the mentions too (#1415); grid auto-placement looks for free cells a 64-bit word at a time and skips what blocks it, with the same placements (#1083); with the pen out, Enter or a double-click ends the path and edits the vector with the Move tool instead of drawing on (#1430); a recovery copy made while a version is previewed holds the file, not the version shown; a FigJam `.jam` file opens as a board (#1342); the Actions palette offers only commands that run from it, and no second row reading like a menu item: Place image opens the file dialog (#1574); share links can comment: a third access level beside edit and view, whose holder sees the file live and adds, replies to, reacts to and resolves comments but can't change the design; Preferences ▸ Scroll wheel zoom: a mouse wheel zooms the canvas about the pointer and Cmd/Ctrl+wheel scrolls (#1390); `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); 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-11-07 · **Change:** patch (the handoff mode reads Developer Mode in the interface, its ids and saved fields unchanged; the left rail's and tab strip's icons are larger, 20 pt (#553); Suggest auto layout adds nested auto layout frames by rows and columns and keeps the placement of the design, as the help center describes (#1823); the Local variables window selects several variables (click, Shift+click for a range, Cmd/Ctrl+click to toggle, Escape to clear) and delete, move to a group and scope act on the selected local variables in one undo step (#2002); the desktop UI takes a system face for Ethiopic, Devanagari, Gurmukhi, Gujarati, Tamil, Telugu, Kannada and Malayalam, Sinhala, Lao, Myanmar, Khmer, Armenian and Georgian as it did for Hebrew and Arabic, so a String variable's Amharic value draws in the panels instead of boxes (#2014); a layer's effect binding takes only a field its effect has; ; the boolean operation menu leads each entry with an original icon (#2003); glass takes one sample instead of its dispersion window where the colours split under 1/256 px; glass bends towards its core, and rounded rectangles work it out in closed form; glass's light is a line on its bevel and splay turns the bend towards the centre; glass bends with a thick-lens profile and splits its colours over 8 steps; glass effects take variables and keep their frost within 0–100; web host bridge: `?host=parent` or `data-host="page"` hands a page the app's files, documented in the hosting guide; a connector end moved with Cmd/Ctrl held goes on any part of an object, and stays there as the object moves or resizes; a tap on a plugin panel's button keeps what was typed in its fields; a Dash cap setting (none, round or square) on Dash and Custom strokes, drawn on both ends of every dash; library analytics list how many layers and files use each style and variable; the Controller menu names each button as PlayStation and Switch controllers do; widgets draw lines and SVG pictures (plugin API 6); a connector end dropped along a side of a shape attaches to that side (#1708); regression tests pin a rounded shape's one-sided stroke and SVG ids and font families without XML-forbidden characters (#1351, #1124); a damaged image is refused with a reason instead of showing as an empty box (#1474); an image copied in another app pastes again on the desktop, and in the browser for the first time (#492); SwiftUI and Compose code names a bound variable by its iOS or Android code syntax; the colour picker's Image tab shows a placeholder and Upload from computer, an asset preview, and a file dropped on it replaces the image, keeping the fill mode, crop and position (#1682); the Frame tool's Phone presets have the iPhone 17 family (#1691); Show in folder works in the Flatpak build and selects the file on Linux (#1701); a dashed line starts and ends with a half-length dash, on the canvas and in exports; the Pages, Layers and Assets search fields keep their text in the middle of the field (#1703); a board connector's path can be reshaped by dragging its path handles (#1710); boards have 16 more shapes (arrows, polygons, star, plus and flowchart symbols), in pickers that lay them out in a grid; library analytics count insertions and show insertions and detachments over the past 30 days, 90 days or year; a stroke's Custom style takes a dash pattern of any length as a comma-separated list (#1681); a control request can set its own timeout, up to 10 minutes (`timeoutMs`; `figcraft-cli mcp --attach PORT --timeout SECONDS`) (#1696); gap 6: a widget opens its plugin's panel (plugin API 5), whose callbacks redraw it; the keyboard's Menu key opens the context menu, as Shift+F10 does; an Open link action can open in place of the prototype, with "Open in new tab" off (gap 15); a collection of this file can be extended (Extend collection on its row, `variable.extendCollection`), its extension following its edits at once, and extended collections extend again in the file; the eyedropper with Cmd/Ctrl+Shift makes a colour style or variable of what it picks; a live session's welcome sends the document compressed to clients that read it, about seven times smaller (#1665); a control request that times out says truthfully whether it was dropped or is still running and will take effect, and only screenshots and input blame the window (#1673); a board keeps earlier votes' results, listed newest first in Results and deleted one at a time; the object a stamp will stick to is outlined while the stamp tool is over it or a stamp is dragged over it, and board objects take no drops (gap 24); a server can list the files shared from it now for everyone who reaches it, Home ▸ Online shows them and their links carry no secret (`team_files`, part of #742); team library analytics count detached instances per component and file (gap 10); skipping the Open link notice by command is saved with the preferences at once (gap 15); a selected connector's bar on a board sets its start and end points, solid or dashed, its weight and any colour (#1596); cells copied from a spreadsheet paste onto a board as a table; a long layer name stays on one line in the Export button and the Dev Mode header, ending with an ellipsis, and no longer covers the Design panel (#1650); with hundreds of layers selected, the Design panel and the selection overlays stop going over the whole selection on every frame, so dragging 648 layers averages about 1.1 ms of UI per frame (#1571); plugin panels show pictures: an `image` control, base64 within 256 KB and 4,096 px a side, named by its label (gap 6); `.fig` connector labels without a saved snapshot are kept, centred on their line (gap 1); a drag moving many layers re-runs only what sits around them, not each layer's bindings, sizing, text and instance checks, on every move (#1571); damaged font tracking table offsets are checked instead of wrapping on 32-bit targets, and font indexing makes its existing offset bounds explicit (#1125); Bosnian (`bs`) draft catalog; text style dedupe is O(spans) via a last-two cache and fingerprint buckets (#1102); pasted layers whose image the clipboard lacks are named and show a placeholder that reads as missing (#1646); paint-style colours on layers pasted from another design app keep their style instead of turning black (#1638); progress docs cleaned of what merges left twice: rows merged into one, repeated status items removed, and `cargo xtask docs` (in `ci`) fails on a repeated table row or status item; a name typed for a variable or collection finds the one with that exact id before one named like it, and stored mode ids, font family variables and the panels' labels read exact ids (#1613); plugin widgets have hover styles: a layer's `hover` fill, stroke, opacity or text colour shows while the pointer is over it (gap 6); `.fig` elbowed and curved connectors without a saved path are routed like board connectors instead of drawn straight (gap 1); every file a command reads whole by path is a regular file within a cap that fits it: documents 16 GiB, placed images 256 MiB, CLI scripts 1 GiB; `figcraft-cli connect publish` publishes a code repository's code connect mappings to a design file (gap 16); while a drag only moves a large selection, its outlines, box, slots, spacing and Design panel values are worked out once and shifted, not rebuilt on every move (#1571); Shift+Cmd/Ctrl+Enter on a selected board shape adds the next shape already joined to it, and goes on typing (#1597); an imported or pasted file finds an installed family under the other names its fonts give it, and with other case and spacing, instead of reporting it missing (#1292); a development example generates figcraft-cli's shell completions and man pages from its clap definitions (part of #1396); `.fig` ZIP image reads share a total byte budget and count skipped images in the import warnings (#1076); German terminology corrections (M10): gradient stops, OpenType stylistic sets and the Home-screen preference; a component set's property rows fit the 240 px right panel: variant values wrap two to a line there and every + and − stays in view; the server key is checked by comparing SHA-256 hashes of the presented and the kept key, as the deployment guide says (#1130); opening a file contacts no team library server it names until you check it (#1097); a hostile server can no longer grow a client's memory with made-up people, and a session remembers a bounded number of deleted layer ids (#1121); `doc.list` gives AI agents and plugins the open files' names, not their paths (#1117); with a large selection, frames where nothing changed reuse the selection's outlines, box and slots and the Design panel's instance and position values instead of working them out again (#1571); team libraries and plugins larger than the 16 MiB download burst can be downloaded again, more slowly (#1089); a batch keeps one undo snapshot, not one per step (#1099); files nested 256 levels deep import on the browser's 1 MB stack (#1101); SVG and PDF exports count pattern sources and the renders they embed from the layer's depth, so one cap bounds a whole export (#1082); an explicit variables file path (`variable.import`, the `variables` of `file.open` and `file.import`) is refused past 256 MiB or when it isn't a regular file, as the sibling `variables.json` already was (part of #1114); joining and leaving a file nobody is editing reads and writes nothing on the server: it stays loaded a minute after everyone leaves (#1074); opening a `.figcraft` file unpacks at most 6 GiB in all (2 GiB in the browser), every area together (#1079); hovering the canvas hit-tests once per pointer move, not on every frame, and moving an auto layout frame no longer lays out its contents again on every move (#1571); `.fig` instance expansion no longer walks variable nodes for every instance or copies every outer override row into each nested instance (#1077); `comment.people` lists names that were only @mentioned, and `comment.next` and `comment.prompt` carry the mentions too (#1415); grid auto-placement looks for free cells a 64-bit word at a time and skips what blocks it, with the same placements (#1083); with the pen out, Enter or a double-click ends the path and edits the vector with the Move tool instead of drawing on (#1430); a recovery copy made while a version is previewed holds the file, not the version shown; a FigJam `.jam` file opens as a board (#1342); the Actions palette offers only commands that run from it, and no second row reading like a menu item: Place image opens the file dialog (#1574); share links can comment: a third access level beside edit and view, whose holder sees the file live and adds, replies to, reacts to and resolves comments but can't change the design; Preferences ▸ Scroll wheel zoom: a mouse wheel zooms the canvas about the pointer and Cmd/Ctrl+wheel scrolls (#1390); `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); 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 @@ -13,6 +13,7 @@ start with [`docs/gaps.md`](docs/gaps.md). | | Value | Kind | |---|---|---| +| 2026-11-07 | patch | Naming: the handoff mode is called Developer Mode in the interface, its shortcut row, its toasts, the control channel's refusal, the board refusal, the measurement parameter doc, the `.fig` export note, the CLI help and every catalog (24 languages, translations awaiting native review), since Figma holds a US registration for Dev Mode (7574348); command ids (`view.devMode`, `devMode.*`), preference keys and saved-document fields are unchanged, and the Actions palette still finds the command by the old name | | Menu/command breadth | **100%** (212 / 212) | measured, [`docs/parity-checklist.md`](docs/parity-checklist.md) (`cargo xtask parity`) | | Feature breadth | **~85%** (core editor ~94%) | estimated, [`docs/target-app-parity.md`](docs/target-app-parity.md) | | Ready for real work | **~53%** | estimated, same doc (written dimension weights and scores) | diff --git a/apps/figcraft-cli/src/args.rs b/apps/figcraft-cli/src/args.rs index fb4759f7f..4696b8664 100644 --- a/apps/figcraft-cli/src/args.rs +++ b/apps/figcraft-cli/src/args.rs @@ -243,7 +243,7 @@ pub enum ConnectCommand { /// Link a design file's components to their code as a figcraft.connect.json says, and save it /// -/// The mappings are what Dev Mode's Export code connections writes, kept in the code repository: +/// The mappings are what Developer Mode's Export code connections writes, kept in the code repository: /// each links a component (or component set) of that name to its code component for a language. /// Entries that don't fit (an unknown component, a bad template) are reported and skipped. #[derive(Args)] diff --git a/crates/engine/src/boards/board.rs b/crates/engine/src/boards/board.rs index 1f10f5d94..1a66eed22 100644 --- a/crates/engine/src/boards/board.rs +++ b/crates/engine/src/boards/board.rs @@ -76,7 +76,7 @@ pub use reshape::selected_connector_handles; /// Why a board command refuses on a design file. pub const NEEDS_BOARD: &str = "this is for boards: make one with File ▸ New board"; /// Why prototyping and Dev Mode refuse on a board. -pub const NOT_ON_BOARDS: &str = "boards have no prototypes or Dev Mode: use a design file for those"; +pub const NOT_ON_BOARDS: &str = "boards have no prototypes or Developer Mode: use a design file for those"; /// A sticky note's side (it grows taller for long notes). pub const STICKY: f64 = 200.0; diff --git a/crates/engine/src/catalog.rs b/crates/engine/src/catalog.rs index a8a18b38c..576e42cb6 100644 --- a/crates/engine/src/catalog.rs +++ b/crates/engine/src/catalog.rs @@ -18,6 +18,10 @@ macro_rules! m { pub const SEP: &str = "-"; +/// Extra search terms the Actions palette reads, by command id: a name a feature had before it was +/// renamed, so people who know the old name still find the command. Never shown. +pub static SEARCH_ALIASES: &[(&str, &str)] = &[("view.devMode", "Dev Mode")]; + pub static MENU: &[MenuItem] = &[ // File m!("File", "New design file", "file.new"), diff --git a/crates/engine/src/cmd/view.rs b/crates/engine/src/cmd/view.rs index 43feb6c39..46ca701ff 100644 --- a/crates/engine/src/cmd/view.rs +++ b/crates/engine/src/cmd/view.rs @@ -347,7 +347,7 @@ pub fn specs() -> Vec { spec!("view.design", "Design panel", "{}", false, |s, _| ui(s, "panel.design")), spec!("view.prototype", "Prototype panel", "{}", false, |s, _| ui(s, "panel.prototype")), spec!("view.designPrototype", "Switch between Design and Prototype", "{}", false, |s, _| ui(s, "panel.designPrototype")), - spec!("view.devMode", "Dev Mode", "{}", false, |s, _| ui(s, "panel.devMode")), + spec!("view.devMode", "Developer Mode", "{}", false, |s, _| ui(s, "panel.devMode")), spec!("view.leftSidebar", "Show/Hide left sidebar", "{}", false, |s, _| ui(s, "panel.leftSidebar")), spec!( "view.panelWidths", diff --git a/crates/engine/src/dev/dev_measurements.rs b/crates/engine/src/dev/dev_measurements.rs index c5df56ea2..6c5091fc9 100644 --- a/crates/engine/src/dev/dev_measurements.rs +++ b/crates/engine/src/dev/dev_measurements.rs @@ -200,7 +200,7 @@ pub fn specs() -> Vec { spec!( "devMode.addMeasurement", "Add measurement", - "{from? (default the first selected layer), to? (a layer on the same page), side? (top|right|bottom|left)} keeps a measurement on the canvas: between `from` and `to`, across one side of `from`, or (neither) to the layer around it; Dev Mode draws it and it follows the layers → {from, index, lines}", + "{from? (default the first selected layer), to? (a layer on the same page), side? (top|right|bottom|left)} keeps a measurement on the canvas: between `from` and `to`, across one side of `from`, or (neither) to the layer around it; Developer Mode draws it and it follows the layers → {from, index, lines}", true, add ), diff --git a/crates/engine/src/keymap.rs b/crates/engine/src/keymap.rs index da75cfdd0..e45ced8d4 100644 --- a/crates/engine/src/keymap.rs +++ b/crates/engine/src/keymap.rs @@ -224,7 +224,7 @@ pub static KEYS: &[Binding] = &[ k(View, "view.design", &["Alt+8"]).label("Design panel"), k(View, "view.prototype", &["Alt+9"]).label("Prototype panel"), k(View, "view.designPrototype", &["Shift+E"]), - k(View, "view.devMode", &["Shift+D"]).label("Dev Mode on/off"), + k(View, "view.devMode", &["Shift+D"]).label("Developer Mode on/off"), k(View, "view.minimize", &["Shift+Cmd+\\"]), k(View, "view.nextRegion", &["F6"]).win(&["F6", "Ctrl+F6"]).label("Focus the next region (toolbar, layers, canvas, properties)").both(), k(View, "view.previousRegion", &["Shift+F6"]).label("Focus the previous region").both(), diff --git a/crates/format/src/fig/export/layer.rs b/crates/format/src/fig/export/layer.rs index b4fda9138..bebd09059 100644 --- a/crates/format/src/fig/export/layer.rs +++ b/crates/format/src/fig/export/layer.rs @@ -304,7 +304,7 @@ impl<'d> Exporter<'d> { || !n.measurements.is_empty() || !n.dev_status.is_none() { - self.warn("Dev Mode annotations, statuses, resources, measurements and code links were left out"); + self.warn("Developer Mode annotations, statuses, resources, measurements and code links were left out"); } if !n.plugin_data.is_empty() { self.warn("plugin data was left out"); diff --git a/crates/ui-egui/locales/ar.toml b/crates/ui-egui/locales/ar.toml index da4474e79..e50a9b9c4 100644 --- a/crates/ui-egui/locales/ar.toml +++ b/crates/ui-egui/locales/ar.toml @@ -433,9 +433,9 @@ status = "draft — needs native review" "Detach styles" = "فصل الأنماط" "Detach variable" = "فصل المتغير" "Details" = "التفاصيل" -"Dev Mode" = "وضع المطور" -"Dev Mode only inspects: switch to Design (Shift+D) to edit" = "وضع المطور للفحص فقط؛ انتقل إلى التصميم (Shift+D) للتحرير" "Dev resources" = "موارد المطورين" +"Developer Mode" = "وضع المطور" +"Developer Mode only inspects: switch to Design (Shift+D) to edit" = "وضع المطور للفحص فقط؛ انتقل إلى التصميم (Shift+D) للتحرير" "Development" = "التطوير" "Device" = "الجهاز" "Device at 100%" = "الجهاز بحجم 100%" diff --git a/crates/ui-egui/locales/bn.toml b/crates/ui-egui/locales/bn.toml index 5d01928bf..b79877271 100644 --- a/crates/ui-egui/locales/bn.toml +++ b/crates/ui-egui/locales/bn.toml @@ -415,8 +415,8 @@ status = "draft — needs native review" "Detach styles" = "স্টাইলগুলো বিচ্ছিন্ন করুন" "Detach variable" = "ভেরিয়েবল বিচ্ছিন্ন করুন" "Details" = "বিস্তারিত" -"Dev Mode" = "ডেভ মোড" "Dev resources" = "ডেভ রিসোর্স" +"Developer Mode" = "ডেভেলপার মোড" "Development" = "ডেভেলপমেন্ট" "Device" = "ডিভাইস" "Device at 100%" = "১০০%-এ ডিভাইস" diff --git a/crates/ui-egui/locales/bs.toml b/crates/ui-egui/locales/bs.toml index 36d71664c..919bc0381 100644 --- a/crates/ui-egui/locales/bs.toml +++ b/crates/ui-egui/locales/bs.toml @@ -436,9 +436,9 @@ status = "draft — needs native review" "Detach styles" = "Odvoji stilove" "Detach variable" = "Odvoji varijablu" "Details" = "Detalji" -"Dev Mode" = "Dev Mode" -"Dev Mode only inspects: switch to Design (Shift+D) to edit" = "Dev Mode služi samo za pregled: prebacite se na Dizajn (Shift+D) za uređivanje" "Dev resources" = "Resursi za programere" +"Developer Mode" = "Razvojni režim" +"Developer Mode only inspects: switch to Design (Shift+D) to edit" = "Razvojni režim služi samo za pregled: prebacite se na Dizajn (Shift+D) za uređivanje" "Development" = "Razvoj" "Device" = "Uređaj" "Device at 100%" = "Uređaj na 100%" @@ -2513,7 +2513,7 @@ status = "draft — needs native review" "view.contextMenu" = "Prikaži kontekstni meni" "view.design" = "Panel dizajna" "view.designPrototype" = "Prebacuj između Dizajna i Prototipa" -"view.devMode" = "Dev Mode" +"view.devMode" = "Razvojni režim" "view.frameOutlines" = "Konture okvira" "view.fullscreen" = "Preko cijelog ekrana" "view.get" = "Dohvati prikaz" diff --git a/crates/ui-egui/locales/cs.toml b/crates/ui-egui/locales/cs.toml index d401654ac..7ecc6779a 100644 --- a/crates/ui-egui/locales/cs.toml +++ b/crates/ui-egui/locales/cs.toml @@ -405,8 +405,8 @@ status = "draft — needs native review" "Detach styles" = "Odpojit styly" "Detach variable" = "Odpojit proměnnou" "Details" = "Podrobnosti" -"Dev Mode" = "Režim pro vývojáře" "Dev resources" = "Zdroje pro vývojáře" +"Developer Mode" = "Režim pro vývojáře" "Development" = "Vývoj" "Device" = "Zařízení" "Device at 100%" = "Zařízení ve 100 %" diff --git a/crates/ui-egui/locales/de.toml b/crates/ui-egui/locales/de.toml index 6fd1838d6..f57fcc5d3 100644 --- a/crates/ui-egui/locales/de.toml +++ b/crates/ui-egui/locales/de.toml @@ -404,9 +404,9 @@ status = "draft — needs native review" "Detach styles" = "Stile lösen" "Detach variable" = "Variable lösen" "Details" = "Details" -"Dev Mode" = "Entwicklermodus" -"Dev Mode only inspects: switch to Design (Shift+D) to edit" = "Der Entwicklermodus prüft nur: Wechseln Sie zu Design (Shift+D), um zu bearbeiten" "Dev resources" = "Entwicklerressourcen" +"Developer Mode" = "Entwicklermodus" +"Developer Mode only inspects: switch to Design (Shift+D) to edit" = "Der Entwicklermodus prüft nur: Wechseln Sie zu Design (Shift+D), um zu bearbeiten" "Development" = "Entwicklung" "Device" = "Gerät" "Device at 100%" = "Gerät bei 100 %" diff --git a/crates/ui-egui/locales/en.toml b/crates/ui-egui/locales/en.toml index bb8546ee6..625ee3bd8 100644 --- a/crates/ui-egui/locales/en.toml +++ b/crates/ui-egui/locales/en.toml @@ -479,9 +479,9 @@ name = "English" "Detach styles" = "Detach styles" "Detach variable" = "Detach variable" "Details" = "Details" -"Dev Mode" = "Dev Mode" -"Dev Mode only inspects: switch to Design (Shift+D) to edit" = "Dev Mode only inspects: switch to Design (Shift+D) to edit" "Dev resources" = "Dev resources" +"Developer Mode" = "Developer Mode" +"Developer Mode only inspects: switch to Design (Shift+D) to edit" = "Developer Mode only inspects: switch to Design (Shift+D) to edit" "Development" = "Development" "Device" = "Device" "Device at 100%" = "Device at 100%" diff --git a/crates/ui-egui/locales/es.toml b/crates/ui-egui/locales/es.toml index 2baa2b31e..258a1527d 100644 --- a/crates/ui-egui/locales/es.toml +++ b/crates/ui-egui/locales/es.toml @@ -438,9 +438,9 @@ status = "draft — needs native review" "Detach styles" = "Separar estilos" "Detach variable" = "Separar variable" "Details" = "Detalles" -"Dev Mode" = "Modo desarrollo" -"Dev Mode only inspects: switch to Design (Shift+D) to edit" = "El modo desarrollo solo inspecciona: cambia a Diseño (Shift+D) para editar" "Dev resources" = "Recursos de desarrollo" +"Developer Mode" = "Modo de desarrollo" +"Developer Mode only inspects: switch to Design (Shift+D) to edit" = "El modo de desarrollo solo inspecciona: cambia a Diseño (Shift+D) para editar" "Development" = "Desarrollo" "Device" = "Dispositivo" "Device at 100%" = "Dispositivo al 100%" @@ -2476,7 +2476,7 @@ status = "draft — needs native review" "view.contextMenu" = "Mostrar menú contextual" "view.design" = "Panel de diseño" "view.designPrototype" = "Alternar entre Diseño y Prototipo" -"view.devMode" = "Modo desarrollo" +"view.devMode" = "Modo de desarrollo" "view.frameOutlines" = "Contornos de marcos" "view.get" = "Obtener vista" "view.layers" = "Panel de capas" diff --git a/crates/ui-egui/locales/fi.toml b/crates/ui-egui/locales/fi.toml index 2f5db319a..88fb21e06 100644 --- a/crates/ui-egui/locales/fi.toml +++ b/crates/ui-egui/locales/fi.toml @@ -439,9 +439,9 @@ status = "draft — needs native review" "Detach styles" = "Irrota tyylit" "Detach variable" = "Irrota muuttuja" "Details" = "Tiedot" -"Dev Mode" = "Kehittäjätila" -"Dev Mode only inspects: switch to Design (Shift+D) to edit" = "Kehittäjätilassa voi vain tarkastella: vaihda Suunnittelu-tilaan (Shift+D) muokataksesi" "Dev resources" = "Kehittäjäresurssit" +"Developer Mode" = "Kehittäjätila" +"Developer Mode only inspects: switch to Design (Shift+D) to edit" = "Kehittäjätilassa voi vain tarkastella: vaihda Suunnittelu-tilaan (Shift+D) muokataksesi" "Development" = "Kehitys" "Device" = "Laite" "Device at 100%" = "Laite koossa 100 %" diff --git a/crates/ui-egui/locales/fr.toml b/crates/ui-egui/locales/fr.toml index d47ad9597..e40b3a060 100644 --- a/crates/ui-egui/locales/fr.toml +++ b/crates/ui-egui/locales/fr.toml @@ -415,9 +415,9 @@ status = "draft — needs native review" "Detach styles" = "Détacher les styles" "Detach variable" = "Détacher la variable" "Details" = "Détails" -"Dev Mode" = "Mode Dev" -"Dev Mode only inspects: switch to Design (Shift+D) to edit" = "Le mode Dev sert seulement à inspecter : passez en Design (Shift+D) pour modifier" "Dev resources" = "Ressources de développement" +"Developer Mode" = "Mode développeur" +"Developer Mode only inspects: switch to Design (Shift+D) to edit" = "Le mode développeur sert seulement à inspecter : passez en Design (Shift+D) pour modifier" "Development" = "Développement" "Device" = "Appareil" "Device at 100%" = "Appareil à 100 %" @@ -2413,7 +2413,7 @@ status = "draft — needs native review" "view.contextMenu" = "Afficher le menu contextuel" "view.design" = "Panneau Design" "view.designPrototype" = "Basculer entre Design et Prototype" -"view.devMode" = "Mode Dev" +"view.devMode" = "Mode développeur" "view.frameOutlines" = "Contours des cadres" "view.get" = "Obtenir l’affichage" "view.layers" = "Panneau Calques" diff --git a/crates/ui-egui/locales/id.toml b/crates/ui-egui/locales/id.toml index cbebaf1c4..59dbd492c 100644 --- a/crates/ui-egui/locales/id.toml +++ b/crates/ui-egui/locales/id.toml @@ -419,8 +419,8 @@ status = "draft — needs native review" "Detach styles" = "Lepas gaya" "Detach variable" = "Lepas variabel" "Details" = "Detail" -"Dev Mode" = "Dev Mode" "Dev resources" = "Sumber daya dev" +"Developer Mode" = "Mode Pengembang" "Development" = "Pengembangan" "Device" = "Perangkat" "Device at 100%" = "Perangkat pada 100%" @@ -2367,7 +2367,7 @@ status = "draft — needs native review" "view.contextMenu" = "Tampilkan menu konteks" "view.design" = "Panel desain" "view.designPrototype" = "Beralih antara Desain dan Prototype" -"view.devMode" = "Dev Mode" +"view.devMode" = "Mode Pengembang" "view.frameOutlines" = "Outline frame" "view.get" = "Ambil tampilan" "view.layers" = "Panel layer" diff --git a/crates/ui-egui/locales/it.toml b/crates/ui-egui/locales/it.toml index 364aeedf8..f61ee86fe 100644 --- a/crates/ui-egui/locales/it.toml +++ b/crates/ui-egui/locales/it.toml @@ -398,8 +398,8 @@ status = "draft — needs native review" "Detach styles" = "Scollega stili" "Detach variable" = "Scollega variabile" "Details" = "Dettagli" -"Dev Mode" = "Modalità sviluppo" "Dev resources" = "Risorse per sviluppatori" +"Developer Mode" = "Modalità sviluppo" "Development" = "Sviluppo" "Device" = "Dispositivo" "Device at 100%" = "Dispositivo al 100%" diff --git a/crates/ui-egui/locales/ja.toml b/crates/ui-egui/locales/ja.toml index 5a8918a20..d440e910f 100644 --- a/crates/ui-egui/locales/ja.toml +++ b/crates/ui-egui/locales/ja.toml @@ -413,9 +413,9 @@ status = "draft — needs native review" "Detach styles" = "スタイルを切り離す" "Detach variable" = "変数を切り離す" "Details" = "詳細" -"Dev Mode" = "開発モード" -"Dev Mode only inspects: switch to Design (Shift+D) to edit" = "開発モードは検査専用です。編集するにはデザイン (Shift+D) に切り替えてください" "Dev resources" = "開発リソース" +"Developer Mode" = "開発者モード" +"Developer Mode only inspects: switch to Design (Shift+D) to edit" = "開発者モードは検査専用です。編集するにはデザイン (Shift+D) に切り替えてください" "Development" = "開発" "Device" = "デバイス" "Device at 100%" = "デバイス(100%)" @@ -2391,7 +2391,7 @@ status = "draft — needs native review" "view.contextMenu" = "コンテキストメニューを表示" "view.design" = "デザインパネル" "view.designPrototype" = "デザインとプロトタイプを切り替え" -"view.devMode" = "開発モード" +"view.devMode" = "開発者モード" "view.frameOutlines" = "フレームのアウトライン" "view.get" = "ビューを取得" "view.layers" = "レイヤーパネル" diff --git a/crates/ui-egui/locales/ka.toml b/crates/ui-egui/locales/ka.toml index 2af58f385..4c6764afa 100644 --- a/crates/ui-egui/locales/ka.toml +++ b/crates/ui-egui/locales/ka.toml @@ -426,9 +426,9 @@ status = "draft — reviewed by a native speaker, awaiting a maintainer's approv "Detach styles" = "სტილების გამოყოფა" "Detach variable" = "ცვლადის გამოყოფა" "Details" = "დეტალები" -"Dev Mode" = "დეველოპერის რეჟიმი" -"Dev Mode only inspects: switch to Design (Shift+D) to edit" = "დეველოპერის რეჟიმში მხოლოდ ინსპექტირებაა შესაძლებელი: რედაქტირებისთვის გადაერთეთ დიზაინზე (Shift+D)" "Dev resources" = "დეველოპერის რესურსები" +"Developer Mode" = "დეველოპერის რეჟიმი" +"Developer Mode only inspects: switch to Design (Shift+D) to edit" = "დეველოპერის რეჟიმში მხოლოდ ინსპექტირებაა შესაძლებელი: რედაქტირებისთვის გადაერთეთ დიზაინზე (Shift+D)" "Development" = "დეველოპმენტი" "Device" = "მოწყობილობა" "Device at 100%" = "მოწყობილობა 100%-ზე" diff --git a/crates/ui-egui/locales/ko.toml b/crates/ui-egui/locales/ko.toml index ebea678d1..b4c960fbc 100644 --- a/crates/ui-egui/locales/ko.toml +++ b/crates/ui-egui/locales/ko.toml @@ -397,9 +397,9 @@ status = "draft — needs native review" "Detach styles" = "스타일 분리" "Detach variable" = "변수 분리" "Details" = "세부 정보" -"Dev Mode" = "개발 모드" -"Dev Mode only inspects: switch to Design (Shift+D) to edit" = "개발 모드는 검사 전용입니다. 편집하려면 디자인(Shift+D)으로 전환하세요" "Dev resources" = "개발 리소스" +"Developer Mode" = "개발자 모드" +"Developer Mode only inspects: switch to Design (Shift+D) to edit" = "개발자 모드는 검사 전용입니다. 편집하려면 디자인(Shift+D)으로 전환하세요" "Development" = "개발" "Device" = "기기" "Device at 100%" = "기기 100%" @@ -2423,7 +2423,7 @@ status = "draft — needs native review" "view.contextMenu" = "컨텍스트 메뉴 표시" "view.design" = "디자인 패널" "view.designPrototype" = "디자인과 프로토타입 전환" -"view.devMode" = "개발 모드" +"view.devMode" = "개발자 모드" "view.frameOutlines" = "프레임 윤곽선" "view.get" = "보기 가져오기" "view.layers" = "레이어 패널" diff --git a/crates/ui-egui/locales/nl.toml b/crates/ui-egui/locales/nl.toml index e43d40b5e..2d6816ab6 100644 --- a/crates/ui-egui/locales/nl.toml +++ b/crates/ui-egui/locales/nl.toml @@ -417,8 +417,8 @@ status = "draft — needs native review" "Detach styles" = "Stijlen loskoppelen" "Detach variable" = "Variabele loskoppelen" "Details" = "Details" -"Dev Mode" = "Dev Mode" "Dev resources" = "Dev-bronnen" +"Developer Mode" = "Ontwikkelaarsmodus" "Development" = "Ontwikkeling" "Device" = "Apparaat" "Device at 100%" = "Apparaat op 100%" @@ -1602,7 +1602,7 @@ status = "draft — needs native review" "Wedge" = "Wedge" "Weekly planner" = "Weekplanner" "Weight" = "Dikte" -"What changed since it was marked ready for dev" = "Wat er veranderd is sinds het is gemarkeerd als klaar voor Dev Mode" +"What changed since it was marked ready for dev" = "Wat er veranderd is sinds het is gemarkeerd als klaar voor ontwikkeling" "What is wrong with this file? The server's operator gets your report." = "Wat klopt er niet aan dit bestand? Je melding gaat naar de beheerder van de server." "What it's for" = "Waarvoor het dient" "What this changed from isn't known: it changed before FigCraft kept a copy of ready-for-dev frames. Resolve the change (or mark it ready again) to compare from now on." = "Vanwaar dit is veranderd, is niet bekend: het veranderde voordat FigCraft een kopie bewaarde van dev-klare frames. Los de wijziging op (of markeer het weer als te vergelijken) om vanaf nu te kunnen vergelijken." @@ -2408,7 +2408,7 @@ status = "draft — needs native review" "view.contextMenu" = "Contextmenu tonen" "view.design" = "Ontwerppaneel" "view.designPrototype" = "Wisselen tussen Design en Prototype" -"view.devMode" = "Dev Mode" +"view.devMode" = "Ontwikkelaarsmodus" "view.frameOutlines" = "Framecontouren" "view.fullscreen" = "Volledig scherm" "view.get" = "Weergave ophalen" diff --git a/crates/ui-egui/locales/pl.toml b/crates/ui-egui/locales/pl.toml index 68fe136d7..bcc9f0051 100644 --- a/crates/ui-egui/locales/pl.toml +++ b/crates/ui-egui/locales/pl.toml @@ -3,6 +3,7 @@ # Wording reviewed and approved by Lucas (@lukasz-sklad). # Complete Polish localization for FigCraft. # See docs/translating.md; plural forms follow Polish CLDR integer rules. +# The Developer Mode label (2026-11-07) is machine-translated and awaits review. [meta] name = "Polski" @@ -416,8 +417,8 @@ name = "Polski" "Detach styles" = "Odłącz style" "Detach variable" = "Odłącz zmienną" "Details" = "Szczegóły" -"Dev Mode" = "Tryb deweloperski" "Dev resources" = "Zasoby programistyczne" +"Developer Mode" = "Tryb deweloperski" "Development" = "Programowanie" "Device" = "Urządzenie" "Device at 100%" = "Urządzenie w 100%" @@ -2426,7 +2427,7 @@ name = "Polski" "view.contextMenu" = "Pokaż menu kontekstowe" "view.design" = "Panel projektu" "view.designPrototype" = "Przełącz między Projektem a Prototypem" -"view.devMode" = "Tryb programisty" +"view.devMode" = "Tryb deweloperski" "view.frameOutlines" = "Kontury ramek" "view.fullscreen" = "Tryb pełnoekranowy" "view.get" = "Pobierz widok" diff --git a/crates/ui-egui/locales/pt-BR.toml b/crates/ui-egui/locales/pt-BR.toml index c2ccba835..9108dea4d 100644 --- a/crates/ui-egui/locales/pt-BR.toml +++ b/crates/ui-egui/locales/pt-BR.toml @@ -396,9 +396,9 @@ status = "draft — needs native review" "Detach styles" = "Desvincular estilos" "Detach variable" = "Desvincular variável" "Details" = "Detalhes" -"Dev Mode" = "Modo Dev" -"Dev Mode only inspects: switch to Design (Shift+D) to edit" = "O modo Dev só inspeciona: mude para Design (Shift+D) para editar" "Dev resources" = "Recursos de desenvolvimento" +"Developer Mode" = "Modo do desenvolvedor" +"Developer Mode only inspects: switch to Design (Shift+D) to edit" = "O modo do desenvolvedor só inspeciona: mude para Design (Shift+D) para editar" "Development" = "Desenvolvimento" "Device" = "Dispositivo" "Device at 100%" = "Dispositivo a 100%" @@ -2422,7 +2422,7 @@ status = "draft — needs native review" "view.contextMenu" = "Mostrar menu de contexto" "view.design" = "Painel de design" "view.designPrototype" = "Alternar entre Design e Protótipo" -"view.devMode" = "Modo Dev" +"view.devMode" = "Modo do desenvolvedor" "view.frameOutlines" = "Contornos dos quadros" "view.get" = "Obter visualização" "view.layers" = "Painel de camadas" diff --git a/crates/ui-egui/locales/ru.toml b/crates/ui-egui/locales/ru.toml index 01e1d86ac..36e9abeaa 100644 --- a/crates/ui-egui/locales/ru.toml +++ b/crates/ui-egui/locales/ru.toml @@ -1,8 +1,9 @@ # FigCraft interface strings: Russian. # Wording reviewed and approved by VanTan. # Strings added since that review (2026-10-20) are a machine draft awaiting review. -# Keep Frame, Dev Mode and Smart animate as familiar tool names. +# Keep Frame and Smart animate as familiar tool names. # See docs/translating.md; plural forms follow Russian integer rules. +# The Developer Mode label (2026-11-07) is machine-translated and awaits review. [meta] name = "Русский" @@ -416,8 +417,8 @@ name = "Русский" "Detach styles" = "Отвязать стили" "Detach variable" = "Отвязать переменную" "Details" = "Подробности" -"Dev Mode" = "Dev Mode" "Dev resources" = "Ресурсы для разработки" +"Developer Mode" = "Режим разработчика" "Development" = "Разработка" "Device" = "Устройство" "Device at 100%" = "Устройство в масштабе 100%" @@ -2427,7 +2428,7 @@ name = "Русский" "view.contextMenu" = "Показать контекстное меню" "view.design" = "Панель дизайна" "view.designPrototype" = "Переключить дизайн/прототип" -"view.devMode" = "Dev Mode" +"view.devMode" = "Режим разработчика" "view.frameOutlines" = "Контуры Frame" "view.get" = "Получить вид" "view.layers" = "Панель слоёв" diff --git a/crates/ui-egui/locales/sv.toml b/crates/ui-egui/locales/sv.toml index a94feb2a9..ce02b3f7f 100644 --- a/crates/ui-egui/locales/sv.toml +++ b/crates/ui-egui/locales/sv.toml @@ -2,6 +2,7 @@ # # Written from the English catalog with ordinary Swedish software vocabulary; no other catalog and no # proprietary localisation was translated from. Brand names and keyboard glyphs stay as they are. +# The Developer Mode label (2026-11-07) is machine-translated and awaits review. [meta] name = "Svenska" @@ -442,9 +443,9 @@ name = "Svenska" "Detach styles" = "Koppla bort stilar" "Detach variable" = "Koppla bort variabel" "Details" = "Detaljer" -"Dev Mode" = "Utvecklarläge" -"Dev Mode only inspects: switch to Design (Shift+D) to edit" = "Utvecklarläget kan bara granska: växla till Design (Shift+D) för att redigera" "Dev resources" = "Utvecklarresurser" +"Developer Mode" = "Utvecklarläge" +"Developer Mode only inspects: switch to Design (Shift+D) to edit" = "Utvecklarläget kan bara granska: växla till Design (Shift+D) för att redigera" "Development" = "Utveckling" "Device" = "Enhet" "Device at 100%" = "Enhet i 100 %" diff --git a/crates/ui-egui/locales/th.toml b/crates/ui-egui/locales/th.toml index 81b581ce0..f4662a2b8 100644 --- a/crates/ui-egui/locales/th.toml +++ b/crates/ui-egui/locales/th.toml @@ -414,9 +414,9 @@ status = "draft — needs native review" "Detach styles" = "ถอดสไตล์" "Detach variable" = "ถอดตัวแปร" "Details" = "รายละเอียด" -"Dev Mode" = "Dev Mode" -"Dev Mode only inspects: switch to Design (Shift+D) to edit" = "Dev Mode ใช้ตรวจสอบเท่านั้น: สลับไปที่ดีไซน์ (Shift+D) เพื่อแก้ไข" "Dev resources" = "แหล่งข้อมูลสำหรับนักพัฒนา" +"Developer Mode" = "โหมดนักพัฒนา" +"Developer Mode only inspects: switch to Design (Shift+D) to edit" = "โหมดนักพัฒนาใช้ตรวจสอบเท่านั้น: สลับไปที่ดีไซน์ (Shift+D) เพื่อแก้ไข" "Development" = "การพัฒนา" "Device" = "อุปกรณ์" "Device at 100%" = "อุปกรณ์ที่ 100%" @@ -2385,7 +2385,7 @@ status = "draft — needs native review" "view.contextMenu" = "แสดงเมนูบริบท" "view.design" = "แผงดีไซน์" "view.designPrototype" = "สลับระหว่างดีไซน์และโปรโตไทป์" -"view.devMode" = "Dev Mode" +"view.devMode" = "โหมดนักพัฒนา" "view.frameOutlines" = "เค้าร่างเฟรม" "view.get" = "ดึงมุมมอง" "view.layers" = "แผงเลเยอร์" diff --git a/crates/ui-egui/locales/tr.toml b/crates/ui-egui/locales/tr.toml index b07d25462..b98f8389b 100644 --- a/crates/ui-egui/locales/tr.toml +++ b/crates/ui-egui/locales/tr.toml @@ -5,6 +5,7 @@ # address the user politely (-in/-ın: "seçin", "tıklayın"). The percent sign goes before the number # (%50). Turkish nouns stay singular after a count, so plural entries give only `other`. See # docs/translating.md. +# The Developer Mode label (2026-11-07) is machine-translated and awaits review. [meta] name = "Türkçe" @@ -409,8 +410,8 @@ name = "Türkçe" "Detach styles" = "Stilleri ayır" "Detach variable" = "Değişkeni ayır" "Details" = "Ayrıntılar" -"Dev Mode" = "Geliştirici Modu" "Dev resources" = "Geliştirici kaynakları" +"Developer Mode" = "Geliştirici Modu" "Development" = "Geliştirme" "Device" = "Cihaz" "Device at 100%" = "Cihaz %100'de" diff --git a/crates/ui-egui/locales/vi.toml b/crates/ui-egui/locales/vi.toml index 9a4d600fe..5bd9cf7ff 100644 --- a/crates/ui-egui/locales/vi.toml +++ b/crates/ui-egui/locales/vi.toml @@ -399,8 +399,8 @@ status = "draft — needs native review" "Detach styles" = "Tách các kiểu" "Detach variable" = "Tách biến" "Details" = "Chi tiết" -"Dev Mode" = "Dev Mode" "Dev resources" = "Tài nguyên dev" +"Developer Mode" = "Chế độ nhà phát triển" "Development" = "Phát triển" "Device" = "Thiết bị" "Device at 100%" = "Thiết bị ở mức 100%" @@ -2306,7 +2306,7 @@ status = "draft — needs native review" "view.contextMenu" = "Hiện trình đơn ngữ cảnh" "view.design" = "Bảng Thiết kế" "view.designPrototype" = "Chuyển đổi giữa Thiết kế và Nguyên mẫu" -"view.devMode" = "Dev Mode" +"view.devMode" = "Chế độ nhà phát triển" "view.frameOutlines" = "Đường viền khung" "view.fullscreen" = "Toàn màn hình" "view.get" = "Lấy khung nhìn" diff --git a/crates/ui-egui/locales/zh-Hans.toml b/crates/ui-egui/locales/zh-Hans.toml index b48345acc..f78f0d938 100644 --- a/crates/ui-egui/locales/zh-Hans.toml +++ b/crates/ui-egui/locales/zh-Hans.toml @@ -401,9 +401,9 @@ status = "draft — needs native review" "Detach styles" = "分离样式" "Detach variable" = "分离变量" "Details" = "详细" -"Dev Mode" = "开发模式" -"Dev Mode only inspects: switch to Design (Shift+D) to edit" = "开发模式仅用于检查:切换到设计 (Shift+D) 进行编辑" "Dev resources" = "开发资源" +"Developer Mode" = "开发者模式" +"Developer Mode only inspects: switch to Design (Shift+D) to edit" = "开发者模式仅用于检查:切换到设计 (Shift+D) 进行编辑" "Development" = "开发" "Device" = "设备" "Device at 100%" = "设备 100%" @@ -2322,7 +2322,7 @@ status = "draft — needs native review" "view.contextMenu" = "显示上下文菜单" "view.design" = "设计面板" "view.designPrototype" = "在设计和原型之间切换" -"view.devMode" = "开发模式" +"view.devMode" = "开发者模式" "view.frameOutlines" = "画框轮廓" "view.get" = "获取视图" "view.layers" = "图层面板" diff --git a/crates/ui-egui/locales/zh-Hant.toml b/crates/ui-egui/locales/zh-Hant.toml index f14698b00..a1179aa0c 100644 --- a/crates/ui-egui/locales/zh-Hant.toml +++ b/crates/ui-egui/locales/zh-Hant.toml @@ -453,9 +453,9 @@ status = "draft — needs native review" "Detach styles" = "分離樣式" "Detach variable" = "分離變數" "Details" = "詳細資料" -"Dev Mode" = "Dev Mode" -"Dev Mode only inspects: switch to Design (Shift+D) to edit" = "Dev Mode 僅供檢視:切換至「設計」(Shift+D) 以編輯" "Dev resources" = "開發資源" +"Developer Mode" = "開發者模式" +"Developer Mode only inspects: switch to Design (Shift+D) to edit" = "開發者模式僅供檢視:切換至「設計」(Shift+D) 以編輯" "Development" = "開發" "Device" = "裝置" "Device at 100%" = "裝置 100%" @@ -2572,7 +2572,7 @@ status = "draft — needs native review" "view.contextMenu" = "顯示內容選單" "view.design" = "設計面板" "view.designPrototype" = "在設計與原型之間切換" -"view.devMode" = "Dev Mode" +"view.devMode" = "開發者模式" "view.frameOutlines" = "框架外框" "view.fullscreen" = "全螢幕" "view.get" = "取得檢視" diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs index 1290f4d5b..f0513520b 100644 --- a/crates/ui-egui/src/app.rs +++ b/crates/ui-egui/src/app.rs @@ -358,11 +358,7 @@ impl FigApp { // each side of the panel (240 pt by default). The edge is drawn in `right_panel`. .frame(egui::Frame::NONE.fill(t.panel)) .show(ui, |ui| { - let label = match self.ui.right_tab { - RightTab::Design => i18n::tr("Design"), - RightTab::Prototype => i18n::tr("Prototype"), - RightTab::Inspect => i18n::tr("Dev Mode"), - }; + let label = right_tab_label(self.ui.right_tab); a11y::region(&mut self.ui.nav, ui, a11y::Region::Properties, egui::accesskit::Role::Pane, label); right::right_panel(self, ui) }); @@ -443,3 +439,12 @@ impl FigApp { self.observe_ime(&ctx, !typing && !self.field_focus && self.ui.palette.is_none()); } } + +/// The right panel's tab name, which its accessibility region reads. +pub(crate) fn right_tab_label(tab: RightTab) -> &'static str { + match tab { + RightTab::Design => i18n::tr("Design"), + RightTab::Prototype => i18n::tr("Prototype"), + RightTab::Inspect => i18n::tr("Developer Mode"), + } +} diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 18d888c82..251ff25e6 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -235,7 +235,7 @@ pub fn show(app: &mut FigApp, ui: &mut Ui, worker: &mut Worker) { let label = if documents { tr("Drop to open") } else if app.ui.right_tab == crate::RightTab::Inspect { - tr("Dev Mode only inspects: switch to Design (Shift+D) to edit") + tr("Developer Mode only inspects: switch to Design (Shift+D) to edit") } else { tr("Drop to import") }; diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs index ee9f39e2d..925b1fc39 100644 --- a/crates/ui-egui/src/control.rs +++ b/crates/ui-egui/src/control.rs @@ -1062,7 +1062,7 @@ fn ui_key(app: &mut FigApp, ctx: &Context, p: &Value) -> Result { }; // Dev Mode only inspects, as the window's key path says. if crate::menus::dev_mode_refuses(app, cmd) { - return Err("Dev Mode only inspects: switch to Design to edit".into()); + return Err("Developer Mode only inspects: switch to Design to edit".into()); } app.session.execute(cmd, ¶ms).map_err(|e| e.to_string()) } diff --git a/crates/ui-egui/src/devmode_tail_tests.rs b/crates/ui-egui/src/devmode_tail_tests.rs index b7c7a755c..688f01e90 100644 --- a/crates/ui-egui/src/devmode_tail_tests.rs +++ b/crates/ui-egui/src/devmode_tail_tests.rs @@ -319,7 +319,7 @@ fn drop_on_canvas(u: &mut crate::layers_drag_tests::Ui, names: &[&str], bytes: & #[test] fn dev_mode_imports_no_dropped_file_into_the_design_and_says_why() { let svg = br#""#; - let refusal = "Dev Mode only inspects: switch to Design (Shift+D) to edit"; + let refusal = "Developer Mode only inspects: switch to Design (Shift+D) to edit"; let mut u = crate::layers_drag_tests::Ui::new(Session::new()); let layers = |u: &crate::layers_drag_tests::Ui| u.app.session.doc().unwrap().node(u.app.session.page().unwrap()).unwrap().children.len(); u.app.ui.right_tab = crate::RightTab::Inspect; diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 11c96da74..60c68f526 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -184,6 +184,8 @@ mod mirror_tests; #[cfg(test)] mod missing_fonts_tests; #[cfg(test)] +mod naming_tests; +#[cfg(test)] mod online_tests; #[cfg(test)] mod open_issues_tests; diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 98fd881d1..a5361778c 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -53,7 +53,7 @@ pub const UI_COMMANDS: &[&str] = &[ pub fn dev_mode_refuses(app: &mut FigApp, id: &str) -> bool { let refused = app.ui.right_tab == crate::RightTab::Inspect && !figcraft_engine::devinspect::dev_mode_allows(id); if refused { - app.toast(tr("Dev Mode only inspects: switch to Design (Shift+D) to edit")); + app.toast(tr("Developer Mode only inspects: switch to Design (Shift+D) to edit")); } refused } diff --git a/crates/ui-egui/src/menus/palette.rs b/crates/ui-egui/src/menus/palette.rs index 69c2408cf..7b3697660 100644 --- a/crates/ui-egui/src/menus/palette.rs +++ b/crates/ui-egui/src/menus/palette.rs @@ -242,6 +242,19 @@ fn draw_row(ui: &mut Ui, t: &Tokens, row: &ActionRow, highlighted: bool) -> egui } /// Whether `label` (or `extra`) matches the query, in any case. +/// What a search reads besides the label in the UI's language: the English label, the id, and a +/// name the feature had before it was renamed ([`figcraft_engine::catalog::SEARCH_ALIASES`]). +fn search_text(english: &str, id: &str) -> String { + let mut text = format!("{english} {id}"); + for (alias_id, alias) in figcraft_engine::catalog::SEARCH_ALIASES { + if *alias_id == id { + text.push(' '); + text.push_str(alias); + } + } + text +} + fn matches(q: &str, label: &str, extra: &str) -> bool { q.is_empty() || label.to_lowercase().contains(q) || extra.to_lowercase().contains(q) } @@ -295,7 +308,7 @@ pub(crate) fn command_sections(q: &str) -> Vec<(String, Vec)> { } else if menu_labels.contains(&same) { continue; } - if matches(&ql, &label, &format!("{english} {id}")) { + if matches(&ql, &label, &search_text(english, id)) { hits.push((group, command_row(id, label.into_owned()))); } } diff --git a/crates/ui-egui/src/naming_tests.rs b/crates/ui-egui/src/naming_tests.rs new file mode 100644 index 000000000..757a542c8 --- /dev/null +++ b/crates/ui-egui/src/naming_tests.rs @@ -0,0 +1,101 @@ +//! The handoff mode is named "Developer Mode" in every label a person reads. Figma holds a US +//! registration for DEV MODE (US 7574348) and sent a cease-and-desist over it in 2025, so neither +//! our labels nor a translation may call it that again. +//! +//! Identifiers keep their names (`view.devMode`, `devMode.*`, `dev_mode`, the module and file +//! names), and the Actions palette keeps the old name as a search alias so people who know it still +//! find the command (`palette_tests::the_old_name_still_finds_the_mode`). +//! +//! The labels are read where they are published: the engine's command labels and parameter docs, +//! the menu catalog, and the baked-in catalogs. A `tr("Dev Mode")` added to the code would need an +//! English entry, which this rejects. + +use figcraft_engine::catalog::MENU; + +use crate::i18n::{LANGS, Text}; + +/// The retired name, as a label would read it. +const RETIRED: &str = "Dev Mode"; + +fn is_word(c: char) -> bool { + c.is_alphanumeric() || c == '_' +} + +/// `text` reads the retired name: whole words, so "Developer Mode" and "devMode" don't. +fn retired(text: &str) -> bool { + text.match_indices(RETIRED) + .any(|(at, _)| !text[..at].chars().next_back().is_some_and(is_word) && !text[at + RETIRED.len()..].chars().next().is_some_and(is_word)) +} + +/// The strings one catalog entry shows. +fn entry_texts(text: &Text) -> Vec<&str> { + match text { + Text::Plain(value) => vec![*value], + Text::Plural(forms) => [forms.one, forms.few, forms.many].into_iter().flatten().chain([forms.other]).collect(), + } +} + +/// Every label the app publishes, with where it comes from. +fn labels() -> Vec<(String, &'static str)> { + let mut out: Vec<(String, &'static str)> = + figcraft_engine::all_commands().iter().flat_map(|c| [(format!("{} label", c.id), c.label), (format!("{} params", c.id), c.params)]).collect(); + out.extend(MENU.iter().map(|m| (format!("menu {}", m.id), m.label))); + out +} + +#[test] +fn command_labels_and_parameter_docs_read_developer_mode() { + let bad: Vec = labels().into_iter().filter(|(_, text)| retired(text)).map(|(what, text)| format!("{what}: {text:?}")).collect(); + assert!(bad.is_empty(), "a label still names the retired mode:\n{}", bad.join("\n")); + let id = figcraft_engine::find_command("view.devMode").expect("the mode's command exists"); + assert_eq!(id.label, "Developer Mode", "the mode's command label"); +} + +#[test] +fn the_catalogs_name_developer_mode() { + let mut bad = Vec::new(); + for lang in LANGS { + for (key, text) in lang.ui { + if retired(key) { + bad.push(format!("{}: key {key:?}", lang.code)); + } + for value in entry_texts(text).into_iter().filter(|value| retired(value)) { + bad.push(format!("{}: {key:?} = {value:?}", lang.code)); + } + } + for (id, text) in lang.commands { + for value in entry_texts(text).into_iter().filter(|value| retired(value)) { + bad.push(format!("{}: {id} = {value:?}", lang.code)); + } + } + for (id, text) in lang.menu { + for value in entry_texts(text).into_iter().filter(|value| retired(value)) { + bad.push(format!("{}: {id} = {value:?}", lang.code)); + } + } + } + assert!(bad.is_empty(), "a catalog still names the retired mode:\n{}", bad.join("\n")); +} + +#[test] +fn the_inspect_tab_and_its_refusal_read_developer_mode() { + assert_eq!(crate::app::right_tab_label(crate::RightTab::Inspect), "Developer Mode", "the panel's name"); + // The engine's refusal on a board, and the toast the UI shows where the mode refuses an edit. + let board = figcraft_engine::board::NOT_ON_BOARDS; + assert!(!retired(board), "{board:?}"); + assert_eq!(crate::i18n::LANGS.first().map(|l| l.code), Some("en"), "English is baked in first"); + assert!( + LANGS.first().is_some_and(|l| l.ui.iter().any(|(k, _)| *k == "Developer Mode only inspects: switch to Design (Shift+D) to edit")), + "the refusal toast is in the catalog" + ); +} + +#[test] +fn the_guard_reads_words_not_identifiers() { + for label in ["Dev Mode", "Dev Mode on/off", "the Dev Mode panel"] { + assert!(retired(label), "{label}"); + } + for label in ["Developer Mode", "Developer Mode on/off", "view.devMode", "devMode.addMeasurement", "Development", "Ready for dev"] { + assert!(!retired(label), "{label}"); + } +} diff --git a/crates/ui-egui/src/palette_tests.rs b/crates/ui-egui/src/palette_tests.rs index 6f456322e..eb395e818 100644 --- a/crates/ui-egui/src/palette_tests.rs +++ b/crates/ui-egui/src/palette_tests.rs @@ -50,6 +50,21 @@ fn query_filters_to_matches() { assert!(crate::menus::command_sections("zzz-no-such-action").iter().all(|(_, r)| r.is_empty())); } +/// The handoff mode is "Developer Mode", and the name it had before is still a search term: a +/// person who knows "Dev Mode" finds the command, and its row reads the new name. +#[test] +fn the_old_name_still_finds_the_mode() { + let rows: Vec<(String, String)> = + crate::menus::command_sections("dev mode").iter().flat_map(|(_, rows)| rows.iter().map(|r| (r.key.clone(), r.label.clone()))).collect(); + assert!(rows.iter().any(|(key, _)| key == "command:view.devMode"), "'dev mode' finds the mode: {rows:?}"); + assert!( + rows.iter().filter(|(key, _)| key == "command:view.devMode").all(|(_, label)| label == "Developer Mode"), + "and reads the new name: {rows:?}" + ); + // Without the alias the words don't match the label or the id. + assert!(!rows.iter().any(|(key, label)| key != "command:view.devMode" && label.contains("Developer Mode")), "only that row: {rows:?}"); +} + #[test] fn gone_entries_drop_out_of_recents() { let u = Ui::new(Session::new()); diff --git a/docs/architecture.md b/docs/architecture.md index 18d7ac550..6ef06da49 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -27,7 +27,7 @@ replaced. | L4 | `figcraft-svg` | ~3.7k | SVG export and import (usvg) | | L4 | `figcraft-pdf` | ~2.3k | PDF export (krilla) with print options | | L4 | `figcraft-vectordrawable` | ~1.2k | Android Vector Drawable (XML) export: baked path data, gradients through `aapt:attr`, clip groups, compatibility modes and warnings | -| L5 | `figcraft-engine` | ~39.3k | Sessions, undo history (`Arc` snapshots), the command registry (`cmd/*`, `spec!`), pointer tools and vector edit mode, snapping and measuring, components sync, libraries, prototype runtime, Dev Mode code generation, the menu catalog (`catalog.rs`) and the keymap (`keymap.rs`), a panic guard | +| L5 | `figcraft-engine` | ~39.3k | Sessions, undo history (`Arc` snapshots), the command registry (`cmd/*`, `spec!`), pointer tools and vector edit mode, snapping and measuring, components sync, libraries, prototype runtime, Developer Mode code generation, the menu catalog (`catalog.rs`) and the keymap (`keymap.rs`), a panic guard | | L6 | `figcraft-ui-egui` | ~31.9k | The UI3-style frontend: canvas (render worker), toolbar, layers, Design/Prototype/Inspect panels, colour picker, menus, dialogs, presenter, comments, accessibility (AccessKit), the control channel server | | L6 | `figcraft-mcp` | ~2.4k | MCP server (JSON-RPC over stdio), headless or bridged to a running app | | L6 | `figcraft-collab-net` | ~3.0k | Live sessions over the network: the WebSocket transport (tungstenite, a reader and a writer thread per connection), the hub that runs a collaboration core server per open file for the session host the app embeds and for `figcraft-server` (behind a `Backend`), the joining and managing clients (publishing), invite links, presence relay and limits; the browser client; the only crate allowed networking dependencies | diff --git a/docs/boards-parity.md b/docs/boards-parity.md index 45f47a4e7..16456492c 100644 --- a/docs/boards-parity.md +++ b/docs/boards-parity.md @@ -29,7 +29,7 @@ all end with the same geometry. | Board files: File ▸ New board, the home screen's New board card, a light page | done | `file.newBoard`; the kind shows in `inspect_document`, tabs and file rows (a note mark; design files a pen nib) | | Board toolbar at the bottom | done | Move/Hand, Marker/Highlighter/Eraser, Sticky (colour flyout), Shapes (flyout), Connector, Text, Section, Stamp (flyout), Comment, Actions, then the templates and the Design panel's buttons — on the same 32 pt grid as a design file's (no Resources button, as there) | | Design panel hidden by default; a contextual bar above the selection | done | colour, shape, text size (S/M/L/XL); for connectors straight/elbow/curved, the start and end points (none, round, square, line arrow, triangle arrow, reversed triangle, circle, diamond), solid or dashed, the weight, and any colour in the colour picker beside the board colours (#1596); each control says what it is on hover; the Design panel opens with its button or View ▸ Design panel | -| Prototyping and Dev Mode refuse on boards | done | `proto.*`, `dev.*`, `view.prototype`, `view.devMode` say why | +| Prototyping and Developer Mode refuse on boards | done | `proto.*`, `dev.*`, `view.prototype`, `view.devMode` say why | | Sticky notes | done | a square frame (200 wide, growing taller through auto layout), 8 soft colours, a soft shadow, the note's text size shrinking as it grows (24 → 11, by characters and lines), the author's name at the bottom; a click places one and starts typing; Enter or a double-click edits; Shift+Cmd/Ctrl+Enter adds the next one beside it | | Sticky options: hide the author, change size presets, sticky stacks | partial | `board.sticky {author: false}`; no size presets or stacks | | Shapes with text | done | rectangle, rounded rectangle, ellipse, diamond, triangle, speech bubble, chevron; text centred and wrapping inside the outline; shape type and text size from the contextual bar | diff --git a/docs/cli.md b/docs/cli.md index 292268331..a83fbe5c3 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -126,7 +126,7 @@ page 1 "Screens": 380 nodes, 4 top-level written from public format notes, including variables with their modes and bindings and shared styles with their layer links. Checked by opening it again in FigCraft and, on the example documents and small test files, in other software; other apps may not open every file. What was - left out (comments, Dev Mode data, some bindings and settings…) is printed as `note:` lines; + left out (comments, Developer Mode data, some bindings and settings…) is printed as `note:` lines; `docs/file-format-parity.md` lists what was checked and what is unchecked. - `--format xml` (or an `` ending in `.xml`) writes an Android Vector Drawable, e.g. `figcraft-cli export app.figcraft --node Icon -o ic_icon.xml`. What the format can't draw is @@ -299,7 +299,7 @@ lists); it is never printed. The bundle is checked before it is sent and again b ### `connect publish [--mappings FILE] [--dry-run]` Publishes a code repository's component mappings to a design file: each link in the mappings file -(default `figcraft.connect.json` in the current folder, the file Dev Mode's **Export code +(default `figcraft.connect.json` in the current folder, the file Developer Mode's **Export code connections** and `codeConnect.export` write) connects every component or component set of that name to its code component for a language, replacing its link for that language, and the file is saved in place, written whole or not at all, with its version history. Links that don't fit (no diff --git a/docs/collaboration-parity.md b/docs/collaboration-parity.md index 65545447d..4fb173ccc 100644 --- a/docs/collaboration-parity.md +++ b/docs/collaboration-parity.md @@ -17,8 +17,8 @@ cursors, selections, text carets, cursor chat and spotlights shown live ([`colla | Cursor chat, audio, high-fives | yes | **partial, in live sessions**: cursor chat (`/` opens a bubble at your pointer; everyone sees it as you type, it fades a few seconds after the last change, Enter sends and Escape takes it back, 100 characters; `collab.cursorChat` for agents); sent as ephemeral presence the host checks and rate-limits, never stored. No audio, high-fives or emote reactions | 2–5 | | Share links, permissions (view / edit), teams and projects | yes | **partial**: sharing with a join code from the Share popover (online: the file goes to the server in Preferences and the server names its edit, comment and view links with short codes; local network: the app hosts and the code carries its address, with an edit code and a view-only code, picked before or while sharing, whose holders see every change and can't make one; one field joins with a code or a link, in the popover and on the home screen, and the Join dialog then shows the join until its file is ready (Cancel), or why it failed (Try again, #1381); the browser build also joins from a link in its URL's fragment, `#join=…`, which never reaches the web host's logs, capped at 6 KiB and checked like a `figcraft://` link (#712; unit-tested, not run in a browser), and shares online and publishes to a server as the desktop app does, though not on the local network (#917; the owner token of a file it puts on an open server lasts as long as the page)); `figcraft-server` share links per file with edit, comment or view access (a viewer sees changes and presence, a "View only" badge and only the moving tools; a commenter also adds, replies to, reacts to and resolves comment threads, with a "Can comment" badge and the comment tool, #1525; the server refuses their other edits), revocable at once, tokens stored as hashes; managed from the app's Share popover (first links), the server's command line or a managing connection. On a server open to the public, whoever published a file manages it with its owner token (links, name, removal: `collab.manageFile`), which the app remembers: Home ▸ Online lists your online files (open again as owner, start and stop sharing, copy the code, rename, remove, forget), with one request per server for what became of them; anyone in a file can report it, and the operator blocks addresses. The person hosting a session, or a file's owner, removes someone from it (right-click their avatar, `collab.removePerson`): they are disconnected and their address can't join that file again while it runs. The list is per computer; a server only a team reaches can also list the files shared from it now for everyone (`team_files`): Home ▸ Online shows that team list for the server in Preferences, a row joins its file, and its links (`figcraft://join?server=…&file=…&team=1`, or the browser build's) carry no secret and stop working with the share. No accounts, teams or projects | 8–17 | | Comments: pins on frames and layers, threads, reactions, @mentions, resolve, unread, search and filters, links, in the presenter | yes (shared) | **done, local**: stored in the file, your name from Preferences (an AI agent attached over MCP comments as its own name, marked Agent); a behaviour suite from the documentation (pinning and following layers, threads, own edits and deletes, mentions, reactions, C and Shift+C, stepping, filters, saving, copy and paste); restoring a version keeps them. `comment.list` (CLI, MCP) gives each message its `mentions` (`{name, known, you}`, by the highlighting rule). In a live session threads sync and anyone in it can reply, react and resolve (no accounts: a name is who you are) | (shared once multiplayer lands) | -| Version history: named versions, autosave checkpoints, view before restore, restore, duplicate | yes (cloud) | **done**, saved in the `.figcraft` file: restoring keeps the replaced design as an autosave first, a titled autosave becomes a named version, merging a branch records the file before and after. Versions are per person in a live session (a shared history needs accounts); no comparing two versions (Dev Mode compares a ready-for-dev layer with its state when marked) | — | -| Branching and merging | yes (paid plans) | **done, also in live sessions**: branches saved in the `.figcraft` file, each opened in its own tab; Review and merge shows both sides' changes since the branch was made and the conflicts (the same property on both sides, or a layer deleted on one side and edited on the other), each resolved for the branch or main; merge as one undo step (the branch is archived, the main file's history gets a version for the merge), update from main, archive. Each branch keeps its own version history (saved, viewed and restored on the branch tab, kept when it is merged or archived; entries for its creation and updates from main); fills, strokes, effects, layout grids, export settings, guides, interactions, annotations and Dev Mode resources merge item by item. Auto layout settings (a frame's and a child's) merge field by field, each field changed both ways a conflict of its own (`/layout.padding`), and sizes and places auto layout works out (hugging, filling, a child's place) or that an instance takes from its main are no conflict: the merged file lays them out again. In a live session a branch stays with its author (its ids in their own client space, far above the main file's) and merging it is one edit everyone gets; peers' edits merge against the branch's own base and conflicts resolve as offline; a view link refuses merging. Left: others in the session can't see or open your branches (the server would have to keep them); text runs conflict as a whole; a branch made offline that adds layers merges only outside a session | 1–2 | +| Version history: named versions, autosave checkpoints, view before restore, restore, duplicate | yes (cloud) | **done**, saved in the `.figcraft` file: restoring keeps the replaced design as an autosave first, a titled autosave becomes a named version, merging a branch records the file before and after. Versions are per person in a live session (a shared history needs accounts); no comparing two versions (Developer Mode compares a ready-for-dev layer with its state when marked) | — | +| Branching and merging | yes (paid plans) | **done, also in live sessions**: branches saved in the `.figcraft` file, each opened in its own tab; Review and merge shows both sides' changes since the branch was made and the conflicts (the same property on both sides, or a layer deleted on one side and edited on the other), each resolved for the branch or main; merge as one undo step (the branch is archived, the main file's history gets a version for the merge), update from main, archive. Each branch keeps its own version history (saved, viewed and restored on the branch tab, kept when it is merged or archived; entries for its creation and updates from main); fills, strokes, effects, layout grids, export settings, guides, interactions, annotations and Developer Mode resources merge item by item. Auto layout settings (a frame's and a child's) merge field by field, each field changed both ways a conflict of its own (`/layout.padding`), and sizes and places auto layout works out (hugging, filling, a child's place) or that an instance takes from its main are no conflict: the merged file lays them out again. In a live session a branch stays with its author (its ids in their own client space, far above the main file's) and merging it is one edit everyone gets; peers' edits merge against the branch's own base and conflicts resolve as offline; a view link refuses merging. Left: others in the session can't see or open your branches (the server would have to keep them); text runs conflict as a whole; a branch made offline that adds layers merges only outside a session | 1–2 | | Team / organization libraries | yes | **partial**: a file's library is published to `figcraft-server` under the file and enabled in other files by a share link (per-asset versions, background update checks, overrides kept), usage counts per component and file, swapping a library by names ([components-parity](components-parity.md), [server.md](server.md#team-libraries)). No team-wide library list, accounts or analytics over time | 4–8 | | Notifications, activity | yes | no | with sharing | diff --git a/docs/components-parity.md b/docs/components-parity.md index d6b1ee5e6..8fcaea78a 100644 --- a/docs/components-parity.md +++ b/docs/components-parity.md @@ -87,7 +87,7 @@ updates with overrides kept: `library_tests.rs`, `components_scale_tests.rs`. | 2026-11-02 | minor | M3, #1885: glass effects take variables, as the reference tool's do: its light angle, intensity, refraction, depth, dispersion, frost and splay bind to number variables on a layer (`variable.bind`, `effects//`, `radius` being the frost) and in an effect style (`style.bind`), and the Design panel's glass fields take a variable like the shadow and blur fields. A variable reads as the field shows it: intensity, refraction, dispersion and splay in percent. Tests: `binding_lists_tests::glass_fields_take_number_variables`, `effects_tests::glass_fields_take_bound_numbers_as_the_panel_shows_them`. Grade unchanged. | | 2026-11-01 | patch | M3: Delete on a layer inside an instance hides it (an override) instead of being refused ("You can't delete a layer or object from an instance. If you try, Figma will only toggle the layer's visibility instead of removing it", Guide to auto layout); `edit.delete` reports those ids under `hidden`, the structure and the main component stay as they were, one undo step; deleting the instance itself still removes it. Found by a documentation audit of the auto layout page. Test: `components_behaviour_tests::deleting_a_layer_inside_an_instance_hides_it_instead` (the refusal row for it is gone from `what_instances_cannot_override_is_refused`). Numbers unchanged | | 2026-10-31 | minor | M3, gap 10: library analytics cover styles and variables. A file reports how many of its layers use each of a library's styles and variables, and Show usage (`library.usage`) lists them beside the components, by a Type choice, with their layers and files. Layers inside instances count for their component. Insertions, detachments and modes aren't counted for them yet. Numbers unchanged. Tests: `library_assets_tests`. | -| 2026-10-31 | minor | M6: as the help center describes code syntax, Dev Mode's SwiftUI and Compose code now writes a bound variable's iOS or Android code syntax in place of its value, as CSS already wrote the Web one: fills, strokes and their weight, sizes, corner radius, gaps, padding, opacity, the font family and size and the characters. A variable with no code syntax for the platform keeps the value, since a name made up here wouldn't exist in the code it lands in. ArkUI and Flutter have no code syntax platform and are unchanged. Test: `variables_behaviour_tests::swiftui_and_compose_name_variables_by_their_code_syntax`. Numbers unchanged. Grade unchanged | +| 2026-10-31 | minor | M6: as the help center describes code syntax, Developer Mode's SwiftUI and Compose code now writes a bound variable's iOS or Android code syntax in place of its value, as CSS already wrote the Web one: fills, strokes and their weight, sizes, corner radius, gaps, padding, opacity, the font family and size and the characters. A variable with no code syntax for the platform keeps the value, since a name made up here wouldn't exist in the code it lands in. ArkUI and Flutter have no code syntax platform and are unchanged. Test: `variables_behaviour_tests::swiftui_and_compose_name_variables_by_their_code_syntax`. Numbers unchanged. Grade unchanged | | 2026-10-31 | minor | M3, gap 10: library analytics over time. A file counts the instances of library components it inserts (placed, created, pasted or duplicated, Alt-dragged copies too; not those nested in another instance) and reports them like its detaches (`ReportInserted`). The server counts what a file's insertion and detach totals grow past their highest in the current week, a Monday at 00:00 UTC, so an undo and a redo count once, and keeps a year of weeks in `actions.json` (`Actions`). `library.usage {days?}` adds `insertions`, `detachments` and `weeks`, and per component `insertions` and `detachments`; Show usage has a 30 days, 90 days or 1 year choice, the period's totals, and each component's counts on a line of their own. The new variants are appended, so older messages keep their bytes; an older server refuses them and Show usage says it doesn't count insertions. A file's first report after this counts its total in that week. Tests: `library_actions_tests`, `inserting_library_instances_counts_them`. Grade unchanged | | 2026-10-30 | minor | M3, gap 14: a collection of this file can be extended, as the help center shows (right-click a collection, Extend collection); before, only a library's could. `variable.extendCollection {collection, name?}` makes " (extended)" with its modes and one inheriting variable per variable, and the Variables editor's collection menu offers it beside Rename and Delete collection. Edits of the collection it extends reach it at once (values in modes it doesn't override, new variables and modes, renames, mode order), so normalising keeps extensions in step whenever variables change, not only on library updates; undo takes both back. An extended collection extends again (parents settle first; an extension doesn't take its parent's overrides as its own), and a loop of extensions from a hostile file comes apart into plain collections keeping their values. Deleting the parent leaves a plain collection with the values it showed, as for libraries. Tests: `extended_chain_tests` (engine), `variables_rename_tests::a_collection_of_this_file_extends_from_its_context_menu` (headless UI). Grade unchanged | | 2026-10-30 | minor | Library analytics count detaches: a file that detaches an instance of a library component counts it (kept with the file) and its app reports the counts to the library's server beside its usage; **Show usage** and `library.usage` list them per component and per file (`detached`, `detachesKnown`). Older servers and apps are unaffected: the reports are new messages and the old ones keep their encoding. Still nothing over time. Numbers unchanged | diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 819fbcbaf..4835bd1e1 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -194,9 +194,9 @@ layout (including grid `columnTracks` / `rowTracks`), sizing, `minWidth` / `maxW children), `gridCell` (a grid auto layout child's column, row and spans), clip, text properties, component/instance links, overrides, `slot` (a slot layer's property name), boundVariables, interactions, exports, `devStatus` (handoff status), `devChanged` (changed since it was marked ready for dev), -`annotations` (Dev Mode notes: `[{label, properties, category?}]`), `codeLinks` (a component's +`annotations` (Developer Mode notes: `[{label, properties, category?}]`), `codeLinks` (a component's links to code: `[{language, importPath, name, template}]`), `devResources` (links for developers, -its own and inherited: `[{name, url, index?, from?}]`), `measurements` (Dev Mode measurements +its own and inherited: `[{name, url, index?, from?}]`), `measurements` (Developer Mode measurements starting from the layer: `[{to} | {side}]`), `pluginData` (layer data written with `node.set {props: {pluginData: {key: value}}}`, pages included: `{key: value}`, values as strings; only when there is some). The auto layout props @@ -379,7 +379,7 @@ closes it), `commentDraft` (`{x, y}`: start a new comment there), `commentFilter (`date|page`), `commentSearch` (the panel's search text), `commentReply` (the open thread's reply text, focused with the cursor at its end: an `@` there shows the mention picker), `commentEdit` (a message index of the open thread to edit, 0 for the first; `null` stops), `presentComments` (bool: -the presenter shows the presented frame's comments, also Shift+C there). Dev Mode: `devCompare` (a +the presenter shows the presented frame's comments, also Shift+C there). Developer Mode: `devCompare` (a frame or section id opens the Compare changes view, like `dev.compare {id, show: true}`; `null` closes it) with `devCompareVersion` (a version id to compare with; `null`: when it was marked ready for dev), `devCompareMode` (`sideBySide|overlay`) and `devCompareSplit` (the diff --git a/docs/dev-mode-parity.md b/docs/dev-mode-parity.md index 8ece694a8..87dc3739f 100644 --- a/docs/dev-mode-parity.md +++ b/docs/dev-mode-parity.md @@ -1,6 +1,6 @@ # Dev Mode parity -> **Last reviewed:** 2026-10-24 · **Last updated:** 2026-10-26 · **Change:** minor (class names transliterate non-ASCII layer names (#1235); measurements kept on the canvas; a read-only layers panel in Dev Mode; earlier: import lines in generated layer trees; compare with any saved version; Dev Mode behaviour suite: an inspect-only canvas, Changed in the ready list, statuses only on frames and sections; plugin panels from the Inspect tab) · **Target:** Figma Design Dev Mode (desktop app 126.10.7) +> **Last reviewed:** 2026-10-24 · **Last updated:** 2026-11-07 · **Change:** patch (the handoff mode reads Developer Mode in the interface, its ids and saved fields unchanged; class names transliterate non-ASCII layer names (#1235); measurements kept on the canvas; a read-only layers panel in Dev Mode; earlier: import lines in generated layer trees; compare with any saved version; Dev Mode behaviour suite: an inspect-only canvas, Changed in the ready list, statuses only on frames and sections; plugin panels from the Inspect tab) · **Target:** Figma Design Dev Mode (desktop app 126.10.7) **Breadth ~98%, ready ~78% (estimated).** The inspect surface is broad, components connect to the code components of a repository (with their imports, also inside generated layer trees), @@ -10,6 +10,7 @@ repository, third-party integrations. Remaining: 1–2 h. | Feature | Status | Notes | |---|---|---| +| 2026-11-07 | patch | Naming: the handoff mode is called Developer Mode in the interface, its shortcut row, its toasts, the control channel's refusal, the board refusal, the measurement parameter doc, the `.fig` export note, the CLI help and every catalog (24 languages, translations awaiting native review), since Figma holds a US registration for Dev Mode (7574348); command ids (`view.devMode`, `devMode.*`), preference keys and saved-document fields are unchanged, and the Actions palette still finds the command by the old name | | Dev Mode toggle (Shift+D), Inspect panel | done | the canvas only inspects: presses select, pan and comment but never move, resize, draw or edit text and vectors; menus and shortcuts refuse design edits with a note (Dev Mode's annotations, statuses, dev resources, measurements, code connections and comments still work); a drawing tool chosen in the toolbar goes back to the design. The layers panel only selects and expands (no renaming, reordering, hiding or locking; a double click says why), and the Pages list only opens pages (no adding, renaming, reordering, duplicating or deleting). Files dropped on the canvas aren't imported (a dropped document still opens as a file of its own), and the Assets panel places, swaps and applies nothing, doesn't change Hide when publishing, and doesn't accept library updates or add, remove or swap libraries; each says why | | Properties with their styles and variables; redlines on hover | done | positions from the frame around the layer (groups aren't frames), rotation, radius (CSS corner order), opacity, blend; colours in hex, RGB, CSS, HSL or HSB (`inspect.properties {colorFormat}`, the Inspect tab's Color format) | | Code: CSS, iOS (SwiftUI), Android (Compose), Flutter | done | plus HTML, React, Vue and Leptos (ours; Leptos: `#[component]` functions with `#[prop]` defaults, variant enums and `view!` markup; Flutter is ours too: a `StatelessWidget` for a component, a widget tree for a layer); components take properties as parameters, instances are calls; CSS in px or rem (`inspect.code {unit, rootFontSize}`), with rotation, blend modes, gradient directions and kinds (linear, radial, conic), text alignment, case and decoration; class names from layer names transliterate non-ASCII scripts into readable ASCII (로그인 화면 → `rogeuin-hwamyeon`, кнопка → `knopka`, über → `uber`; symbols-only names take the node's kind, `.text`), and the 40-character cut lands on a word boundary (#1235) | diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index a90038427..dd04fdf75 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -23,7 +23,7 @@ and prototype interactions are still unchecked there. | `.jam` (whiteboards) | native to FigJam | **yes, best effort** (`file.open`, `file.import`, File ▸ Open, drop, CLI, MCP) | no | the same archive as a `.fig` (its canvas header reads `fig-jam.`) through the same reader (#1342): it opens as a board (the file kind; a `.fig` stays a design file), with generated sticky and shape backgrounds and text kept (#1341) as ordinary layers rather than native board objects (see [boards-parity](boards-parity.md)); tables, stamps, code blocks and the like come in as frames with their text and connectors as vectors (an elbowed connector keeps the path it was saved with, #1910), with an import note; a layer type the reader doesn't know is left out with a note naming it; writing `.jam` is not supported | — | | `.deck` (Slides) | native to other Figma products | no | no | out of scope | — | | REST API file JSON (`GET /v1/files/:key`) + local variables JSON + `images/` folder | the API's output | **yes** (`file.open`, `file.import`, CLI) | no | pages, layer tree, geometry, paints, strokes, effects, text runs, vectors, auto layout (sizing modes left out read as hug, as documented; grid track sizes from `gridColumnsSizing` / `gridRowsSizing`: px, fr, auto, `minmax(0, 1fr)`, `repeat()`), constraints, grids, components, sets, instances with overrides (sizes and resizing included), component properties, slots (`SLOT` layers bound to their slot property; an instance's slot keeps its content as the instance's own unless its property value is the main's default content; slot limits and preferred components are warnings), styles, prototyping (incl. conditionals and variable modes), variables with modes, aliases, scopes and bindings, image adjustments (the same tint sign as `.fig`); the import report after opening; warnings for what's skipped (video triggers and actions, modifier keys). Unit tests in `crates/format/src/rest/*_tests.rs` | — | -| `.figcraft` (ours: zip of `document.json` + images, versions inside) | — | yes | yes | streamed JSON, 50k layers save ~0.09 s / load ~0.16 s (`cargo xtask bench`); old files keep opening (`large_tests.rs`); image members are `images/`, and a key that can't be one — path text, a space, over 128 chars — is left out with a complaint instead of loaded (`image_tests`); saves write a unique staging file and rename it into place (#284); optional auto-save of files with a path (`pref.autoSave`, #305); Save and auto-save refuse to write over a file another app (figcraft-cli, say) changed since it was opened or saved here, judged by its size and modification time: Revert to saved loads those changes, Save as keeps both, `file.save {overwrite: true}` replaces them (#1330); `kind: "board"` marks a board (left out for design files, so files from before boards are design files; the home screen reads it for the file's mark); board objects keep their `board` metadata on the layer; Dev Mode measurements are kept on the layer they start from (`measurements: [{to} | {side}]`, left out when there are none, so older files open with none) | — | +| `.figcraft` (ours: zip of `document.json` + images, versions inside) | — | yes | yes | streamed JSON, 50k layers save ~0.09 s / load ~0.16 s (`cargo xtask bench`); old files keep opening (`large_tests.rs`); image members are `images/`, and a key that can't be one — path text, a space, over 128 chars — is left out with a complaint instead of loaded (`image_tests`); saves write a unique staging file and rename it into place (#284); optional auto-save of files with a path (`pref.autoSave`, #305); Save and auto-save refuse to write over a file another app (figcraft-cli, say) changed since it was opened or saved here, judged by its size and modification time: Revert to saved loads those changes, Save as keeps both, `file.save {overwrite: true}` replaces them (#1330); `kind: "board"` marks a board (left out for design files, so files from before boards are design files; the home screen reads it for the file's mark); board objects keep their `board` metadata on the layer; Developer Mode measurements are kept on the layer they start from (`measurements: [{to} | {side}]`, left out when there are none, so older files open with none) | — | | Local library files | team libraries in the cloud | yes (enable a `.figcraft` as a library) | yes (publish) | per-asset versions, updates with overrides kept | see [collaboration-parity](collaboration-parity.md) | ## Import (place / paste / drop) @@ -135,7 +135,7 @@ scales and empty-style clears; they have not been checked in other software. missing, or more than 32 variables to read), variable bindings the format has no field for (component properties, arc fields, font weight: written as the values they give), text styles on part of a text (written as values), variables' publishing, code syntax and inheritance settings, extended - collections (written as collections of their own), comments, Dev Mode annotations, statuses, + collections (written as collections of their own), comments, Developer Mode annotations, statuses, resources, measurements and code links, plugin data, ruler guides, prototype device settings, slot properties and slot content, brush strokes (written plain), strokes wider on one side of their path (written as filled outlines, or diff --git a/docs/localization-parity.md b/docs/localization-parity.md index c3490a195..3b48ac54c 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -26,7 +26,7 @@ supported by both apps (CJK, Arabic and Devanagari shaping, IME); Figma has no v a missing translation shows English, never a raw key. One English text that needs two translations takes a context (`"Fill|sizing"`). - **Strings:** 1,747 UI keys in `en.toml` (42 with plural forms; ~110 are labels from engine and - document data, such as tool names, blend modes and Dev Mode properties) plus 574 command labels + document data, such as tool names, blend modes and Developer Mode properties) plus 574 command labels and 31 menu wordings by command id: 2,352 entries (counted against `en.toml` and the complete Spanish catalog's command and menu tables). Command ids, the CLI, MCP and the control channel stay English. Accessibility names come from the same strings. @@ -51,7 +51,7 @@ supported by both apps (CJK, Arabic and Devanagari shaping, IME); Figma has no v added later shows English until someone translates it, and `cargo xtask i18n-report` (part of `cargo xtask ci`) lists what each language lacks. Every draft translated every string on 2026-10-12; by 2026-10-17 the strings added since left them at 95–99% (table below). -- **Not yet localized:** messages the engine writes (errors, a few toasts, Dev Mode property values +- **Not yet localized:** messages the engine writes (errors, a few toasts, Developer Mode property values such as "inside"), number formatting (decimal points stay `.`), and decimal plural rules (`trn` takes integers: Czech and Bosnian use one/few/other and Russian one/few/many; French and Brazilian Portuguese take the singular for 0 as well; Japanese, Korean, Chinese and Vietnamese have one form; diff --git a/docs/mcp.md b/docs/mcp.md index aebcbf46d..04b37e1f0 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -373,7 +373,7 @@ a later step can use `{"$step": 0, "field": "ids/0"}` to target the first match. | Tool | What it does | |---|---| | `list_commands {filter?}` | Every command: id, label, menu, shortcut, undoable, params doc. | -| `run_command {command, params}` | Run any command (e.g. `align.left`, `layout.add`, `component.create`, `instance.place` (drop an instance at a page point into the frame or auto layout slot under it), `component.exposeInstance` / `instance.exposed` (nested instance properties on instances), `component.makeSlot` / `instance.slotInsert` / `instance.resetSlot` (slots, below), `inspect.code` (CSS, HTML + CSS for a whole frame, React, Leptos, Vue, SwiftUI, Jetpack Compose, ArkUI or Flutter for a layer; components take their properties as parameters and instances are calls, see Dev Mode below), `inspect.properties` (layout, auto layout, typography, fills, strokes and effects with their style and variable names; `colorFormat`: hex, rgb, css, hsl or hsb), `inspect.assets` (layers under the selection with export settings, icons and images, each with its `kind`), `inspect.code` takes `unit: rem` and `rootFontSize` for CSS, HTML, React, Leptos and Vue, `style.create`, `variable.bind`, `variable.update` (rename, describe, scope), `variable.set` with `{alias}` or a colour calculation (`{alpha}`, `{mix}`), `variable.renameMode` / `variable.removeMode`, `variable.deleteCollection`, `proto.interaction.add`, `codeConnect.set` (components linked to real code, see Dev Mode below)). | +| `run_command {command, params}` | Run any command (e.g. `align.left`, `layout.add`, `component.create`, `instance.place` (drop an instance at a page point into the frame or auto layout slot under it), `component.exposeInstance` / `instance.exposed` (nested instance properties on instances), `component.makeSlot` / `instance.slotInsert` / `instance.resetSlot` (slots, below), `inspect.code` (CSS, HTML + CSS for a whole frame, React, Leptos, Vue, SwiftUI, Jetpack Compose, ArkUI or Flutter for a layer; components take their properties as parameters and instances are calls, see Developer Mode below), `inspect.properties` (layout, auto layout, typography, fills, strokes and effects with their style and variable names; `colorFormat`: hex, rgb, css, hsl or hsb), `inspect.assets` (layers under the selection with export settings, icons and images, each with its `kind`), `inspect.code` takes `unit: rem` and `rootFontSize` for CSS, HTML, React, Leptos and Vue, `style.create`, `variable.bind`, `variable.update` (rename, describe, scope), `variable.set` with `{alias}` or a colour calculation (`{alpha}`, `{mix}`), `variable.renameMode` / `variable.removeMode`, `variable.deleteCollection`, `proto.interaction.add`, `codeConnect.set` (components linked to real code, see Developer Mode below)). | | `batch {commands: [{command, params}], label?}` | Several commands as one undo step (`command.batch`), which keeps one copy of the document however many steps there are (an `edit.undo` step undoes the batch's edits so far together); all-or-nothing for open documents and their document state. Failed inner commands are removed from the journal; preferences, clipboard, tool state and UI requests are not rolled back. A step can use what an earlier step returned: name it with `"as": "card"` and `{"$ref": "card"}` anywhere in a later step's params becomes the `id` in its result (`{"$step": n}` does the same by zero-based position); `"field"` takes another key or a slash path such as `ids/0` (see Step references below). | | `create_node {type, x, y, width, height, parent, index, name, text, path, props}` | Create a frame, rectangle, ellipse, polygon, star, line, arrow, text, vector, section, component or slice; returns `{id, node}`. | | `set_properties {ids, props}` | `node.set` on layers (geometry, fills, strokes, effects, corner radius, auto layout, sizing, constraints, masks (`isMask`, `maskType: alpha\|vector\|luminance`), handoff status (`devStatus: none\|readyForDev\|completed`; marking again clears "Changed"), a section's `sectionContentsHidden`, text…); returns the updated layers. | @@ -1179,7 +1179,7 @@ import uses: the layer tree, geometry, paints with image bytes, strokes, effects runs, vector networks, auto layout, constraints, components, sets, instances with their overrides and property values, variable collections with their modes, variables and bindings, shared styles with their layer links, and prototype interactions. `warnings` lists what was left out (comments, -Dev Mode data, some bindings and settings and the like). Round trips through FigCraft's own +Developer Mode data, some bindings and settings and the like). Round trips through FigCraft's own importer are tested, and the example documents and small test files were opened in other software (`docs/file-format-parity.md` says what was checked); other apps may not open every file. Without `path` the file comes back as base64 `data`. Check one by opening it again: @@ -1365,7 +1365,7 @@ branches). Driven with `run_command`: changes formatted like Inspect (`fill #2563EB → #DC2626`); `document` lists styles, variables, comments and file settings. A conflict is the same property changed on both sides to different values (key `/`, e.g. `4/fills`; fills, strokes, effects, layout grids, - export settings, guides, prototype interactions, annotations and Dev Mode resources compare item + export settings, guides, prototype interactions, annotations and Developer Mode resources compare item by item, so changing different items of one list on each side, or adding items at its end on one side, merges without a conflict; other lists, such as code links, compare whole; a layer's parent and position are one property, `parent`; auto layout settings, `layout` and `childLayout`, compare @@ -1690,7 +1690,7 @@ permissions: without it nothing is downloaded); `plugin.directory.publish {path {"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"run_command","arguments":{"command":"plugin.directory.install","params":{"id":"org.figcraft.examples.swatch-grid","confirm":true}}}} ``` -### Dev Mode +### Developer Mode - **Pages as React + Tailwind:** `inspect.code {id, language: "tailwind"}` writes a layer tree as a React function component styled with Tailwind CSS v4 utilities and no stylesheet. Auto layout @@ -1791,11 +1791,11 @@ permissions: without it nothing is downloaded); `plugin.directory.publish {path saved with the file and synced; the same two layers are measured once (either way round), a layer starts at most 64. One whose other layer was deleted or moved to another page isn't listed or drawn (an undo brings it back); a copy keeps the side measurements and those to layers copied - with it. In the app, Dev Mode draws them with the hover redlines, and the Inspect tab lists them + with it. In the app, Developer Mode draws them with the hover redlines, and the Inspect tab lists them (with nothing selected, every one on the page), selects a measurement's layer on a click, removes one with −, and places new ones from its Add measurement menu. - **Annotations:** notes for developers on any layer, each with optional pinned properties that - Dev Mode shows with their current values (and the style or variable behind them). + Developer Mode shows with their current values (and the style or variable behind them). `annotation.add {id?, label?, properties?}` (a note, pinned properties or both; returns `{id, index}`), `annotation.edit {id?, index, label?, properties?}` (`properties` replaces the list), `annotation.remove {id?, index?}` (no index: all of the layer's), all undoable, and @@ -1827,9 +1827,9 @@ permissions: without it nothing is downloaded); `plugin.directory.publish {path (`changed`: ready for dev and edited since). The Inspect tab sets the status of the selected frames and, with nothing selected, lists them with a status filter (Ready for dev, Changed, Completed, Any status; changed ones carry a Changed badge; click one to go to it, or its focus - button to open it in the focus view). In the app's Dev Mode the canvas only inspects: presses + button to open it in the focus view). In the app's Developer Mode the canvas only inspects: presses select (and pan, and comment), never move, resize, draw or edit text and vectors, and the menus - and shortcuts refuse commands that edit the design (Dev Mode's own annotations, statuses, dev + and shortcuts refuse commands that edit the design (Developer Mode's own annotations, statuses, dev resources, measurements, code connections and comments still work); choosing a drawing tool in the toolbar goes back to the design, and the layers panel only selects and expands. Commands sent over MCP or the control channel aren't affected. - **Compare changes:** when a layer marked Ready for dev first changes (it turns "Changed"), the diff --git a/docs/plugins.md b/docs/plugins.md index bc31cca63..ea70c3130 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -256,7 +256,7 @@ plugin directory requests (`plugin.directory.*` says so). Import plugins with `p | `api` | The plugin API version the plugin was written for: `1` (the default), `2` (panels and settings), `3` (widgets), `4` (saving files, widget fields and property menus) or `5` (panels from widgets). A manifest asking for a newer one than FigCraft runs is refused, so a plugin that needs panels, widgets or `saveFile` fails clearly on an older FigCraft. | | `permissions.saveFile` | API 4: `true` lets the plugin offer a file to save (an exporter's output). FigCraft asks the user where it goes with its own Save dialog (a download in the browser); the plugin never gets a path. Manage plugins lists it ("can save files"). A plugin directory's entries don't carry it (their wire format stays as it is), so installing any API 4 plugin from one says it may offer files to save. | | `widget` | API 3: `true` when the plugin draws a widget (it's listed in Plugins ▸ Widgets). | -| `permissions.access` | `read` (the default): commands that leave the document as it is; such plugins also run from Dev Mode's Inspect tab. `write`: edits too. | +| `permissions.access` | `read` (the default): commands that leave the document as it is; such plugins also run from Developer Mode's Inspect tab. `write`: edits too. | | `permissions.commands` | Optional allowlist: exact command ids or prefixes ending in `*` (`node.*`). Empty: any command the access allows. | | `commands` | The menu commands, each `{id, label, params?}`. Without any, the plugin has one, `run`, unless it's a widget (a widget needs no command). | | `params[]` | `{name, label?, type, default?, options?, min?, max?}`; `type` is `text`, `number` (clamped to `min`/`max`), `boolean`, `select` (one of `options`, a menu), `list` (one of `options`, shown as a list), `slider` (a number between `min` and `max`, both required, shown as a slider with its value beside it) or `color` (hex, `#RRGGBB` or `#RRGGBBAA`). A default that doesn't fit is dropped. | diff --git a/docs/roadmap.md b/docs/roadmap.md index 75de5f1af..f2dd92966 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-30 · **Change:** minor (a connector end moved with Cmd/Ctrl held goes on any part of an object, and stays there as the object moves or resizes; a tap on a plugin panel's button keeps what was typed in its fields; a Dash cap setting (none, round or square) on Dash and Custom strokes, drawn on both ends of every dash; library analytics list how many layers and files use each style and variable; the Controller menu names each button as PlayStation and Switch controllers do; widgets draw lines and SVG pictures (plugin API 6); a connector end dropped along a side of a shape attaches to that side (#1708); regression tests pin a rounded shape's one-sided stroke and SVG ids and font families without XML-forbidden characters (#1351, #1124); a damaged image is refused with a reason instead of showing as an empty box (#1474); SwiftUI and Compose code names a bound variable by its iOS or Android code syntax; the colour picker's Image tab shows a placeholder and Upload from computer, an asset preview, and a file dropped on it replaces the image, keeping the fill mode, crop and position (#1682); the Frame tool's Phone presets have the iPhone 17 family (#1691); Show in folder works in the Flatpak build and selects the file on Linux (#1701); a dashed line starts and ends with a half-length dash, on the canvas and in exports; the Pages, Layers and Assets search fields keep their text in the middle of the field (#1703); a board connector's path can be reshaped by dragging its path handles (#1710); boards have 16 more shapes (arrows, polygons, star, plus and flowchart symbols), in pickers that lay them out in a grid; library analytics count insertions and show insertions and detachments over the past 30 days, 90 days or year; a stroke's Custom style takes a dash pattern of any length as a comma-separated list (#1681); a control request can set its own timeout, up to 10 minutes (`timeoutMs`; `figcraft-cli mcp --attach PORT --timeout SECONDS`) (#1696); gap 6: a widget opens its plugin's panel (plugin API 5), whose callbacks redraw it; the keyboard's Menu key opens the context menu, as Shift+F10 does; an Open link action can open in place of the prototype, with "Open in new tab" off (gap 15); a collection of this file can be extended (Extend collection on its row, `variable.extendCollection`), its extension following its edits at once, and extended collections extend again in the file; the eyedropper with Cmd/Ctrl+Shift makes a colour style or variable of what it picks; a live session's welcome sends the document compressed to clients that read it, about seven times smaller (#1665); a control request that times out says truthfully whether it was dropped or is still running and will take effect, and only screenshots and input blame the window (#1673); a board keeps earlier votes' results, listed newest first in Results and deleted one at a time; a server can list the files shared from it now for everyone who reaches it, Home ▸ Online shows them and their links carry no secret (`team_files`, part of #742); a selected connector's bar on a board sets its start and end points, solid or dashed, its weight and any colour (#1596); cells copied from a spreadsheet paste onto a board as a table; a long layer name stays on one line in the Export button and the Dev Mode header, ending with an ellipsis, and no longer covers the Design panel (#1650); with hundreds of layers selected, the Design panel and the selection overlays stop going over the whole selection on every frame, so dragging 648 layers averages about 1.1 ms of UI per frame (#1571); plugin panels show pictures: an `image` control, base64 within 256 KB and 4,096 px a side, named by its label (gap 6); a drag moving many layers re-runs only what sits around them, not each layer's bindings, sizing, text and instance checks, on every move (#1571); damaged font tracking table offsets are checked instead of wrapping on 32-bit targets, and font indexing makes its existing offset bounds explicit (#1125); pasted layers whose image the clipboard lacks are named and show a placeholder that reads as missing (#1646); paint-style colours on layers pasted from another design app keep their style instead of turning black (#1638); Shift+Cmd/Ctrl+Enter on a selected board shape adds the next shape already joined to it, and goes on typing (#1597); a name typed for a variable or collection finds the one with that exact id before one named like it, and stored mode ids, font family variables and the panels' labels read exact ids (#1613); plugin widgets have hover styles: a layer's `hover` fill, stroke, opacity or text colour shows while the pointer is over it (gap 6); every file a command reads whole by path is a regular file within a cap that fits it: documents 16 GiB, placed images 256 MiB, CLI scripts 1 GiB; compact layer trees with fields and outline (M0.9, part of #996); while a drag only moves a large selection, its outlines, box, slots, spacing and Design panel values are worked out once and shifted, not rebuilt on every move (#1571); an imported or pasted file finds an installed family under the other names its fonts give it, and with other case and spacing, instead of reporting it missing (#1292); a panic in a grouped closure outside any command rolls the group back like a failed group: documents, selection, undo and redo, history limit and journal as before (#1085); a development example generates figcraft-cli's shell completions and man pages from its clap definitions (part of #1396); `.fig` ZIP image reads share a total byte budget and count skipped images in the import warnings (#1076); stored variable and collection references keep their identity when another object is named like their id (follow-up to #1542); a component set's property rows fit the 240 px right panel: variant values wrap two to a line there and every + and − stays in view; the server key is checked by comparing SHA-256 hashes of the presented and the kept key, as the deployment guide says (#1130); opening a file contacts no team library server it names until you check it (#1097); a hostile server can no longer grow a client's memory with made-up people, and a session remembers a bounded number of deleted layer ids (#1121); `doc.list` gives AI agents and plugins the open files' names, not their paths (#1117); with a large selection, frames where nothing changed reuse the selection's outlines, box and slots and the Design panel's instance and position values instead of working them out again (#1571); team libraries and plugins larger than the 16 MiB download burst can be downloaded again, more slowly (#1089); a batch keeps one undo snapshot, not one per step (#1099); files nested 256 levels deep import on the browser's 1 MB stack (#1101); SVG and PDF exports count pattern sources and the renders they embed from the layer's depth, so one cap bounds a whole export (#1082); an explicit variables file path (`variable.import`, the `variables` of `file.open` and `file.import`) is refused past 256 MiB or when it isn't a regular file, as the sibling `variables.json` already was (part of #1114); joining and leaving a file nobody is editing reads and writes nothing on the server: it stays loaded a minute after everyone leaves (#1074); opening a `.figcraft` file unpacks at most 6 GiB in all (2 GiB in the browser), every area together (#1079); hovering the canvas hit-tests once per pointer move, not on every frame, and moving an auto layout frame no longer lays out its contents again on every move (#1571); `.fig` instance expansion no longer walks variable nodes for every instance or copies every outer override row into each nested instance (#1077); `comment.people` lists names that were only @mentioned, and `comment.next` and `comment.prompt` carry the mentions too (#1415); grid auto-placement looks for free cells a 64-bit word at a time and skips what blocks it, with the same placements (#1083); with the pen out, Enter or a double-click ends the path and edits the vector with the Move tool instead of drawing on (#1430); a recovery copy made while a version is previewed holds the file, not the version shown; a FigJam `.jam` file opens as a board (#1342); the Actions palette offers only commands that run from it, and no second row reading like a menu item: Place image opens the file dialog (#1574); share links can comment: a third access level beside edit and view, whose holder sees the file live and adds, replies to, reacts to and resolves comments but can't change the design; Preferences ▸ Scroll wheel zoom: a mouse wheel zooms the canvas about the pointer and Cmd/Ctrl+wheel scrolls (#1390); `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); 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-11-07 · **Change:** patch (the handoff mode reads Developer Mode in the interface, its ids and saved fields unchanged; a connector end moved with Cmd/Ctrl held goes on any part of an object, and stays there as the object moves or resizes; a tap on a plugin panel's button keeps what was typed in its fields; a Dash cap setting (none, round or square) on Dash and Custom strokes, drawn on both ends of every dash; library analytics list how many layers and files use each style and variable; the Controller menu names each button as PlayStation and Switch controllers do; widgets draw lines and SVG pictures (plugin API 6); a connector end dropped along a side of a shape attaches to that side (#1708); regression tests pin a rounded shape's one-sided stroke and SVG ids and font families without XML-forbidden characters (#1351, #1124); a damaged image is refused with a reason instead of showing as an empty box (#1474); SwiftUI and Compose code names a bound variable by its iOS or Android code syntax; the colour picker's Image tab shows a placeholder and Upload from computer, an asset preview, and a file dropped on it replaces the image, keeping the fill mode, crop and position (#1682); the Frame tool's Phone presets have the iPhone 17 family (#1691); Show in folder works in the Flatpak build and selects the file on Linux (#1701); a dashed line starts and ends with a half-length dash, on the canvas and in exports; the Pages, Layers and Assets search fields keep their text in the middle of the field (#1703); a board connector's path can be reshaped by dragging its path handles (#1710); boards have 16 more shapes (arrows, polygons, star, plus and flowchart symbols), in pickers that lay them out in a grid; library analytics count insertions and show insertions and detachments over the past 30 days, 90 days or year; a stroke's Custom style takes a dash pattern of any length as a comma-separated list (#1681); a control request can set its own timeout, up to 10 minutes (`timeoutMs`; `figcraft-cli mcp --attach PORT --timeout SECONDS`) (#1696); gap 6: a widget opens its plugin's panel (plugin API 5), whose callbacks redraw it; the keyboard's Menu key opens the context menu, as Shift+F10 does; an Open link action can open in place of the prototype, with "Open in new tab" off (gap 15); a collection of this file can be extended (Extend collection on its row, `variable.extendCollection`), its extension following its edits at once, and extended collections extend again in the file; the eyedropper with Cmd/Ctrl+Shift makes a colour style or variable of what it picks; a live session's welcome sends the document compressed to clients that read it, about seven times smaller (#1665); a control request that times out says truthfully whether it was dropped or is still running and will take effect, and only screenshots and input blame the window (#1673); a board keeps earlier votes' results, listed newest first in Results and deleted one at a time; a server can list the files shared from it now for everyone who reaches it, Home ▸ Online shows them and their links carry no secret (`team_files`, part of #742); a selected connector's bar on a board sets its start and end points, solid or dashed, its weight and any colour (#1596); cells copied from a spreadsheet paste onto a board as a table; a long layer name stays on one line in the Export button and the Dev Mode header, ending with an ellipsis, and no longer covers the Design panel (#1650); with hundreds of layers selected, the Design panel and the selection overlays stop going over the whole selection on every frame, so dragging 648 layers averages about 1.1 ms of UI per frame (#1571); plugin panels show pictures: an `image` control, base64 within 256 KB and 4,096 px a side, named by its label (gap 6); a drag moving many layers re-runs only what sits around them, not each layer's bindings, sizing, text and instance checks, on every move (#1571); damaged font tracking table offsets are checked instead of wrapping on 32-bit targets, and font indexing makes its existing offset bounds explicit (#1125); pasted layers whose image the clipboard lacks are named and show a placeholder that reads as missing (#1646); paint-style colours on layers pasted from another design app keep their style instead of turning black (#1638); Shift+Cmd/Ctrl+Enter on a selected board shape adds the next shape already joined to it, and goes on typing (#1597); a name typed for a variable or collection finds the one with that exact id before one named like it, and stored mode ids, font family variables and the panels' labels read exact ids (#1613); plugin widgets have hover styles: a layer's `hover` fill, stroke, opacity or text colour shows while the pointer is over it (gap 6); every file a command reads whole by path is a regular file within a cap that fits it: documents 16 GiB, placed images 256 MiB, CLI scripts 1 GiB; compact layer trees with fields and outline (M0.9, part of #996); while a drag only moves a large selection, its outlines, box, slots, spacing and Design panel values are worked out once and shifted, not rebuilt on every move (#1571); an imported or pasted file finds an installed family under the other names its fonts give it, and with other case and spacing, instead of reporting it missing (#1292); a panic in a grouped closure outside any command rolls the group back like a failed group: documents, selection, undo and redo, history limit and journal as before (#1085); a development example generates figcraft-cli's shell completions and man pages from its clap definitions (part of #1396); `.fig` ZIP image reads share a total byte budget and count skipped images in the import warnings (#1076); stored variable and collection references keep their identity when another object is named like their id (follow-up to #1542); a component set's property rows fit the 240 px right panel: variant values wrap two to a line there and every + and − stays in view; the server key is checked by comparing SHA-256 hashes of the presented and the kept key, as the deployment guide says (#1130); opening a file contacts no team library server it names until you check it (#1097); a hostile server can no longer grow a client's memory with made-up people, and a session remembers a bounded number of deleted layer ids (#1121); `doc.list` gives AI agents and plugins the open files' names, not their paths (#1117); with a large selection, frames where nothing changed reuse the selection's outlines, box and slots and the Design panel's instance and position values instead of working them out again (#1571); team libraries and plugins larger than the 16 MiB download burst can be downloaded again, more slowly (#1089); a batch keeps one undo snapshot, not one per step (#1099); files nested 256 levels deep import on the browser's 1 MB stack (#1101); SVG and PDF exports count pattern sources and the renders they embed from the layer's depth, so one cap bounds a whole export (#1082); an explicit variables file path (`variable.import`, the `variables` of `file.open` and `file.import`) is refused past 256 MiB or when it isn't a regular file, as the sibling `variables.json` already was (part of #1114); joining and leaving a file nobody is editing reads and writes nothing on the server: it stays loaded a minute after everyone leaves (#1074); opening a `.figcraft` file unpacks at most 6 GiB in all (2 GiB in the browser), every area together (#1079); hovering the canvas hit-tests once per pointer move, not on every frame, and moving an auto layout frame no longer lays out its contents again on every move (#1571); `.fig` instance expansion no longer walks variable nodes for every instance or copies every outer override row into each nested instance (#1077); `comment.people` lists names that were only @mentioned, and `comment.next` and `comment.prompt` carry the mentions too (#1415); grid auto-placement looks for free cells a 64-bit word at a time and skips what blocks it, with the same placements (#1083); with the pen out, Enter or a double-click ends the path and edits the vector with the Move tool instead of drawing on (#1430); a recovery copy made while a version is previewed holds the file, not the version shown; a FigJam `.jam` file opens as a board (#1342); the Actions palette offers only commands that run from it, and no second row reading like a menu item: Place image opens the file dialog (#1574); share links can comment: a third access level beside edit and view, whose holder sees the file live and adds, replies to, reacts to and resolves comments but can't change the design; Preferences ▸ Scroll wheel zoom: a mouse wheel zooms the canvas about the pointer and Cmd/Ctrl+wheel scrolls (#1390); `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); 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 @@ -29,6 +29,7 @@ builders' side-by-side checks with synthetic files; nothing has been run on real | Workflow | Works end to end? | Evidence | Hours to pass | |---|---|---|---| +| 2026-11-07 | patch | Naming: the handoff mode is called Developer Mode in the interface, its shortcut row, its toasts, the control channel's refusal, the board refusal, the measurement parameter doc, the `.fig` export note, the CLI help and every catalog (24 languages, translations awaiting native review), since Figma holds a US registration for Dev Mode (7574348); command ids (`view.devMode`, `devMode.*`), preference keys and saved-document fields are unchanged, and the Actions palette still finds the command by the old name | | Design screens: frames, shapes, vectors, text, auto layout, styles; save, reopen, export assets | yes | tools and panels in `crates/engine` and `crates/ui-egui`; `.figcraft` round trips (`crates/format/src/large_tests.rs`); PNG/JPG/SVG/PDF export tests | — | | Build and use a design system: components, variants, properties, variables and modes, libraries | yes | `components_scale_tests.rs` (300 components, 2,000 instances), `library_tests.rs`, `library_variables_tests.rs`; libraries are local files, not shared | — | | Prototype and present: interactions, flows, overlays, smart animate, variables | yes | `proto_*_tests.rs`, `present_*_tests.rs` | — | diff --git a/docs/translating.md b/docs/translating.md index 6eb472281..5f647816b 100644 --- a/docs/translating.md +++ b/docs/translating.md @@ -21,7 +21,7 @@ The catalogs: English (`en`, the source), and Spanish (`es`, with the Latin Amer (`fi`), Bengali (`bn`), Italian (`it`), Vietnamese (`vi`), Thai (`th`) and Bosnian (`bs`), machine-drafted and waiting for native speakers' review. Polish (`pl`, Polski) was reviewed and approved by Lucas (@lukasz-sklad). Georgian (`ka`) was written with AI assistance and reviewed by a native speaker, and stays a draft until a maintainer changes its status. -Russian (`ru`, Русский) was reviewed and approved by VanTan; its catalog keeps Frame, Dev Mode and +Russian (`ru`, Русский) was reviewed and approved by VanTan; its catalog keeps Frame and Smart animate as familiar tool names and uses « » quotes. Turkish (`tr`) was machine-drafted and reviewed by a native speaker, and Swedish (`sv`) translates every UI string, command label and menu wording. diff --git a/docs/ui-parity.md b/docs/ui-parity.md index aa0ef25bd..463be3b47 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -97,7 +97,7 @@ interaction feel not yet compared. | Boolean groups: clicked where the result is, double-click or Cmd/Ctrl-click into operands, live results while dragging or editing points, per-operand operations | done | a group past the path operations' caps (250 000 segments, 2 million possible crossings, 4 million segments swept over a fold) draws without its result, with a warning, and Flatten refuses it (#1104); the Design panel's Boolean groups menu leads Union, Subtract, Intersect and Exclude with their own icons, two offset squares with the result filled at reduced alpha (#2003) | | Text: point, auto height, fixed; caret, words, lines, IME | done | Backspace or Delete at the start of a list item takes its marker and keeps its level, the paragraph starting where the item's text did ("delete the counter, but keep the same level of indentation", Create bulleted and numbered lists; `text_tail_tests::list_keys_at_list_items`, text crate `behaviour_tests`); max lines on auto-width text keep it auto width, hugging up to that many lines and growing with its longest line ("Text resizing is set to auto height or auto width", Explore text properties; `text_behaviour_tests::max_lines_on_auto_width_text_keeps_auto_width_and_hugs_that_many_lines`); Alt+Enter while editing opens the link at the caret, web and mail links only (`text.openLink`; "press ⌥Option Return/Alt Enter to activate a focused link", Accessibility at Figma; `text_link_tests`); the font picker previews a family on the canvas while hovered (no undo step; Escape restores, #653) and Up/Down step through it (#352); a selection highlight after a line break starts at that line's start and an empty last line shows its mark (#665); carets step over whole emoji and accented letters; colour emoji draw from their colour tables (#300); IME composition replayed in tests as macOS sends it, not yet checked on a Mac (#747); CJK words by script run (no dictionary); vertical trim in Type settings; no spell check | | Text: wrap style (Auto, Balance, Pretty) | done | per text layer (`wrapStyle` on `node.set`, `text.wrapStyle`, the Type settings control under Vertical trim, inspect, CSS `text-wrap: balance` or `pretty`): Auto breaks greedily; Balance narrows each paragraph as far as it goes without another line, so its lines come out close to the same length (what browsers do for `text-wrap: balance`); Pretty keeps lines full and, when the last line is one word, narrows one of the last four lines just enough to give a word to the lines after it (Explore text properties: "Auto: Figma fits as many words as possible on each line before wrapping to the next", "Balance: Figma distributes text as evenly as possible across every line, so each line is close to the same length", "Pretty: Figma keeps each line full, but adjusts the last four lines to avoid an orphan"). Saved line breaks from a file still win. Not yet included: per-paragraph settings, text-style support and the `.fig` import/export mapping (#1827). Test: `text_behaviour_tests::balance_and_pretty_wrap_styles_break_lines_as_documented` | -| Text: direction override (Text ▸ Text direction) | done | per paragraph (`ParaList::direction`: auto, ltr, rtl): Text ▸ Text direction ▸ Auto / Left to right / Right to left and `text.direction {direction, ids?}` set the edited paragraphs while editing, else every paragraph of the selected texts; `node.set textDirection` the layer's (or the chars `start`/`end` name); the Type settings control appears once a right-to-left script is in the text or a direction is forced; inspect reports `textDirection` when the paragraphs agree and `lineDirections` otherwise; Dev Mode's CSS writes `direction: ltr` or `rtl` when every paragraph is forced the same way (with `text-align: left` beside `rtl`, as Left is absolute on the canvas); `.fig` import and export carry each line's `sourceDirectionality`, read up to the paragraph count. Layout gives the paragraph its base level, so a forced right-to-left paragraph of Latin letters starts at the right, mirrors its list gutter and indent and orders its runs right to left, and a forced left-to-right paragraph of Hebrew the other way ("By default, Figma automatically handles text direction based on language detection. However, controls are available to override text direction per paragraph", "Go to Text > Text direction. Select Left to right or Right to left", Add right-to-left text). Not included: a direction in text styles (the article names none). Tests: `text_behaviour_tests::text_direction_is_forced_per_paragraph`, `fig::text_direction_tests` | +| Text: direction override (Text ▸ Text direction) | done | per paragraph (`ParaList::direction`: auto, ltr, rtl): Text ▸ Text direction ▸ Auto / Left to right / Right to left and `text.direction {direction, ids?}` set the edited paragraphs while editing, else every paragraph of the selected texts; `node.set textDirection` the layer's (or the chars `start`/`end` name); the Type settings control appears once a right-to-left script is in the text or a direction is forced; inspect reports `textDirection` when the paragraphs agree and `lineDirections` otherwise; Developer Mode's CSS writes `direction: ltr` or `rtl` when every paragraph is forced the same way (with `text-align: left` beside `rtl`, as Left is absolute on the canvas); `.fig` import and export carry each line's `sourceDirectionality`, read up to the paragraph count. Layout gives the paragraph its base level, so a forced right-to-left paragraph of Latin letters starts at the right, mirrors its list gutter and indent and orders its runs right to left, and a forced left-to-right paragraph of Hebrew the other way ("By default, Figma automatically handles text direction based on language detection. However, controls are available to override text direction per paragraph", "Go to Text > Text direction. Select Left to right or Right to left", Add right-to-left text). Not included: a direction in text styles (the article names none). Tests: `text_behaviour_tests::text_direction_is_forced_per_paragraph`, `fig::text_direction_tests` | | Text: superscript and subscript | done | `sups` / `subs` use the font's own glyphs where it has the feature and every letter or digit of the run gets one; otherwise the standard glyphs are drawn shrunk and raised or lowered by the font's OS/2 superscript and subscript metrics (0.65 em at 0.35 em up or 0.14 em down without them), and a run mixing available and unavailable glyphs is synthesised whole, as Explore text properties says ("Figma draws a synthesized version by using the font's standard version of the character, shrinking it, and positioning it accordingly"; mixed glyphs "all update to the synthesized form for consistency"), #1828; tests `text::tests::superscript_native_or_synthesised_and_never_mixed`, `text_behaviour_tests::superscript_and_subscript_are_synthesised_without_glyphs_for_them` | | Eyedropper (I) | done | picks the colour seen under the pointer (opaque) into the selection's fill and the recent colours, one undo step (`fx_behaviour_tests`); with Cmd/Ctrl+Shift held it makes a colour style or variable of it instead, as the reference app's eyedropper does: a Create from color window asks which, its name (the next free "Color n") and a variable's collection (the file's first, or a new one made with it in the same undo step), and leaves the selection's fills alone (`eyedropper_create_tests`, `sampled_color_tests`) | | Comment (C), Shift+C to show/hide | done | local only | @@ -205,7 +205,7 @@ in the table. `crates/ui-egui/src/shortcut_keys_tests.rs` drives the keys a look | 2026-11-05 | patch | M1, #1758: a mouse's side buttons (4 and 5) pan the canvas as the middle button does, the pan ending on release over the canvas or off it; the grab cursor shows while they're held. | | 2026-11-04 | patch | M1, #1852: a press on one of the open gradient's handles or stops on the canvas is the colour picker's own, not a press outside it (Figma keeps the picker and the handles up while a handle or stop is dragged; the stops are made to be dragged: "You can click and drag a stop to change its placement on the gradient", Use gradients as a fill or stroke). The canvas took the press and began the drag, then the picker, drawn later in the same frame, saw a press outside its card and closed, committing the drag's interaction at once; the handles vanished with it and the drag went on without an undo step of its own. Now the picker stays while a gradient handle or stop drags, and the drag is one undo step; a press elsewhere on the canvas still closes it. A handle drag whose button goes up off the canvas (over a panel or the picker) ends there too, instead of waiting, uncommitted, to move the handle to the next press on the canvas. Tests: `colorpicker_selection_tests::dragging_a_gradient_handle_on_the_canvas_keeps_the_picker_open`, `colorpicker_selection_tests::a_gradient_drag_released_off_the_canvas_ends_there`. | | 2026-11-02 | patch | M8, #1875: a click with the Bend tool on a sharp point between two segments gives it handles along the line between its neighbours, a third of the way to each ("Click on a point or path where you want to add a curve", Edit vector layers); a click on a curved point still makes it sharp, a drag still sets the handles by hand, an endpoint is left alone. Between a handle's end and a point both within reach of the pointer, the nearer one on the page is hit (the handle on a tie), so a short path's point (its handles a few pixels long) can be clicked again. Before, only a drag curved a point, and a short one left two tiny handles in its direction. Tests: `vectoredit::tests::short_handles_leave_their_point_clickable`, `vectoredit::tests::a_bend_click_curves_a_sharp_point_between_its_neighbours`, `bend_tool_smooths_and_sharpens_points`. Numbers unchanged. | -| 2026-11-02 | minor | M4, #1827: wrap style on a text layer (`WrapStyle`: Auto, Balance, Pretty): Balance re-breaks each paragraph at the narrowest width that keeps its line count (bisection over the greedy breaker, as browsers balance), Pretty narrows one of the last four lines just enough so the last line isn't one word; `text.wrapStyle`, `node.set wrapStyle` (`textWrap` too), inspect `wrapStyle`, Dev Mode CSS `text-wrap` (Tailwind `text-balance` / `text-pretty`), the Type settings control under Vertical trim. Per-paragraph settings, text-style support and the `.fig` import/export mapping stay open. Test: `text_behaviour_tests::balance_and_pretty_wrap_styles_break_lines_as_documented`. | +| 2026-11-02 | minor | M4, #1827: wrap style on a text layer (`WrapStyle`: Auto, Balance, Pretty): Balance re-breaks each paragraph at the narrowest width that keeps its line count (bisection over the greedy breaker, as browsers balance), Pretty narrows one of the last four lines just enough so the last line isn't one word; `text.wrapStyle`, `node.set wrapStyle` (`textWrap` too), inspect `wrapStyle`, Developer Mode CSS `text-wrap` (Tailwind `text-balance` / `text-pretty`), the Type settings control under Vertical trim. Per-paragraph settings, text-style support and the `.fig` import/export mapping stay open. Test: `text_behaviour_tests::balance_and_pretty_wrap_styles_break_lines_as_documented`. | | 2026-11-02 | patch | M4: Backspace or Delete at the start of a list item takes its marker and keeps its level ("Use Backspace or Delete at the beginning of a list item to delete the counter, but keep the same level of indentation", Create bulleted and numbered lists): a plain paragraph may now carry a list level, and the text layout starts it where that level's item text starts (the gutters stay even when no list is left). Backspace used to drop the level too, and Delete at an item's start deleted the next character. Tab on such a paragraph types a tab, as outside a list. Tests: `text_tail_tests::list_keys_at_list_items`, text crate `behaviour_tests::a_plain_paragraph_at_a_list_level_starts_where_the_items_text_does` | | 2026-11-02 | patch | M4: max lines on an auto-width text keep it auto width: the box hugs up to that many lines and still grows with its longest line ("Max lines is available only if truncate text is enabled, and: Text resizing is set to auto height or auto width", Explore text properties). `text.maxLines` turned such a text into a fixed truncating box with a stale height, so later typing no longer widened it; fixed-size text still becomes a truncating box, auto-height text hugs as before. Test: `text_behaviour_tests::max_lines_on_auto_width_text_keeps_auto_width_and_hugs_that_many_lines` | | 2026-11-02 | patch | M4, #1828: superscript and subscript synthesised where the font has no `sups` / `subs` glyphs (the standard glyph shrunk and shifted by the OS/2 metrics, the usual 0.65 em and 0.35 / 0.14 em offsets without them), and a run mixing available and unavailable glyphs synthesised whole, as Explore text properties says. Tests: `text::tests::superscript_native_or_synthesised_and_never_mixed`, `text_behaviour_tests::superscript_and_subscript_are_synthesised_without_glyphs_for_them`. |