Skip to content

ADR-044, ADR-045: Duplicate layer disambiguation + provenance signals#60

Open
nathanacurtis wants to merge 21 commits into
mainfrom
adr/044-duplicate-layer-disambiguation
Open

ADR-044, ADR-045: Duplicate layer disambiguation + provenance signals#60
nathanacurtis wants to merge 21 commits into
mainfrom
adr/044-duplicate-layer-disambiguation

Conversation

@nathanacurtis
Copy link
Copy Markdown
Member

Summary

  • ADR-044 (Duplicate Layer Name Disambiguation): Collision-safe numeric suffixes for duplicate Figma layer names, originalName extension for traceability, and a two-phase processing architecture (evaluation then disambiguation) with a five-level ordered heuristic chain (name, type, ancestry, anchor, sibling index)
  • ADR-045 (Processing Provenance Signals): matchMethod field on AnatomyElement extensions recording which heuristic resolved each element (unique, layer-type, ancestry, anchor-adjacent, sibling-index)
  • Both ship together in schema 0.22.0 (MINOR — additive optional fields only)

Test plan

  • Review ADR-044 algorithm design and worked examples
  • Review ADR-045 enum values align 1:1 with ADR-044 heuristic chain
  • Confirm schema change is additive (MINOR) with no breaking changes
  • Validate cross-references between ADR-044 and ADR-045 are consistent

Generated with Claude Code

@nathanacurtis nathanacurtis changed the base branch from main to release/schema-0.21.0+cli-0.14.0 May 7, 2026 13:12
nathanacurtis and others added 17 commits May 7, 2026 09:13
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>
@nathanacurtis nathanacurtis force-pushed the adr/044-duplicate-layer-disambiguation branch from e089c66 to 7de8c1c Compare May 7, 2026 13:13
nathanacurtis added a commit that referenced this pull request May 11, 2026
…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>
nathanacurtis added a commit that referenced this pull request May 11, 2026
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.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does Figma disambiguate layers, if at all? Some sort of layer identifier? could that be useful to us in anyway?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consider adding a numeric suffix to all when it's part of a disambiguated set? So, start at index=0?

Comment on lines +480 to +485
##### 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. |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this acceptable given the goal of losslessness?

Comment on lines +491 to +496
#### 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.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this actually an enum? Should the values have enum casing if so?

Base automatically changed from release/schema-0.21.0+cli-0.14.0 to main May 15, 2026 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants