Strokes can use a brush: eight presets drawn in code (four stretched along the path, four scattered with spacing, size, rotation and offset jitter from a fixed seed), drawn by the shared stroke-outline code in canvas, PNG, SVG, PDF and outline stroke, with caps on copies and steps. Pointer events carry pen pressure; the Pencil records it as the stroke's width profile, which brushes follow (Windows pens and browser styluses; constant width elsewhere).
* M9: real-time collaboration protocol (design)
docs/collab-protocol.md: WebSocket sessions with a server-ordered,
per-property last-writer-wins document sync, client-scoped ids,
fractional positions for children, ephemeral presence (cursors,
selections, follow), per-user undo, hosting from the app first and a
self-hosted server next, and the implementation plan. ROADMAP gains M9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M9: collaboration core: ids, change sets, server ordering, convergence
Step 1 of docs/collab-protocol.md, no networking:
- Client-scoped node ids: Document::set_client_id makes alloc_id hand out
client << 32 | counter; single-user files allocate as before; the id
counter is no longer part of document equality.
- crates/collab (layer 2): the synced object/property model (every saved
Node field, plus document-level styles, comments, variables, images,
dev snapshots, library data), Change batches, an O(changed) diff, strict
and lenient apply, fractional sibling positions, snapshots.
- Server (ordering, validation, rejects with fixes) and Client (optimistic
view, replay under remote batches, author-side repair of derived data)
as pure state machines; binary (version byte + postcard, JSON values) and
JSON message forms, fuzzed decoders.
- Engine: normalize as the recompute hook, collab.diff.
- Tests: round trips over random real commands, an N-client convergence
fuzz test over delayed in-order links, junk batches.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M9: fmt after merge
* M9: live sessions: host from the app, invite links, live cursors and presence
Step 2 of docs/collab-protocol.md, networking and presence half:
- crates/collab-net (layer 6): WebSocket transport (sync tungstenite, a
reader and a writer thread per connection, no async runtime), the
session host the app embeds (runs figcraft_collab::Server as the
authority, carries its messages), invite links, presence throttling
(~30 Hz, latest wins), limits (sockets, hello/idle timeouts, message
size, rate, slow clients), keep-alive, clean close, reconnect with
backoff; a browser stand-in until the web-sys client (step 4).
- engine: live session state (status, peers, presence, follow) and the
collab.host/stop/join/leave/peers/follow/cursor commands; the network
is reached only through the LiveLink the app installs.
- ui-egui: remote cursors (pointer drawn in code, name tag, gliding),
selection outlines (once documents sync), avatars in the right panel
with follow, the Share popover (start, invite link + URL with Copy,
stop), File > Join live session.
- desktop app and MCP headless session install the link; xtask layers
keeps networking crates out of L0-L5.
- Documents don't sync between apps yet (follow-up).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M9: live sessions: document sync between apps
- The host's session runs figcraft_collab::Server seeded with the shared
document; every app (the host's too, through the hub without a socket)
is a collab Client. Guests open the host's file from the welcome as a
new tab; leaving keeps it as a local copy.
- Engine: after each local action (a gesture once it ends; nothing during
version or text previews) the session document is diffed into one
Changes batch; server batches go through Client::receive with
EngineRecompute and are installed with Session::apply_remote, which
never touches the undo history. Batches arriving mid-gesture wait.
- Transport: Changes/Reject/Welcome reach the engine as Event::Sync;
LiveLink::send carries batches in order; hellos are limited to 64 KiB,
joined clients to the core's message size.
- Tests: engine sync against a real Server (ids in the client's space,
one batch per gesture, remote edits skip undo, deferral, closing the
file leaves), three apps over loopback converging (create, move,
recolour, delete, rename) and a late joiner getting the server's state.
- Docs: collab-protocol, mcp, README, roadmap docs; the UI's
"cursors only" note is gone, replaced by the per-person undo caveat.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
PDF export gains print options, saved on PDF export settings and taken
by file.export, file.exportLayers, file.exportFramesPdf, export.add /
export.update, figcraft-cli export and the MCP export tool:
- a bleed (points or a length like 3mm / 0.125in, up to 2 in) that grows
each page and shows what lies past the frame: its fills reach out to
the bleed edge and its children are clipped there instead; a print
page of one frame is trimmed to the frame's box;
- crop and bleed marks: 0.25 pt hairlines outside the bleed in
registration colour;
- MediaBox / BleedBox / TrimBox;
- CMYK colour: fills, strokes, text, gradient stops, images and
rasterised effects converted from sRGB with the naive, deterministic
K = 1 - max(R, G, B) formula; DeviceCMYK without an output intent (no
CMYK ICC profile can ship in the repository), soft masks stay RGB.
The Design panel shows a PDF row's print settings as a summary line that
opens them; the Export dialog has print options for its PDF preset.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
One keymap (figcraft_engine::keymap) now holds every default shortcut,
grouped like the public shortcut reference, with macOS and Windows/Linux
keys where they differ and canvas / text-editing contexts. The menus,
canvas, control channel, MCP key tool and Keyboard shortcuts panel all
resolve keys there; the catalog keeps only the menu tree.
- Fixed: Cmd/Ctrl+K made a link on the canvas (now Actions; link only
while typing), Shift+1 and Ctrl+Shift+? never matched (key names are
normalised), mask / bring to front / send to back / tidy up / layout
grids on Windows and Linux, distribute on macOS, outlines (Shift+Cmd+O),
pixel grid (Cmd+'), paint bucket B in vector edit mode.
- Added: Cmd/Ctrl+/, + and - zoom, Cmd/Ctrl+0, hold Z zoom tool
(view.zoomAt), two quick digits for exact opacity (object.opacityKeys),
Alt+Shift+U/S/I/E booleans, Alt+/ and Shift+/ remove fill/stroke, pixel
preview, Shift+E / Shift+D / Shift+Cmd+\ panel keys, Ctrl+Tab between
files (doc.nextTab / doc.previousTab), Shift+I, Shift+Space preview,
Home/End frames, Ctrl+C pick colour on macOS.
- Keyboard shortcuts panel: docked at the bottom, a tab per group, search,
keycaps per platform; tooltips show the platform's keys.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M6: REST import of prototypes and variables; export panel polish
REST API file JSON import now brings prototyping and variables along:
- interactions: on click/hover/press/drag, after delay, mouse enter/leave/down/up and key
triggers; navigate, open/swap/close overlay, back, scroll to, change to, open link and set
variable actions (lone unconditional blocks inlined); dissolve, smart animate, move, push and
slide transitions with easing incl. custom bezier and spring; the legacy transitionNodeID
- flow starting points (and the deprecated start node), overflowDirection, scrollBehavior,
prototypeDevice and prototype background
- the local variables response (collections, modes with the default first, values, aliases,
scopes), passed as `variables` to file.open / file.import (object, JSON text or path), found
next to the file (<name>.variables.json / variables.json) or embedded; boundVariables, paint
and text style bindings and explicitVariableModes map onto the fields variable.bind uses
- file.import appends variables, flows and prototype settings with the pages
- unsupported parts (video, gamepad, conditionals, variable modes, library variables) warn
Export panel: rows with a typed or preset size (2x, 512w, 512h), suffix and all five formats
through the new export.update command; export.add steps up (1x, 2x @2x, ...); a preview;
"Export N layers" through the new file.exportLayers (one file per setting, unique safe names)
in the panel and the Export dialog; file.export takes `size`. MCP open_file/export pass the new
params.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M6: Inspect tab: properties with styles and variables, redlines on hover (#41)
The Inspect tab lists the selected layer's properties for handoff:
layout (size, position from the parent, rotation, opacity, radius), auto
layout (direction, gap, padding, alignment), typography, fills, strokes
and effects, each with the style or variable it comes from. Clicking a
value copies it. With the Inspect tab open, hovering measures distances
from the selection without holding Alt.
New command inspect.properties returns the same groups for agents.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>