mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-25 19:06:47 -06:00
Merge remote-tracking branch 'origin/main' into feat/spell-check
# Conflicts: # ROADMAP.md # docs/gaps.md # docs/roadmap.md # docs/target-app-parity.md # docs/ui-parity.md
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# FigCraft architecture
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** minor (added the `plugin` and `collab` crates from #101 and #103) · **Target:** Figma Design
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (the shared hub in `collab-net` and the `figcraft-server` app) · **Target:** Figma Design
|
||||
|
||||
How FigCraft is built today. Engine first, a thin swappable UI, everything a command; a Figma-shaped
|
||||
data model (a scene graph of frames with auto layout, components and variables). ~108,800 lines of
|
||||
@@ -29,8 +29,8 @@ replaced.
|
||||
| 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 |
|
||||
| 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` | ~2.4k | Live sessions over the network: the WebSocket transport (tungstenite, a reader and a writer thread per connection), the session host the app embeds (runs the collaboration core's server), invite links, presence relay and limits; the only crate allowed networking dependencies |
|
||||
| apps | `figcraft`, `figcraft-cli`, `figcraft-web` | ~1.9k | Desktop app (eframe + wgpu), command line, browser build (WASM, WebGPU/WebGL2) |
|
||||
| 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 |
|
||||
| apps | `figcraft`, `figcraft-cli`, `figcraft-web`, `figcraft-server` | ~3.2k | Desktop app (eframe + wgpu), command line, browser build (WASM, WebGPU/WebGL2), collaboration server (files, share links, persistence; [`server.md`](server.md)) |
|
||||
| plugins | `plugins/` (own workspace) | | Rust plugin SDK and two example plugins, built for wasm32 by `cargo xtask plugins` ([`plugins.md`](plugins.md)) |
|
||||
| tools | `xtask` | | `ci`, `layers`, `assets`, `parity`, `version`, `icon`, `wasm`, `plugins`, `bench` |
|
||||
|
||||
@@ -80,5 +80,6 @@ input.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-11 | minor | `collab-net` hosts through one hub for the app's sessions and the standalone server; added the `figcraft-server` app |
|
||||
| 2026-10-10 | minor | Merged main after #101–#103: WASM plugins (plugins area 0% → 35% breadth, 20% ready; ecosystem 5% → 20%) and the multiplayer core without networking (multiplayer 5% → 15% breadth, 5% ready; collaboration 8% → 12%); menu breadth 97.5% → 98.5% (200/203); ready for real work ~46% → ~47%; full parity 420–790 h → 390–745 h; beta 210–380 h → 200–365 h. Fixed the stated weight sums (areas 1–16 sum to 94, total 113; percentages were already computed with those) |
|
||||
| 2026-10-10 | major | Created from the code as of PR #99 |
|
||||
|
||||
+6
-1
@@ -6,6 +6,10 @@ commands, serve MCP and measure performance. Build it with
|
||||
|
||||
Bad arguments print usage and exit with code 2; failures exit with 1. The CLI never panics on input.
|
||||
|
||||
The collaboration server is a binary of its own, `figcraft-server` (`cargo build --release -p
|
||||
figcraft-server`): it keeps files with share links and serves their live sessions, and its
|
||||
`key`, `files` and `links` subcommands manage them. See [server.md](server.md).
|
||||
|
||||
## Subcommands
|
||||
|
||||
### `info <file> [--json]`
|
||||
@@ -99,7 +103,8 @@ machines.
|
||||
### `mcp [--attach PORT]`
|
||||
|
||||
Serve MCP on stdio: headless by default, or attached to a running app started with
|
||||
`figcraft --control PORT`. See [mcp.md](mcp.md).
|
||||
`figcraft --control PORT`. See [mcp.md](mcp.md). The headless session can host, join and publish
|
||||
live sessions (`collab.*`); the one-shot `run` can't (no network link).
|
||||
|
||||
### `perf [--layers N[,N...]]`
|
||||
|
||||
|
||||
+108
-23
@@ -10,8 +10,11 @@ handling and offline queueing with reconnect (`crates/collab`, wired into the en
|
||||
session and the transport). **Step 4 is implemented**: the browser build joins sessions a desktop
|
||||
app hosts, and `figcraft://` invite links open the app and join. Styles, variables, collections,
|
||||
modes and comments created in a session take ids from their creator's id space, and others' text
|
||||
carets show on the canvas. The rest is the plan the next
|
||||
implementation PRs follow and update; sections say what is built.
|
||||
carets show on the canvas. **Step 5 is implemented**: the
|
||||
standalone `figcraft-server` (`apps/figcraft-server`, running it: [`server.md`](server.md)) keeps
|
||||
many files with persistence and compaction, and share links that let people edit or only view;
|
||||
the app publishes to it. The rest is the plan the next implementation PRs follow and update;
|
||||
sections say what is built.
|
||||
|
||||
Goal: several people edit the same `.figcraft` file at the same time and see each other's edits,
|
||||
cursors and selections live, on the desktop app and in the browser, without a central cloud
|
||||
@@ -136,7 +139,7 @@ As built:
|
||||
hash, the encoded bytes as base64). The page order is the pages' positions.
|
||||
- **Next**: this file's published library (it embeds a snapshot document; it moves with an asset
|
||||
channel, together with images, which then stop travelling inside batches); the version history
|
||||
(kept beside the document, not in it) with the standalone server; finer entries for modes,
|
||||
(kept beside the document, not in it) on the standalone server; finer entries for modes,
|
||||
per-mode variable values and comment replies (see Identifiers).
|
||||
- **Changes** (`Change`): `Create {id, kind, parent, position, props}` (only the properties that
|
||||
differ from a new node of that kind), `Set {id, prop, value}`, `Delete {id}` (the subtree; a
|
||||
@@ -152,8 +155,9 @@ As built:
|
||||
|
||||
## Messages
|
||||
|
||||
*Built* (`figcraft_collab::message`): the types below and both encodings. The transport and the
|
||||
presence UI come in step 2.
|
||||
*Built* (`figcraft_collab::message`): the types below and both encodings. Protocol version 2 (the
|
||||
`Hello`'s `protocol`): the welcome says what the client may do, and a connection can manage a
|
||||
server's files instead of joining one; a client of another version is refused with the reason.
|
||||
|
||||
**Encoding (decided):** a binary message is a 1-byte wire version followed by **postcard** (serde),
|
||||
which is compact and needs no schema files. Property values inside are compact JSON text: the
|
||||
@@ -174,17 +178,20 @@ Client → server:
|
||||
| `Presence {cursor?, selection?, viewport?, editing_text? {node, caret, selection?}}` | Ephemeral; throttled to ~30 Hz, latest wins. |
|
||||
| `Ack {seq}` | Highest sequence applied (lets the server trim per-client buffers). |
|
||||
| `Ping` | Keep-alive. |
|
||||
| `Manage {protocol, key, client_version}` | Instead of `Hello`: manage a server's files with its key. |
|
||||
| `Admin {publish {name, snapshot} \| files \| links {file} \| createLink {file, access, label} \| revoke {file, link} \| remove {file}}` | A request on a managing connection; each gets one `Admin` reply, in order. |
|
||||
|
||||
Server → client:
|
||||
|
||||
| Message | Meaning |
|
||||
|---|---|
|
||||
| `Welcome {session, client_id, seq, catch_up: Snapshot or Delta, processed, peers}` | Join accepted: the full document, or for a reconnecting client the batches it missed; what the server did with that client's batches (`processed {last_batch, refused}`). |
|
||||
| `Welcome {session, client_id, seq, catch_up: Snapshot or Delta, processed, peers, access}` | Join accepted: the full document, or for a reconnecting client the batches it missed; what the server did with that client's batches (`processed {last_batch, refused}`); `access` is `edit` or `view` (a view link: every batch is refused). |
|
||||
| `Changes {seq, author, batch_id, changes}` | Accepted changes, in order, to everyone (the author gets its own back as confirmation). |
|
||||
| `Reject {batch_id, reason, fixes: [Change]}` | A batch (or part of it) was invalid: the current values to roll back to. |
|
||||
| `Presence {client_id, …}` / `PeerJoined` / `PeerLeft` | Other people's cursors, selections and arrivals. |
|
||||
| `Refused {reason}` | The connection is refused (protocol version, token, session full, a message out of place). |
|
||||
| `Pong` | Keep-alive. |
|
||||
| `Admin {ready \| published {file, edit, view} \| files \| links \| link \| done \| failed {reason}}` | A managing connection's answer: the key is right, a published file with its first two links (tokens shown once), listings, a new link, done, or why not. |
|
||||
|
||||
`Change` is one of: `Create {id, type, props}`, `Set {id, prop, value}`, `Delete {id}` (deleting a
|
||||
node deletes its subtree; ids are never allocated twice), plus the document-level `SetDoc`, `Put`
|
||||
@@ -352,11 +359,12 @@ resuming with its offline edits, and leaving the session going back to snapshot
|
||||
`ws://`/`wss://` URL). Others on the same network (or through a forwarded port / VPN such as a
|
||||
mesh VPN) join with "File ▸ Join live session" or by opening the link. When the host leaves, the
|
||||
session ends; everyone keeps a local copy.
|
||||
- **Self-hosted server** (next step): the same server as a `figcraft-server` binary that keeps many
|
||||
files, persists them (a snapshot plus an append-only log of accepted batches, compacted into new
|
||||
snapshots), and survives restarts. TLS comes from a reverse proxy (`wss://`).
|
||||
- **Auth**: an unguessable per-session token in the invite link (first step); accounts and per-file
|
||||
permissions (view, edit) come with the standalone server.
|
||||
- **Self-hosted server**: the same server as a `figcraft-server` binary that keeps many files,
|
||||
persists them (a snapshot plus an append-only log of accepted batches, compacted into new
|
||||
snapshots), and survives restarts. TLS comes from a reverse proxy (`wss://`). *Built*, below.
|
||||
- **Auth**: an unguessable per-session token in the invite link (hosting from the app); per-file
|
||||
share links that let people edit or only view, and a server key that manages files (the
|
||||
standalone server). User accounts come later.
|
||||
|
||||
*Built* (hosting from the app and joining):
|
||||
|
||||
@@ -426,8 +434,56 @@ resuming with its offline edits, and leaving the session going back to snapshot
|
||||
page refuses other hosts up front with a message instead of failing obscurely. To join from
|
||||
other machines, serve the web build over `http://` on a trusted network (locally:
|
||||
`python3 -m http.server 8767` in the built site, then `http://localhost:8767/` or the machine's
|
||||
LAN address); `wss://` comes with the standalone server behind TLS. Some browsers also ask
|
||||
before a public site may reach private network addresses.
|
||||
LAN address), or use `wss://` links of a `figcraft-server` behind a TLS reverse proxy (the
|
||||
browser opens them; see *Built (the standalone server)*). Some browsers also ask before a public
|
||||
site may reach private network addresses.
|
||||
|
||||
*Built* (the standalone server, `apps/figcraft-server`; running it: [`server.md`](server.md)):
|
||||
|
||||
- **One hub for both hosts.** The app's session host and the server run the same hub
|
||||
(`crates/collab-net/src/hub.rs`): the listener, a reader and a writer thread per connection,
|
||||
and one thread owning a core `Server` per open file. What differs sits behind a `Backend`: which
|
||||
hellos it admits and with what access, where a file that isn't open comes from, what it keeps
|
||||
of the changes, and the managing connections. The app's backend has one file and its session
|
||||
token; the server's is its data directory. Hellos carry the token to the backend, which checks
|
||||
it; the sessions themselves keep no secret.
|
||||
- **Files and share links.** Each file has links, each with an access (`edit` or `view`), a label
|
||||
and a 128-bit token stored only as its SHA-256 hash (compared in constant time). A hello names
|
||||
the file and a link's token; the server joins it with that link's access (`Server::join_as`). A
|
||||
viewer gets every change and everyone's presence and sends its own presence; each batch it
|
||||
sends is refused (`Reject`, "you can only view this file") and rolled back. Revoking a link
|
||||
(or removing a file) disconnects whoever joined with it, with the reason; the link no longer
|
||||
joins.
|
||||
- **Links** name the server's endpoint: `figcraft://join?host=<host:port>&file=…&token=…` for a
|
||||
plain `ws://` endpoint at `/`, `figcraft://join?server=<ws(s)://host[:port][/path]>&file=…&token=…`
|
||||
for any endpoint (TLS, a path behind a proxy), or `ws(s)://…?file=…&token=…`. The desktop app
|
||||
can't open `wss://` yet (no TLS of its own); the browser can.
|
||||
- **Managing files.** A connection that opens with `Manage {key}` instead of `Hello` sends
|
||||
`Admin` requests: publish a document (its snapshot) as a new file, which returns an edit link
|
||||
and a view link; list files and links; make and revoke links; remove a file. The server key is
|
||||
128 bits in the data directory. The server's command line (`figcraft-server files|links|key`)
|
||||
uses the same requests when a server is running on the directory, else it changes the directory
|
||||
itself (one process holds it at a time: an OS file lock).
|
||||
- **Publishing from the app.** `collab.publish {server, name?}` (Share ▸ Publish to server…)
|
||||
uploads the open document over a managing connection, then joins the new file with its edit
|
||||
link on the same connection thread; the tab becomes the session's synced document (read-only
|
||||
until the welcome arrives, its undo history per person after) and `collab.peers` reports
|
||||
`published {server, file, name, edit, view}`.
|
||||
- **Persistence.** Each open file's server keeps a journal of what changes its lasting state
|
||||
(`Server::journaling`: client ids handed out, accepted batches, refused batch ids); the store
|
||||
appends it to the file's log and syncs it once per burst of messages, before the hub sends what
|
||||
the server said, so a confirmed edit is never lost to a crash. A restart restores the last
|
||||
snapshot (`Server::restore`) and replays the log (`Server::replay`; entries already in the
|
||||
snapshot change nothing, a log that stops making sense is cut there). Compaction writes the
|
||||
next generation's snapshot (`Server::state`) and an empty log of that generation (a log of an
|
||||
older generation is from a compaction cut short and is ignored), after 1,000 entries or 16 MiB
|
||||
of log, when everyone leaves (the file is then closed) and when the server stops. Snapshots and
|
||||
logs carry CRC-32 checksums; writes go to a temporary file that is flushed and renamed.
|
||||
Damaged files are skipped and reported at startup, never deleted; a damaged log end is dropped.
|
||||
- **Restarts.** A stopping server closes connections with code 1012 (service restart), which
|
||||
clients take as a dropped connection: they reconnect with their usual backoff and resume as the
|
||||
same clients (the session id, client ids and log come back with the file), sending what they
|
||||
made meanwhile. The app's session ending (code 1001) stays final.
|
||||
|
||||
*Built* (opening invite links):
|
||||
|
||||
@@ -487,7 +543,8 @@ Decisions, as built:
|
||||
- **Session state.** `Server::state` returns what a server needs to carry on after a restart
|
||||
(`SessionState`: snapshot, sequence number, session id, client-id counter, the clients that can
|
||||
reconnect and what was done with their batches, deleted ids, the log), as one serde value; the
|
||||
standalone server will store it.
|
||||
standalone server stores it as each file's snapshot, with a journal of the changes since
|
||||
(`Server::journaling`, `Server::replay`).
|
||||
|
||||
Tests: random sessions where clients drop and come back mid-session converge, with the default log
|
||||
and with logs short enough that long absences fall back to snapshots; targeted tests cover a lost
|
||||
@@ -496,9 +553,9 @@ saved state.
|
||||
|
||||
## Agents, MCP and the CLI
|
||||
|
||||
Everything stays a command: `collab.host`, `collab.join`, `collab.leave`, `collab.peers`,
|
||||
`collab.follow` are ordinary commands, so the CLI, the control channel and MCP can host and join
|
||||
sessions; an agent can be a participant with its own cursor.
|
||||
Everything stays a command: `collab.host`, `collab.join`, `collab.publish`, `collab.leave`,
|
||||
`collab.peers`, `collab.follow` are ordinary commands, so the CLI, the control channel and MCP can
|
||||
host, publish and join sessions; an agent can be a participant with its own cursor.
|
||||
|
||||
*Built*: the desktop app (control channel) and the MCP server's headless session (`figcraft-cli
|
||||
mcp`) both have the network link installed; `collab.join` returns at once with status
|
||||
@@ -522,12 +579,12 @@ chooses to share on the network; the default is the invite link with a random to
|
||||
popover says who can join.
|
||||
- **The token.** 128 bits from the operating system's random number generator, URL-safe base64,
|
||||
new for every session (stopping and starting again invalidates old links). It is compared in
|
||||
constant time (the core's `Server`). It is in the invite link by design, so whoever holds the link
|
||||
constant time (the hub's backend). It is in the invite link by design, so whoever holds the link
|
||||
can join until the session stops: share it like a password. It never travels in the WebSocket
|
||||
request (the path is `/`), so proxies don't log it; FigCraft never logs it (logs name addresses
|
||||
and ports only), and errors about links never echo the link. Traffic is not encrypted (`ws://`):
|
||||
on a shared network, others on the path could read presence and the token. TLS (`wss://`) comes
|
||||
with the standalone server behind a reverse proxy; until then, use a trusted LAN or a VPN.
|
||||
on a shared network, others on the path could read presence and the token. Use a trusted LAN
|
||||
or a VPN; TLS (`wss://`) comes from a reverse proxy in front of a `figcraft-server`.
|
||||
- **Limits.** At most 64 open sockets (joined or not) and the core's 256 people; a connection must
|
||||
finish the WebSocket handshake and say hello within 10 s, and one silent for 45 s is dropped
|
||||
(clients ping every 15 s); client messages are at most 16 MiB (what the host sends keeps the
|
||||
@@ -536,8 +593,32 @@ chooses to share on the network; the default is the invite link with a random to
|
||||
outgoing queue fills (it can't keep up); names are cut to 64 characters without control
|
||||
characters; presence numbers must be finite and selections are cut to 10,000 ids.
|
||||
- **Malformed input.** Anything that isn't a binary message of the current wire version that
|
||||
decodes in full is answered with `Refused` and a close; the first message must be `Hello`, for
|
||||
this file. The invite parser and the host's reader are fuzzed (`crates/collab-net`).
|
||||
decodes in full is answered with `Refused` and a close; the first message must be `Hello` (for
|
||||
this file) or, on a server, `Manage`. The invite parser and the host's reader are fuzzed
|
||||
(`crates/collab-net`), and so are the server's readers of its own files and its port
|
||||
(`apps/figcraft-server`).
|
||||
|
||||
*Built* (the standalone server), and what it adds to the threat model:
|
||||
|
||||
- **Who can connect.** Whoever can reach the server's port (127.0.0.1 by default; the operator
|
||||
chooses an interface or a reverse proxy). Joining a file needs one of its link tokens; managing
|
||||
files needs the server key. There are no accounts: a link is a bearer secret for one file and
|
||||
one access level, so share it like a password and revoke it when it leaks; the server key is
|
||||
the administrator's and should stay with trusted people.
|
||||
- **Secrets at rest.** Link tokens are kept only as SHA-256 hashes, so a copy of the data
|
||||
directory can't be used to join (the documents themselves are in it, unencrypted). The server
|
||||
key is plain text in the data directory (owner-only on Unix): reading the directory already
|
||||
gives every file. A lost link can't be shown again; revoke it and make a new one.
|
||||
- **View access is enforced by the server**, not by the app: a viewer's batches are refused
|
||||
whatever client sends them; the app's read-only state is for clarity.
|
||||
- **Abuse limits.** On top of the transport's: at most `max_files` files, `max_file_mb` per file
|
||||
(a publish over it is refused), `max_links` links per file, `max_people` per file and
|
||||
`max_connections` sockets.
|
||||
- **TLS.** None in the server: a reverse proxy terminates it. Tokens and keys are never in the
|
||||
WebSocket request (only in the first message), so proxies don't log them, and the server logs
|
||||
file and link ids, never secrets.
|
||||
- **Not yet**: accounts and per-person permissions, per-address rate limits, audit logs,
|
||||
encryption at rest.
|
||||
|
||||
## Implementation plan
|
||||
|
||||
@@ -558,4 +639,8 @@ chooses to share on the network; the default is the invite link with a random to
|
||||
carets on the canvas.
|
||||
4. **Browser client** (WebSocket through `web-sys`). **Done** (the web build joins
|
||||
desktop-hosted sessions; `figcraft://` links open the desktop app and join).
|
||||
5. **Standalone `figcraft-server`** with persistence, compaction and accounts.
|
||||
5. **Standalone `figcraft-server`** with persistence, compaction, share links and permissions.
|
||||
**Done** (`apps/figcraft-server` on the shared hub; edit and view links, the server key,
|
||||
publishing from the app; see [`server.md`](server.md)).
|
||||
6. **Accounts**: people signing in, per-person permissions, teams and projects; TLS in the
|
||||
desktop app.
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
# Collaboration parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (wording: live sessions, the browser joins them, invite links open the app; collaboration polish: collision-free ids, remote text carets; numbers unchanged) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (wording: the standalone server with share links to edit or view; numbers unchanged) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
|
||||
Figma is multiplayer first; FigCraft is local first. **Ready ~12% (estimated):** comments and version
|
||||
history work inside one file; the real-time collaboration core landed in #103 (sync model,
|
||||
server ordering, client reconciliation, convergence tests), and live sessions (preview) let
|
||||
several apps co-edit a file hosted by one desktop app, joined from the desktop app or the browser,
|
||||
with others' cursors, selections and text carets shown live ([`collab-protocol.md`](collab-protocol.md)). Remaining: 55–110 h
|
||||
several apps co-edit a file hosted by one desktop app or kept on a self-hosted `figcraft-server`
|
||||
with share links to edit or view, joined from the desktop app or the browser, with others'
|
||||
cursors, selections and text carets shown live ([`collab-protocol.md`](collab-protocol.md), [`server.md`](server.md)). Remaining: 55–110 h
|
||||
(multiplayer 45–90 h, shared libraries 10–20 h), plus branching 8–15 h.
|
||||
|
||||
| Feature | Figma | FigCraft | Hours |
|
||||
|---|---|---|---|
|
||||
| Real-time co-editing | yes | **core built, no networking** (#103): client-scoped ids, per-property change sets with fractional positions, server ordering and validation, optimistic clients, binary messages; randomized multi-client convergence tests over real commands. Live sessions over the network landed (`crates/collab-net`): the WebSocket transport (keep-alive, size and rate limits, reconnect with backoff), hosting from the app (this machine or the local network) with invite links, live cursors with name tags, selections, avatars and follow, `collab.*` commands. Document sync between apps works over them (the host's server orders every batch; guests open the host's file as a new tab). Undo is per person (only your own steps, others' later writes left alone), refused edits roll back with a toast, and a reconnect resumes as the same client with what it missed (or a snapshot) and sends what was made offline. The browser build joins (same messages through the browser's WebSocket API); `figcraft://` invite links open the desktop app and join. Styles, variables, collections, modes and comments made in a session take ids from their creator's space (the server checks it), so concurrent creations all survive; modes, per-mode values and replies inside one entry still resolve last-writer-wins. Left: standalone server with TLS | 25–55 |
|
||||
| Real-time co-editing | yes | **core built, no networking** (#103): client-scoped ids, per-property change sets with fractional positions, server ordering and validation, optimistic clients, binary messages; randomized multi-client convergence tests over real commands. Live sessions over the network landed (`crates/collab-net`): the WebSocket transport (keep-alive, size and rate limits, reconnect with backoff), hosting from the app (this machine or the local network) with invite links, live cursors with name tags, selections, avatars and follow, `collab.*` commands. Document sync between apps works over them (the host's server orders every batch; guests open the host's file as a new tab). Undo is per person (only your own steps, others' later writes left alone), refused edits roll back with a toast, and a reconnect resumes as the same client with what it missed (or a snapshot) and sends what was made offline. The browser build joins (same messages through the browser's WebSocket API); `figcraft://` invite links open the desktop app and join. Styles, variables, collections, modes and comments made in a session take ids from their creator's space (the server checks it), so concurrent creations all survive; modes, per-mode values and replies inside one entry still resolve last-writer-wins. A standalone `figcraft-server` keeps many files, persists them (a snapshot plus a log, compacted), loses nothing confirmed across a restart (apps resume as the same participants) and takes files published from the app (Share ▸ Publish to server…). Left: TLS in the desktop app (browsers use `wss://` through a reverse proxy), packaging the server | 25–55 |
|
||||
| Live cursors, selections, viewports; follow / observation mode; spotlight | yes | **partial, in live sessions**: named cursors, selection outlines, avatars, follow (click an avatar), others' text carets and selected characters with their names, a note when two people type in one layer; no spotlight | 8–15 |
|
||||
| Cursor chat, audio, high-fives | yes | no | 4–8 |
|
||||
| Share links, permissions (view / edit), teams and projects | yes | no | 8–17 |
|
||||
| Share links, permissions (view / edit), teams and projects | yes | **partial**: `figcraft-server` share links per file with edit or view access (a viewer sees changes and presence, a "View only" badge and only the moving tools; the server refuses its 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. 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 | (shared once multiplayer lands) |
|
||||
| Version history: named versions, autosave checkpoints, view before restore, restore, duplicate | yes (cloud) | **done**, saved in the `.figcraft` file | — |
|
||||
| Branching and merging | yes (paid plans) | no | 8–15 |
|
||||
@@ -25,6 +26,7 @@ with others' cursors, selections and text carets shown live ([`collab-protocol.m
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-11 | minor | Collaboration wording after the standalone server: `figcraft-server` keeps many files, persisted as a snapshot plus a log of what changed (compacted, checksummed, damaged files skipped and reported), restarts lose nothing confirmed and apps resume where they were; the app publishes its file to it; share links per file let people edit or only view (the server refuses a viewer's edits), are revocable and stored as hashes; TLS comes from a reverse proxy (the desktop app has none of its own yet); percentages and hours unchanged until the next re-measure |
|
||||
| 2026-10-11 | minor | Collaboration wording after the collaboration polish: styles, variables, collections, modes and comments created in a live session take ids from their creator's id space (concurrent creations no longer overwrite each other; the server checks it), others' text carets and selections show on the canvas, and typing in a layer someone else is typing in says so; percentages unchanged |
|
||||
| 2026-10-11 | minor | Wording only: live sessions (#104), then the browser joining them and `figcraft://` invite links opening the desktop app; percentages and hours unchanged until the next re-measure |
|
||||
| 2026-10-10 | minor | Merged main after #101–#103: WASM plugins (plugins area 0% → 35% breadth, 20% ready; ecosystem 5% → 20%) and the multiplayer core without networking (multiplayer 5% → 15% breadth, 5% ready; collaboration 8% → 12%); menu breadth 97.5% → 98.5% (200/203); ready for real work ~46% → ~47%; full parity 420–790 h → 390–745 h; beta 210–380 h → 200–365 h. Fixed the stated weight sums (areas 1–16 sum to 94, total 113; percentages were already computed with those) |
|
||||
|
||||
@@ -90,10 +90,12 @@ pointer press, so the focus ring shows) and the name of the `control` with the k
|
||||
screen reader announces; empty when nothing has it). `reduceMotion` is true when the preference or
|
||||
the system's accessibility setting turns the interface's animations off. `live` is the live
|
||||
session as the window shows it: `status`, `role`, the number of `peers`, who you `follow`, the
|
||||
peers whose cursors are drawn on the canvas (`cursorsShown`) and whether the Join dialog is open
|
||||
(`joinDialog`; Escape closes it); the session itself is `engine.execute {command: "collab.peers"}`
|
||||
(hosting and joining are `collab.host` and `collab.join`, opening invite links
|
||||
`collab.registerLinks`, see [mcp.md](mcp.md#live-sessions)). The app also joins the invite link it
|
||||
peers whose cursors are drawn on the canvas (`cursorsShown`), whether the Join and Publish to
|
||||
server dialogs are open (`joinDialog`, `publishDialog`; Escape closes them) and why the active
|
||||
document can't be edited now (`readOnly`: the "View only" or "Publishing" reason, else null); the
|
||||
session itself is `engine.execute {command: "collab.peers"}` (hosting, joining and publishing are
|
||||
`collab.host`, `collab.join` and `collab.publish`, opening invite links `collab.registerLinks`,
|
||||
see [mcp.md](mcp.md#live-sessions)). The app also joins the invite link it
|
||||
was started with (`figcraft "figcraft://join?…"`).
|
||||
|
||||
### `ui.pointer {kind, x, y | sx, sy, to?, steps?, shift?, alt?, cmd?, ctrl?}`
|
||||
|
||||
+28
-14
@@ -1,6 +1,6 @@
|
||||
# Where FigCraft falls short of Figma
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (spell check while editing text on Windows and macOS; menu breadth 201/204) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (spell check while editing text on Windows and macOS; menu breadth 201/204; the standalone server with share links to edit or view, wording only; live boolean editing and the vector tail: feature breadth ~79%) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
|
||||
Every known shortfall against Figma, one entry each, ranked by how much it stops a Figma user from
|
||||
doing real work in FigCraft. Agents: pick work from the top unless the owner says otherwise. The
|
||||
@@ -15,7 +15,7 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe
|
||||
| # | Gap | Kind | Hours | Doc |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `.fig` files can't be opened (**beta blocker**) | File format | 30–60 | [file-format-parity](file-format-parity.md) |
|
||||
| 2 | No real-time multiplayer or sharing (**beta blocker**) | Feature | 45–90 | [collaboration-parity](collaboration-parity.md) |
|
||||
| 2 | Multiplayer and sharing are a preview, without accounts (**beta blocker**) | Feature | 45–90 | [collaboration-parity](collaboration-parity.md) |
|
||||
| 3 | Nothing checked against real Figma files | Fidelity | 15–30 | [target-app-parity](target-app-parity.md) |
|
||||
| 4 | Interaction fidelity not compared tool by tool | UI/UX | 40–60 | [ui-parity](ui-parity.md) |
|
||||
| 5 | No localization layer; English only | Localization | 60–100 | [localization-parity](localization-parity.md) |
|
||||
@@ -26,7 +26,7 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe
|
||||
| 10 | Shared (team) libraries | Feature | 10–20 | [components-parity](components-parity.md) |
|
||||
| 11 | Spell check on Linux, FreeBSD and the web; one language | Feature | 2–5 | [target-app-parity](target-app-parity.md) |
|
||||
| 12 | Video and animated GIF fills; HEIC/TIFF images | File format | 6–12 | [file-format-parity](file-format-parity.md) |
|
||||
| 13 | Live boolean editing and the vector tail | Feature | 6–10 | [target-app-parity](target-app-parity.md) |
|
||||
| 13 | Boolean groups hit-tested by their operands (the rest of the vector tail landed) | Feature | 1–3 | [target-app-parity](target-app-parity.md) |
|
||||
| 14 | Variables: extended collections, import/export | Feature | 4–8 | [components-parity](components-parity.md) |
|
||||
| 15 | Prototyping: media triggers and actions, gamepad | Feature | 3–6 | [prototyping-parity](prototyping-parity.md) |
|
||||
| 16 | Dev Mode: Code Connect, editor integrations | Feature | 4–8 | [dev-mode-parity](dev-mode-parity.md) |
|
||||
@@ -54,21 +54,26 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe
|
||||
model), with a corpus of synthetic `.fig` files saved from the installed Figma. Export to `.fig`
|
||||
is a further 15–30 h and lower priority.
|
||||
|
||||
### 2. No real-time multiplayer or sharing
|
||||
- **Missing:** co-editing, live cursors and selections, observation mode, cursor chat, spotlight,
|
||||
audio, share links and permissions. Comments and versions are local to the file.
|
||||
### 2. Multiplayer and sharing are a preview, without accounts
|
||||
- **Missing:** accounts, teams and projects, per-person permissions, spotlight, cursor chat, audio,
|
||||
shared comments and version history, TLS in the desktop app. Comments and versions are local to
|
||||
the file.
|
||||
- **Evidence:** the core landed in #103 (`crates/collab`: client-scoped ids, change sets, server
|
||||
ordering and validation, client reconciliation, binary messages, randomized convergence tests over
|
||||
real commands), and live sessions run over the network (`crates/collab-net`: hosting from the
|
||||
app, invite links that open the app, document sync between apps, live cursors, selections,
|
||||
text carets, avatars and follow, and the browser build joins) with per-person undo, refused
|
||||
edits rolled back with a reason, reconnects that resume and send what was made offline, and
|
||||
styles, variables and comments that several people create at once all kept; but sessions are
|
||||
plain `ws://` (no TLS, so an https page can only join on the same computer), and there are no
|
||||
share links or permissions (`docs/collab-protocol.md`).
|
||||
styles, variables and comments that several people create at once all kept; a standalone
|
||||
`figcraft-server` keeps many files (snapshot plus log, compacted; restarts lose nothing
|
||||
confirmed), takes files published from the app and hands out share links that let people edit
|
||||
or only view (`docs/server.md`). But there are no accounts (a link is the access), and the
|
||||
desktop app speaks only plain `ws://` (TLS comes from a reverse proxy, which only the browser
|
||||
can use so far) (`docs/collab-protocol.md`).
|
||||
- **Impact:** Figma's defining feature; most teams use it every day. Solo designers can work
|
||||
without it.
|
||||
- **Estimate:** 45–90 h: sharing and permissions and a standalone server with TLS.
|
||||
- **Estimate:** 45–90 h: accounts, teams and per-person permissions, TLS in the desktop app,
|
||||
shared comments and versions (numbers from before the server landed; re-measured next).
|
||||
|
||||
### 3. Nothing checked against real Figma files
|
||||
- **Missing:** a corpus of real documents (design systems, app screens, prototypes) imported and
|
||||
@@ -163,10 +168,17 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe
|
||||
(help center, checked 2026-10-10). FigCraft decodes PNG, JPEG, WebP, GIF (first frame) and BMP.
|
||||
- **Estimate:** 6–12 h (video playback in the presenter is most of it).
|
||||
|
||||
### 13. Live boolean editing and the vector tail
|
||||
- **Missing:** editing a boolean group's operands in place with live results, Design panel arc
|
||||
fields, shape builder on strokes and open paths, one-sided variable widths.
|
||||
- **Estimate:** 6–10 h.
|
||||
### 13. Boolean groups hit-tested by their operands
|
||||
- **Landed (2026-10-11):** live boolean editing (operands selected by double-click or Cmd/Ctrl-click;
|
||||
moving, resizing or editing an operand's points redraws the result during the drag, with every
|
||||
operand outlined; per-operand operations from the boolean controls and `node.set`; a drag frame
|
||||
recombines only the operands from the edited one up), Design panel Arc fields (Start, Sweep,
|
||||
Ratio; Mixed across ellipses; bindable), the shape builder on strokes and open paths (strokes take
|
||||
part as their outlined areas), one-sided variable widths (Alt-drag a width handle; canvas, PNG,
|
||||
SVG, PDF and outline stroke).
|
||||
- **Missing:** a click is tested against a boolean group's operands, not its result, so a click in
|
||||
a subtracted hole still picks the group.
|
||||
- **Estimate:** 1–3 h.
|
||||
|
||||
### 14. Variables: extended collections, import/export
|
||||
- **Missing:** extended collections (a library collection extended with overrides), variables
|
||||
@@ -223,6 +235,8 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-11 | minor | Spell check while editing text: misspelled words underlined, suggestions, Ignore and Add to dictionary in the context menu, Text ▸ Spelling ▸ Check spelling (the system's checker on Windows and macOS, in the system's language; none on Linux, FreeBSD or the web yet). Text 85% → 88% breadth, 64% → 66% ready, 8–14 h → 5–10 h; menu breadth 200/203 → 201/204 (98.5%); gap 11 narrowed to the remaining platforms and languages (4–8 h → 2–5 h); weighted percentages unchanged, hour totals down 3–4 h (headline ranges unchanged within their rounding) |
|
||||
| 2026-10-11 | minor | Collaboration wording after the standalone server: `figcraft-server` keeps many files, persisted as a snapshot plus a log of what changed (compacted, checksummed, damaged files skipped and reported), restarts lose nothing confirmed and apps resume where they were; the app publishes its file to it; share links per file let people edit or only view (the server refuses a viewer's edits), are revocable and stored as hashes; TLS comes from a reverse proxy (the desktop app has none of its own yet); percentages and hours unchanged until the next re-measure |
|
||||
| 2026-10-11 | minor | Live boolean editing, Design panel arc fields, the shape builder on strokes and open paths and one-sided variable widths landed (gap 13): Shapes & vector breadth 92% → 97%, ready 66% → 68%, 8–14 h → 1–3 h; feature breadth ~78% → ~79%; ready for real work unchanged (~47%); full parity ~390–745 h → ~385–730 h |
|
||||
| 2026-10-11 | minor | Collaboration wording after the collaboration polish: styles, variables, collections, modes and comments created in a live session take ids from their creator's id space (concurrent creations no longer overwrite each other; the server checks it), others' text carets and selections show on the canvas, and typing in a layer someone else is typing in says so; percentages unchanged |
|
||||
| 2026-10-11 | minor | Wording only: live sessions (#104), then the browser joining them and `figcraft://` invite links opening the desktop app; percentages and hours unchanged until the next re-measure |
|
||||
| 2026-10-10 | minor | Merged main after #101–#103: WASM plugins (plugins area 0% → 35% breadth, 20% ready; ecosystem 5% → 20%) and the multiplayer core without networking (multiplayer 5% → 15% breadth, 5% ready; collaboration 8% → 12%); menu breadth 97.5% → 98.5% (200/203); ready for real work ~46% → ~47%; full parity 420–790 h → 390–745 h; beta 210–380 h → 200–365 h. Fixed the stated weight sums (areas 1–16 sum to 94, total 113; percentages were already computed with those) |
|
||||
|
||||
+53
-8
@@ -218,6 +218,16 @@ points: `vector.edit.widthPoint {t, width? (px) | scale?}` adds one, `{index, t?
|
||||
moves or resizes it, `{action: "remove", index}` deletes it. Rendering, PNG/SVG/PDF export and
|
||||
`vector.outlineStroke` follow the profile.
|
||||
|
||||
One-sided widths: a width point can be wider on one side of the path than the other (left and
|
||||
right looking along the path, y down). Points are `[t, left, right]` (or `{t, left, right}`) where
|
||||
each side's scale is what `scale` would be with both sides that wide, so `[t, s]` equals
|
||||
`[t, s, s]`; `get_node` reports one-sided points that way. `vector.edit.widthPoint {index, side:
|
||||
left|right, width? | scale?}` changes one side (`width`: the distance from the centre line to that
|
||||
edge; with `t` it adds a point keeping the other side as it is); a width without `side` makes the
|
||||
point symmetric again. With `pointer`, `alt: true` while dragging a handle (or the path) changes
|
||||
that side only; `vector.edit.state` lists `widthPoints` with both handle positions (`sides`:
|
||||
right, left) and `oneSided`.
|
||||
|
||||
### Prototypes
|
||||
|
||||
Interactions are `proto.interaction.add` / `set` / `remove` / `clear` (a navigate action can keep
|
||||
@@ -367,15 +377,35 @@ remove and reorder layers there, unlike the rest of an instance). It is a compon
|
||||
|
||||
`get_node` shows `"slot": "<property>"` on slot layers.
|
||||
|
||||
### Boolean groups
|
||||
|
||||
`bool.union` / `bool.subtract` / `bool.intersect` / `bool.exclude {ids?}` wrap layers in a new
|
||||
boolean group, except: with one boolean group selected they set its operation (every operand follows
|
||||
it again), and with layers inside boolean groups they set those operands' own operation. Each
|
||||
operand above the bottom one combines with the result of the operands below it by its own
|
||||
operation, or the group's (`node.set {operandOperation: union|subtract|intersect|exclude | null}`
|
||||
on any operand, or `booleanOperation` on an operand that isn't itself a boolean group; on a group
|
||||
`booleanOperation` is the group's). `get_node` shows `operandOperation` on operands that have one.
|
||||
Operands stay editable layers: `select.children` (Enter) or a double-click selects them, `cmd: true`
|
||||
clicks reach them directly, and moving, resizing or editing an operand's points (`vector.edit.*`)
|
||||
updates the group's result on every pointer move, one undo step per gesture; a dragged operand
|
||||
stays in its group. `bool.flatten` bakes the result. Only the operands from the edited one up are
|
||||
recombined during a drag (`figcraft_render::boolean`; timed by `cargo xtask bench`).
|
||||
|
||||
### Shape builder
|
||||
|
||||
The selected shapes' outlines split the canvas into regions (areas covered by the same shapes).
|
||||
`shapeBuilder.regions {ids?}` lists them: an SVG `path` in page coordinates, the `sources` covering
|
||||
each one (top-most first), `bounds` and a `point` inside. `shapeBuilder.merge {ids?, regions?,
|
||||
points?}` makes one vector from the regions (by index, or the regions under page points), styled
|
||||
like the top-most shape over the first one; `shapeBuilder.remove {ids?, regions?, points?}` deletes
|
||||
them. Either way every shape covering a picked region loses that area (it becomes a vector, or goes
|
||||
when nothing is left), and each call is one undo step. The tool is `tool.shapeBuilder` (Shift+M):
|
||||
each one (top-most first), the `parts` covering it (`{id, stroke}`: a shape's fill, or its stroke's
|
||||
outlined area; top-most first), `bounds` and a `point` inside. Visible strokes take part as their
|
||||
outlined areas, so stroked lines and open paths split the regions they cross and a stroke's band is a
|
||||
region of its own. `shapeBuilder.merge {ids?, regions?, points?}` makes one vector from the regions
|
||||
(by index, or the regions under page points), styled like the top-most fill or stroke over the first
|
||||
one (a stroke's region is filled with the stroke's paints); `shapeBuilder.remove {ids?, regions?,
|
||||
points?}` deletes them. Either way every shape covering a picked region loses that area (it becomes
|
||||
a vector, or goes when nothing is left); the strokes of the shapes involved become outlined vectors
|
||||
just above them (`outlines` in the result; an open path is replaced by its outline), and each call
|
||||
is one undo step. The tool is `tool.shapeBuilder` (Shift+M):
|
||||
click or drag across regions with `pointer` to merge, add `alt` to remove; `shapeBuilder.mode
|
||||
{mode: merge|remove}` swaps what a plain click does.
|
||||
|
||||
@@ -510,12 +540,24 @@ joined file opens as a new document).
|
||||
`collab.peers` does, with `link` (`figcraft://join?host=…&file=…&token=…`) and `url`
|
||||
(`ws://…/?file=…&token=…`): both carry the session's secret, so share them only with people you
|
||||
invite. `collab.stop {}` ends it.
|
||||
- `collab.join {link}` (either form) returns at once with `status: "connecting"`; poll
|
||||
- `collab.join {link}` (any form: an app's invite, a `figcraft-server` share link, or the
|
||||
`ws(s)://…?file=…&token=…` URL) returns at once with `status: "connecting"`; poll
|
||||
`collab.peers` until `status` is `"live"`, or `"failed"` with a `reason` (a wrong token, a full
|
||||
session, an unreachable host). `collab.leave {}` leaves.
|
||||
session, a revoked link, an unreachable host). `collab.leave {}` leaves; the synced document
|
||||
stays open as your copy.
|
||||
- `collab.publish {server, name?, key?}` puts the active document on a `figcraft-server`
|
||||
([`server.md`](server.md)) and syncs it from there: `server` is the address
|
||||
`figcraft-server key` prints (`ws://host:47900/?key=…`; or give `key` separately), `name` the
|
||||
file's name (default: the document's). It returns at once with `status: "connecting"`; the
|
||||
document is read-only until the upload is done, then `collab.peers` has `published: {server,
|
||||
file, name, edit, view}` with the file's first edit link and view link (they carry their
|
||||
tokens: share them like passwords). Someone joining with the view link gets `access: "view"`:
|
||||
every edit on the synced document is refused with "View only: …" (the server refuses them too).
|
||||
- `collab.peers {}` → `{available, canHost (false in the web build), status
|
||||
(off|connecting|live|reconnecting|failed), reason?,
|
||||
attempt?, role (host|guest), me, name, color, fileName, pages, link, url, address, network,
|
||||
access (edit|view), readOnly (the active document can't be edited now: view only, or being
|
||||
published), published (a file you published: {server, file, name, edit, view}),
|
||||
follow, syncing (the document arrived), document (the index of the synced open document),
|
||||
editing (the text layer you are typing in: {node, layer, caret, selection}, or null), peers: [{id,
|
||||
name, color, editing (the text layer they are typing in, same shape, or null), presence: {cursor:
|
||||
@@ -726,7 +768,10 @@ guide, along the frame's axis nearer the ruler's.
|
||||
the start. **Start** turns the arc and keeps its sweep. **Ratio** (the centre, or the inner edge
|
||||
halfway round a ring) drags out to make a ring, and into the gap to show the other part.
|
||||
`shift: true` snaps angles to 15° steps and the ratio to 5% steps. Each drag is one undo step
|
||||
and writes `node.set`'s `arcStart` / `arcSweep` (degrees) and `arcInner` (0–0.99).
|
||||
and writes `node.set`'s `arcStart` / `arcSweep` (degrees) and `arcInner` (0–0.99). The Design
|
||||
panel's Arc fields (Start °, Sweep %, Ratio %) write the same keys; number variables bound to
|
||||
`arcSweep` and `arcInner` (`variable.bind`) hold percentages, as the fields show them, and ones
|
||||
bound to `arcStart` degrees.
|
||||
`tool.overlay.arc` holds the readout while dragging (`{handle, label: "Sweep 75%", at}`), and
|
||||
`tool.overlay.arc_hover` holds the handle under the pointer.
|
||||
- **Select**: `select.layersAt {x, y}` lists every layer under a page point, front-most first with its
|
||||
|
||||
@@ -70,6 +70,14 @@ is in the workspace: `packaging/env.sh` sets `BINS`/`HAS_CLI` from that, and `pa
|
||||
`figcraft --version` prints the version and, in CI builds, the commit (`FIGCRAFT_BUILD_SHA`,
|
||||
read by `apps/figcraft/build.rs`).
|
||||
|
||||
**The collaboration server** (`apps/figcraft-server`, binary `figcraft-server`, see
|
||||
[`server.md`](server.md)) is in the workspace but not packaged yet: releases don't ship it, and
|
||||
operators build it with `cargo build --release -p figcraft-server`. Shipping it means a
|
||||
`figcraft-server-<v>-<platform>-<arch>` archive per desktop platform (a headless binary: no
|
||||
bundle, installer entry or desktop file), signed like `figcraft-cli` on macOS and Windows; that
|
||||
is a change to `release.yml` and `packaging/` of its own, not part of `BINS` (which every
|
||||
desktop package installs).
|
||||
|
||||
**Fonts.** Every build job checks out [craft-fonts](https://github.com/storytold/craft-fonts) at
|
||||
`CRAFT_FONTS_REF` (top of `release.yml`) and builds with `CRAFT_FONTS_DIR` and
|
||||
`CRAFT_FONTS_REQUIRED=1`, so releases embed its fonts and fail rather than ship without them.
|
||||
|
||||
+7
-5
@@ -1,6 +1,6 @@
|
||||
# FigCraft roadmap: milestones and what's next
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (spell check while editing text on Windows and macOS; menu breadth 201/204) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (spell check while editing text on Windows and macOS; menu breadth 201/204; the standalone server with share links to edit or view, wording only; live boolean editing and the vector tail: feature breadth ~79%) · **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
|
||||
@@ -17,7 +17,7 @@ are one Opus 5.5 agent; most milestones split across 4–6 agents on disjoint cr
|
||||
40–60 h, parallel by tool.
|
||||
4. **Localization layer**, then Figma's own languages first (ja, ko, es, pt-BR, fr, de): 15–25 h
|
||||
for the layer, 3–5 h per language plus native review.
|
||||
5. **Multiplayer M9**: a standalone server with TLS and sharing on top of the live sessions (the WebSocket transport, hosting from the app, invite links that open the app, document sync, live cursors, selections, text carets, avatars and follow, per-user undo, reconnect with offline edits, collision-free ids for styles, variables, collections and comments, and joining from the browser landed), per [`collab-protocol.md`](collab-protocol.md): 45–90 h.
|
||||
5. **Multiplayer M9**: accounts, teams and TLS in the desktop app on top of the live sessions and the standalone server (the WebSocket transport, hosting from the app, invite links that open the app, document sync, live cursors, selections, text carets, avatars and follow, per-user undo, reconnect with offline edits, collision-free ids for styles, variables, collections and comments, joining from the browser, and `figcraft-server` with persistence, publishing from the app and share links to edit or view landed), per [`collab-protocol.md`](collab-protocol.md) and [`server.md`](server.md): 45–90 h.
|
||||
|
||||
## Alpha gate
|
||||
|
||||
@@ -31,7 +31,7 @@ builders' side-by-side checks with synthetic files; nothing has been run on real
|
||||
| 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` | — |
|
||||
| Hand off to developers: Dev Mode inspect, code, measurements, asset export, ready for dev | yes | `devinspect_tests.rs`, `component_codegen_tests.rs`, `devcompare_tests.rs`, `annotations_tests.rs` | — |
|
||||
| Review with others: comments, versions, sharing the work | partial (not blocking) | comments with threads, mentions and resolve plus version history work inside a file (`comments_tests.rs`, `version_tests.rs`); the file is shared by passing it along; live sessions (preview: hosted from the desktop app, joined from the app or the browser) but no share links or per-person undo yet ([collaboration-parity](collaboration-parity.md)) | beta gap: 45–90 |
|
||||
| Review with others: comments, versions, sharing the work | partial (not blocking) | comments with threads, mentions and resolve plus version history work inside a file (`comments_tests.rs`, `version_tests.rs`); the file is shared by passing it along; live sessions (preview: hosted from the desktop app or a self-hosted `figcraft-server` with edit and view links, joined from the app or the browser, undo per person); no accounts or teams ([collaboration-parity](collaboration-parity.md)) | beta gap: 45–90 |
|
||||
| Continue existing Figma work (open `.fig`) | no (beta gap) | no `.fig` reader; REST JSON import works for files fetched through Figma's API ([file-format-parity](file-format-parity.md)) | beta gap: 30–60 |
|
||||
|
||||
**Passes, so FigCraft stays alpha:** every daily design workflow works end to end on FigCraft's
|
||||
@@ -50,12 +50,12 @@ blockers.
|
||||
| M1 Core editing completeness (tools, panels, menus, clipboard, files) | ~75% | Open recent, outline mode variants, keyboard box selection, interaction pass | 8–14 + pass |
|
||||
| M2 Layout depth | ~80% | grid `fr` rows, real-file QA | 6–12 |
|
||||
| M3 Components & styles depth | ~75% | extended collections, variables import/export, real libraries | 10–20 |
|
||||
| M4 Vector & text depth | ~70% | live booleans, spell check on Linux and the web, Figma Draw (brushes, pressure, text on a path, patterns) | 24–40 |
|
||||
| M4 Vector & text depth | ~75% (live booleans, arc fields, shape builder on strokes, one-sided widths landed 2026-10-11) | spell check on Linux and the web, Figma Draw (brushes, pressure, text on a path, patterns), boolean hit testing on the result | 18–32 |
|
||||
| M5 Prototype & present | ~85% | media, gamepad, separate window | 5–9 |
|
||||
| M6 Interop & Dev Mode | ~55% | `.fig` import, video/HEIC/TIFF, CMYK intent, Code Connect | 40–80 |
|
||||
| M7 Release & fidelity (signed builds, look & feel, shortcuts) | builds done; fidelity pass started | tool-by-tool pass | 40–60 |
|
||||
| M8 Hardening & performance (bench, retained canvas, accessibility, idle) | ~60% | real-file corpus, platform QA, GPU canvas, screen readers | 60–110 |
|
||||
| M9 Real-time collaboration | core done (#103: ids, change sets, server ordering, convergence); live sessions (WebSocket transport with limits and reconnect, hosting from the app, invite links, document sync between apps, cursors, selections, avatars, follow, `collab.*` commands); per-user undo, rejection handling, offline queue and resuming after a reconnect; styles, variables, collections, modes and comments created in a session get ids from their creator's space; others' text carets and selections on the canvas; the browser client (joins desktop-hosted sessions); `figcraft://` links open the app and join (Windows registers per user; macOS and Linux packages declare the scheme) | standalone server with TLS, sharing | 45–90 |
|
||||
| M9 Real-time collaboration | core done (#103: ids, change sets, server ordering, convergence); live sessions (WebSocket transport with limits and reconnect, hosting from the app, invite links, document sync between apps, cursors, selections, avatars, follow, `collab.*` commands); per-user undo, rejection handling, offline queue and resuming after a reconnect; styles, variables, collections, modes and comments created in a session get ids from their creator's space; others' text carets and selections on the canvas; the browser client (joins desktop-hosted sessions); `figcraft://` links open the app and join (Windows registers per user; macOS and Linux packages declare the scheme); the standalone `figcraft-server` (persistence with compaction, restarts that lose nothing confirmed, publishing from the app, share links to edit or view, revocable) | accounts, teams, TLS in the desktop app, packaging the server | 45–90 |
|
||||
| M10 Localization (new) | not started | layer + 11 languages | 60–100 |
|
||||
| M11 Plugins | runtime, API, menu, manager and SDK done (#101) | run-time plugin UI, settings storage, distribution, widgets; Figma plugin compatibility is an owner decision | 25–55 |
|
||||
| M12 AI features (new, owner decision) | not started | local models | 30–60 |
|
||||
@@ -72,6 +72,8 @@ blockers.
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-11 | minor | Spell check while editing text: misspelled words underlined, suggestions, Ignore and Add to dictionary in the context menu, Text ▸ Spelling ▸ Check spelling (the system's checker on Windows and macOS, in the system's language; none on Linux, FreeBSD or the web yet). Text 85% → 88% breadth, 64% → 66% ready, 8–14 h → 5–10 h; menu breadth 200/203 → 201/204 (98.5%); gap 11 narrowed to the remaining platforms and languages (4–8 h → 2–5 h); weighted percentages unchanged, hour totals down 3–4 h (headline ranges unchanged within their rounding) |
|
||||
| 2026-10-11 | minor | Collaboration wording after the standalone server: `figcraft-server` keeps many files, persisted as a snapshot plus a log of what changed (compacted, checksummed, damaged files skipped and reported), restarts lose nothing confirmed and apps resume where they were; the app publishes its file to it; share links per file let people edit or only view (the server refuses a viewer's edits), are revocable and stored as hashes; TLS comes from a reverse proxy (the desktop app has none of its own yet); percentages and hours unchanged until the next re-measure |
|
||||
| 2026-10-11 | minor | Live boolean editing, Design panel arc fields, the shape builder on strokes and open paths and one-sided variable widths landed (gap 13): Shapes & vector breadth 92% → 97%, ready 66% → 68%, 8–14 h → 1–3 h; feature breadth ~78% → ~79%; ready for real work unchanged (~47%); full parity ~390–745 h → ~385–730 h |
|
||||
| 2026-10-11 | minor | Collaboration wording after the collaboration polish: styles, variables, collections, modes and comments created in a live session take ids from their creator's id space (concurrent creations no longer overwrite each other; the server checks it), others' text carets and selections show on the canvas, and typing in a layer someone else is typing in says so; percentages unchanged |
|
||||
| 2026-10-11 | minor | Wording only: live sessions (#104), then the browser joining them and `figcraft://` invite links opening the desktop app; percentages and hours unchanged until the next re-measure |
|
||||
| 2026-10-10 | minor | Merged main after #101–#103: WASM plugins (plugins area 0% → 35% breadth, 20% ready; ecosystem 5% → 20%) and the multiplayer core without networking (multiplayer 5% → 15% breadth, 5% ready; collaboration 8% → 12%); menu breadth 97.5% → 98.5% (200/203); ready for real work ~46% → ~47%; full parity 420–790 h → 390–745 h; beta 210–380 h → 200–365 h. Fixed the stated weight sums (areas 1–16 sum to 94, total 113; percentages were already computed with those) |
|
||||
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
# figcraft-server
|
||||
|
||||
`figcraft-server` keeps FigCraft files for a team and serves their live sessions. A file published
|
||||
to it stays there when everyone has left; people open it with a **share link** that lets them
|
||||
**edit** it or only **view** it. Edits are kept as a snapshot plus a log of what changed, so a
|
||||
restart (or a crash) loses nothing that was confirmed, and apps reconnect where they were. It is
|
||||
headless, native only (no web build), and runs on the same platforms as the desktop app.
|
||||
|
||||
There are no user accounts yet: a share link is the access to one file, and the **server key**
|
||||
manages files (publishing, links, removal). Accounts and per-person permissions come later.
|
||||
|
||||
The protocol and the design behind it are in [`collab-protocol.md`](collab-protocol.md).
|
||||
|
||||
## Running it
|
||||
|
||||
```sh
|
||||
cargo build --release -p figcraft-server
|
||||
target/release/figcraft-server --listen 0.0.0.0:47900 --data /srv/figcraft
|
||||
```
|
||||
|
||||
- `--listen` defaults to `127.0.0.1:47900`: only this machine can connect. `0.0.0.0:47900` (or an
|
||||
interface's address) lets the network in; put it behind a TLS reverse proxy before you expose
|
||||
it beyond a trusted network (see below).
|
||||
- `--data` is the data directory (default `./figcraft-data`), made if missing. One server at a
|
||||
time uses a directory: a second one refuses to start.
|
||||
- Logs go to standard output (`RUST_LOG=debug` for more). They name files, link ids and addresses,
|
||||
never keys or tokens.
|
||||
- Stop it like any service (Ctrl+C, `systemctl stop`, `kill`). Every change it accepted is on disk
|
||||
before anyone is told about it, so nothing confirmed is lost; a file's log is folded into a new
|
||||
snapshot the next time everyone leaves it. Apps that were in a file keep editing and reconnect
|
||||
once the server is back (for about a minute and a half), as the same participants.
|
||||
|
||||
## Publishing a file
|
||||
|
||||
```sh
|
||||
figcraft-server --data /srv/figcraft key
|
||||
```
|
||||
|
||||
prints the address to publish to, `ws://<host>:47900/?key=<server key>`. In the desktop app,
|
||||
**Share ▸ Publish to server…** (or **File ▸ Publish to server…**) asks for that address and the
|
||||
file's name. The open file is uploaded as a new file and the tab keeps syncing through the
|
||||
server; until the upload is done the tab is read-only ("Publishing…" in the toolbar). The Share
|
||||
popover then shows an **edit link** and a **view link** to copy. Agents do the same with
|
||||
`collab.publish {server, name?}` ([`mcp.md`](mcp.md#live-sessions)).
|
||||
|
||||
Whoever has the server key can publish files and manage every file's links: give it only to
|
||||
people you trust.
|
||||
|
||||
## Joining
|
||||
|
||||
Share links look like the desktop app's invite links and join the same way (**File ▸ Join live
|
||||
session…**, opening the link, or `collab.join {link}`):
|
||||
|
||||
- `figcraft://join?host=<host:port>&file=<id>&token=<token>` for a plain `ws://` server;
|
||||
- `figcraft://join?server=wss://figcraft.example.com/&file=<id>&token=<token>` for a server behind
|
||||
a TLS reverse proxy or under a path;
|
||||
- `ws://<host:port>/?file=<id>&token=<token>` or `wss://…` (the same link as a WebSocket URL).
|
||||
|
||||
The file opens as a new tab. With a **view link** you see everyone's changes and presence, and
|
||||
others see your cursor and selection, but you can't change anything: the toolbar shows a **View
|
||||
only** badge with only the Move and Hand tools, edits are refused with a message (and the server
|
||||
refuses them too). **Leave session** keeps the file open as your own copy, editable again.
|
||||
|
||||
## Managing files and links
|
||||
|
||||
| Command | What it does |
|
||||
|---|---|
|
||||
| `figcraft-server key [--rotate]` | The address to publish to, with the key; `--rotate` makes a new key (the server must be stopped) |
|
||||
| `figcraft-server files [list]` | The files: id, name, people in it now, last change, size |
|
||||
| `figcraft-server files remove FILE` | Remove a file and its links for good (whoever is in it is disconnected) |
|
||||
| `figcraft-server links list FILE` | A file's links: id, access, created, label |
|
||||
| `figcraft-server links create FILE --access edit\|view [--label TEXT]` | A new link, printed once |
|
||||
| `figcraft-server links revoke FILE LINK` | Revoke a link: it no longer joins, and whoever joined with it is disconnected |
|
||||
|
||||
Each takes `--data` (or `--config`). When a server is running on the directory, the command asks
|
||||
it over the same port with the server key (so a revoked link's users are disconnected at once);
|
||||
otherwise it changes the directory itself. A new link is shown only when it is made: the server
|
||||
keeps only a hash of its token, so a lost link can't be recovered (revoke it and make another).
|
||||
Publishing makes an edit link and a view link labelled `Published (edit)` and `Published (view)`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Every flag is also a key of a TOML file given with `--config server.toml` (flags win; unknown keys
|
||||
are errors):
|
||||
|
||||
| Key | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `listen` | `127.0.0.1:47900` | Where to listen |
|
||||
| `data` | `figcraft-data` | The data directory |
|
||||
| `public_url` | (the listen address) | How links name the server, when people reach it another way (a reverse proxy: `wss://figcraft.example.com/`) |
|
||||
| `max_files` | 1000 | Most files kept |
|
||||
| `max_file_mb` | 64 | Largest file (its snapshot), in MiB; a welcome carries the whole file, and messages are at most 64 MiB |
|
||||
| `max_links` | 100 | Most share links per file |
|
||||
| `max_connections` | 512 | Most open connections |
|
||||
| `max_people` | 256 | Most people in one file at once |
|
||||
| `compact_after` | 1000 | Compact a file's log into a new snapshot after this many entries… |
|
||||
| `compact_mb` | 16 | …or once its log is this large (MiB) |
|
||||
|
||||
With `listen` on every interface (`0.0.0.0`) and no `public_url`, links name this machine's
|
||||
network address.
|
||||
|
||||
## The data directory
|
||||
|
||||
```text
|
||||
<data>/LOCK held while the server (or a command) uses the directory
|
||||
<data>/server.json where the running server listens (read by the commands)
|
||||
<data>/server.key the server key
|
||||
<data>/files/<id>/meta.json the file's name, when it was created and last changed
|
||||
<data>/files/<id>/links.json its links: id, access, label, created, SHA-256 of the token
|
||||
<data>/files/<id>/snapshot.bin its state at the last compaction (with a checksum)
|
||||
<data>/files/<id>/log.bin what changed since, one checksummed record per change
|
||||
```
|
||||
|
||||
- **Writes are atomic**: a snapshot, a description or a link list is written to a temporary file
|
||||
beside it, flushed, then renamed over the old one. The log is only ever appended to, and synced
|
||||
before the server tells anyone about the change.
|
||||
- **Compaction** writes the file's state as the next snapshot and starts an empty log, after
|
||||
`compact_after` entries or `compact_mb` of log, when the last person leaves, and when the server
|
||||
stops. A log left over from a compaction cut short is recognised and ignored (the snapshot holds
|
||||
it all).
|
||||
- **Damage is skipped and reported, never deleted.** At startup a folder whose description, links
|
||||
or snapshot can't be read is left out and logged; a log that ends in a cut-short or damaged
|
||||
record loses only that end (logged when the file opens). Leftover temporary files are removed.
|
||||
- The server key is stored as plain text (on Unix readable by the owner only): whoever can read
|
||||
the data directory can read every file anyway. Link tokens are stored only as hashes.
|
||||
|
||||
## Backups
|
||||
|
||||
Copy the data directory while the server is stopped, or take an atomic snapshot of the
|
||||
filesystem it lives on (ZFS, Btrfs, LVM, a VSS shadow copy). A plain copy of a running server's
|
||||
directory can catch a file between its snapshot and its log; it restores, but may lose that
|
||||
file's changes since its last compaction. To restore, stop the server, put the directory back and
|
||||
start it: apps that were in a file catch up with a fresh copy (their unsent edits are kept when
|
||||
the server still knows them, else they are told what couldn't be kept).
|
||||
|
||||
## Behind a reverse proxy (TLS)
|
||||
|
||||
The server speaks plain WebSocket (`ws://`); TLS comes from a reverse proxy in front of it, which
|
||||
gives browsers the `wss://` they need on an `https://` page.
|
||||
|
||||
- Run the server on the loopback interface (`listen = "127.0.0.1:47900"`), and set
|
||||
`public_url` to the address people use, for example `wss://figcraft.example.com/`. Links and
|
||||
the publish address then name the proxy.
|
||||
- **Caddy**: a site block for your host name whose only directive is a `reverse_proxy` to
|
||||
`127.0.0.1:47900`. Caddy obtains the certificate and passes WebSocket upgrades through on its
|
||||
own.
|
||||
- **nginx**: a `server` block listening on 443 with your certificate, and a `location` that
|
||||
passes requests to `http://127.0.0.1:47900` over HTTP/1.1 with the `Upgrade` and `Connection`
|
||||
request headers forwarded (that is what lets the WebSocket upgrade through). Raise the proxy's
|
||||
read timeout above the server's 45-second idle limit or leave its default (60 s): apps ping
|
||||
every 15 seconds.
|
||||
- **Under a path**: the server answers on any path, so the proxy can forward a prefix such as
|
||||
`/figcraft/` unchanged; set `public_url = "wss://example.com/figcraft/"`.
|
||||
- Tokens never travel in the WebSocket request (the client sends them inside its first message),
|
||||
so the proxy's access logs don't record them.
|
||||
|
||||
The desktop app has no TLS of its own yet: it can't open `wss://` links. Inside a trusted network
|
||||
or a VPN, let it reach the server's `ws://` address directly (keep the port closed to the
|
||||
internet), and use `wss://` links from the browser build.
|
||||
|
||||
## Security
|
||||
|
||||
- **Access** is a share link (128-bit random token) per file and access level, or the server key
|
||||
(128-bit) for managing files. Tokens are stored as SHA-256 hashes and every comparison runs in
|
||||
constant time; revoking a link disconnects whoever joined with it. A view link's batches are
|
||||
refused by the server whatever the client does.
|
||||
- **Transport**: plain `ws://` on the server's port; anyone on the network path can read traffic
|
||||
(presence, changes, tokens) unless TLS terminates in front of it. Use a reverse proxy for
|
||||
anything beyond a trusted network.
|
||||
- **Limits**: the transport's (message sizes, 100 messages a second per connection, idle and
|
||||
handshake timeouts, `max_connections`) and the collaboration core's (layers, changes per batch,
|
||||
`max_people`), plus `max_files`, `max_file_mb` and `max_links`. Every message is decoded
|
||||
defensively, and fuzzed in the tests.
|
||||
- **Not there yet**: user accounts, per-person permissions, rate limits per address, audit logs.
|
||||
+24
-17
@@ -1,6 +1,6 @@
|
||||
# FigCraft parity with Figma
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (spell check while editing text on Windows and macOS; menu breadth 201/204) · **Target:** Figma Design (desktop app 126.10.7, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (spell check while editing text on Windows and macOS; menu breadth 201/204; the standalone server with share links to edit or view, wording only; live boolean editing and the vector tail: feature breadth ~79%; ready ~47%; last major: full re-measure against Figma desktop 126.10.7) · **Target:** Figma Design (desktop app 126.10.7, macOS)
|
||||
|
||||
The authoritative assessment of how close FigCraft is to Figma Design. [`ROADMAP.md`](../ROADMAP.md)
|
||||
summarizes it; [`gaps.md`](gaps.md) is the ranked work list; the measured menu checklist is
|
||||
@@ -16,12 +16,12 @@ summarizes it; [`gaps.md`](gaps.md) is the ranked work list; the measured menu c
|
||||
| Number | Value | Kind |
|
||||
|---|---|---|
|
||||
| Menu/command breadth | **98.5%** (201 / 204 catalogued menu items have a live command) | **measured** (`cargo xtask parity`, re-checked from source on 2026-10-10) |
|
||||
| Feature breadth (does each Figma Design feature exist, weighted) | **~78%** (core editor areas 1–16: ~90%) | estimated from the area table below |
|
||||
| Feature breadth (does each Figma Design feature exist, weighted) | **~79%** (core editor areas 1–16: ~90%) | estimated from the area table below |
|
||||
| Ready for real work | **~47%** | estimated (dimension table below: written weights × written per-dimension scores) |
|
||||
| Essentials user | **~53%** | estimated ([below](#essentials-user): core features 77.8% × discounts 0.92 × 0.92 × 0.8) |
|
||||
| Mainstream practitioner | **~43%** | estimated ([below](#mainstream-practitioner): weekly areas 66.5% × discounts 0.9 × 0.9 × 0.8) |
|
||||
| Mainstream practitioner | **~43%** | estimated ([below](#mainstream-practitioner): weekly areas 66.7% × discounts 0.9 × 0.9 × 0.8) |
|
||||
| Remaining to beta | **~200–365 h** one Opus 5.5 agent (~40–80 h wall-clock with 5 parallel agents) | estimated |
|
||||
| Remaining to full parity | **~390–745 h** one agent (~95–170 h wall-clock with 5 parallel agents) | estimated |
|
||||
| Remaining to full parity | **~385–730 h** one agent (~95–165 h wall-clock with 5 parallel agents) | estimated |
|
||||
| Stage | **alpha**: the alpha gate passes ([roadmap](roadmap.md#alpha-gate)); `.fig` import and multiplayer are beta gaps (see [ROADMAP](../ROADMAP.md#stage)) | |
|
||||
|
||||
The previous figures (ROADMAP of 2026-10-10, "feature depth ~79%", "full parity 115–195 h") were
|
||||
@@ -35,8 +35,8 @@ tool; presence was being graded as behaviour in places.
|
||||
|
||||
| Audience | Ready % | Opus 5.5 agent wall-clock hours to ~95% | Work that dominates |
|
||||
|---|---|---|---|
|
||||
| Full target (ready for real work) | ~47% | ~375–705 h | multiplayer (45–90), localization (60–100), plugins (25–55), the interaction pass (40–60), `.fig` import (30–60), GPU canvas (30–60), AI (30–60) |
|
||||
| Mainstream practitioner | ~43% | ~180–330 h | core editor depth incl. `.fig` import (95–184), the interaction pass (40–60), real-file corpus and platform QA (25–50), `.fig` export for handing work back (15–30) |
|
||||
| Full target (ready for real work) | ~47% | ~370–695 h | multiplayer (45–90), localization (60–100), plugins (25–55), the interaction pass (40–60), `.fig` import (30–60), GPU canvas (30–60), AI (30–60) |
|
||||
| Mainstream practitioner | ~43% | ~175–320 h | core editor depth incl. `.fig` import (88–173), the interaction pass (40–60), real-file corpus and platform QA (25–50), `.fig` export for handing work back (15–30) |
|
||||
| Essentials user | ~53% | ~85–160 h | `.fig` import so files people send open (30–60), core tool polish (25–45), interaction pass on the core tools (15–25), stability QA (10–20), onboarding and starter templates (5–10) |
|
||||
|
||||
Hours are one agent working sequentially, calibrated from this repo's history (about 1 agent-hour
|
||||
@@ -97,7 +97,7 @@ Breadth: does it exist. Ready: depth, correctness and fidelity for real projects
|
||||
| 1 | Canvas & navigation | 6 | 95% | 72% | Outline mode variants (`view.outlinesSelection`, `view.maskOutlines`), Display P3 on screen, QA at extreme zooms | 3–6 |
|
||||
| 2 | Selection & transform | 8 | 95% | 68% | Handles, measuring and snapping checked tool by tool against Figma; rotated groups and mixed selections | 6–10 |
|
||||
| 3 | Layers panel | 5 | 95% | 78% | Keyboard box selection, keyboard rename/context menu, long-list QA | 2–4 |
|
||||
| 4 | Shapes & vector | 8 | 92% | 66% | Live boolean editing, Design panel arc fields, shape builder on strokes and open paths, one-sided widths | 8–14 |
|
||||
| 4 | Shapes & vector | 8 | 97% | 68% | Boolean groups hit-tested by their operands rather than their result; vector QA with real files (live boolean editing, arc fields, shape builder on strokes and open paths and one-sided widths landed) | 1–3 |
|
||||
| 5 | Paint | 7 | 85% | 66% | Video and animated GIF fills, wide-gamut canvas, embedded image profiles, HEIC/TIFF images | 5–9 |
|
||||
| 6 | Strokes | 3 | 90% | 72% | Stroke styles, brush strokes (see 20) | 2–4 |
|
||||
| 7 | Effects | 4 | 100% | 72% | Glass and progressive blur tuned side by side | 3–6 |
|
||||
@@ -110,22 +110,22 @@ Breadth: does it exist. Ready: depth, correctness and fidelity for real projects
|
||||
| 14 | Import/export | 6 | 65% | 35% | **`.fig` import** (the blocking gap), `.fig` export, HEIC/TIFF/video import, CMYK output intent ([file-format-parity](file-format-parity.md)) | 30–60 |
|
||||
| 15 | Dev Mode / inspect | 2 | 80% | 62% | Code Connect, plugins in Dev Mode, editor integrations ([dev-mode-parity](dev-mode-parity.md)) | 4–8 |
|
||||
| 16 | Comments, versions, find, misc | 3 | 80% | 58% | Comments are local only; branching; Open recent | 4–8 |
|
||||
| 17 | Multiplayer & sharing | 8 | 15% | 5% | Core landed (`crates/collab`: ids, change sets, server ordering, convergence tested); live sessions (preview): WebSocket transport, hosting from the desktop app, joining from the app or the browser, invite links that open the app, cursors, selections and follow. Left: per-person undo, offline resume, cursor chat, share links and permissions ([collaboration-parity](collaboration-parity.md)) | 45–90 |
|
||||
| 17 | Multiplayer & sharing | 8 | 15% | 5% | Core landed (`crates/collab`: ids, change sets, server ordering, convergence tested); live sessions (preview): WebSocket transport, hosting from the desktop app, joining from the app or the browser, invite links that open the app, cursors, selections and follow, per-person undo, offline resume; a self-hosted `figcraft-server` with persistence and share links to edit or view. Left: accounts and teams, cursor chat, TLS in the desktop app ([collaboration-parity](collaboration-parity.md)) | 45–90 |
|
||||
| 18 | Plugins & widgets | 4 | 35% | 20% | Own sandboxed WASM plugins landed (#101: manifest, permissions, menu, Run last, Manage, SDK, 2 examples). Left: plugin UI at run time, settings storage, distribution, widgets; Figma's plugins are JavaScript and don't run (a JS engine would be an owner decision) ([plugins.md](plugins.md)) | 25–55 |
|
||||
| 19 | Team libraries & organization | 2 | 40% | 25% | Local library files exist; no shared/cloud library, no library analytics | 10–20 |
|
||||
| 20 | Figma Draw extras | 2 | 45% | 30% | Have: variable width, lasso, shape builder, noise/texture. Missing: brushes (stretch/scatter), pressure, text on a path, pattern fills, multi-edit | 10–18 |
|
||||
| 21 | AI features | 3 | 0% | 0% | First draft, rename layers, image edit/remove background, visual search, Make: needs local models (owner decision) | 30–60 |
|
||||
| | **Weighted (÷113)** | 113 | **~78%** | **~57%** | | **215–427** |
|
||||
| | Core editor only (1–16, ÷94) | 94 | ~90% | ~67% | | 95–184 |
|
||||
| | **Weighted (÷113)** | 113 | **~79%** | **~57%** | | **208–416** |
|
||||
| | Core editor only (1–16, ÷94) | 94 | ~90% | ~67% | | 88–173 |
|
||||
|
||||
## By dimension (ready for real work)
|
||||
|
||||
| Dimension | Weight | Ready | Hours | Evidence | Doc |
|
||||
|---|---|---|---|---|---|
|
||||
| Features (depth) | 35 | 57% | 75–142 (areas 1–13, 15, 16, 20) | The table above; core editor ~67% | this file |
|
||||
| Features (depth) | 35 | 57% | 68–131 (areas 1–13, 15, 16, 20) | The table above; core editor ~67% | this file |
|
||||
| UI/UX fidelity | 15 | 58% | 40–60 | Look & feel compared side by side with UI3 (~65% by the builders); tool-by-tool interaction (cursors, modifiers while dragging, hover states, popovers) not yet compared; accessibility names and keyboard navigation landed | [ui-parity](ui-parity.md) |
|
||||
| File formats | 12 | 40% | 30–60 | No `.fig` read or write; REST JSON import, SVG in/out, PNG/JPG/WebP/PDF export solid | [file-format-parity](file-format-parity.md) |
|
||||
| Collaboration | 10 | 12% | 55–110 | Local comments and version history; the multiplayer core (sync model, server ordering, client reconciliation, convergence tests) landed in #103, and live sessions (preview) co-edit a file hosted by a desktop app, joined from the app or the browser (`docs/collab-protocol.md`) | [collaboration-parity](collaboration-parity.md) |
|
||||
| Collaboration | 10 | 12% | 55–110 | Local comments and version history; the multiplayer core (sync model, server ordering, client reconciliation, convergence tests) landed in #103, and live sessions (preview) co-edit a file hosted by a desktop app or kept on a self-hosted `figcraft-server` with edit and view links, joined from the app or the browser (`docs/collab-protocol.md`) | [collaboration-parity](collaboration-parity.md) |
|
||||
| Stability | 8 | 55% | 20–40 | Never-crash lints, command guard with rollback, recovery copies, 963 tests incl. junk-parameter sweeps and a seeded retained-render walk; but two days old, one release (v0.2.0), no field reports, QA mostly on macOS | [gaps](gaps.md) |
|
||||
| Performance | 6 | 60% | 30–60 | `cargo xtask bench`: 50k-layer page ~8 ms zoomed out, 10k-layer edits ~0.2 ms, 2,000-instance design system edits 4–64 ms; CPU (tiny-skia) canvas where Figma renders on the GPU; nothing timed against Figma on the same file | [hardware-parity](hardware-parity.md) |
|
||||
| Hardware | 3 | 50% | 15–30 | Trackpad pinch and HiDPI yes; no pen pressure, no GPU canvas, no P3 on screen, one window | [hardware-parity](hardware-parity.md) |
|
||||
@@ -133,7 +133,7 @@ Breadth: does it exist. Ready: depth, correctness and fidelity for real projects
|
||||
| Platforms | 3 | 75% | 10–20 | macOS, Windows (x64/x86/arm64), Linux, FreeBSD, web all packaged (v0.2.0); Figma also has iOS/Android apps; non-macOS QA thin | [hardware-parity](hardware-parity.md) |
|
||||
| Ecosystem/plugins | 3 | 20% | 25–55 | Own WASM plugins with a Rust SDK and two examples (#101); no Figma plugin compatibility, no widgets, no distribution or community files; the CLI, control channel and MCP server are ahead of Figma for agents | [gaps](gaps.md) |
|
||||
| AI features | 2 | 0% | 30–60 | None | [gaps](gaps.md) |
|
||||
| **Ready for real work** | 100 | **~47%** | **~390–745** | | |
|
||||
| **Ready for real work** | 100 | **~47%** | **~385–730** | | |
|
||||
|
||||
Dimension hours add up to the full-parity total without double counting: the feature-area rows
|
||||
for import/export (14), multiplayer and team libraries (17, 19), plugins (18) and AI (21) are
|
||||
@@ -187,7 +187,7 @@ libraries (19, cloud/team admin), Figma Draw extras (20, not weekly for a produc
|
||||
localization beyond English, specialist hardware. Real-time multiplayer (17) is cloud collaboration
|
||||
and is left out as an area, but its effect on working with Figma users is counted in discount 3.
|
||||
|
||||
**2. Average depth** ("Ready" column, weighted): **66.5%**.
|
||||
**2. Average depth** ("Ready" column, weighted): **66.7%**.
|
||||
|
||||
**3. Discounts for what still stops real work:**
|
||||
|
||||
@@ -197,7 +197,7 @@ and is left out as an area, but its effect on working with Figma users is counte
|
||||
| Stability on real machines | ×0.90 | Never-crash lints, command guard with rollback, recovery copies and 963 tests, but no field record at all: the repository is private, v0.2.0 has had no outside users or issue reports, CI tests run on Linux only and hands-on QA is on macOS. Harsher than VectorCraft's ×0.92 because there is no outside evidence either way |
|
||||
| File exchange with Figma users | ×0.80 | A Figma designer's work lives in shared Figma files. FigCraft can't open `.fig` or write anything Figma opens as a document; incoming work can come through the REST API JSON (one way, needs an access token), outgoing only as SVG, PDF, PNG or code. Teammates, reviewers and developers would all need FigCraft ([file-format-parity](file-format-parity.md), [collaboration-parity](collaboration-parity.md)) |
|
||||
|
||||
66.5% × 0.90 × 0.90 × 0.80 = **~43%** (estimated).
|
||||
66.7% × 0.90 × 0.90 × 0.80 = **~43%** (estimated).
|
||||
|
||||
**4. User evidence (2026-10-10):** 0 issues, 0 praise or "switched from Figma" reports, 0 comments
|
||||
from anyone but the maintainers (`gh issue list`, `gh api .../issues/comments`; the repository is
|
||||
@@ -254,8 +254,13 @@ Hours in this narrative are the builders' estimates; the tables above supersede
|
||||
other vectors, the usual layer targets and the pixel grid, with guides and a marker on the point
|
||||
landed on; Ctrl/Cmd turns it off), arc handles on the canvas (sweep, start and ratio with a
|
||||
live readout, Shift snapping, closing at the start, the other part from the gap; rotated and
|
||||
flipped ellipses, any zoom). *Left:* live boolean editing, one-sided widths, Design panel arc
|
||||
fields. 4–8 h.
|
||||
flipped ellipses, any zoom), Design panel Arc fields (Start, Sweep, Ratio; bindable), live
|
||||
boolean editing (operands selected by double-click or Cmd/Ctrl-click; moving, resizing or
|
||||
editing an operand's points redraws the result during the drag with every operand outlined;
|
||||
per-operand operations from the boolean controls; only the operands from the edited one up are
|
||||
recombined), the shape builder on strokes and open paths (strokes as their outlined areas), and
|
||||
one-sided variable widths (Alt-drag a width handle; canvas, exports and outline stroke).
|
||||
*Left:* boolean groups are hit-tested by their operands rather than their result. 1–3 h.
|
||||
2. **Text editing depth**: *landed:* OpenType features panel, small caps, lists, title case,
|
||||
truncation/max lines, font picker with previews and recents, word/line selection, IME commit,
|
||||
inline IME composition (underlined at the caret, reflowing, candidate window at the caret; the
|
||||
@@ -573,6 +578,8 @@ Carried over from the ROADMAP's "Shipped so far" (2026-10-10). The test count th
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-11 | minor | Spell check while editing text: misspelled words underlined, suggestions, Ignore and Add to dictionary in the context menu, Text ▸ Spelling ▸ Check spelling (the system's checker on Windows and macOS, in the system's language; none on Linux, FreeBSD or the web yet). Text 85% → 88% breadth, 64% → 66% ready, 8–14 h → 5–10 h; menu breadth 200/203 → 201/204 (98.5%); gap 11 narrowed to the remaining platforms and languages (4–8 h → 2–5 h); weighted percentages unchanged, hour totals down 3–4 h (headline ranges unchanged within their rounding) |
|
||||
| 2026-10-11 | minor | Collaboration wording after the standalone server: `figcraft-server` keeps many files, persisted as a snapshot plus a log of what changed (compacted, checksummed, damaged files skipped and reported), restarts lose nothing confirmed and apps resume where they were; the app publishes its file to it; share links per file let people edit or only view (the server refuses a viewer's edits), are revocable and stored as hashes; TLS comes from a reverse proxy (the desktop app has none of its own yet); percentages and hours unchanged until the next re-measure |
|
||||
| 2026-10-11 | minor | Live boolean editing, Design panel arc fields, the shape builder on strokes and open paths and one-sided variable widths landed (gap 13): Shapes & vector breadth 92% → 97%, ready 66% → 68%, 8–14 h → 1–3 h; feature breadth ~78% → ~79%; ready for real work unchanged (~47%); full parity ~390–745 h → ~385–730 h |
|
||||
| 2026-10-11 | minor | Wording only: live sessions (#104), then the browser joining them and `figcraft://` invite links opening the desktop app; percentages and hours unchanged until the next re-measure |
|
||||
| 2026-10-10 | minor | Merged main after #101–#103: WASM plugins (plugins area 0% → 35% breadth, 20% ready; ecosystem 5% → 20%) and the multiplayer core without networking (multiplayer 5% → 15% breadth, 5% ready; collaboration 8% → 12%); menu breadth 97.5% → 98.5% (200/203); ready for real work ~46% → ~47%; full parity 420–790 h → 390–745 h; beta 210–380 h → 200–365 h. Fixed the stated weight sums (areas 1–16 sum to 94, total 113; percentages were already computed with those) |
|
||||
| 2026-10-10 | minor | Readiness-by-audience table: hours to ~95% for full (~400–750 h), mainstream (~180–330 h) and essentials (~85–160 h). Full number confirmed as an additive weighted sum; unchanged |
|
||||
|
||||
+6
-4
@@ -1,6 +1,6 @@
|
||||
# UI and interaction parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (spell check while editing text on Windows and macOS; menu breadth 201/204) · **Target:** Figma Design UI3 (desktop app 126.10.7)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (spell check while editing text on Windows and macOS; menu breadth 201/204; 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:
|
||||
@@ -52,10 +52,11 @@ interaction feel not yet compared.
|
||||
|---|---|---|
|
||||
| Move, hand (Space), zoom (hold Z, Alt out, drag area) | done | |
|
||||
| Frame (presets panel, swap orientation), section, slice | done | |
|
||||
| Rectangle, ellipse (arc handles), polygon, star, line, arrow; Shift/Alt | done | Design panel arc fields missing |
|
||||
| Rectangle, ellipse (arc handles and Design panel arc fields), polygon, star, line, arrow; Shift/Alt | done | |
|
||||
| Pen: vector networks, branch, close, bend; pencil | done | no pen pressure |
|
||||
| Vector edit mode: bend, paint bucket, lasso, cut, eraser, transform box, point snapping, mirroring | done | live boolean editing missing |
|
||||
| Shape builder (Shift+M), variable width (Shift+W) | done | |
|
||||
| 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: double-click or Cmd/Ctrl-click into operands, live results while dragging or editing points, per-operand operations | done | hit-tested by the operands, not the result |
|
||||
| Text: point, auto height, fixed; caret, words, lines, IME; spell check | done | spell check uses the system's checker on Windows and macOS only, in its language |
|
||||
| Eyedropper (I) | done | |
|
||||
| Comment (C), Shift+C to show/hide | done | local only |
|
||||
@@ -91,4 +92,5 @@ interaction feel not yet compared.
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-11 | minor | Spell check while editing text: misspelled words underlined, suggestions, Ignore and Add to dictionary in the context menu, Text ▸ Spelling ▸ Check spelling (the system's checker on Windows and macOS, in the system's language; none on Linux, FreeBSD or the web yet). Text 85% → 88% breadth, 64% → 66% ready, 8–14 h → 5–10 h; menu breadth 200/203 → 201/204 (98.5%); gap 11 narrowed to the remaining platforms and languages (4–8 h → 2–5 h); weighted percentages unchanged, hour totals down 3–4 h (headline ranges unchanged within their rounding) |
|
||||
| 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 |
|
||||
|
||||
Reference in New Issue
Block a user