- Scrubbing the label: a step a point, x10 with Shift, x0.1 with Alt, each
stretch at its own rate, counted from the press; on past the screen edge
where the platform reports the mouse's motion.
- Arrow keys: pixel fields by the nudge preferences (Shift: the big nudge),
other fields by their own step and ten of it; also with the keyboard on
the field; the stepped value stays selected.
- Typing: brackets and units in arithmetic; +10, *2, /4 and (outside
percentage fields) 50% are relative to the value.
- A field left as it was commits nothing (no undo step, no rounded display
written back); Tab commits and moves on; Escape restores.
- Mixed values: a relative edit or an arrow key goes to each layer from its
own value in one undo step (X, Y, W, H, rotation, opacity, corner radius,
stroke weight).
- Fields take keys in a window that isn't in front (control channel).
- Docs for the everyday editing partials: ui-parity rows, ROADMAP,
target-app-parity, gaps, mcp, control protocol, the parity checklist
(211/211).
A behaviour suite for canvas navigation, rulers, guides, layout grids, outline mode and measuring
(`crates/engine/src/view_behaviour_tests.rs`, 18 tests, ~120 table rows and checks), written from
the public documentation with a one-line derivation per case; where the documentation is silent the
case says so and pins today's behaviour. Plus the fixes it found:
- **Pixel grid** shows from 400% (it waited for 800%). The threshold, pixel preview's resolution
and wheel panning (`ViewState::pixel_grid_shown`, `pixel_preview_scale`, `scroll`) now live in the
engine; the canvas calls them.
- **Guides belong to the page or a top-level frame.** A guide dropped over a nested frame goes to
the frame holding it (`guides::drop_owner`), `guide.add` refuses nested frames, and a frame moved
into another frame keeps its guides but they stop drawing, snapping and measuring.
- **Selecting guides:** a click selects a guide (`guide.select`, new command, Spanish label added);
Delete/Backspace removes it, the arrows across it nudge it (Shift: big nudge), Escape deselects;
right-click opens the context menu with Remove guide (`guide.remove` without an index removes the
selected guide). A click without a move no longer records an empty undo step.
- **Alt-drag** on a guide drops a copy; **holding Alt while dragging** a guide shows its distances
to the nearest edges inside the frame it crosses (`distance::from_guide`).
- **Layout grids:** columns/rows with a fixed width and count 0 are **Auto** (as many as fit; the
Count field shows Auto); grids don't show or snap on a **rotated frame**.
- **Outline mode:** shapes are hit on their outlines, so a click reaches layers drawn behind
(`geometry::hit_outlines`).
UI strings come from per-language TOML catalogs compiled in (English text as the key and fallback, named placeholders, one/other plurals, context suffixes); command labels are translated at display time while ids, CLI, MCP and control output stay English; accessibility names follow. Preferences > Language (System / English / Español, live; the system language from the OS or the browser), pref.language for scripts. A key check test guards the catalogs. The Spanish catalog is a machine draft awaiting native review; docs/translating.md explains adding a language.
* M8: accessibility: screen reader names, keyboard navigation mode
- Screen readers: the custom-painted controls report a role, name, value and
state to AccessKit (number and text fields, dropdowns, segmented and toggle
buttons, tools with their keys, layer rows as tree items, pages, tabs, menu
items, the canvas describing the selection), from the shared widget helpers
and a `Tip` trait for ad-hoc icon buttons. Regions are named containers.
- Keyboard navigation: F6 / Shift+F6 (view.nextRegion / view.previousRegion)
cycle the toolbar, layers, canvas and properties; Tab / Shift+Tab move within
a region and wrap; Enter or Space activate; arrows move along the toolbar and
through the layer rows; menus and flyouts opened from the keyboard take it and
give it back; Escape returns to the canvas. A focus ring (new `focus` token)
shows until the next pointer press. Tab on the canvas selects the next layer
again instead of moving egui's focus to the tab strip; Enter that starts a
field edit no longer ends it.
- Reduced motion: pref.reduceMotion and the system setting (Windows
UISettings, macOS, GNOME, prefers-reduced-motion) turn egui's animations off.
- Contrast: light secondary and accent text meet WCAG AA.
- ui.inspect reports focus {region, keyboard, control} and reduceMotion;
ui.set {focusRegion}.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M8: parity report command count after merging main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M8: accessibility: regression test against nested egui context locks
A debug build's temporary logging called the context inside a context closure and hung; the shipped code never does. Document the rule in a11y.rs and run every accessibility path with the AccessKit tree on, so a nested call fails the test (debug builds panic after 10 s) instead of hanging.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Expressions as text: Expr::parse (variables by name, literals, and/or/not, comparisons,
arithmetic, text joins) with plain-English errors, type checks for conditions and set variable
values; display reads back as the same expression.
- New commands proto.action.add/set/remove/move and proto.conditional.addBlock/setCondition/
removeBlock edit single actions by path, inside conditionals too; `expression` text accepted by
these and proto.interaction.add/set.
- Interaction editor: an action list (open, move, remove, add), Add action > Conditional, if /
else if / else blocks with a condition builder (variable picker, operator, value or variable) or
an expression field with inline errors, nested actions, set variable expressions.
- Presenter: hotspot hints toggle (Options menu, proto.presentOptions {hotspotHints}), hotspots from
the engine (proto_run::hotspots) and in ui.inspect; an Inspect panel showing the hovered layer's
properties ({inspect}).
- Fields keep the keyboard on the click that opens them, and the Enter that commits a field no
longer reaches the shortcuts.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Fill, stroke, effect and layout grid rows drag to reorder by their grip (a handle shows over the
row, an insertion line where it lands, the row's label follows the pointer like a dragged layer;
Escape cancels; lists that differ across the selection have nothing to drag). New commands
`fill.move`, `stroke.move` and `grid.move {ids?, from, to}` join `effect.move` (lists bottom first,
one undo step; items keep everything, linked list styles are unlinked).
Variable bindings on list items follow their items when a list is reordered or shrinks:
paint colours bind per paint (`fills/<i>/color`, `strokes/<i>/color`; `fill`/`stroke` stay the
first paint's), so a moved bound paint keeps its variable and its neighbours are never recoloured.
`fill.remove`, `stroke.remove` and `grid.remove` refuse negative indices.
Layout grids get settings in the Design panel (size; count, type, width or height, margin or
offset, gutter; colour) whose numbers bind to number variables (`layoutGrids/<i>/<field>`),
re-applied on variable and mode changes. Instance boolean and text properties bind to boolean and
string variables (`componentProperties/<name>`, from the instance's properties panel), kept across
instance rebuilds, following property renames and dropped on delete, reset and detach.
Inspect names the variable of each paint.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Responsive presentation: `proto.settings {responsive}` (saved in the
page's prototype settings). The player resizes the presented frame to the
device's screen, or without a device to its whole area at 100%, and lays
it out again exactly as an editor resize does (constraints, auto layout,
text; an axis the frame hugs keeps hugging), live as the window, device or
file change. The player keeps its design document and lays the screen out
from it each time (`cmd::proto::respond`). In the presenter's Options menu
(Responsive beside the size options, or Responsive / Fixed size on a
device) and the Prototype panel.
- Device colours: each device model lists its colour variants
(`proto.settings {color}`), drawn in code as the body's band round black
glass; screen corners are masked exactly. Colour picker with swatches in
the Prototype panel.
- Custom size: a bare screen of any size (`proto.settings {width, height}`
picks it), W/H fields and a swap button in the Prototype panel, imported
from REST `CUSTOM` devices.
- Library collections in the Design panel's Variable modes section, under
their library's name (`library.setVariableMode`; copies whose library
isn't read stay listed).
- Flow descriptions in the presenter's flow picker (and its hover) and under
each flow in the Prototype panel.
- Tests in new modules: engine proto_responsive_tests, ui-egui
proto_responsive_tests; REST device test updated.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Publishing a library now includes the file's variable collections (modes,
aliases, scopes), versioned per variable and collection; variables hidden
from publishing stay out unless a published variable aliases them or a
published component binds them. Files using the library see its
collections read-only in the Variables editor and the variable pickers;
binding, aliasing or setting a collection's mode copies the variables in
as read-only linked copies, library components keep their bindings, and
"Library updates available" covers changed variables (bound layers follow).
New commands: library.importVariable, library.bindVariable,
library.aliasVariable, library.setVariableMode; variable.update takes
hiddenFromPublishing.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Typography section's line height and letter spacing fields now take
number variables (in pixels) like the other number fields: a variable
button on hover opens the picker (scoped to line height or letter
spacing), and a bound field shows the variable's chip to change or
detach it. While a text range is selected they keep taking plain values.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Local variables window becomes a table editor: collections in a
sidebar, variables grouped by name with one value column per mode, typed
value cells (colour swatch + hex + alpha, number, string, boolean), alias
chips with a variable picker and detach, double-click rename, context
menus, and an Edit variable window for description and scopes.
New commands: variable.update (name, description, scopes),
variable.duplicate, variable.renameMode, variable.removeMode,
variable.renameCollection, variable.deleteCollection. variable.set
validates aliases (same type, no loops) and stores them by id; addMode
always picks a fresh mode id. Pickers offer only variables scoped to the
property. ui.set {variables} opens the editor on a collection.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Number fields for opacity (variables hold percentages), individual corner
radii, auto layout padding and gaps, min/max sizes and font size now take
variables like width and corner radius already did. Text content binds to
string variables and visibility to boolean ones from the Typography and
Appearance section headers; bound ones show as a variable chip that
changes or detaches the binding.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Width, height, corner radius, gap and stroke weight fields show a variable button on hover; it
opens a picker of the document's number variables by collection (search, values shown). A
bound field shows the variable's name in a chip (click to change, detach to unbind).
- The fill and stroke style picker lists colour variables; a bound first paint shows the
variable's name with a detach button.
- Fix: editing a variable's value (or adding a mode value) now updates bound layers; before,
normalisation skipped the change because no layer was touched directly.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>