Conversation
save-fixture now downloads PNG exports (@2x) for nodes with IMAGE fills, saved with sanitized node names (e.g. "hero-banner@2x.png" instead of "I593-8035@2x.png"). A mapping.json links node IDs to filenames. design-tree outputs url(images/hero-banner@2x.png) instead of [IMAGE] when the image directory and mapping are available. - collectImageNodes: walks tree for IMAGE fill nodes - sanitizeFilename: node name → kebab-case, dedup with -2, -3 - mapping.json: nodeId → filename for design-tree lookup - Auto-detect images/ dir in design-tree CLI (mirrors vectors/ pattern) - 3 new tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR adds a new Changes
Sequence DiagramsequenceDiagram
participant User as User/CLI
participant CLI as implement<br/>Handler
participant FSys as File System
participant FigmaAPI as Figma API<br/>(optional)
participant DTEngine as Design-Tree<br/>Engine
User->>CLI: canicode implement <input> --options
CLI->>CLI: Validate input (URL or path)
alt Input is Figma URL
CLI->>FigmaAPI: Fetch document, vectors
FigmaAPI-->>CLI: Design data, nodeIds
CLI->>FigmaAPI: Export images (IMAGE fills)
FigmaAPI-->>CLI: PNG blobs + mapping
CLI->>FSys: Write images/ + mapping.json
else Input is Fixture Path
CLI->>FSys: Load existing fixture
FSys-->>CLI: analysis.json, vectors/, images/
end
CLI->>DTEngine: generateDesignTreeWithStats(analysis, imageDir, vectorDir)
DTEngine->>FSys: Read images/mapping.json (if provided)
FSys-->>DTEngine: File mapping
DTEngine-->>CLI: design-tree.txt (with image URLs or [IMAGE])
CLI->>FSys: Write output package (analysis.json, design-tree.txt, PROMPT.md, vectors/, images/)
CLI-->>User: Package generated
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
save-fixture now accepts --image-scale <n> (default 2, range 1-4). Filenames reflect the scale: hero-banner@3x.png for mobile. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split DESIGN-TO-CODE-PROMPT.md into base rules + stack-specific templates: - stacks/html-css.md (default, calibration) - stacks/react-tailwind.md - stacks/react-css-modules.md - stacks/vue-css.md Base prompt now includes image asset handling rules and stack selection guide. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eDir) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CLI command that prepares a design-to-code package:
- analysis.json (issues + scores)
- design-tree.txt (with token estimate)
- images/ + vectors/ (asset directories)
- PROMPT.md (base + stack-specific)
Supports: --stack (html-css, react-tailwind, react-css-modules, vue-css)
--image-scale (2 for PC, 3 for mobile)
--output (default: ./canicode-implement/)
Also adds /canicode-implement skill for Claude Code.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- README: add "Design to Code" section + "Generate code from design" row - CLAUDE.md: add implement to CLI description - CLI docs: add "implement" topic with usage, options, stacks, workflow Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove fixed stack selection (html-css, react-tailwind, etc.) in favor of --prompt <path> for user-supplied prompt files. Default remains built-in HTML+CSS prompt. - Remove stacks/ directory and VALID_STACKS constant - Add --prompt option to implement command - Simplify prompt assembly (custom file OR built-in default) - Update README, CLAUDE.md, CLI docs to reflect new approach Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/cli/docs.ts (1)
340-342:⚠️ Potential issue | 🟡 MinorError message missing
implementtopic.The available topics list in the error message doesn't include the newly added
implementtopic.📝 Proposed fix
} else { console.error(`Unknown docs topic: ${topic}`); - console.error(`Available topics: setup, rules, config, visual-compare, design-tree`); + console.error(`Available topics: setup, rules, config, implement, visual-compare, design-tree`); process.exit(1); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cli/docs.ts` around lines 340 - 342, Update the error output in the docs topic handling to include the newly added "implement" topic: modify the console.error that prints available topics (the lines referencing Available topics: setup, rules, config, visual-compare, design-tree) to also list implement so the message reflects the true set of choices when topic (variable topic) is unknown in the docs command handler in src/cli/docs.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.claude/skills/canicode-implement/SKILL.md:
- Line 63: The fenced code block in SKILL.md is missing a language specifier for
the opening backticks; update the opening fence that currently reads just ``` to
include a language (e.g., use ```text) so the block containing
"canicode-implement/" is marked as ```text, ensuring proper rendering and syntax
highlighting for that snippet.
- Around line 40-43: The fenced code block in SKILL.md lacks blank lines
surrounding it; add an empty line before the opening ```bash and an empty line
after the closing ``` so the block is properly separated from surrounding text
(specifically ensure there's a blank line between the code fence and the
preceding list item and another blank line before the "4. Clean up temp fixture
if desired" paragraph).
- Around line 29-31: The CLI example in SKILL.md uses the inconsistent flag
--stack; update the example command string (the code block containing npx
canicode implement "https://www.figma.com/design/ABC/File?node-id=1-234") to use
--prompt ./my-vue-prompt.md instead of --stack vue-css so it matches the
intended usage; replace the --stack token with the --prompt token and the sample
path as shown.
- Around line 24-25: Update the example command to use the current CLI option
--prompt instead of the removed --stack option: replace "npx canicode implement
./fixtures/my-design --stack react-tailwind" with "npx canicode implement
./fixtures/my-design --prompt ./my-react-prompt.md" and add a short note that
built-in stack templates were removed and custom prompt files should be passed
with --prompt.
- Around line 47-59: Update the options table and explanatory section to replace
the removed `--stack <name>` option with the new `--prompt <file>` option:
change the table row to "`--prompt <file>` | Custom prompt file (replaces
built-in stacks) | `./prompts/default.json` or unset" (or appropriate default),
remove or rewrite the "Available stacks" list that references built-in stacks,
and instead add a short explanation that custom prompt files should be used
(with an example file name and how to select it). Ensure references to `--stack`
in the surrounding text are removed or replaced with `--prompt` and that any
examples or defaults (like `html-css`) are updated to reflect the new
prompt-based workflow.
In `@src/cli/docs.ts`:
- Around line 299-301: Update the workflow example that currently shows
"canicode implement ./my-fixture --stack react-tailwind" to use the new CLI
option name "--prompt" instead of "--stack" (e.g., "canicode implement
./my-fixture --prompt react-tailwind" or reference a prompt file), since the CLI
implementation uses "--prompt"; modify the text in the docs.ts constant/string
containing the WORKFLOW example and replace occurrences of "--stack" with
"--prompt" to keep docs and src/cli/index.ts consistent.
In `@src/cli/index.ts`:
- Around line 1038-1051: The design tree is being generated by
generateDesignTreeWithStats before live Figma images are downloaded because
fixtureBase (and thus imageDir) is undefined for URL inputs, producing [IMAGE]
placeholders; update the flow so image assets are downloaded first (or call
generateDesignTreeWithStats again after downloads) and ensure imageDir is set
when invoking generateDesignTreeWithStats (reference isJsonFile, isFixtureDir,
fixtureBase, imageDir, vectorDir, and generateDesignTreeWithStats) so the final
design-tree.txt reflects the downloaded images rather than placeholders.
- Around line 1135-1169: Add creation of a mapping from image node id →
downloaded file path during the image download loop (use the existing
imgNodes/filename variables), write that mapping JSON to images/mapping.json
(e.g., writeFile(resolve(imgOutDir, "mapping.json"), JSON.stringify(mapping,
null, 2))), and after the try block completes re-run the same logic that
generates/writes design-tree.txt (the earlier design-tree.txt writer used around
lines 1045-1051) so the tree can substitute image placeholders with
url(images/...) using the newly written mapping.json.
---
Outside diff comments:
In `@src/cli/docs.ts`:
- Around line 340-342: Update the error output in the docs topic handling to
include the newly added "implement" topic: modify the console.error that prints
available topics (the lines referencing Available topics: setup, rules, config,
visual-compare, design-tree) to also list implement so the message reflects the
true set of choices when topic (variable topic) is unknown in the docs command
handler in src/cli/docs.ts.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4d44d3dc-b9a6-4bad-81df-73286ca7d0cc
📒 Files selected for processing (8)
.claude/skills/canicode-implement/SKILL.md.claude/skills/design-to-code/PROMPT.mdCLAUDE.mdREADME.mdsrc/cli/docs.tssrc/cli/index.tssrc/core/engine/design-tree.test.tssrc/core/engine/design-tree.ts
| ```bash | ||
| npx canicode implement fixtures/_mcp-temp --stack html-css | ||
| ``` | ||
| 4. Clean up temp fixture if desired |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Markdown formatting: add blank lines around fenced code block.
📝 Proposed fix
3. **Run implement** on the saved fixture:
+
```bash
npx canicode implement fixtures/_mcp-temp --stack html-css
```
+
4. Clean up temp fixture if desired📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ```bash | |
| npx canicode implement fixtures/_mcp-temp --stack html-css | |
| ``` | |
| 4. Clean up temp fixture if desired | |
| 3. **Run implement** on the saved fixture: |
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 40-40: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
[warning] 42-42: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.claude/skills/canicode-implement/SKILL.md around lines 40 - 43, The fenced
code block in SKILL.md lacks blank lines surrounding it; add an empty line
before the opening ```bash and an empty line after the closing ``` so the block
is properly separated from surrounding text (specifically ensure there's a blank
line between the code fence and the preceding list item and another blank line
before the "4. Clean up temp fixture if desired" paragraph).
… cleanup Major fixes: - Reorder implement command: download assets FIRST, then generate design tree — so live URL images get url() paths instead of [IMAGE] - Write mapping.json for live URL image downloads - Update imageDir/vectorDir to point to downloaded assets Minor fixes: - SKILL.md: replace --stack with --prompt throughout - docs.ts: fix --stack reference in workflow example Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.claude/skills/canicode-implement/SKILL.md:
- Around line 49-59: The documented output tree is missing images/mapping.json;
update the SKILL.md Output Structure block to include images/mapping.json under
the images/ directory so the example reflects the actual CLI output and the
design tree's url(images/...) substitution. Specifically, add a line like
"images/mapping.json # Mapping of original asset names to output filenames
(used for url(images/...))" beneath the images/ entry in the canicode-implement
tree so readers see that file is produced when image assets are present.
In `@src/cli/docs.ts`:
- Around line 319-325: The invalid-topic hint's hard-coded list of available
docs topics needs to include the new "implement" topic so typed commands like
`canicode docs implemnt` don't incorrectly indicate it's unavailable; update the
string list/message that prints available topics (the code that references
DOCS_TOPICS keys for the invalid-topic hint) to include "implement" (match the
key in DOCS_TOPICS) or, better, derive the hint dynamically from DOCS_TOPICS'
keys so the help output stays in sync with DOCS_TOPICS (modify the code that
currently prints the hard-coded list of topics to use Object.keys(DOCS_TOPICS)
or add "implement" to that literal list).
- Around line 291-298: Update the documented package layout under the OUTPUT
section (the block starting with the literal header "OUTPUT" and the list
entries like "images/") to include the new images/mapping.json file; add a line
describing "images/mapping.json — JSON map used by the design tree to replace
[IMAGE] with url(images/...)" so the documented output matches the implement
behavior that writes/copies images/mapping.json and the design tree uses it for
image URL substitution.
In `@src/cli/index.ts`:
- Around line 888-923: The mapping.json is built from nodeIdToFilename
regardless of whether the image was actually downloaded; change the code to only
persist mappings for successfully written files by creating a new Map or object
(e.g., downloadedMapping) and, inside the successful fetch/write branch (the
block where resp.ok and writeFile succeed), add downloadedMapping.set(id,
filename) (or downloadedMapping[id] = filename); after the download loop
serialize downloadedMapping to mapping.json instead of nodeIdToFilename; apply
the same change to the other image-export loop referenced (the block analogous
around generateDesignTreeWithStats) so mapping.json contains only files that
exist on disk.
- Around line 1016-1026: In the Implement CLI action handler (the async .action
callback in src/cli/index.ts), detect when options.nodeId (or equivalent node-id
property on ImplementOptions) is not provided and emit a visible warning before
proceeding (e.g., console.warn or processLogger.warn) that the command will
process the entire Figma file and may produce large prompts/assets; place this
check just before calling loadFile or packaging so users see it early and keep
existing behavior when nodeId is present.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 3133544f-174d-4604-9612-d2a7ddf6c7df
📒 Files selected for processing (3)
.claude/skills/canicode-implement/SKILL.mdsrc/cli/docs.tssrc/cli/index.ts
| ## Output Structure | ||
|
|
||
| ```text | ||
| canicode-implement/ | ||
| analysis.json # Full analysis with issues and scores | ||
| design-tree.txt # DOM-like tree with styles, structure, embedded SVGs | ||
| PROMPT.md # Code generation prompt (default or custom) | ||
| screenshot.png # Figma screenshot (if available) | ||
| vectors/ # SVG assets for VECTOR nodes | ||
| images/ # PNG assets for IMAGE fill nodes (hero-banner@2x.png) | ||
| ``` |
There was a problem hiding this comment.
Show images/mapping.json in the documented output tree.
The CLI writes/copies that file when image assets are present, and the design tree uses it for url(images/...) substitution. Leaving it out here makes the skill package layout incomplete.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.claude/skills/canicode-implement/SKILL.md around lines 49 - 59, The
documented output tree is missing images/mapping.json; update the SKILL.md
Output Structure block to include images/mapping.json under the images/
directory so the example reflects the actual CLI output and the design tree's
url(images/...) substitution. Specifically, add a line like "images/mapping.json
# Mapping of original asset names to output filenames (used for
url(images/...))" beneath the images/ entry in the canicode-implement tree so
readers see that file is produced when image assets are present.
| OUTPUT | ||
| canicode-implement/ | ||
| analysis.json Analysis report with issues and scores | ||
| design-tree.txt DOM-like tree with CSS styles (~N tokens) | ||
| images/ PNG assets with human-readable names (hero-banner@2x.png) | ||
| vectors/ SVG assets for vector nodes | ||
| PROMPT.md Stack-specific code generation prompt | ||
|
|
There was a problem hiding this comment.
Document images/mapping.json in the package layout.
implement now writes/copies that file when image assets are present, and the design tree uses it to replace [IMAGE] with url(images/...). Leaving it out here makes the documented output incomplete.
As per coding guidelines, "**/*.{ts,js}: If the code change introduces behavior that contradicts existing documentation (README.md, CLAUDE.md, JSDoc comments), flag it and suggest updating the relevant documentation to stay in sync."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/cli/docs.ts` around lines 291 - 298, Update the documented package layout
under the OUTPUT section (the block starting with the literal header "OUTPUT"
and the list entries like "images/") to include the new images/mapping.json
file; add a line describing "images/mapping.json — JSON map used by the design
tree to replace [IMAGE] with url(images/...)" so the documented output matches
the implement behavior that writes/copies images/mapping.json and the design
tree uses it for image URL substitution.
| const DOCS_TOPICS: Record<string, () => void> = { | ||
| setup: printDocsSetup, | ||
| install: printDocsSetup, // alias | ||
| rules: printDocsRules, | ||
| config: printDocsConfig, | ||
| implement: printDocsImplement, | ||
| "visual-compare": printDocsVisualCompare, |
There was a problem hiding this comment.
Update the invalid-topic hint for the new topic.
Adding implement here also needs the hard-coded list on Line 341 updated. Right now a typo like canicode docs implemnt tells users that implement is unavailable even though it is now routed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/cli/docs.ts` around lines 319 - 325, The invalid-topic hint's hard-coded
list of available docs topics needs to include the new "implement" topic so
typed commands like `canicode docs implemnt` don't incorrectly indicate it's
unavailable; update the string list/message that prints available topics (the
code that references DOCS_TOPICS keys for the invalid-topic hint) to include
"implement" (match the key in DOCS_TOPICS) or, better, derive the hint
dynamically from DOCS_TOPICS' keys so the help output stays in sync with
DOCS_TOPICS (modify the code that currently prints the hard-coded list of topics
to use Object.keys(DOCS_TOPICS) or add "implement" to that literal list).
| const usedNames = new Map<string, number>(); | ||
| const nodeIdToFilename = new Map<string, string>(); | ||
| for (const { id, name } of imageNodes) { | ||
| let base = sanitizeFilename(name); | ||
| const count = usedNames.get(base) ?? 0; | ||
| usedNames.set(base, count + 1); | ||
| if (count > 0) base = `${base}-${count + 1}`; | ||
| nodeIdToFilename.set(id, `${base}@${imgScale}x.png`); | ||
| } | ||
|
|
||
| let imgDownloaded = 0; | ||
| for (const [id, imgUrl] of Object.entries(imageUrls)) { | ||
| if (!imgUrl) continue; | ||
| const filename = nodeIdToFilename.get(id); | ||
| if (!filename) continue; | ||
| try { | ||
| const resp = await fetch(imgUrl); | ||
| if (resp.ok) { | ||
| const buf = Buffer.from(await resp.arrayBuffer()); | ||
| await writeFile(resolve(imageDir, filename), buf); | ||
| imgDownloaded++; | ||
| } | ||
| } catch { | ||
| // Skip failed downloads | ||
| } | ||
| } | ||
|
|
||
| const mapping: Record<string, string> = {}; | ||
| for (const [id, filename] of nodeIdToFilename) { | ||
| mapping[id] = filename; | ||
| } | ||
| await writeFile( | ||
| resolve(imageDir, "mapping.json"), | ||
| JSON.stringify(mapping, null, 2), | ||
| "utf-8" | ||
| ); |
There was a problem hiding this comment.
Only persist mappings for image files that were actually written.
Both blocks build mapping.json from the requested node set, not from successful exports. If getNodeImages() omits a node or one fetch fails, generateDesignTreeWithStats() will still emit url(images/...) for a file that is not on disk, and the [IMAGE] fallback is lost. Track a separate downloaded-only mapping and serialize that instead.
Also applies to: 1135-1167
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/cli/index.ts` around lines 888 - 923, The mapping.json is built from
nodeIdToFilename regardless of whether the image was actually downloaded; change
the code to only persist mappings for successfully written files by creating a
new Map or object (e.g., downloadedMapping) and, inside the successful
fetch/write branch (the block where resp.ok and writeFile succeed), add
downloadedMapping.set(id, filename) (or downloadedMapping[id] = filename); after
the download loop serialize downloadedMapping to mapping.json instead of
nodeIdToFilename; apply the same change to the other image-export loop
referenced (the block analogous around generateDesignTreeWithStats) so
mapping.json contains only files that exist on disk.
| .action(async (input: string, options: ImplementOptions) => { | ||
| try { | ||
|
|
||
| const outputDir = resolve(options.output ?? "canicode-implement"); | ||
| mkdirSync(outputDir, { recursive: true }); | ||
|
|
||
| console.log("\nPreparing implementation package...\n"); | ||
|
|
||
| // 1. Load file | ||
| const { file } = await loadFile(input, options.token); | ||
| console.log(`Design: ${file.name}`); |
There was a problem hiding this comment.
Warn before packaging an unscoped Figma URL.
implement silently processes the entire file when node-id is missing. The other CLI entry points already warn in that case, and this new command can produce unexpectedly large prompts and asset downloads.
📝 Suggested fix
.action(async (input: string, options: ImplementOptions) => {
try {
+ if (isFigmaUrl(input) && !parseFigmaUrl(input).nodeId) {
+ console.warn("\nWarning: No node-id specified. Implementing entire file may produce a very large package.");
+ console.warn("Tip: Add ?node-id=XXX to target a specific section.\n");
+ }
+
const outputDir = resolve(options.output ?? "canicode-implement");
mkdirSync(outputDir, { recursive: true });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/cli/index.ts` around lines 1016 - 1026, In the Implement CLI action
handler (the async .action callback in src/cli/index.ts), detect when
options.nodeId (or equivalent node-id property on ImplementOptions) is not
provided and emit a visible warning before proceeding (e.g., console.warn or
processLogger.warn) that the command will process the entire Figma file and may
produce large prompts/assets; place this check just before calling loadFile or
packaging so users see it early and keep existing behavior when nodeId is
present.
Summary
design-to-code를 사용자 구현 도구로 제공. 분석 → 디자인 트리 → 에셋 → 프롬프트를 한 패키지로.
canicode implement <input>출력:
변경 사항
이미지 에셋 매핑
url(images/hero-banner@2x.png)출력해상도 옵션
--image-scale: 2 (PC, default), 3 (mobile)커스텀 프롬프트
--prompt <path>: 사용자 프롬프트 파일문서
canicode docs implement: 가이드 토픽E2E 검증 완료
Test plan
pnpm test:run— 317 tests passedpnpm lint— cleanCloses #35
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation