Skip to content

figma-transformer: decode hyperlinks and reaction URL/navigation links#336

Merged
chubes4 merged 1 commit into
trunkfrom
cook/figma-decode-reactions-links
Jun 29, 2026
Merged

figma-transformer: decode hyperlinks and reaction URL/navigation links#336
chubes4 merged 1 commit into
trunkfrom
cook/figma-decode-reactions-links

Conversation

@chubes4

@chubes4 chubes4 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Wires Figma hyperlinks and prototype navigation through the figma-transformer. Per coverage issue #328, the normalizer's normalizeReactionLink() / normalizeHyperlinkValue() and the emitter's <a class="figma-link"> wrapping already existed, but hyperlink and the prototype-interaction list were not in the selective Kiwi decoder's field policy, so links carried in .fig files were silently skipped at gate 1. Buttons and linked text therefore never became real anchors.

What changed

  • FigKiwiDecoder.php — whitelist hyperlink, prototypeInteractions, reactions, and transitionNodeID on NodeChange, plus the supporting Hyperlink, PrototypeInteraction, PrototypeEvent, and PrototypeAction struct policies needed for the URL/navigation fields to decode. Kiwi names/enum tokens were verified by decoding a real .fig schema chunk (the schema embeds Hyperlink {url, guid}, PrototypeInteraction {event, actions}, PrototypeAction {transitionNodeID, connectionType, connectionURL, navigationType}, ConnectionType {NONE, INTERNAL_NODE, URL, ...}, NavigationType {NAVIGATE, OVERLAY, SWAP, SCROLL_TO}).
  • ScenegraphNormalizer.php — the decoded Kiwi shape differs from the REST shape the normalizer was written for, so bridge the gaps without changing behavior:
    • read the Kiwi Hyperlink.guid as a node target (the struct has no REST type field);
    • iterate prototypeInteractions alongside reactions (same list, different name);
    • map PrototypeAction.connectionType (URL/INTERNAL_NODE), connectionURL, navigationType, and the transitionNodeID GUID onto the existing action-link extraction.
  • Emitter — unchanged; wrapWithLink() already wraps in <a href>, resolving node navigation to the target frame's page_path (or a # placeholder + link_target_unresolved diagnostic).

Scope boundary (prototype animation)

This is about links — URL and node navigation — not full prototype fidelity. PrototypeAction also carries transition animations, overlay/swap behavior, and variable mutations; those fields are intentionally left undecoded. Full prototype-animation/overlay/swap support is a separate, larger effort.

Tests (real output)

Added to tests/contract/run.php and passing (php tests/contract/run.php):

  • a Kiwi-shaped hyperlink URL node emits <a class="figma-link" href="https://..." data-figma-link-type="url">;
  • an ON_CLICK prototypeInteractions entry with a URL action emits the same anchor wrapper;
  • a binary-Kiwi decode fixture proving the new field policy carries the hyperlink URL, the connection type/URL, the interaction trigger, and the GUID navigation destination through the real generic decoder (gate-1 proof).

Refs #328.

AI assistance

  • AI assistance: Yes
  • Tool(s): Claude Sonnet 4.6 via Claude Code
  • Used for: Implementation

The normalizer's normalizeHyperlinkValue()/normalizeReactionLink() and the
emitter's <a class="figma-link"> wrapping already existed, but `hyperlink` and
the prototype-interaction list were absent from the selective Kiwi decoder's
field policy, so links carried in .fig files were silently skipped at gate 1
(coverage issue #328). Buttons and linked text therefore never became anchors.

Decode the link surface and bridge the REST-vs-Kiwi shape gaps:

- FigKiwiDecoder: whitelist `hyperlink`, `prototypeInteractions`, `reactions`,
  and `transitionNodeID` on NodeChange, plus the supporting `Hyperlink`,
  `PrototypeInteraction`, `PrototypeEvent`, and `PrototypeAction` struct
  policies needed for the URL/navigation fields to reach the IR.
- ScenegraphNormalizer: the Kiwi schema names differ from REST. Read the Kiwi
  `Hyperlink.guid` as a node target, iterate `prototypeInteractions` alongside
  `reactions`, and map `PrototypeAction.connectionType`/`connectionURL`/
  `navigationType`/`transitionNodeID` (GUID) onto the existing action-link path.

Scope is link extraction only: URL links and node navigation (mapped to the
target frame's page_path, else a `#` placeholder + diagnostic). The richer
prototype payload PrototypeAction also carries (transition animations, overlay
and swap behavior, variable mutations) is intentionally left undecoded; full
prototype-fidelity is a separate, larger effort.

Tests (tests/contract/run.php): assert a Kiwi-shaped `hyperlink` URL and an
ON_CLICK `prototypeInteractions` URL action each emit a real <a href> anchor,
plus a binary-Kiwi decode fixture proving the new field policy carries the
hyperlink URL, connection type/URL, trigger, and GUID destination through the
real generic decoder.
@chubes4 chubes4 force-pushed the cook/figma-decode-reactions-links branch from 31af0c4 to 9070340 Compare June 29, 2026 01:11
@chubes4 chubes4 merged commit 83a2dd5 into trunk Jun 29, 2026
@chubes4 chubes4 deleted the cook/figma-decode-reactions-links branch June 29, 2026 01:11
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