Skip to content

Add /adhd:pull-component skill#4

Merged
hhff merged 9 commits into
mainfrom
adhd/pull-component
May 11, 2026
Merged

Add /adhd:pull-component skill#4
hhff merged 9 commits into
mainfrom
adhd/pull-component

Conversation

@hhff
Copy link
Copy Markdown
Member

@hhff hhff commented May 11, 2026

Summary

Adds /adhd:pull-component <react-path | figma-url> [--allow-unbound] — pulls a Figma Component Set back into a React source file. Inverse direction of /adhd:push-component. Updates only design-token lookup tables (Record<Union, string>) and union type aliases — function body, JSX, hooks, handlers, and imports are never touched.

Architecture: LLM as the diff/apply engine

This skill runs inside Claude Code, so the LLM is already in the orchestration loop. It reads the React source, extracts the Figma Component Set via use_figma, computes the diff in working memory, prompts the user via AskUserQuestion, and applies changes via Edit tool calls. Traditional code is reserved for the deterministic, testable parts:

  • lib/pull-component/config-writer.js — reads & idempotently writes components.<path>.figma.url in adhd.config.ts. Regex + brace-counting parser; zero deps; 9 unit tests.
  • lib/pull-component/cli.js — three subcommands (config-write/read/reverse); 8 surface tests.
  • lint-engine (existing, reused) — pre-flight runs the same checkStructure that /adhd:lint uses.
  • skills/pull-component/SKILL.md — the 11-phase orchestrator that handles all the intelligent work via Read/use_figma/AskUserQuestion/Edit.

The first draft of this design had parse-react.js / differ.js / apply.js modules doing brittle TS-compiler-API extraction + golden-file-tested AST surgery. User gut-checked: "Claude Code is the reason we're doing this code gen. I want the intelligence of Claude Code to know how to diff this stuff. I don't want to use rigid, brittle code to do it when we have a full beautiful LLM to do it." The revised design pushes intelligence into the SKILL prompt where it belongs; the library shrinks to ~200 lines covering only schema-level config mutation.

Pipeline (in SKILL)

  1. Validate config
  2. Resolve target (path / URL / scaffold mode) — via config-writer CLI
  3. Pre-flight lint of the Figma Component Set (same lint-engine as /adhd:lint)
  4. Read React source (LLM) + extract Figma variants (use_figma)
  5. Diff (in working memory)
  6. Prompt per-divergence via AskUserQuestion
  7. Drift check (re-hash Figma; abort on change)
  8. Apply via Edit tool calls (update mode) or Write (scaffold mode)
  9. Write component mapping if scaffold mode
  10. Per-axis commit (ADHD pull: <Component>.<axis> (<N> changes))
  11. Cleanup

Key design

  • The React file IS the snapshot — no parallel state stored in the repo. Record<Union, string> lookup tables already encode every design-token value Figma cares about.
  • Bidirectional mapping in adhd.config.ts under components.<path>.figma.url. Written by push on first push (new Phase 11.5 added to push-component as part of this PR), by pull on first scaffold.
  • Symmetric pre-flight: STRUCT003/004/005 violations on the Figma side block the pull. Designer-side variable discipline enforced in both directions.
  • Escape hatch: --allow-unbound (or allowUnboundFigma: true in config) converts the abort to a confirm-prompt. Off-system entries land in code with // adhd:off-system comments — greppable, self-healing on future pulls.
  • Function body invariant: the SKILL prompt explicitly tells Claude not to touch function declarations, function bodies, JSX, hooks, handlers, or imports.

Out of scope (v1)

  • JSX / function body changes — manual only.
  • Multi-component pulls in one command.
  • Components without the Record<Union, string> lookup-table convention — reported and aborted; the convention is now documented as part of the plugin's expectations.

Test plan

  • config-writer unit tests (9): idempotent add, append-to-existing, update-url, reverse lookup, no-components-field handling, etc.
  • cli surface tests (8): all three subcommands, hit + miss paths, error exit codes
  • SKILL frontmatter validated (6/6)
  • Example app builds clean
  • Full lib suite green (268/268)
  • Manual smoke test against the merged-main Avatar component:
    • Path form against in-sync Figma → "No changes"
    • Edit one variant's bound color in Figma → 1-cell diff → apply → commit; verify function body untouched
    • URL form with no mapping → scaffold mode prompts for target path, creates file, writes mapping

🤖 Generated with Claude Code

hhff and others added 9 commits May 10, 2026 20:10
Inverse of /adhd:push-component. Reads a Figma Component Set and
reconciles its variant properties + lookup-table values back into a
React source file. Updates only the design-token surface (Record<Union,
string> tables and union type members); function body, JSX, hooks,
handlers, and imports are invariant.

Key design choices:
- The React file IS the snapshot — no parallel state stored in the
  repo. Lookup tables already encode every design-token value the
  Figma side cares about.
- The mapping lives in adhd.config.ts under components.<path>.figma.url,
  matching the parent config schema. Bidirectional: written by push on
  first push, by pull on first scaffold.
- Pre-flight uses the same lint engine /adhd:lint and push-component
  preflight use; STRUCT003/004/005 (raw color, fontSize, effects) on
  the Figma side blocks the pull. Symmetric pipeline — designer-side
  variable discipline is enforced in both directions.
- Escape hatch: --allow-unbound (or allowUnboundFigma: true in config)
  converts the abort to a confirm-prompt; off-system entries land in
  code with // adhd:off-system comments for greppability and self-healing
  on future pulls.
- 1- and 2-axis Record<Union, string> tables supported. Other patterns
  (inline literals, non-string Records, tables inside function bodies)
  are reported and skipped — no silent inference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 tasks decomposing the spec into TDD steps:
- Task 1: scaffold lib + CI + Badge synthetic fixture
- Task 2: parse-react.js — TS compiler API extraction
- Task 3: class-resolver.js — wraps lint-engine for symmetric pipeline
- Task 4: differ.js — pure local/figma comparator
- Task 5: apply.js — AST-aware source rewrite (function body invariant)
- Task 6: config-writer.js — components mapping in adhd.config.ts
- Task 7: cli.js subcommand wiring
- Task 8: SKILL.md orchestrator (11 phases)
- Task 9: push-component additive (write mapping on first push)
- Task 10: README + marketplace docs
- Task 11: smoke + PR prep

Test coverage maps 1:1 to spec acceptance criteria. Each module gets
zero-deps unit tests; integration testing uses a synthetic Badge fixture
with 4 Figma scenarios (clean, cell-change, added-variant,
removed-variant) and golden output files for byte-identity apply
verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Initial draft had parse-react.js + differ.js + apply.js modules
reinventing TypeScript-compiler-API source extraction and AST-aware
text replacement. User gut-checked: "Claude Code is the reason we're
doing this code gen. I want the intelligence of Claude Code to know
how to diff this stuff. I don't want to use rigid, brittle code to
do it when we have a full beautiful LLM to do it. For anything not
deterministic, we pretty much always use the LLM because the LLM is
only getting better."

Revised design:
- Library shrinks to one module: config-writer.js (deterministic
  schema-level adhd.config.ts mutation, idempotent, unit-testable).
- The SKILL prompt is the brain: reads the React source via Read,
  extracts the Figma Component Set via use_figma, computes the diff
  in working memory, prompts via AskUserQuestion, applies via Edit
  tool calls. Every invariant (function body untouched, off-system
  comment format, abort conditions) is stated explicitly in the SKILL
  so any Claude Code agent executes it the same way.
- Pre-flight reuses lint-engine via subprocess (no new bridge module).

Plan collapses from 11 tasks to 5: scaffold lib + SKILL + push-component
additive + README/marketplace + PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Deterministic surface only: read/write components.<path>.figma.url
in adhd.config.ts. Everything intelligent (parsing the React source,
diffing against Figma, applying edits) lives in the SKILL prompt
where the LLM handles it. Brittle AST/regex approaches don't apply
when Claude Code is already in the orchestration loop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove unused escapeForRegex helper.
- Remove COMPONENTS_OPEN_RE.lastIndex reset; the regex has no /g flag
  so lastIndex is never set by exec(), making the reset meaningless.
- Collapse a 4-line comment that just restated variable names into a
  2-line note about the iterator's quote-index invariant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The skill is the brain: reads the React source, extracts the Figma
Component Set via use_figma, computes the diff in working memory,
prompts via AskUserQuestion, applies edits via the Edit tool. Every
invariant (function body untouched, off-system comment format,
abort conditions) is stated explicitly in the prompt.

Pre-flight reuses lint-engine via subprocess for STRUCT003/004/005
enforcement. Config mapping read/written via config-writer CLI.
- Phase 3: spell out the variable-id → name resolution path. The prior
  text suggested reading the variable name from Phase 2.5's vars.json,
  but that map is keyed by name (not id), so the lookup was unreachable.
  Now the SKILL tells Claude to call getVariableByIdAsync directly and
  describes the intermediate shape to return from use_figma.
- Phase 4: replace the hardcoded "Avatar" placeholder in the in-sync
  message with <ComponentName>.
- Phase 5: align the "Keep ALL" option label with the resolution — it
  proceeds to the final report rather than exiting silently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hhff hhff merged commit 7cb5a1e into main May 11, 2026
4 checks passed
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