ADR-044, ADR-045: Duplicate layer disambiguation + provenance signals#60
ADR-044, ADR-045: Duplicate layer disambiguation + provenance signals#60nathanacurtis wants to merge 21 commits into
Conversation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ADR for handling duplicate Figma layer names in Anatomy, Elements, and Layout. Proposes numeric suffix disambiguation with optional originalName metadata on AnatomyElement for traceability. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…depth - Fix name collision: suffix algorithm now skips reserved names (icon2 exists as original → disambiguated icon gets icon3, not icon2) - Move originalName into $extensions["com.figma"] per established DTCG extension convention (matches TokenReference, PropExtensions) - Expand cross-variant key stability with two detailed strategies: per-variant merge vs global registry two-pass approach Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace two-strategy section with single global registry approach enhanced with anchor-relative positioning - Unique siblings serve as stable reference points across variants — duplicates get identity from segment + position within segment - Detailed examples: single-anchor, multi-anchor, edge cases - Fallback to absolute position when no anchors available - Remove per-variant Strategy A (non-starter for cross-variant stability) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace left-aligned positional matching with anchor-adjacent matching: elements closest to an anchor have strongest identity and are matched first. Before-anchor segments match right-to-left, after-anchor segments match left-to-right. New/extra elements appear at segment edges, farther from anchors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add explicit rationale for anchor-adjacent over left-to-right: terminal semantic roles, right-edge growth dominance, graceful fallback - Adversarial analysis against 10 real UI patterns (alert, breadcrumb, form, card, stars, tabs, stepper, accordion, table row, segmented) - Categorize pattern outcomes: high/medium/low confidence - Error risk scale by anchor density with tolerance analysis - Performance analysis of two-pass approach (negligible overhead) - Add formatKey collision edge case Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add scope callout in Context: reviewers must evaluate both schema and processing architecture sections - Fix traversal section (§1) to be coherent with global registry (§2): survey-first approach, no retroactive renaming - Add implementation sequencing: schema first, then specs-from-figma in three phases (survey infra → disambiguation → parity testing) - Add testing strategy: unit tests for collision safety, anchor matching, passthrough; parity tests for duplicate-name fixtures - Add rollout impact: no change for unique-name components, no feature flag needed, not breaking for downstream parsers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add MatchMethod enum (unique | anchor-adjacent | positional) to AnatomyElement.$extensions[com.figma] - exposes the heuristic used for each element cross-variant identity determination - Present on all elements when component has any duplicates; omitted entirely when component has no duplicates (zero noise for common case) - Update type, schema, and example output to show matchMethod - Add provenance-as-design-principle consequence: deterministic steps should embed confidence signals so LLMs can reason about data quality Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove MatchMethod enum, matchMethod field, and provenance-as-principle from ADR-044. Disambiguation (collision-safe suffixes, anchor-adjacent matching, $extensions.originalName) stands on its own. A subsequent ADR will evaluate provenance signals as a separable decision. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add matchMethod (unique | anchor-adjacent | positional) to AnatomyElement.$extensions["com.figma"] as a confidence signal for cross-variant element matching. Establishes the pattern for future processing provenance fields. Depends on ADR-044; designed to ship in the same schema release. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rchy Move Form Fields from high to medium confidence — anchor-adjacent from Submit mismatches when new fields are inserted just before Submit (the common pattern). Add Disambiguation Hierarchy section naming three signal levels: parent containment (strongest), anchor-adjacent, positional fallback. Elevate Card Icons to explicitly call out parent containment as the primary heuristic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…solution Rename "parent containment" to "ancestry containment" — the full chain of named ancestors contributes to scoping, not just the immediate parent. Add top-down resolution: same-named parents are disambiguated at their depth level before their children are scoped. Includes worked example showing Section/Section2 recursive descent. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add limitation to ancestry containment: when containers migrate between parents across variants, ancestry paths conflict (immediate parent vs longest prefix). Resolution rule: match containers by disambiguated key, not path — consistent with the flat Anatomy record model. Connect to floating elements section. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…identity Distinguish unique-name floating (identity preserved) from duplicate-name floating (identity lost when scope changes). Add worked example showing V1 L4>Icon and L6>Icon vs V2 L1>Icon — treated as three distinct elements because scopes differ. This is correct given available information: scope change is indistinguishable from replacement without external metadata. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…y flat key Remove incorrect claim that duplicate-named elements changing parents lose identity. Cross-variant matching is ALWAYS by flat key, independent of ancestry. Ancestry scoping only affects within-variant disambiguation (which sibling gets which suffix). Clarify the two-step model: 1 within-variant disambiguation (per-parent scope) 2) cross-variant matching (flat key only) The real risk is suffix instability, not parent changes — which is what the global two-pass registry solves. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> EOF )
…signment First-traversal-occurrence naming causes 95 wrong comparisons when V1 has two Icons but V2-V96 have one — the persistent element gets a suffix in V1 and the base name in V2-V96, diverging the flat keys. Fix: assign base name to the identity appearing in the MOST variants. Traversal order is only the tiebreaker. Update algorithm, worked examples, and cross-variant matching section with explicit prevalence examples. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ADR-044: Replace monolithic algorithm with two-phase architecture (signal collection then ordered heuristic chain). Five checks in order: name, type, ancestry, anchor, sibling index. Remove convoluted sections. ADR-045: Update MatchMethod enum from 3 to 5 values, mapping 1:1 to ADR-044 heuristic chain levels. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
e089c66 to
7de8c1c
Compare
…ompositions main owns ADR-043 (custom-color-format-config) and PR #60 owns ADR-044/045 (duplicate-layer-disambiguation, processing-provenance-signals). This branch's three ADRs collide with both. Move ours to the next free range so PR #60 and already-merged work remain valid: - 043-component-examples -> 046-component-examples - 044-slot-content -> 047-slot-content - 045-prop-configurations-binding -> 048-prop-configurations-binding Reserve 049 for the deferred recursion ADR (Nested Slot Compositions, called out as a follow-on in ADR-047). Stub authored. Updates all ADR-NNN cross-references and branch-name references inside our four ADRs and INDEX.md. INDEX.md description for the slot-content row also brought in line with the redesigned ADR-047 title (the prior description referenced removed concepts like SlotExample / defaultComposition). Indexing change only - no design decisions altered. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cherry-pick ADR numbers 045-049 on main so concurrent branches don't collide. - 045: Processing Provenance Signals (PR #60, was missing from index) - 046: Component Examples - 047: Slot Content - 048: PropConfigurations PropBinding - 049: Nested Slot Compositions (deferred recursion follow-on to 047) ADR files for 045 live in PR #60; files for 046-049 are drafted on the `042-composition-type` branch. This commit only registers the numbers in INDEX.md; no ADR files are added. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements ADRs 044 and 045 under a revised open-passthrough policy: AnatomyElement.$extensions is typed as Record<string, unknown> with additionalProperties: true in schema. specs-from-figma writes com.figma.originalName (ADR-044) and com.figma.matchMethod (ADR-045) as producer conventions; the schema does not constrain inner fields. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| - `Elements = Record<string, Element>` | ||
| - `Layout` serializes to `{ [nodeName]: LayoutNode[] }` | ||
|
|
||
| The schema contract implicitly assumes **unique layer names within a component**. When a Figma component contains duplicate layer names — which is common and valid in Figma — the processing engine has no schema-level affordance to represent them distinctly. This causes silent data loss: duplicate names overwrite each other in every `Record`-keyed structure, and variant differencing produces incorrect results because elements can't be matched across variants. |
There was a problem hiding this comment.
How does Figma disambiguate layers, if at all? Some sort of layer identifier? could that be useful to us in anyway?
There was a problem hiding this comment.
I just realized that across variants, the same icon (from a design intent standpoint) would be a different layer completely, so I see why this won't work.
| - **Deterministic disambiguation**: The same Figma input must always produce the same disambiguated keys. No randomness, no side effects. | ||
| - **Stable variant matching**: Disambiguated keys must be consistent across variants so that differencing produces correct diffs. An element that exists in multiple variants must receive the same key in each. | ||
| - **Additive change**: Prefer a MINOR (additive) schema change over a MAJOR (breaking) one. Existing specs with unique names should be unaffected. | ||
| - **Human-readable keys**: Disambiguated keys should remain meaningful to consumers (designers, developers reading spec output). |
There was a problem hiding this comment.
I guess if there was a Figma layer identifier then this requirement would make that less useful, but I wonder if there are ways for human readability which involve a computer-friendly identifier.
| originalName: checkbox | ||
| icon2: | ||
| type: glyph # "icon2" IS the original Figma name | ||
| icon: |
There was a problem hiding this comment.
Should we consider adding a numeric suffix to all when it's part of a disambiguated set? So, start at index=0?
| ##### Low confidence — may mismatch | ||
|
|
||
| | Pattern | Scenario | What goes wrong | | ||
| |---------|----------|----------------| | ||
| | **Semantic swap** | VA: `[icon-A, icon-B]`, VB: `[icon-B, icon-A]` — both named `Icon`, swapped | No check can detect semantic identity swaps without external metadata. | | ||
| | **Column reorder** | All named `Cell`, only last is anchored | Reordering before the anchor swaps suffixes. Acceptable: output is still lossless, only diff granularity degrades. | |
There was a problem hiding this comment.
Is this acceptable given the goal of losslessness?
| #### 9. Edge cases | ||
|
|
||
| - **No anchors**: All siblings share the same name → Check 5 (positional). Weakest but always available. | ||
| - **Anchor absent in some variants**: Only names present in every variant that contains them qualify as anchors. If an anchor is optional, its segments merge in variants where it's absent. | ||
| - **formatKey collisions**: `My Icon` and `myIcon` both → `myIcon` in camelCase. Treated as a duplicate. `originalName` records the formatted key of the dominant name. | ||
| - **Anchor ordering inconsistency**: If anchors appear in different orders across variants, segment boundaries diverge. Falls back to positional for affected segments. |
There was a problem hiding this comment.
You obviously know how people use Figma more than I do, but it seems less than ideal to move forward with a solution which doesn't solve even these most awkward/uncommon edge cases. Even if that solution is a logged warning or breaking error in the CLI. Should we account for the edge cases that way?
| When `specs-from-figma` runs ADR-044's disambiguation, it writes — at the point where the heuristic chain resolves each element's identity — a `matchMethod` key under `$extensions["com.figma"]`. The value vocabulary is: | ||
|
|
||
| ```yaml | ||
| matchMethod values (ordered by confidence, highest first): |
There was a problem hiding this comment.
Is this actually an enum? Should the values have enum casing if so?
Summary
Test plan
Generated with Claude Code