Skip to content

feat: resolve interaction destinations and show hover styles in design-tree#136

Merged
let-sunny merged 5 commits intomainfrom
feat/interaction-variant-data
Mar 27, 2026
Merged

feat: resolve interaction destinations and show hover styles in design-tree#136
let-sunny merged 5 commits intomainfrom
feat/interaction-variant-data

Conversation

@let-sunny
Copy link
Copy Markdown
Owner

@let-sunny let-sunny commented Mar 27, 2026

Summary

Enable hover variant data in the design-tree so AI can implement :hover CSS from actual Figma data.

What changed

  • component-resolver.ts: New collectInteractionDestinationIds() and resolveInteractionDestinations() — collects destinationId from ON_HOVER → CHANGE_TO interactions and fetches the variant nodes via Figma API
  • figma-node.ts: New interactionDestinations field on AnalysisFile
  • design-tree.ts: Outputs [hover]: blocks showing style differences between current state and hover variant
  • loader.ts / save-fixture.ts / figma-file-loader.ts: Wire up resolution and persistence

Example output

Button (INSTANCE, 120x40) [component: Button]
  style: background: #2C2C2C
  [hover]: background: #1E1E1E /* var:VariableID:3919:36431 */

Why

Verified

  • Tested on live Figma file: Resolved 5 interaction destination(s)
  • Design-tree shows [hover]: blocks with style diffs ✅
  • All 625 tests pass ✅
  • Type check passes ✅

Test plan

  • All 625 existing tests pass
  • Live fixture test: save-fixture resolves interaction destinations
  • implement outputs [hover]: lines in design-tree
  • Fixture rebuild needed to include interactionDestinations in data.json

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Resolve and store prototype interaction destinations when loading or saving files.
    • Design tree displays hover interactions with computed before/after visual/style diffs.
    • Saved fixtures preserve validated interaction destination data and restore it on load.
    • Missing destinations are fetched incrementally and tolerate partial fetch failures without blocking processing.

…n-tree

Add support for fetching and displaying hover variant data:

- component-resolver: collectInteractionDestinationIds() collects all
  destinationId values from ON_HOVER/CHANGE_TO interactions
- component-resolver: resolveInteractionDestinations() fetches hover
  variant nodes via Figma API (batched, same pattern as component defs)
- figma-node schema: add interactionDestinations field to AnalysisFile
- loader/save-fixture: wire up interaction resolution after component defs
- figma-file-loader: preserve interactionDestinations from saved fixtures
- design-tree: output [hover] blocks showing style diffs between current
  state and hover variant (background, border, opacity, shadow, text color)

Example output:
  Button (INSTANCE, 120x40) [component: Button]
    style: background: #2C2C2C
    [hover]: background: #1E1E1E /* var:VariableID:3919:36431 */

This enables survey item #11 (hover/interaction states) to measure
actual value with real data instead of hypothetical questions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 27, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8915b633-b91b-4755-9e2c-e30f4f22178c

📥 Commits

Reviewing files that changed from the base of the PR and between da75cd6 and 42c9f49.

📒 Files selected for processing (1)
  • src/core/engine/design-tree.ts

📝 Walkthrough

Walkthrough

Adds collection and resolution of prototype interaction destination nodes, persists them in fixtures, and passes resolved interaction destinations into the design-tree renderer to produce hover-state diffs and annotate nodes with “[hover]: …”.

Changes

Cohort / File(s) Summary
Interaction destination resolver
src/core/adapters/component-resolver.ts
Added collectInteractionDestinationIds() and resolveInteractionDestinations() to gather destination IDs and fetch/transform destination nodes in batches; batch fetch failures are logged and treated as non-fatal.
File contract
src/core/contracts/figma-node.ts
Extended AnalysisFileSchema to optionally include interactionDestinations: Record<string, AnalysisNode>.
Fixture loader
src/core/adapters/figma-file-loader.ts
Preserves interactionDestinations from existing fixtures by validating entries with AnalysisNodeSchema and assigning validated nodes to file.interactionDestinations when present.
Loader & CLI integration
src/core/engine/loader.ts, src/cli/commands/save-fixture.ts
After resolving component definitions, call resolveInteractionDestinations() and store non-empty results on file.interactionDestinations; errors are caught/logged without failing the flow.
Design-tree rendering
src/core/engine/design-tree.ts
Thread interactionDests into renderNode(), detect ON_HOVERCHANGE_TO interactions, compute visual diffs between source and destination nodes (including first-level child diffs), and append [hover]: ... annotations when diffs exist.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI (save-fixture)
    participant Loader as Engine Loader
    participant Resolver as Component Resolver
    participant Client as Figma Client
    participant FileDB as File Storage
    participant Renderer as Design Tree Renderer

    CLI->>Loader: request file load / save
    Loader->>Resolver: resolveComponentDefinitions(file)
    Resolver->>Client: getFileNodes(...) (batched)
    Client-->>Resolver: component master nodes
    Resolver-->>Loader: componentDefinitions

    Loader->>Resolver: collectInteractionDestinationIds(document)
    Resolver-->>Loader: destination IDs

    Loader->>Resolver: resolveInteractionDestinations(fileKey, document, existing)
    Resolver->>Client: getFileNodes(destIds...) (batched)
    Client-->>Resolver: destination nodes
    Resolver-->>Loader: interactionDestinations

    Loader-->>FileDB: persist file (includes interactionDestinations)
    FileDB-->>Renderer: load file for rendering
    Renderer->>Renderer: renderNode(node, interactionDests)
    Renderer->>Renderer: for ON_HOVER CHANGE_TO: computeHoverDiff()
    Renderer-->>CLI: design tree output with [hover] annotations
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I hop through nodes and gather where they lead,
Batched fetches hum as destinations take seed,
When cursors brush, a tiny hover-change appears,
I stitch the diffs and whisper them in ears,
A tree now shows its subtle, fluttered gears.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding interaction destination resolution and displaying hover styles in the design tree output.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/interaction-variant-data

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/adapters/component-resolver.ts`:
- Around line 132-163: resolveInteractionDestinations currently returns only
newly fetched nodes and omits IDs already present in existingDefinitions,
causing previously-resolved component destinations to disappear; update
resolveInteractionDestinations so the returned record includes both
existingDefinitions entries for the relevant dest IDs and the newly fetched
nodes (e.g., initialize allDestinations by copying existingDefinitions for the
collected destIds or merge existingDefinitions into allDestinations before/after
fetching), keeping the existing logic that fetches missing ids (referencing
resolveInteractionDestinations, existingDefinitions,
collectInteractionDestinationIds, and allDestinations) so callers like
loader.ts/save-fixture.ts receive a complete map.
- Around line 32-60: collectInteractionDestinationIds is currently adding every
action.destinationId; change it to only collect destinationIds for hover
interactions by checking the interaction trigger type and action navigation. In
the walk inside collectInteractionDestinationIds, only call
ids.add(action.destinationId) when the interaction object has trigger?.type ===
'ON_HOVER' (or equivalent enum) and the action has navigation === 'CHANGE_TO';
preserve existing null/undefined guards and Array.isArray checks for
n.interactions and n.children to avoid runtime errors.

In `@src/core/engine/design-tree.ts`:
- Around line 188-216: computeHoverDiff only iterates keys present in hover, so
removals or resets aren't reported; update computeHoverDiff to diff over the
union of keys between current and hover (for the root node and for each
first-level child) by building a Set of Object.keys(current) ∪
Object.keys(hover) and for each key use hover[key] if defined else emit an
explicit reset value (derive the reset/default value consistently, e.g. from
extractVisualStyles defaults or a known baseline) and push `${key}: ${value}`
(or `${cc.name}: ${key}: ${value}` for children) so style removals and
reset-to-default transitions are included.
- Around line 168-185: extractVisualStyles is recording text fills as a
"background" which causes bogus background diffs for TEXT nodes; change the
logic that sets styles["background"] from getFillInfo(node) so it does not
assign a background when node.type === "TEXT" (keep text color handling via
getFill(node) for TEXT nodes), i.e., only set styles["background"] when
fillInfo.color exists AND node.type !== "TEXT" (refer to extractVisualStyles,
getFillInfo, getFill, and the node.type === "TEXT" branch).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6915446b-1492-4793-969a-02b8e25e0deb

📥 Commits

Reviewing files that changed from the base of the PR and between c43afa5 and d2d3f25.

📒 Files selected for processing (6)
  • src/cli/commands/save-fixture.ts
  • src/core/adapters/component-resolver.ts
  • src/core/adapters/figma-file-loader.ts
  • src/core/contracts/figma-node.ts
  • src/core/engine/design-tree.ts
  • src/core/engine/loader.ts

Ensure [hover] rendering only uses ON_HOVER + CHANGE_TO targets and that style diffs include resets/removals while avoiding bogus TEXT background diffs.

Made-with: Cursor
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/engine/design-tree.ts`:
- Around line 227-236: The current loop compares children by array index which
yields incorrect diffs when order changes; update the logic in the block that
iterates currentNode.children and hoverNode.children (the code that uses cc =
currentNode.children[i], hc = hoverNode.children[i], extractVisualStyles and
appendStyleDiffs) to match children by stable key (preferably id, fallback to
name) instead of by index: build a lookup (map) of hover children by id/name,
iterate currentNode.children, find the corresponding hover child by id/name, and
only call extractVisualStyles/appendStyleDiffs for matched pairs; handle
unmatched children separately (new/removed) as needed and fall back to index
only when no stable key exists.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d22808e3-c728-451f-b978-a5f4c8aa8169

📥 Commits

Reviewing files that changed from the base of the PR and between d2d3f25 and d40f3e7.

📒 Files selected for processing (2)
  • src/core/adapters/component-resolver.ts
  • src/core/engine/design-tree.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/engine/design-tree.ts`:
- Around line 245-251: The loop over currentNode.children uses the loop index i
to access hoverUnmatchedByIndex which causes index drift when keyed children are
present; introduce a separate unkeyed counter (e.g., unkeyedIdx) initialized
before the loop and, inside the loop, when getChildStableKey(cc) returns falsy,
use hoverUnmatchedByIndex[unkeyedIdx] and increment unkeyedIdx only in that
branch—keep using hoverByStableKey.get(stableKey) for keyed children and do not
advance unkeyedIdx for them so unkeyed matches stay aligned with
hoverUnmatchedByIndex.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4cbbb66e-5c9e-442b-a8af-007d68fb3a6a

📥 Commits

Reviewing files that changed from the base of the PR and between d40f3e7 and 90bff30.

📒 Files selected for processing (1)
  • src/core/engine/design-tree.ts

Use separate unkeyedIdx counter for fallback matching instead of
the loop index i, which drifts when keyed children precede unkeyed ones.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/engine/design-tree.ts`:
- Around line 217-257: getChildStableKey currently returns id first which masks
name-based matching because id is always present; change getChildStableKey to
prefer a stable semantic key (e.g., name-based key like `name:${node.name}` when
name exists) and fall back to id only when name is absent so computeHoverDiff
can match variant children whose ids differ; update getChildStableKey (and any
callers such as computeHoverDiff) to use the new preference so
hoverByStableKey/unkeyed matching can find corresponding hover children.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7ed95740-ddea-4ed1-ace2-d08d911365ca

📥 Commits

Reviewing files that changed from the base of the PR and between 90bff30 and da75cd6.

📒 Files selected for processing (1)
  • src/core/engine/design-tree.ts

Variant children share the same name but have different ids.
Using name-first matching ensures hover diffs correctly pair
corresponding children across default and hover variants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@let-sunny let-sunny merged commit 05455c9 into main Mar 27, 2026
2 of 3 checks passed
@let-sunny let-sunny deleted the feat/interaction-variant-data branch March 27, 2026 12:44
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.

1 participant