Merge remote-tracking branch 'origin/main' into feat/devmode-tail

# Conflicts:
#	ROADMAP.md
#	crates/engine/src/devinspect.rs
This commit is contained in:
bflatastic
2026-10-16 02:26:38 +07:00
122 changed files with 5695 additions and 324 deletions
+14 -4
View File
@@ -52,9 +52,11 @@ page 1 "Screens": 380 nodes, 4 top-level
- `<out>` has an image extension: one file with the given `--node`, or the whole page composited.
- `--node` takes a layer id (`12` or `0:12`) or a layer name.
- `--format fig` (or an `<out>` ending in `.fig`) writes the whole file as `.fig`, experimental:
written from public format notes and checked only by opening it again in FigCraft; other apps
may not open every file. What was left out (shared style links, variables, comments, Dev Mode
data…) is printed as `note:` lines.
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;
`docs/file-format-parity.md` lists what was checked and what is unchecked.
- The format comes from `--format`, else `<out>`'s extension, else png. `--scale` (default 1, max 64)
applies to raster formats.
- SVG/PDF exports with visible descendants more than 256 levels below an exported root fail with
@@ -149,7 +151,7 @@ size and SHA-256. The server's key comes from the address or, better, from the
lists); it is never printed. The bundle is checked before it is sent and again by the server. See
[plugins.md](plugins.md#plugin-directories).
### `perf [--layers N[,N...]]`
### `perf [--layers N[,N...]] [--instances N[,N...]]`
Builds synthetic documents of each size (default `1000,10000` layers: screens with an auto layout
header, a card group, button instances and a grid of shapes and text) and prints a Markdown table of
@@ -158,3 +160,11 @@ and hover, single-layer edits through the engine (fill, move, text in auto layou
with all its instances), undo/redo, a drag preview frame, `normalize` after a one-node edit,
`document.inspect` and select-all + move. Use a release build for meaningful numbers;
`cargo xtask bench` runs it at 1k/10k/50k layers together with the UI frame benchmarks.
`--layers` picks the document sizes (each value is clamped to 10 to 2,000,000 layers). `--instances`
picks the component workloads (default `2000`): for each count the command builds a synthetic design
system (about 300 components, variables and styles, nested four deep) with that many instances that
carry overrides at every level, in one file and as a library in another. It then prints a second
table with the build time and edits of a deep main component (icon colour, glyph size, an added
layer) and of the 72 button mains, where all copies follow. Zeros are dropped and counts above
100,000 are capped to 100,000; `--instances 0` skips this table.
+2 -1
View File
@@ -18,7 +18,8 @@ reply: {"id": 1, "ok": true, "result": ...}
Requests are queued and run on the UI thread at the start of the next frame, so replies reflect the
real UI state. A request that the UI doesn't answer within 60 s gets a timeout error. A line that is
not JSON gets `{"id": null, "ok": false, "error": "bad JSON: …"}`. The implementation is in
not JSON gets `{"id": null, "ok": false, "error": "bad JSON: …"}` and ends the connection (so an
HTTP request a web page makes the browser send here never runs). The implementation is in
`crates/ui-egui/src/control.rs` (methods) and `apps/figcraft/src/control_server.rs` (socket).
A request line is limited to **32 MiB before the newline**, including whitespace. The reader stops
+10 -1
View File
@@ -37,6 +37,13 @@ supported by both apps (CJK, Arabic and Devanagari shaping, IME); Figma has no v
other Latin American and US Spanish tags to es-419; zh, zh-CN, zh-SG and zh-Hans read zh-Hans,
while Traditional Chinese (zh-Hant, zh-TW, zh-HK, zh-MO) has no catalog and reads English or the
next OS language, never Simplified) or the browser's language on the web.
(Latinoamérica), Français, 日本語, 한국어, Português (Brasil), Svenska, 简体中文), applied at once without a
restart; System takes the first of the OS's preferred languages that has a catalog (Windows'
language list, macOS AppleLanguages, `LANGUAGE` then the locale variables on Linux and BSD; a regional tag
falls back to its language, pt-PT or pt to pt-BR, and es-419, es-MX, es-AR, es-CO and the other
Latin American and US Spanish tags to es-419; zh, zh-CN, zh-SG and zh-Hans read zh-Hans, while
Traditional Chinese (zh-Hant, zh-TW, zh-HK, zh-MO) has no catalog and reads English or the next OS
language, never Simplified) or the browser's language on the web.
`pref.language {language}` sets it from the CLI, MCP and the control channel.
- **Regional overlays:** a regional catalog whose language has a catalog lists only what differs
(`es-419.toml` over `es.toml`); its UI strings, command labels and menu wordings read the
@@ -92,7 +99,9 @@ supported by both apps (CJK, Arabic and Devanagari shaping, IME); Figma has no v
| Korean | ko | 2,076 / 2,076 (100%, draft) | yes | Hangul text and IME in documents; UI text in an installed Korean face (web: the `FIGCRAFT_EMBED_CJK` build) | no | draft | 1–2 (after native review) |
| Vietnamese | vi | 0 (0%) | no | Latin with stacked diacritics | no | none | 3–5 |
Other languages: none shipped (Figma ships none beyond the eight above).
Other languages: Swedish (`sv`): every UI string translated, and 120 of the 588 command labels and
menu wordings (the rest show in English); native review welcome. Figma ships none beyond the eight
above.
The layer is done. Per-language hours above are agent hours for a catalog of ~1,800 entries
(a machine draft, then a pass over the panels for overflow), plus each language's plural rule where
+11 -6
View File
@@ -333,7 +333,8 @@ on; `key` resolves keys the way the canvas does. Commands behind keys:
`$$` a dollar sign. Layers are numbered in stacking order, bottom first. Returns `{names}`.
- Pages: `page.add {name?}` (a name starting with `-` adds a divider: an empty page shown as a line
in the pages list, never opened and holding no layers), `page.list` (each page's `divider`),
`page.rename`, `page.move {id, index}`, `page.duplicate` ("Copy of …", after its source),
`page.rename`, `page.move {id, index}`, `page.duplicate` ("Copy of …", after its source, with its colour,
flows (starting at the copied frames), prototype settings and guides),
`page.delete` (the last page that isn't a divider stays).
- `component.list`: each component's `folder` (the Assets panel's folders: its page, the frames
holding it, then its name's slash parts) and `label` (the name on its tile).
@@ -352,6 +353,7 @@ on; `key` resolves keys the way the canvas does. Commands behind keys:
- `pref.autoSave {on?}`: files that have a path save themselves a few seconds after they change
(off by default; the desktop app's timer does it, so scripts still call `file.save`).
- `pref.language {language: system|en|cs|ru|es|es-419|fr|de|pt-BR|ja|ko|zh-Hans|…}`: the app's interface language, applied live ("system":
- `pref.language {language: system|en|es|es-419|fr|de|pt-BR|ja|ko|sv|zh-Hans|…}`: the app's interface language, applied live ("system":
the OS or browser language; a language without a catalog shows English). Command ids, labels and
every result stay English.
@@ -766,10 +768,12 @@ skipped. The result's `opened.warnings` list what was approximated or skipped.
ending in `.fig`) writes the whole document as a `.fig` file, from the same public format notes the
import uses: the layer tree, geometry, paints with image bytes, strokes, effects, text with styled
runs, vector networks, auto layout, constraints, components, sets, instances with their overrides
and property values, and prototype interactions. `warnings` lists what was left out (shared style
links, variables, comments, Dev Mode data and the like). Only round trips through FigCraft's own
importer are verified; other apps may not open every file. Without `path` the file comes back as
base64 `data`. Check one by opening it again:
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
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:
```text
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"export","arguments":{"format":"fig","path":"/tmp/handoff.fig"}}}
@@ -1403,7 +1407,8 @@ siblings and the frame's edges) along those axes, keeping whole pixels in the fr
`guide.select {index, frame? | page?}` selects a guide (and deselects the layers; `{}` deselects
it), as clicking one does in the app; `guide.remove {}` without an index removes the selected one
(the context menu's Remove guide). While a guide is selected and no layer is, Delete removes it,
(the context menu's Remove guide), and removing another guide keeps the selected one selected.
While a guide is selected and no layer is, Delete removes it,
the arrows across it nudge it (Shift: the big nudge) and Escape deselects it. In the app, a guide
dragged out of a ruler and dropped on a frame becomes the guide of the top-level frame there, along
the frame's axis nearer the ruler's; Alt-dragging a guide drops a copy, and holding Alt while
+3
View File
@@ -20,6 +20,9 @@ Simplified Chinese (`zh-Hans`), Czech (`cs`) and Indonesian (`id`), machine-draf
native speakers' review. Russian (`ru`, Русский) was reviewed and approved by VanTan; its catalog
keeps Frame, Dev Mode and Smart animate as familiar tool names and uses « » quotes. Turkish (`tr`)
was machine-drafted and reviewed by a native speaker.
`es-419`), French (`fr`), German (`de`), Brazilian Portuguese (`pt-BR`), Japanese (`ja`), Korean (`ko`)
and Simplified Chinese (`zh-Hans`), machine-drafted and waiting for native speakers' review, and
Swedish (`sv`), which translates every UI string and some command labels.
## The catalogs
+7 -2
View File
@@ -1,6 +1,6 @@
# UI and interaction parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (the Small caps sample in Type settings drawn from capitals; recent files as cards; vector edit mode: hovering, hatching, selecting and dragging filled regions; home screen: Templates tab, first-run templates and Getting started tips; number fields: scrub rates, arrow steps, arithmetic with brackets, relative edits of mixed values; outline mode's hidden layers and object bounds; Hide when publishing; cursors per tool and modifier; panel widths; dark pages for files made in the dark theme; drag threshold and double-clicks into text in groups; rotation origin on Alt+R and placing with Enter: no documented shortcut missing; keyboard shortcut parity suite and bindings; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and the vector edit tail; canvas navigation, guides and grids behaviour suite and fixes; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; layers panel keyboard: F2 rename, Shift+F10 context menu, F8 box selection; selection and transform behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; boolean hit testing on the result, group drags, frame outlines and slices; outlines for the selection, mask outlines; arc fields, live boolean editing, shape builder on strokes, one-sided widths) · **Target:** Figma Design UI3 (desktop app 126.10.7)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-15 · **Change:** minor (fields select their text on focus; Typography spacing icons support scrubbing (#373); CSS colour names in hex fields; the Small caps sample in Type settings drawn from capitals; recent files as cards; vector edit mode: hovering, hatching, selecting and dragging filled regions; home screen: Templates tab, first-run templates and Getting started tips; number fields: scrub rates, arrow steps, arithmetic with brackets, relative edits of mixed values; outline mode's hidden layers and object bounds; Hide when publishing; cursors per tool and modifier; panel widths; dark pages for files made in the dark theme; drag threshold and double-clicks into text in groups; rotation origin on Alt+R and placing with Enter: no documented shortcut missing; keyboard shortcut parity suite and bindings; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and the vector edit tail; canvas navigation, guides and grids behaviour suite and fixes; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; layers panel keyboard: F2 rename, Shift+F10 context menu, F8 box selection; selection and transform behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; boolean hit testing on the result, group drags, frame outlines and slices; outlines for the selection, mask outlines; arc fields, live boolean editing, shape builder on strokes, one-sided widths) · **Target:** Figma Design UI3 (desktop app 126.10.7)
Fine canvas control is what Figma users feel first: handles, snapping, nudging, modifier keys,
drag behaviour, numeric precision. This checklist covers the tools, gestures and panels. Status:
@@ -26,6 +26,8 @@ interaction feel not yet compared.
| Keyboard shortcuts panel | done | docked, tabbed by group like the documentation, searchable, per-platform keycaps; held keys, gestures and presentation keys listed beside the bindings; a label too long for its column ends with "…" and shows in full on hover |
| Several windows | partial | File ▸ New window, Open in new window on a tab, a tab dragged off the strip; screen-reader tree in the main window only; positions not restored |
| Multiplayer avatars, Share button | missing | waits for multiplayer |
| Colour names in hex fields | done | every hex field (fills, strokes, text ranges, effects, layout grids, colour variables, page and prototype backgrounds, the colour picker's Hex) takes a CSS colour name as well: `red` becomes `FF0000`; case, spaces and hyphens are ignored (`Light Blue`); names are opaque and keep the colour's opacity; hex wins where both could match. The picker's CSS field takes every CSS name too (`Color::from_hex_or_name`, `color::named`) |
| Fields select their text on focus | done | a click or Tab into a one-line field selects all of its text, so typing replaces the value: hex and colour fields, number fields, inline fields (a fill's hex, variable values, renames), names, search boxes and dialog fields; a drag from the press selects only what it covers; multi-line fields (descriptions, comments) keep the caret where clicked. The first click into an inline field (a fill's hex) now takes the keyboard; it took a second click (`widgets::select_all_on_focus`, `select_all_tests`) |
| Blend mode menus: the layer's (Design panel) and each paint's (colour picker) | done | Pass through and Normal are distinct (Normal isolates a group's or frame's children); paints have no pass-through; plus darker drawn exactly on the canvas |
## Layers and Assets panels
@@ -63,7 +65,7 @@ interaction feel not yet compared.
| Ctrl to suspend snapping while dragging (macOS) | done | Control on macOS (Cmd keeps working), Ctrl elsewhere; moving, resizing and drawing |
| Measure (Alt hover, Cmd+Alt nested, Shift+Alt padding/gaps) | done | labels never overlap |
| Pixel grid snapping, pixel preview, 2x preview | done | moves, resizes and new shapes land on whole pixels; the pixel grid shows from 400% (`view_behaviour_tests`) |
| Numeric fields: arithmetic, scrubbing by dragging the label, mixed values | done | one field widget for every panel (`widgets::number_field`). Scrubbing the label: a step a point (1 px; 1% of opacity, 1° of rotation), ten with Shift, a tenth with Alt, each stretch at the rate held while it was dragged, one undo step when let go; past the edge of the screen it goes on where the platform reports the mouse's own motion (desktop), otherwise it stops there. Arrow keys: Up/Down step a pixel field by the small nudge and Shift by the big nudge (the preferences), other fields by their own step and ten of it; they work while typing (from what is typed) and with the keyboard on the field. Typing: `+ - * /`, brackets, units (`px`, `°`, `%`, `x`, `ms`), a decimal comma; `+10`, `*2`, `/4` are relative to the value, and `50%`, `+10%`, `-10%` too in a field that isn't a percentage. Enter, Tab or a click elsewhere commits (Tab and Shift+Tab move to the next and previous control), Escape restores; a field left as it was commits nothing (no undo step, and a rounded display isn't written back). Mixed values show "Mixed": a number sets them all, a relative edit or an arrow key goes to each layer from its own value in one undo step, for X, Y, W, H, rotation, opacity, corner radius and stroke weight (elsewhere `+10` on Mixed sets 10 and `*2` does nothing) (`field_tests`). Left: scrubbing a Mixed field (nothing happens), `x2`-style suffix edits, the pointer isn't wrapped to the other side of the screen; rates not compared with Figma's |
| Numeric fields: arithmetic, scrubbing by dragging the label, mixed values | done | Numeric panels use `widgets::number_field`; Typography line height, letter spacing and paragraph spacing share its leading-icon scrub logic (#373), retaining their existing unit parsing, Auto/Mixed values, variable chips and selected-character targeting (`typography_scrub_tests`). Scrubbing the label: a step a point (1 px; 1% of opacity, 1° of rotation), ten with Shift, a tenth with Alt, each stretch at the rate held while it was dragged, one undo step when let go; past the edge of the screen it goes on where the platform reports the mouse's own motion (desktop), otherwise it stops there. Arrow keys: Up/Down step a pixel field by the small nudge and Shift by the big nudge (the preferences), other fields by their own step and ten of it; they work while typing (from what is typed) and with the keyboard on the field. Typing: `+ - * /`, brackets, units (`px`, `°`, `%`, `x`, `ms`), a decimal comma; `+10`, `*2`, `/4` are relative to the value, and `50%`, `+10%`, `-10%` too in a field that isn't a percentage. Enter, Tab or a click elsewhere commits (Tab and Shift+Tab move to the next and previous control), Escape restores; a field left as it was commits nothing (no undo step, and a rounded display isn't written back). Mixed values show "Mixed": a number sets them all, a relative edit or an arrow key goes to each layer from its own value in one undo step, for X, Y, W, H, rotation, opacity, corner radius and stroke weight (elsewhere `+10` on Mixed sets 10 and `*2` does nothing) (`field_tests`). Left: scrubbing a Mixed field (nothing happens), `x2`-style suffix edits, the pointer isn't wrapped to the other side of the screen; rates not compared with Figma's |
| Transform box at small sizes | done | a press inside a layer smaller than its handles moves it, the nearest handle wins, handles answer just outside and are hidden under 16 px on screen; cursors follow each handle and the box's rotation |
| Locked layers | done | selectable from the layers panel; no handles, not dragged with the rest of a selection |
| Keyboard box selection | done | Alt+Space on macOS, Ctrl+Space on Windows and Linux, or F8 (`select.area`): a box around the selection or in the middle of the view; arrows move it, Cmd/Ctrl+arrows or Shift+arrows size it (Shift with Cmd/Ctrl: farther), Enter selects like a marquee (Shift+Enter toggles, Cmd/Ctrl+Enter nested), Escape closes |
@@ -164,6 +166,9 @@ in the table. `crates/ui-egui/src/shortcut_keys_tests.rs` drives the keys a look
| Date | Change | Summary |
|---|---|---|
| 2026-10-15 | minor | Hex fields take CSS colour names (`red` → `FF0000`): fills, strokes, effects, grids, variables, backgrounds and the colour picker |
| 2026-10-15 | minor | Typography: drag the line-height, letter-spacing and paragraph-spacing icons to scrub their values; shared numeric-field rates, resize cursor and one Undo step per gesture (#373). |
| 2026-10-15 | minor | A click or Tab into a one-line field selects all of its text (hex, inline, name, search and dialog fields); the first click into a fill's hex takes the keyboard |
| 2026-10-14 | minor | Type settings: the Small caps button draws its sample as a capital and a smaller capital, no longer as a box where the UI font lacks the small-capital letter (egui's default fonts) |
| 2026-10-14 | minor | Home screen recent files as cards with thumbnails on their page colour, a sortable table view, selection, keyboard and a context menu |
| 2026-10-13 | minor | Vector edit mode regions: with the Move tool the filled area under the pointer is hatched with "/" lines in the selection colour (about 1.4 px), clipped by the area's own fill rule so holes stay clear, and the hatch goes when the pointer is 3.5 px from a point or 2 px from a segment (screen pixels at any zoom); the spacing follows octave folding, fitted to measurements at several zoom levels (8 px at every power-of-two zoom, the gap growing with the zoom and stepping back at √2 · 2ⁿ), anchored to the vector so it doesn't slide when panning, one continuous pattern across selected areas; a click selects an area with its points (they move with it but keep the unselected look), Shift adds an area or takes a selected one out, a press on a selected area drags everything selected, drags show no hover hatch, a click on an empty spot (such as a hole) clears the areas and stays in edit mode, the last area listed wins where areas overlap, only filled vectors have areas; `vector.edit.select {regions}` and `vector.edit.state` report them (`regions_tests`, `vector_regions_tests`) |