From 8a983f42cead4edf9deec857e39558fee8417937 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Mon, 12 Oct 2026 02:04:47 +0700 Subject: [PATCH] M3: extended collections tail - Variables a library adds to a collection this file extends are now a library update of their own. `library::pending` counts published variables of an extended collection's library collection that have no copy yet; `library.updates` lists them by name without an `id` (no copy yet); `library.acceptUpdates` brings them in, inheriting. Before, they only arrived with the library's next other update. The old special case in `accept_updates` is gone (the pending list now carries them). - Extending an extended collection: a library collection that is itself an extended collection (a brand theme extending a core theme, published) can be extended in another file; values flow down the chain as each file accepts its library's updates, and every level's overrides stay. This already worked because a library's extended collection arrives as a plain copy; it is now checked end to end and documented, and the `sync_extensions` shortcut note says why local chains aren't followed (extending takes library collections only). --- ROADMAP.md | 9 +- crates/engine/src/cmd/library.rs | 5 +- crates/engine/src/cmd/variables.rs | 6 +- crates/engine/src/extended_chain_tests.rs | 157 ++++++++++++++++++++++ crates/engine/src/lib.rs | 2 + crates/engine/src/library.rs | 39 +++--- docs/components-parity.md | 9 +- docs/gaps.md | 19 ++- docs/mcp.md | 5 +- docs/target-app-parity.md | 15 ++- 10 files changed, 220 insertions(+), 46 deletions(-) create mode 100644 crates/engine/src/extended_chain_tests.rs diff --git a/ROADMAP.md b/ROADMAP.md index f8cbc03..e0f0a54 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **Stage: alpha** · next: beta, ~26 points and ~170–305 h away (one Opus 5.5 agent; ~35–70 h wall-clock with 5 agents) -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (branch version history and finer list merges; headline percentages recomputed from the area table: feature breadth ~84%, core ~94%, mainstream ~50%; text lists, hanging punctuation and decoration styles; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; export and inspect behaviour suite and fixes; .fig import: editable vector networks and more layer kinds; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; styles and variables behaviour suite and fixes; prototyping behaviour suite and fixes; components behaviour suite and fixes; embedded image colour profiles; layers panel keyboard: F2 rename, Shift+F10 context menu, keyboard box selection, 10k-layer QA; per-side stroke weight variables; text behaviour suite: CJK line breaking, metrics, vertical trim; selection and transform behaviour suite and fixes; `.fig` import, best effort; Windows QA pass with eight fixes; auto layout behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings; feature breadth ~80%; ready ~49%) · **Target:** Figma Design (desktop app 126.10.7) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (extended collections tail; branch version history and finer list merges; headline percentages recomputed from the area table: feature breadth ~84%, core ~94%, mainstream ~50%; text lists, hanging punctuation and decoration styles; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; export and inspect behaviour suite and fixes; .fig import: editable vector networks and more layer kinds; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; styles and variables behaviour suite and fixes; prototyping behaviour suite and fixes; components behaviour suite and fixes; embedded image colour profiles; layers panel keyboard: F2 rename, Shift+F10 context menu, keyboard box selection, 10k-layer QA; per-side stroke weight variables; text behaviour suite: CJK line breaking, metrics, vertical trim; selection and transform behaviour suite and fixes; `.fig` import, best effort; Windows QA pass with eight fixes; auto layout behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings; feature breadth ~80%; ready ~49%) · **Target:** Figma Design (desktop app 126.10.7) FigCraft is a clean-room, open-source, pure-Rust reimplementation of the Figma design workflow, native on macOS, Windows, Linux and FreeBSD and in the browser, fully drivable by agents through @@ -19,7 +19,7 @@ start with [`docs/gaps.md`](docs/gaps.md). | Essentials user | **~57%** | estimated: core features 77.8% × 0.92 launch/stability × 0.92 discoverability × 0.88 opening files people send ([method](docs/target-app-parity.md#essentials-user)) | | Mainstream practitioner | **~50%** | estimated: weekly areas 72.3% × 0.9 interaction × 0.9 stability × 0.85 file exchange with Figma users ([method](docs/target-app-parity.md#mainstream-practitioner)) | | Remaining to beta | **~170–305 h** one agent | estimated | -| Remaining to full parity | **~303–590 h** one agent (~86–150 h wall-clock with 5 agents) | estimated | +| Remaining to full parity | **~302–589 h** one agent (~86–150 h wall-clock with 5 agents) | estimated | ### Readiness by audience @@ -56,7 +56,7 @@ Figma's own languages on the landed localization layer (15–25 h). | Dimension | Ready | Hours | Doc | |---|---|---|---| -| Features (depth) | 61% (core editor 72%) | 43–89 | [target-app-parity](docs/target-app-parity.md) | +| Features (depth) | 61% (core editor 72%) | 42–88 | [target-app-parity](docs/target-app-parity.md) | | UI/UX fidelity | 58% | 40–60 | [ui-parity](docs/ui-parity.md) | | File formats | 59% (`.fig` read best effort with editable vectors, no write) | 5–10 | [file-format-parity](docs/file-format-parity.md) | | Collaboration | 13% (core in; live sessions with document sync, cursors and text carets, the browser joins, invite links open the app; a self-hosted server with edit and view links and team libraries; preview) | 49–98 | [collaboration-parity](docs/collaboration-parity.md) | @@ -83,7 +83,7 @@ Figma's own languages on the landed localization layer (15–25 h). | Auto layout & constraints | 98% | 74% | 3–6 | [auto-layout-parity](docs/auto-layout-parity.md) | | Text | 90% | 70% | 3–7 | [target-app-parity](docs/target-app-parity.md) | | Components & variants | 96% | 72% | 4–9 | [components-parity](docs/components-parity.md) | -| Styles & variables | 94% | 76% | 3–5 | [components-parity](docs/components-parity.md) | +| Styles & variables | 95% | 76% | 2–4 | [components-parity](docs/components-parity.md) | | Prototyping | 90% | 74% | 3–6 | [prototyping-parity](docs/prototyping-parity.md) | | Import/export | 84% | 57% | 5–10 | [file-format-parity](docs/file-format-parity.md) | | Dev Mode / inspect | 93% | 70% | 1–3 | [dev-mode-parity](docs/dev-mode-parity.md) | @@ -213,6 +213,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md). | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | Extended collections' tail (gap 14): variables a library adds to a collection this file extends now count as a library update of their own (listed by name without a copy yet) and arrive inheriting when accepted, instead of waiting for the library's next other update; a library collection that is itself an extended collection can be extended again, values flowing down the chain as each file accepts updates with every level's overrides kept (checked end to end). Styles & variables breadth 94% → 95%, ready unchanged (76%), 3–5 h → 2–4 h; gap 14 1–2 h → 0–1 h (real-library checks left); feature breadth (~82%) and ready for real work (~51%) unchanged at this rounding; full parity ~305–594 h → ~304–593 h | | 2026-10-12 | minor | A branch's own version history and finer list merges (gap 17): versions saved or autosaved on a branch tab go into the branch (viewed, restored, renamed, deleted and duplicated there, saved in the file with it); merging adds a version to the main file's history and the merged or archived branch keeps its own; fills, strokes and effects merge item by item, conflicting only when the same item changed both ways, an item was removed on one side or both sides added different items. Comments, versions, find, misc breadth 85% → 87%, ready 60% → 61%, 2–5 h → 1–4 h; gap 17 1–3 h → 1–2 h; feature breadth (~82%) and ready for real work (~51%) unchanged at this rounding; full parity ~306–595 h → ~305–594 h (the Features table here still showed 80%/58%/4–8 h from before branches landed) | | 2026-10-12 | minor | Headline percentages recomputed from the area table by its own method (weights × written scores): feature breadth ~82% → ~84% (core editor ~93% → ~94%), weighted ready ~61% → ~63%, core ready ~70% → ~72%, mainstream weekly areas 71.3% → 72.3% and mainstream ~49% → ~50%; several earlier rows had moved the area scores without carrying them into the totals. Hours are unchanged (left for the next full re-measure) | | 2026-10-12 | minor | Text lists, hanging punctuation and decoration styles, with a behaviour suite written from the public documentation (7 layout tests and 7 command tests, about 60 table rows): lists are set per paragraph (the list buttons, shortcuts and Type settings act on the paragraphs the selection touches, the whole layer when it isn't being edited), nest up to 8 levels (Tab and Shift+Tab; numbers go 1. a. i., bullets • ◦ ▪, counted per level), Enter on an empty item and Backspace at an item's start step out a level and then out of the list, typing `- `, `* ` or `1. ` at a paragraph's start starts one; list spacing (between items, in place of paragraph spacing), hanging lists (markers outside the box) and hanging punctuation (opening punctuation outside a start-aligned edge, closing punctuation outside an end-aligned one, taking no room when lines wrap); underline and strikethrough style (solid, dotted, wavy), thickness, underline offset, skip ink and colour. Canvas, PNG, SVG and PDF; `node.set` keys (`indentation`, `listSpacing`, `hangingPunctuation`, `hangingList`, `textDecorationStyle`/`Thickness`/`Offset`/`SkipInk`/`Color`), inspect (`lineTypes`, `lineIndentations`), CSS (`text-decoration-style`/`-thickness`/`-color`, `text-underline-offset`, `text-decoration-skip-ink`) and the REST import (`lineTypes` with `lineIndentations` per paragraph, which collapsed to one list style before). Text 87%/68% → 90%/70%, 5–10 h → 3–7 h; mainstream weekly areas 71.0% → 71.2%; other percentages unchanged at this rounding; full parity ~306–595 h → ~304–592 h | diff --git a/crates/engine/src/cmd/library.rs b/crates/engine/src/cmd/library.rs index 812918d..3923775 100644 --- a/crates/engine/src/cmd/library.rs +++ b/crates/engine/src/cmd/library.rs @@ -78,7 +78,7 @@ pub fn specs() -> Vec { spec!( "library.updates", "Library updates", - "{} → the components, styles, variable collections and variables with a newer published version", + "{} → the components, styles, variable collections and variables with a newer published version, and variables new to a library collection this file extends (listed without an `id`: no copy yet)", false, updates ), @@ -574,7 +574,8 @@ fn updates(s: &mut Session, _p: &Value) -> Result { let m = &lib.manifest; let name = |r: &ImportedComponent| m.component(r.key).map(|a| a.name.clone()).unwrap_or_default(); let rows = |list: &[ImportedRecord], name: &dyn Fn(&str) -> Option| { - list.iter().map(|r| json!({"id": r.local, "key": r.key, "name": name(&r.key)})).collect::>() + // A variable new to an extended collection has no copy yet: no `id`. + list.iter().map(|r| json!({"id": (!r.local.is_empty()).then_some(&r.local), "key": r.key, "name": name(&r.key)})).collect::>() }; out.push(json!({ "id": l.id, diff --git a/crates/engine/src/cmd/variables.rs b/crates/engine/src/cmd/variables.rs index fbd8226..ae627ab 100644 --- a/crates/engine/src/cmd/variables.rs +++ b/crates/engine/src/cmd/variables.rs @@ -332,8 +332,10 @@ pub fn sync_extensions(doc: &mut Document) { .collect(); for (cid, parent) in exts { let vars = &mut doc.variables.variables; - // shortcut: an extension of an extension isn't followed (extending is offered for library - // collections only), upgrade if extended collections get published and extended again. + // shortcut: an extension of a local extended collection isn't followed: extending takes + // library collections, and a library's extended collection arrives as a plain copy, so + // chains go through libraries (`extended_chain_tests`); follow parents here if extending + // local collections is ever offered. let Some(parent) = parent.filter(|p| p.extends.is_none()) else { for v in vars.iter_mut().filter(|v| v.collection == cid) { v.extends = None; diff --git a/crates/engine/src/extended_chain_tests.rs b/crates/engine/src/extended_chain_tests.rs new file mode 100644 index 0000000..1ca3306 --- /dev/null +++ b/crates/engine/src/extended_chain_tests.rs @@ -0,0 +1,157 @@ +//! Extended collections' tail: variables a library adds to a collection this file extends are an +//! update of their own, and a library collection that is itself an extended collection can be +//! extended again, values flowing down the chain as each file accepts updates. + +use figcraft_doc::*; +use serde_json::{Value, json}; + +use crate::Session; +use crate::params::id_of; + +fn ex(s: &mut Session, cmd: &str, p: Value) -> Value { + s.execute(cmd, &p).unwrap_or_else(|e| panic!("{cmd} {p}: {e}")) +} + +fn var<'a>(s: &'a Session, collection: &str, name: &str) -> &'a Variable { + let doc = s.doc().unwrap(); + doc.variables.variables.iter().find(|v| v.collection == collection && v.name == name).unwrap() +} + +/// `name`'s value in mode `mode` (by name) of `collection`, as text. +fn value(s: &Session, collection: &str, name: &str, mode: &str) -> String { + let doc = s.doc().unwrap(); + let m = doc.variables.collection(collection).and_then(|c| c.find_mode(mode)).unwrap().id.clone(); + match var(s, collection, name).values.get(&m) { + Some(VariableValue::Color(c)) => c.to_hex(), + Some(VariableValue::Number(n)) => n.to_string(), + other => format!("{other:?}"), + } +} + +fn healthy(s: &Session) { + let doc = s.doc().unwrap(); + assert!(doc.validate().is_empty(), "{:?}", doc.validate()); +} + +fn dir(name: &str) -> std::path::PathBuf { + let d = std::env::temp_dir().join(format!("figcraft-extchain-{}-{name}", std::process::id())); + std::fs::create_dir_all(&d).unwrap(); + d +} + +/// A library file with "Theme" (Light/Dark: `primary`, `radius`) and "Other" (`size`), published +/// and saved at `path`; returns (session, library id). +fn core(path: &str) -> (Session, String) { + let mut lib = Session::new(); + ex(&mut lib, "variable.createCollection", json!({"name": "Theme", "modes": ["Light", "Dark"]})); + ex( + &mut lib, + "variable.create", + json!({"collection": "Theme", "name": "primary", "type": "color", "values": {"Light": "#0000ff", "Dark": "#ffffff"}}), + ); + ex(&mut lib, "variable.create", json!({"collection": "Theme", "name": "radius", "type": "number", "value": 4})); + ex(&mut lib, "variable.createCollection", json!({"name": "Other"})); + ex(&mut lib, "variable.create", json!({"collection": "Other", "name": "size", "type": "number", "value": 1})); + let id = ex(&mut lib, "library.publish", json!({}))["id"].as_str().unwrap().to_string(); + ex(&mut lib, "file.saveAs", json!({"path": path})); + (lib, id) +} + +fn republish(lib: &mut Session) { + ex(lib, "library.publish", json!({})); + ex(lib, "file.save", json!({})); +} + +#[test] +fn variables_new_to_an_extended_collection_are_an_update_of_their_own() { + let d = dir("new"); + let path = d.join("core.figcraft").to_string_lossy().into_owned(); + let (mut lib, lid) = core(&path); + let mut s = Session::new(); + ex(&mut s, "library.add", json!({"path": path})); + let ext = ex(&mut s, "library.extendCollection", json!({"library": lid, "collection": "Theme"}))["id"].as_str().unwrap().to_string(); + assert_eq!(ex(&mut s, "library.updates", json!({}))["count"], 0); + + // The library only adds a variable to the extended collection (and one to a collection this + // file doesn't use): that alone is an update, listed by name. + ex(&mut lib, "variable.create", json!({"collection": "Theme", "name": "gap", "type": "number", "value": 8})); + ex(&mut lib, "variable.create", json!({"collection": "Other", "name": "unused", "type": "number", "value": 2})); + republish(&mut lib); + let u = ex(&mut s, "library.updates", json!({})); + assert_eq!(u["count"], 1, "{u}"); + let row = &u["libraries"][0]["variables"][0]; + assert_eq!((&row["name"], &row["id"]), (&json!("gap"), &Value::Null), "no copy yet: no id"); + // Accepting brings it in, inheriting; then nothing is pending. + let done = ex(&mut s, "library.acceptUpdates", json!({})); + assert_eq!(done["variables"], 1); + let gap = var(&s, &ext, "gap"); + assert!(gap.extends.is_some() && gap.overrides.is_empty()); + assert_eq!(value(&s, &ext, "gap", "Dark"), "8"); + assert_eq!(ex(&mut s, "library.updates", json!({}))["count"], 0); + // Docs silent: the variable added to a collection nobody here uses stays out. + assert!(!s.doc().unwrap().variables.variables.iter().any(|v| v.name == "unused")); + healthy(&s); + std::fs::remove_dir_all(&d).ok(); +} + +#[test] +fn an_extended_collection_can_be_extended_again() { + let d = dir("chain"); + let core_path = d.join("core.figcraft").to_string_lossy().into_owned(); + let brand_path = d.join("brand.figcraft").to_string_lossy().into_owned(); + let (mut core_lib, core_id) = core(&core_path); + + // A brand library extends the core theme, overrides a value, adds a variable and publishes. + let mut brand = Session::new(); + ex(&mut brand, "library.add", json!({"path": core_path})); + let bext = ex(&mut brand, "library.extendCollection", json!({"library": core_id, "collection": "Theme", "name": "Brand"}))["id"] + .as_str() + .unwrap() + .to_string(); + let bprimary = var(&brand, &bext, "primary").id.clone(); + ex(&mut brand, "variable.set", json!({"variable": bprimary, "mode": "Light", "value": "#ff0000"})); + ex(&mut brand, "variable.create", json!({"collection": bext, "name": "logo", "type": "number", "value": 3})); + let brand_id = ex(&mut brand, "library.publish", json!({}))["id"].as_str().unwrap().to_string(); + ex(&mut brand, "file.saveAs", json!({"path": brand_path})); + + // A product file extends the brand's extended collection: the brand's values, modes and + // variables come along, and it overrides one of its own. + let mut s = Session::new(); + ex(&mut s, "library.add", json!({"path": brand_path})); + let pext = ex(&mut s, "library.extendCollection", json!({"library": brand_id, "collection": "Brand"}))["id"].as_str().unwrap().to_string(); + let modes: Vec = s.doc().unwrap().variables.collection(&pext).unwrap().modes.iter().map(|m| m.name.clone()).collect(); + assert_eq!(modes, ["Light", "Dark"]); + assert_eq!(value(&s, &pext, "primary", "Light"), "FF0000", "the brand's override is inherited"); + assert_eq!(value(&s, &pext, "primary", "Dark"), "FFFFFF", "the core value is inherited"); + assert_eq!(value(&s, &pext, "logo", "Light"), "3", "the brand's own variable is inherited"); + let radius = var(&s, &pext, "radius").id.clone(); + ex(&mut s, "variable.set", json!({"variable": radius, "mode": "Dark", "value": 12})); + let r = id_of(&ex(&mut s, "shape.rectangle", json!({"x": 0, "y": 0, "width": 10, "height": 10}))["id"]).unwrap(); + let primary = var(&s, &pext, "primary").id.clone(); + ex(&mut s, "variable.bind", json!({"ids": [r], "field": "fill", "variable": primary})); + + // The core changes: the brand accepts and republishes, then the product accepts. Values + // follow down the chain; each level's overrides stay. + ex(&mut core_lib, "variable.set", json!({"variable": "primary", "mode": "Dark", "value": "#222222"})); + ex(&mut core_lib, "variable.set", json!({"variable": "radius", "mode": "Light", "value": 6})); + republish(&mut core_lib); + ex(&mut brand, "library.acceptUpdates", json!({})); + assert_eq!(value(&brand, &bext, "primary", "Dark"), "222222"); + assert_eq!(value(&brand, &bext, "primary", "Light"), "FF0000"); + republish(&mut brand); + assert!(ex(&mut s, "library.updates", json!({}))["count"].as_u64().unwrap() > 0); + ex(&mut s, "library.acceptUpdates", json!({})); + assert_eq!(value(&s, &pext, "primary", "Dark"), "222222"); + assert_eq!(value(&s, &pext, "primary", "Light"), "FF0000"); + assert_eq!(value(&s, &pext, "radius", "Light"), "6"); + assert_eq!(value(&s, &pext, "radius", "Dark"), "12", "the product's override stays"); + healthy(&s); + + // Saved and reopened, the chain's end is the same. + let user = d.join("product.figcraft").to_string_lossy().into_owned(); + ex(&mut s, "file.saveAs", json!({"path": user})); + let mut t = Session::empty(); + ex(&mut t, "file.open", json!({"path": user})); + assert_eq!(t.doc().unwrap().variables, s.doc().unwrap().variables); + std::fs::remove_dir_all(&d).ok(); +} diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 9d9c362..59e5e48 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -1285,6 +1285,8 @@ mod effects_tests; #[cfg(test)] mod export_behaviour_tests; #[cfg(test)] +mod extended_chain_tests; +#[cfg(test)] mod fig_import_tests; #[cfg(test)] mod frame_guides_tests; diff --git a/crates/engine/src/library.rs b/crates/engine/src/library.rs index ea9ae4e..3f0df86 100644 --- a/crates/engine/src/library.rs +++ b/crates/engine/src/library.rs @@ -841,9 +841,25 @@ fn stale(records: &[ImportedRecord], lid: &str, exists: impl Fn(&str) -> bool, v records.iter().filter(|r| r.library == lid && exists(&r.local) && version(&r.key).is_some_and(|v| v > r.version)).cloned().collect() } -/// Pending updates of `doc`'s copies of library `lib`. +/// Pending updates of `doc`'s copies of library `lib`. Variables a library collection gained count +/// too when one of `doc`'s collections extends it (they have no copy yet: `local` is empty). pub fn pending(doc: &Document, lib: &PublishedLibrary) -> Pending { let (m, libs, vars) = (&lib.manifest, &doc.libraries, &doc.variables); + let mut variables = stale(&libs.variables, &m.id, |l| vars.variables.iter().any(|v| v.id == l), |k| m.variable(k).map(|x| x.version)); + let extended: BTreeSet<&str> = vars + .collections + .iter() + .filter_map(|c| libs.collection_import(c.extends.as_deref()?)) + .filter(|r| r.library == m.id) + .map(|r| r.key.as_str()) + .collect(); + let copied = |key: &str| libs.variables.iter().any(|r| r.library == m.id && r.key == key && vars.get(&r.local).is_some()); + variables.extend(published_variables(lib).filter(|v| extended.contains(v.collection.as_str()) && !copied(&v.id)).map(|v| ImportedRecord { + library: m.id.clone(), + key: v.id.clone(), + local: String::new(), + version: 0, + })); Pending { components: libs .components @@ -853,7 +869,7 @@ pub fn pending(doc: &Document, lib: &PublishedLibrary) -> Pending { .collect(), styles: stale(&libs.styles, &m.id, |l| doc.styles.iter().any(|s| s.id == l), |k| m.style(k).map(|x| x.version)), collections: stale(&libs.collections, &m.id, |l| vars.collections.iter().any(|c| c.id == l), |k| m.collection(k).map(|x| x.version)), - variables: stale(&libs.variables, &m.id, |l| vars.variables.iter().any(|v| v.id == l), |k| m.variable(k).map(|x| x.version)), + variables, } } @@ -868,24 +884,9 @@ pub fn accept_updates(doc: &mut Document, lib: &PublishedLibrary) -> Result = doc - .variables - .collections - .iter() - .filter_map(|c| doc.libraries.collection_import(c.extends.as_deref()?)) - .filter(|r| r.library == lib.manifest.id) - .map(|r| r.key.as_str()) - .collect(); - let extended: BTreeSet = - published_variables(lib).filter(|v| extended.contains(v.collection.as_str())).map(|v| v.id.clone()).collect(); - if !p.variables.is_empty() || !p.collections.is_empty() || !extended.is_empty() { + if !p.variables.is_empty() || !p.collections.is_empty() { let keys = |list: &[ImportedRecord]| list.iter().map(|r| r.key.clone()).collect::>(); - let mut vars = keys(&p.variables); - vars.extend(extended); - import_variables(doc, lib, &vars, &keys(&p.collections), true); + import_variables(doc, lib, &keys(&p.variables), &keys(&p.collections), true); } crate::cmd::variables::sync_extensions(doc); let m = &lib.manifest; diff --git a/docs/components-parity.md b/docs/components-parity.md index bc0ce3f..4a81173 100644 --- a/docs/components-parity.md +++ b/docs/components-parity.md @@ -1,11 +1,11 @@ # Components, variants, styles and variables parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (styles and variables behaviour suite and fixes; components behaviour suite and fixes; team libraries on the server, library usage and swapping a library; extended collections, variables import/export, gradient stops and variant properties bound to variables) · **Target:** Figma Design (desktop app 126.10.7) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (extended collections tail; styles and variables behaviour suite and fixes; components behaviour suite and fixes; team libraries on the server, library usage and swapping a library; extended collections, variables import/export, gradient stops and variant properties bound to variables) · **Target:** Figma Design (desktop app 126.10.7) Design systems are why teams adopt Figma. **Components: breadth ~96%, ready ~72%. Styles and -variables: breadth ~92%, ready ~72% (estimated).** QA'd with a synthetic design system of ~300 +variables: breadth ~95%, ready ~76% (estimated).** QA'd with a synthetic design system of ~300 components used 2,000 times (`crates/engine/src/synthetic_ds.rs`, `components_scale_tests.rs`); -not with real libraries. Remaining: 5–11 h here, plus team libraries' tail (4–8 h, partly after +not with real libraries. Remaining: 4–10 h here, plus team libraries' tail (4–8 h, partly after accounts). ## Components and variants @@ -67,7 +67,7 @@ updates with overrides kept: `library_tests.rs`, `components_scale_tests.rs`. | Bindings: fills, strokes, effects, opacity, padding, gaps, sizes, radius, font size/weight, line height, letter spacing, paragraph spacing/indent, text, visibility, layout grids, instance properties | done | type-checked per field; number variables fill text content | | Explicit modes per frame, library collections' modes | done | | | Gradient stops and variant properties bound to variables | done | a stop's binding follows it when earlier stops are removed | -| Extended collections | done | library collections; overrides per mode with reset; follow library updates; modes inherited. New library variables arrive with the next other update ([gaps](gaps.md) #14) | +| Extended collections | done | library collections, including a library's own extended collections (chains through libraries); overrides per mode with reset; follow library updates, variables new to the library collection being an update of their own; modes inherited. Checked with synthetic libraries only ([gaps](gaps.md) #14) | | Variables import/export (JSON) | done | the REST local variables shape (round-trips; merges by collection, mode and variable name); CSS custom properties export | | Expressions in prototype set-variable actions | done | see [prototyping-parity](prototyping-parity.md) | @@ -75,6 +75,7 @@ updates with overrides kept: `library_tests.rs`, `components_scale_tests.rs`. | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | Extended collections' tail (gap 14): variables a library adds to a collection this file extends now count as a library update of their own (listed by name without a copy yet) and arrive inheriting when accepted, instead of waiting for the library's next other update; a library collection that is itself an extended collection can be extended again, values flowing down the chain as each file accepts updates with every level's overrides kept (checked end to end). Styles & variables breadth 94% → 95%, ready unchanged (76%), 3–5 h → 2–4 h; gap 14 1–2 h → 0–1 h (real-library checks left); feature breadth (~82%) and ready for real work (~51%) unchanged at this rounding; full parity ~305–594 h → ~304–593 h | | 2026-10-11 | minor | Styles and variables behaviour suite from the public documentation (18 tests) and its fixes (styled properties edited away unlink, deleted variables unbind with values kept, type-checked bindings, alias checks on create, text styles on text only); style folders, order and duplicates with a Local styles list; mode duplicate and default; code syntax. Found missing: variables inside styles. Styles and variables breadth ~92% → ~94%, ready ~72% → ~76%; remaining here 7–14 h → 9–17 h | | 2026-10-11 | minor | Behaviour suite from the public documentation and its fixes: overrides of padding, gap, alignment, layout grids, export settings, interactions and every text property kept (they were lost when the main changed); geometry, constraints and resizing of layers inside instances refused; variant switches keep only overrides both variants agreed on (text layers keep theirs); Alt-drops from Assets keep only text overrides; top-left default variant; hidden exposed instances hide their properties; push from nested layers goes to the outer main; variant property reordering and conflict warnings. Components ~95%/~68% → ~96%/~72%; remaining here 7–14 h → 5–11 h | | 2026-10-11 | minor | Team libraries on `figcraft-server` (publish under the file, enable by a share link, updates with overrides kept), usage counts per component and file, and swapping a library by names (gap 10) | diff --git a/docs/gaps.md b/docs/gaps.md index 1d86ce3..4154fdd 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Where FigCraft falls short of Figma -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (branch version history and finer list merges; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; `.fig` import: editable vector networks, more layer kinds, variable prototype actions; `.fig` import, best effort: gap 1 down to its tail; Windows QA pass: everyday workflows on Windows 11, eight fixes incl. unsaved work kept after a crash or a forced quit; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings: gap 14 down to its tail) · **Target:** Figma Design (desktop app 126.10.7) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (extended collections tail; branch version history and finer list merges; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; `.fig` import: editable vector networks, more layer kinds, variable prototype actions; `.fig` import, best effort: gap 1 down to its tail; Windows QA pass: everyday workflows on Windows 11, eight fixes incl. unsaved work kept after a crash or a forced quit; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings: gap 14 down to its tail) · **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 @@ -27,7 +27,7 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe | 11 | Spell check | Feature | 4–8 | [target-app-parity](target-app-parity.md) | | 12 | Video fills; HEIC images | File format | 5–10 | [file-format-parity](file-format-parity.md) | | 13 | Vector tail: none left (boolean hit testing on the result landed 2026-10-11) | Feature | — | [target-app-parity](target-app-parity.md) | -| 14 | Variables: extended collections' tail | Feature | 1–2 | [components-parity](components-parity.md) | +| 14 | Variables: extended collections checked on real libraries (the tail landed 2026-10-12) | Feature | 0–1 | [components-parity](components-parity.md) | | 15 | Prototyping: media triggers and actions, gamepad | Feature | 3–6 | [prototyping-parity](prototyping-parity.md) | | 16 | Dev Mode: dev resources links, editor extension (code connect and Dev Mode plugins landed) | Feature | 1–3 | [dev-mode-parity](dev-mode-parity.md) | | 17 | Branching: branches in live sessions, lists other than fills, strokes and effects merged whole (a branch's own version history and per-item list merges landed 2026-10-12) | Feature | 1–2 | [collaboration-parity](collaboration-parity.md) | @@ -204,16 +204,20 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe SVG, PDF and outline stroke). - **Estimate:** none left (vector QA with real files is part of gap 3). -### 14. Variables: extended collections' tail +### 14. Variables: extended collections on real libraries - **Landed:** extended collections (`library.extendCollection`: a library collection's modes and variables in a collection of this file, values overridden per mode with reset, following library updates, variables of its own), variables import/export (`variable.export` / `variable.import`: the REST local variables JSON, merged by name; CSS custom properties), gradient stops and variant properties bound to variables. -- **Missing:** variables a library adds to an extended collection arrive only with the library's - next other update (they don't count as an update of their own); an extended collection can't be - extended again; checked with synthetic files only. -- **Estimate:** 1–2 h. +- **Landed (2026-10-12):** variables a library adds to a collection this file extends are a library + update of their own (`library.updates` lists them by name, without an `id` until they have a + copy) and arrive inheriting when accepted; a library collection that is itself an extended + collection (a brand theme extending a core one) can be extended again, its values flowing down + the chain as each file accepts updates, every level's overrides kept. +- **Missing:** checked with synthetic files only; extending a collection of the same file isn't + offered (extending takes library collections). +- **Estimate:** 0–1 h. ### 15. Prototyping: media triggers and actions, gamepad - **Missing:** video triggers (on media hit/end) and play/pause/mute/seek actions; gamepad input. @@ -303,6 +307,7 @@ No alpha blockers: the alpha gate in [`roadmap.md`](roadmap.md#alpha-gate) passe | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | Extended collections' tail (gap 14): variables a library adds to a collection this file extends now count as a library update of their own (listed by name without a copy yet) and arrive inheriting when accepted, instead of waiting for the library's next other update; a library collection that is itself an extended collection can be extended again, values flowing down the chain as each file accepts updates with every level's overrides kept (checked end to end). Styles & variables breadth 94% → 95%, ready unchanged (76%), 3–5 h → 2–4 h; gap 14 1–2 h → 0–1 h (real-library checks left); feature breadth (~82%) and ready for real work (~51%) unchanged at this rounding; full parity ~305–594 h → ~304–593 h | | 2026-10-12 | minor | A branch's own version history and finer list merges (gap 17): versions saved or autosaved on a branch tab go into the branch (viewed, restored, renamed, deleted and duplicated there, saved in the file with it); merging adds a version to the main file's history and the merged or archived branch keeps its own; fills, strokes and effects merge item by item, conflicting only when the same item changed both ways, an item was removed on one side or both sides added different items. Comments, versions, find, misc breadth 85% → 87%, ready 60% → 61%, 2–5 h → 1–4 h; gap 17 1–3 h → 1–2 h; feature breadth (~82%) and ready for real work (~51%) unchanged at this rounding; full parity ~306–595 h → ~305–594 h | | 2026-10-12 | minor | Comments and version history behaviour suite and fixes added to gap 4's landed list (gap 17 unchanged: a branch's own version history is still left); ranking and hours unchanged | | 2026-10-11 | minor | `.fig` import's tail (gap 1): vectors open as editable vector networks (points, curved segments, filled areas with their winding, per-point corner radius and handle mirroring, end caps from the end points), falling back to outlines when the network is missing or damaged; sticky notes, shapes with text, connectors, tables, code blocks, slides and similar layers import as the closest frames, text layers and vectors with a warning instead of being dropped; set-variable, set-variable-mode and conditional prototype actions come across. New parsers fuzzed on fixtures generated in code. File formats 55% → 58%, 8–15 h → 5–10 h; import/export breadth 80% → 82%, ready 50% → 54%; feature and area totals and ready for real work (~49%) unchanged; beta ~173–310 h → ~170–305 h; full parity ~321–618 h → ~318–613 h | diff --git a/docs/mcp.md b/docs/mcp.md index 0960a1f..0af0444 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -434,7 +434,10 @@ name and sets them this way); `mode` is left out). Variables of its own come from `variable.create`. Modes, and the names of inheriting variables, come from the library collection (renaming, adding or deleting them is refused); accepting library updates brings new values (overrides stay), renames and new - variables. The Variables editor offers Extend collection on a library collection and marks + variables. A variable the library adds to the extended collection is an update of its own + (`library.updates` lists it without an `id`, it has no copy yet). A library collection that is + itself an extended collection can be extended again: the chain follows as each file accepts + its library's updates. The Variables editor offers Extend collection on a library collection and marks overridden values, with a reset button. - **Update**: `library.updates` lists the components, styles, variable collections and variables with a newer published version; `library.acceptUpdates {ids? (library ids)}` pulls them in, diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 3fc6699..bd274cd 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,6 +1,6 @@ # FigCraft parity with Figma -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (branch version history and finer list merges; headline percentages recomputed from the area table: feature breadth ~84%, core ~94%, mainstream ~50%; text lists, hanging punctuation and decoration styles; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; export and inspect behaviour suite and fixes; .fig import: editable vector networks and more layer kinds; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; styles and variables behaviour suite and fixes; prototyping behaviour suite and fixes; components behaviour suite and fixes; embedded image colour profiles; layers panel keyboard: F2 rename, Shift+F10 context menu, keyboard box selection, 10k-layer QA; per-side stroke weight variables; text behaviour suite: CJK line breaking, metrics, vertical trim; selection and transform behaviour suite and fixes; `.fig` import, best effort; auto layout behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings; feature breadth ~80%; ready ~49%; last major: full re-measure against Figma desktop 126.10.7) · **Target:** Figma Design (desktop app 126.10.7, macOS) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (extended collections tail; branch version history and finer list merges; headline percentages recomputed from the area table: feature breadth ~84%, core ~94%, mainstream ~50%; text lists, hanging punctuation and decoration styles; comments and version history behaviour suite and fixes; canvas navigation, guides and grids behaviour suite and fixes; export and inspect behaviour suite and fixes; .fig import: editable vector networks and more layer kinds; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; styles and variables behaviour suite and fixes; prototyping behaviour suite and fixes; components behaviour suite and fixes; embedded image colour profiles; layers panel keyboard: F2 rename, Shift+F10 context menu, keyboard box selection, 10k-layer QA; per-side stroke weight variables; text behaviour suite: CJK line breaking, metrics, vertical trim; selection and transform behaviour suite and fixes; `.fig` import, best effort; auto layout behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; localization layer with a draft Spanish catalog; hosting figcraft-server: wss in the desktop app, server releases, deployment guide; plugin panels and settings; canvas fixes: boolean hit testing on the result, group drags, frame outlines and slices; code connect; stretch and scatter brushes, pen pressure; text on a path and pattern fills; team libraries on the server and swap library; branches: create, update from main, review and merge; animated GIF fills and TIFF images; Open recent, outlines for the selection and mask outlines: menu breadth 100%; extended collections, variables import/export, gradient stop and variant bindings; feature breadth ~80%; ready ~49%; 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 @@ -21,7 +21,7 @@ summarizes it; [`gaps.md`](gaps.md) is the ranked work list; the measured menu c | Essentials user | **~57%** | estimated ([below](#essentials-user): core features 77.8% × discounts 0.92 × 0.92 × 0.88) | | Mainstream practitioner | **~50%** | estimated ([below](#mainstream-practitioner): weekly areas 72.3% × discounts 0.9 × 0.9 × 0.85) | | Remaining to beta | **~170–305 h** one Opus 5.5 agent (~35–70 h wall-clock with 5 parallel agents) | estimated | -| Remaining to full parity | **~303–590 h** one agent (~86–150 h wall-clock with 5 parallel agents) | estimated | +| Remaining to full parity | **~302–589 h** one agent (~86–150 h wall-clock with 5 parallel agents) | estimated | | Stage | **alpha**: the alpha gate passes ([roadmap](roadmap.md#alpha-gate)); multiplayer and checking the best-effort `.fig` import on real files 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 @@ -105,7 +105,7 @@ Breadth: does it exist. Ready: depth, correctness and fidelity for real projects | 9 | Auto layout & constraints | 9 | 98% | 74% | Behaviour against real Figma files; wrapped-line content alignment (behaviour suite from the documentation, `fr` tracks like CSS, vertical wrap, auto gaps evenly/around and cell alignment landed) ([auto-layout-parity](auto-layout-parity.md)) | 3–6 | | 10 | Text | 8 | 90% | 70% | Spell check, real-file text metrics (behaviour suite with CJK line breaking, vertical trim and carets by grapheme cluster landed; lists per paragraph with nesting and list spacing, hanging punctuation and lists, decoration style, thickness, offset, skip ink and colour landed) | 3–7 | | 11 | Components & variants | 9 | 96% | 72% | Real (not synthetic) libraries, `.fig` libraries, component playground (behaviour suite from the documentation, overrides of padding, gap, grids, exports and interactions, refused geometry overrides, variant-switch and Assets-swap override rules, top-left default variant landed) ([components-parity](components-parity.md)) | 4–9 | -| 12 | Styles & variables | 5 | 94% | 76% | Variables inside styles, new library variables reaching extended collections on their own, extending an extended collection, real-library QA (behaviour suite from the documentation, style folders and order, mode order, code syntax, extended collections and variables import/export landed) ([components-parity](components-parity.md)) | 3–5 | +| 12 | Styles & variables | 5 | 95% | 76% | Variables inside styles, real-library QA (behaviour suite from the documentation, style folders and order, mode order, code syntax, extended collections with their tail (new library variables as updates, chains through libraries) and variables import/export landed) ([components-parity](components-parity.md)) | 2–4 | | 13 | Prototyping | 6 | 90% | 74% | Video triggers and media actions, gamepad triggers, delays on mouse triggers, manual overlay offsets, real prototype QA (behaviour suite from the documentation, key combinations, interactive components playing their variants, drag-driven animations, physical springs landed) ([prototyping-parity](prototyping-parity.md)) | 3–6 | | 14 | Import/export | 6 | 84% | 57% | `.fig` import's tail (best-effort import, editable vector networks, stand-ins for non-design layers and variable prototype actions landed; no real-file checks yet), `.fig` export, HEIC/video import, CMYK output intent ([file-format-parity](file-format-parity.md)) | 5–10 | | 15 | Dev Mode / inspect | 2 | 93% | 70% | Dev resources links, import lines for connected instances inside generated layer trees; an editor extension is left to the MCP server (inspect behaviour suite with colour formats, rem and icon and image assets, code connect and plugins in Dev Mode landed) ([dev-mode-parity](dev-mode-parity.md)) | 1–3 | @@ -115,14 +115,14 @@ Breadth: does it exist. Ready: depth, correctness and fidelity for real projects | 19 | Team libraries & organization | 2 | 70% | 40% | Libraries kept on `figcraft-server` under their file and enabled by a share link (per-asset versions, background update checks, overrides kept), usage counts per component and file, swapping a library by names. Left: team-wide library lists, accounts and permissions, analytics over time, pushed update notices | 4–8 | | 20 | Figma Draw extras | 2 | 80% | 55% | Have: variable width, lasso, shape builder, noise/texture, text on a path, pattern fills, stretch and scatter brushes, pen pressure (Windows pens, browser styluses). Missing: multi-edit, pressure from macOS/Linux tablets | 2–5 | | 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 | **~84%** | **~63%** | | **145–304** | -| | Core editor only (1–16, ÷94) | 94 | ~94% | ~72% | | 43–90 | +| | **Weighted (÷113)** | 113 | **~84%** | **~63%** | | **144–303** | +| | Core editor only (1–16, ÷94) | 94 | ~94% | ~72% | | 42–89 | ## By dimension (ready for real work) | Dimension | Weight | Ready | Hours | Evidence | Doc | |---|---|---|---|---|---| -| Features (depth) | 35 | 61% | 43–89 (areas 1–13, 15, 16, 20) | The table above; core editor ~72% | this file | +| Features (depth) | 35 | 61% | 42–88 (areas 1–13, 15, 16, 20) | The table above; core editor ~72% | 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 | 59% | 5–10 | `.fig` read, best effort, with editable vector networks (tested on generated fixtures, not real files), no `.fig` write; REST JSON import, SVG in/out, PNG/JPG/WebP/PDF export solid, with a behaviour suite from the documentation | [file-format-parity](file-format-parity.md) | | Collaboration | 10 | 13% | 55–110 | Local comments, version history and branches with review and merge; 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) | @@ -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 | 28% | 15–40 | Own WASM plugins with a Rust SDK and two examples (#101), plugin panels and settings (ABI v2); 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 | **~51%** | **~303–590** | | | +| **Ready for real work** | 100 | **~51%** | **~302–589** | | | 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 @@ -612,6 +612,7 @@ Carried over from the ROADMAP's "Shipped so far" (2026-10-10). The test count th | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | Extended collections' tail (gap 14): variables a library adds to a collection this file extends now count as a library update of their own (listed by name without a copy yet) and arrive inheriting when accepted, instead of waiting for the library's next other update; a library collection that is itself an extended collection can be extended again, values flowing down the chain as each file accepts updates with every level's overrides kept (checked end to end). Styles & variables breadth 94% → 95%, ready unchanged (76%), 3–5 h → 2–4 h; gap 14 1–2 h → 0–1 h (real-library checks left); feature breadth (~82%) and ready for real work (~51%) unchanged at this rounding; full parity ~305–594 h → ~304–593 h | | 2026-10-12 | minor | A branch's own version history and finer list merges (gap 17): versions saved or autosaved on a branch tab go into the branch (viewed, restored, renamed, deleted and duplicated there, saved in the file with it); merging adds a version to the main file's history and the merged or archived branch keeps its own; fills, strokes and effects merge item by item, conflicting only when the same item changed both ways, an item was removed on one side or both sides added different items. Comments, versions, find, misc breadth 85% → 87%, ready 60% → 61%, 2–5 h → 1–4 h; gap 17 1–3 h → 1–2 h; feature breadth (~82%) and ready for real work (~51%) unchanged at this rounding; full parity ~306–595 h → ~305–594 h | | 2026-10-12 | minor | Headline percentages recomputed from the area table by its own method (weights × written scores): feature breadth ~82% → ~84% (core editor ~93% → ~94%), weighted ready ~61% → ~63%, core ready ~70% → ~72%, mainstream weekly areas 71.3% → 72.3% and mainstream ~49% → ~50%; several earlier rows had moved the area scores without carrying them into the totals. Hours are unchanged (left for the next full re-measure) | | 2026-10-12 | minor | Text lists, hanging punctuation and decoration styles, with a behaviour suite written from the public documentation (7 layout tests and 7 command tests, about 60 table rows): lists are set per paragraph (the list buttons, shortcuts and Type settings act on the paragraphs the selection touches, the whole layer when it isn't being edited), nest up to 8 levels (Tab and Shift+Tab; numbers go 1. a. i., bullets • ◦ ▪, counted per level), Enter on an empty item and Backspace at an item's start step out a level and then out of the list, typing `- `, `* ` or `1. ` at a paragraph's start starts one; list spacing (between items, in place of paragraph spacing), hanging lists (markers outside the box) and hanging punctuation (opening punctuation outside a start-aligned edge, closing punctuation outside an end-aligned one, taking no room when lines wrap); underline and strikethrough style (solid, dotted, wavy), thickness, underline offset, skip ink and colour. Canvas, PNG, SVG and PDF; `node.set` keys (`indentation`, `listSpacing`, `hangingPunctuation`, `hangingList`, `textDecorationStyle`/`Thickness`/`Offset`/`SkipInk`/`Color`), inspect (`lineTypes`, `lineIndentations`), CSS (`text-decoration-style`/`-thickness`/`-color`, `text-underline-offset`, `text-decoration-skip-ink`) and the REST import (`lineTypes` with `lineIndentations` per paragraph, which collapsed to one list style before). Text 87%/68% → 90%/70%, 5–10 h → 3–7 h; mainstream weekly areas 71.0% → 71.2%; other percentages unchanged at this rounding; full parity ~306–595 h → ~304–592 h |