Files
figcraft/docs/ui-parity.md
T
BFlat cc30a261e7 M1: layers panel keyboard (box selection, F2 rename, context menu) and stroke styles (#135)
## What

**Layers panel from the keyboard** (two of the three open items of area 3, plus long-list QA)
- **F2 renames** the selected layer (a second key for `object.rename`, next to Cmd/Ctrl+R) from the canvas, after a click on a row, or from a layer row that has the keyboard (F6 mode, where the canvas shortcuts are off, so the layers branch of `a11y::keys` runs it). When the rename ends the row gets the keyboard back (`a11y::refocus_row`); Escape in the field cancels and stays in the panel instead of jumping to the canvas.
- **Shift+F10 opens the context menu** (`view.contextMenu`, a new command that only asks the UI): at the focused layer row when the layers panel has the keyboard (F6 or a clicked row), else on the canvas just below the selection (the middle of the canvas with nothing selected). The menu takes the keyboard like the other menus opened from the keyboard (`a11y::popup`), and Escape gives it back to the row or the canvas. The Menu key can't be supported: egui 0.36 has no key for it.
- **Keyboard box selection** (`select.area`, F8): for keyboard-only users, a selection box appears around the selection (or in the middle of the view); the arrows move it, Shift+arrows move its far corner (10 screen pixels a press), Enter selects what it covers exactly like a marquee (Shift+Enter toggles, Cmd/Ctrl+Enter takes nested layers), Escape or a click drops it. A toast and the canvas's screen-reader description say the box's size and the keys. With a box (`{x, y, width, height, add?, deep?}`) the command selects directly, for agents. The marquee's rules (top-level layers, frames only when covered, the children of the frame the box starts in, the focus view, Shift toggling) moved into `Session::marquee_within` / `marquee_selection`, shared by the pointer marquee and the command.
- **Long lists (10k layers)**: a test that a layer near the bottom of a 10k-layer list is revealed (parents expanded, list scrolled) and that F6 lands the keyboard on its row and the arrows walk on from it; the UI bench (`perf_ui_frames`) gained rows for expanding every layer, arrow keys down the expanded list, revealing a layer at the bottom and collapsing everything. Release, 10k layers: first frame with every layer expanded 1.22 ms, an arrow key 1.04 ms, reveal 0.30 ms, collapse all 0.37 ms, idle 0.17 ms.

**Stroke styles** (area 6)
- What the public documentation describes for strokes was mostly already in: colour styles apply to strokes from the stroke section's style picker (`style.apply {target: "stroke"}`, linked, following `style.update`, one undo step, `style.detach {which: "stroke"}` keeping the colour), and the stroke weight binds to number variables. A test now covers that path.
- Missing piece, added: **each side's stroke weight binds to a number variable on its own** (`strokeTopWeight`, `strokeRightWeight`, `strokeBottomWeight`, `strokeLeftWeight`): `node.set` keys (setting or binding one side turns on individual strokes with the other sides at the weight), the stroke-weight variable scope, the stroke override group for instances, and REST `boundVariables` import. The Design panel's per-side fields are bindable, two to a row like the corner radii so a variable's name fits.

New strings go through `tr(...)` with `en.toml` / `es.toml` entries, and the two new commands have Spanish labels. Shortcuts are only in `keymap.rs`. Nothing redraws while idle (the box draws only when a key moves it). Selecting from the box leaves no undo step; each stroke edit is one.

## Verified
- **Desktop app** over the control channel (`target/debug/figcraft.exe` from this worktree's target dir, `--control <free port>`, separate `APPDATA`, keys sent as real `ui.input` key events, stopped by PID): F2 on a selected layer opens its name field in the layers panel (screenshot), typing and Enter rename it; F6 to the layers panel, Shift+F10 opens the menu at the Avatar row with Copy focused (screenshot), Escape gives the keyboard back to the row, F2 + typing + Enter renames it and the row keeps the keyboard ("Profile"); Shift+F10 on the canvas opens the menu for the selected frame with Copy focused (screenshot); F8 shows a box around the selected frame, Shift+arrows stretch it over a second layer (screenshot with the hint toast), Enter selects both; a rectangle with the colour style "Brand/Accent" on its stroke and its top weight bound to a number variable (screenshot: the style chip in the stroke section, the red stroke thicker at the top, the per-side field showing the variable).
- **MCP**: `figcraft-cli mcp` (headless): created a paint style, `style.apply {target: "stroke"}`, `object.rename`, bound `strokeLeftWeight` to a variable and ran `select.area` over the layer; `inspect_document` shows the name "Outlined card", `styles.stroke: "S:1"` with the style's colour on the strokes, `strokeSides [2, 2, 2, 6]`, `boundVariables {strokeLeftWeight}`, and one undo step per command.
- **Web**: `cargo xtask wasm` passes (part of the gate).
- **Performance**: release UI bench at 10k layers (numbers above).
- `cargo +1.99.0 xtask ci` passes (fmt, clippy, tests, layers, assets, wasm), after merging #131 and #132.

## Tests
- `ui-egui` `layers_keyboard_tests` (3): F2 from the canvas and from a focused row (the row gets the keyboard back, Escape cancels without leaving the panel); Shift+F10 at a focused row, on the canvas below the selection and after a clicked row, the menu taking and giving back the keyboard; F8: the box around the selection, the screen-reader description, Shift+arrows sizing, Enter selecting, arrows moving it without nudging layers, Escape, the middle of the view with nothing selected, a click dropping it.
- `ui-egui` `perf_tests::a_layer_revealed_at_the_bottom_of_10k_layers_gets_the_keyboard_on_its_row`, plus the new bench rows.
- `engine` `select_area_tests` (2): marquee rules through the command (negative sizes, frames, starting inside a frame, deep, add toggling, empty spots), the UI request with no box, bad boxes refused (the junk sweep covers both new commands too).
- `engine` `stroke_style_tests` (2): a colour style on strokes (link, undo, style edits, detach); stroke weight and per-side weights bound to variables following the frame's mode, `node.set` per-side keys, scopes and override groups.

## Not in this PR
- The Menu (application) key: egui 0.36 doesn't deliver it.
- Enter doesn't rename: on the canvas and a focused row it already selects children / activates the row.
- The keyboard box doesn't pan the view when it is moved off screen (zoom and pan keys still work while it shows).
- Shift+F10 does nothing while a toolbar or properties control has the keyboard.
- Progress docs: area 3 95%/78% → 98%/82%, 2–4 h → 1–2 h; area 6 90%/72% → 95%/75%, 1–3 h → 1–2 h; mainstream depth 67.8% → 68.1%; percentages unchanged at this rounding; full parity ~323–621 h → ~322–618 h (target-app-parity, ROADMAP); ui-parity rows (context menus, keyboard box selection, layers from the keyboard); `docs/mcp.md` documents `object.rename` without a name, `view.contextMenu`, `select.area` and stroke styles and variables.
2026-10-11 23:10:41 +07:00

9.6 KiB
Raw Blame History

UI and interaction parity

Last reviewed: 2026-10-10 · Last updated: 2026-10-11 · Change: minor (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: done (built and checked against Figma's documented behaviour or by eye), partial, missing. Nothing here has been compared tool by tool against the running Figma yet; that pass is the main remaining work (40–60 h, gaps #4).

Summary (estimated): UI/UX fidelity ~58%. Layout and look of UI3 compared side by side (~65%); interaction feel not yet compared.

Window and panels

Item Status Notes
UI3 layout: tab strip as title bar, icon rail, file header, pages + layers, floating bottom toolbar with flyouts, Design / Prototype / Inspect panels done frameless window on Windows/Linux with drawn buttons
Light and dark themes done colours from theme::Tokens
Minimize UI (Cmd+\) done
Resizable panels partial check widths and snap points against Figma
Actions palette (Cmd+K, Cmd+/) done commands; no assets search parity
Context menus done canvas and layers; Shift+F10 opens them from the keyboard (at the focused layer row or below the canvas selection) and the menu takes the keyboard; the Menu key isn't delivered by the windowing layer (egui 0.36 has no key for it)
Main menu from the logo done generated from catalog.rs; popups sized to content
Keyboard shortcuts panel done docked, tabbed by group, searchable, per-platform keycaps
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

Selection and transform

Item Status Notes
Click, Shift-click, marquee, Shift-marquee toggle done a marquee selects what it touches; from a top-level frame's background, the layers inside it; locked and hidden layers are skipped (selection_behaviour_tests)
Deep select (Cmd/Ctrl-click, Cmd/Ctrl-marquee) done
Select layer menu (every layer under the click) done
Enter / Shift+Enter (children / parent), Tab / Shift+Tab (siblings) done Tab renames the next layer while renaming
Resize handles: Shift keeps proportions, Alt from centre, constraints-aware done Alt shrinks too; past an edge flips (several layers mirror); groups and mixed rotations scale each layer along its own axes, keeping rotation and flip (no skew: children turned off a right angle are approximate); Cmd/Ctrl ignores a frame's constraints (transform_behaviour_tests)
Rotate just outside a corner; Shift in 15° steps done rotated handle box when layers share a rotation
Flip, rotate 90° done
Alt-drag duplicate done
Scale tool (K): handles about an anchor, live factor, panel done
Nudge 1 / Shift 10 (preference), Cmd+arrows resize done arrows reorder auto layout children
Smart guides: edges, centres, equal spacing (between two layers or continuing a row's gap, labelled), spacing labels, parent, layout grid, ruler guides done red, snap.rs; thresholds not yet compared with Figma's
Ctrl to suspend snapping while dragging (macOS) missing
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
Numeric fields: arithmetic, scrubbing by dragging the label, mixed values partial check scrub speed and Shift/Alt steps
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 F8 (select.area): a box around the selection or in the middle of the view; arrows move it, Shift+arrows size it, Enter selects like a marquee (Shift+Enter toggles, Cmd/Ctrl+Enter nested); our addition for keyboard-only users

Tools

Tool Status Notes
Move, hand (Space), zoom (hold Z, Alt out, drag area) done
Frame (presets panel, swap orientation), section, slice done
Rectangle, ellipse (arc handles and Design panel arc fields), polygon, star, line, arrow; Shift/Alt done
Pen: vector networks, branch, close, bend; pencil done pen pressure on Windows and from browser styluses only
Vector edit mode: bend, paint bucket, lasso, cut, eraser, transform box, point snapping, mirroring done
Shape builder (Shift+M) on fills, strokes and open paths; variable width (Shift+W), one side with Alt done
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
Text: point, auto height, fixed; caret, words, lines, IME done carets step over whole emoji and accented letters; CJK words by script run (no dictionary); vertical trim in Type settings; no spell check
Eyedropper (I) done
Comment (C), Shift+C to show/hide done local only
Text on a path (Figma Draw) done put on from the Design panel or text.onPath, not by clicking a path with the Text tool; the path is edited by resizing only
Stretch and scatter brushes (Figma Draw) done eight presets drawn in code; the Pencil's brush is set by tool.pencilBrush, not from the toolbar
Image crop handles done

Canvas

Item Status Notes
Pan/zoom: wheel, pinch, Space-drag, Shift+0/1/2, Cmd+0, +/- done
Rulers and guides (drag out, move, remove; rotated frames; frame guides) done
Frame labels (rotated, right-to-left), size badge done
Outline mode done Outlines, outlines for the selection, mask outlines, frame outlines
Slices shown on the canvas (View ▸ Slices) done dashed rectangles
Dragging a layer inside a group keeps it in the group done frames and auto layout take other drops
Layout grids (Ctrl+G) done
Gamut warnings for Display P3 files done colour picker note; View ▸ Soft-proof sRGB stripes what sRGB can't show (the canvas itself is sRGB)
Gradient and padding/gap handles on the canvas done
Hover outline, selection colours, handle sizes done measured from UI3 screenshots
Cursors per tool and modifier partial not compared
Multiplayer cursors, cursor chat, spotlight missing

Accessibility (ours vs Figma's)

Item Status Notes
Screen-reader names, roles, values, states (AccessKit) done on desktop browser: eframe doesn't send the tree yet
Keyboard navigation: F6 regions, Tab within, focus ring done
Layers panel from the keyboard: arrows, F2 / Cmd+R rename (the row gets the keyboard back), Shift+F10 context menu; 10k-layer lists done revealing a layer, expanding, collapsing and arrow keys timed in cargo xtask bench
Reduced motion done system setting or preference
WCAG AA contrast of secondary and accent text done
Tested with VoiceOver, NVDA, Narrator, Orca missing human QA

Revision history

Date Change Summary
2026-10-11 minor Layers panel keyboard: F2 renames (also from a row with the keyboard, which gets it back), Shift+F10 opens the context menu at the focused row or below the canvas selection, F8 draws a selection box the arrows move and size; stroke weights bind to variables side by side
2026-10-11 minor Selection and transform behaviour suite and fixes: Alt shrinks, groups and mixed rotations scale each layer along its own axes, several layers flip together, whole-pixel resizes, small layers move from inside, locked layers stay put, marquees from a top-level frame's background, equal spacing snapping, Cmd/Ctrl ignores constraints while resizing a frame
2026-10-11 minor Gamut warnings: the colour picker flags Display P3 colours outside sRGB; View ▸ Soft-proof sRGB stripes them on the canvas
2026-10-11 minor Several windows: File ▸ New window, Open in new window on a tab, a tab dragged off the strip; each window has its own tabs and panels and asks about unsaved changes when it closes
2026-10-11 minor Canvas fixes: boolean groups clicked where their result is, a layer dragged inside a group stays in it, View ▸ Frame outlines and View ▸ Slices draw
2026-10-11 minor Outline mode done: outlines for the selection (the selected layers draw as outlines, the rest normally) and mask outlines
2026-10-11 minor Design panel arc fields, live boolean editing (operands on the canvas, live results, per-operand operations), shape builder on strokes and open paths, one-sided widths (Alt-drag a width handle)
2026-10-10 major Created from the ROADMAP's look-and-feel and interaction items, the keymap and the canvas code