canicode 0.12.3 → 0.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,172 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. This project adheres
4
+ to [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and, once
5
+ published as v1, will follow [Semantic Versioning](https://semver.org/).
6
+ While in v0.x, minor versions may carry breaking changes — they are called
7
+ out explicitly in the relevant entry.
8
+
9
+ Each entry summarises the user-visible change. Per-PR detail lives in the
10
+ [GitHub release notes](https://github.com/let-sunny/canicode/releases) for
11
+ the corresponding tag, and architecture rationale lives in
12
+ [ADR.md](.claude/docs/ADR.md).
13
+
14
+ ## [Unreleased]
15
+
16
+ ## [0.12.3] - 2026-04-29
17
+
18
+ ### Added
19
+
20
+ - **Phase 3 — bootstrap a design system from screens** (Workflow 3 epic
21
+ [#508](https://github.com/let-sunny/canicode/issues/508)) shipped GA. After
22
+ upgrading, `canicode-roundtrip` performs the full **componentize+swap loop**
23
+ end-to-end on any Stage 3 (`missing-component:structure-repetition`) group:
24
+ one `yes` answer componentizes the document-order first member and swaps
25
+ the rest with instances of the new component. Optional Code Connect
26
+ registration follows automatically when prereqs are present.
27
+ - `applyComponentize` apply primitive — wraps `figma.createComponentFromNode`
28
+ with the #368 instance-child guard and the ADR-023 decision A free-form
29
+ parent guard. Annotate-fallback on every rejection.
30
+ - `applyReplaceWithInstance` apply primitive — wraps
31
+ `mainComponent.createInstance()` + `parent.insertChild` + `target.remove()`
32
+ with an independent swap-site free-form check.
33
+ - `applyGroupComponentize` orchestrator — drives the full loop from a
34
+ single user `yes` answer; aggregates per-target outcomes into a single
35
+ Step 4 line.
36
+ - `groupMembers` field on `RuleViolation` and `GotchaSurveyQuestion` —
37
+ every node id in a Stage 3 fingerprint group surfaces in the survey
38
+ output so the apply step can iterate the whole group.
39
+ - `RuleContext.analysisRoot` — Stage 3 (and now Stage 1/2) walks the
40
+ active analysis subtree instead of the full file, fixing a silent scope
41
+ leak under `--target-node-id` analysis.
42
+ - ADR-023 — Phase 3 design decisions A–E (free-form parent guard, no
43
+ override threshold, name-collision auto-suffix in Figma's native ` 2`
44
+ pattern, shared question UI for Forward/Reverse with `mode` field on the
45
+ answer, silent-skip Code Connect handoff).
46
+
47
+ ### Changed
48
+
49
+ - Stage 3 `missing-component:structure-repetition` is now **scope-wide**:
50
+ duplicates spread across different parents within the analysis scope
51
+ fold into the same fingerprint group instead of being missed by the old
52
+ sibling-only walk. Message wording updates from `N sibling frame(s)` to
53
+ `N other frame(s)`.
54
+ - Stage 1 (`unused-component`) and Stage 2 (`name-repetition`) now also
55
+ honour `analysisRoot` for the frame-name walk — fixes the same
56
+ silent-skip pattern when `--target-node-id` scopes the run.
57
+
58
+ ### Documentation
59
+
60
+ - WORKFLOWS.md Workflow 3 promoted to ✅ Available with the new Today's
61
+ flow, the bundled apply primitives, and two known limits (per-member
62
+ opt-out, Stage 1 reverse case).
63
+
64
+ ## [0.12.2] - 2026-04-28
65
+
66
+ ### Fixed
67
+
68
+ - canicode-roundtrip Phase 2 live-verification follow-ups
69
+ ([#545](https://github.com/let-sunny/canicode/issues/545),
70
+ [#546](https://github.com/let-sunny/canicode/issues/546),
71
+ [#547](https://github.com/let-sunny/canicode/issues/547),
72
+ [#548](https://github.com/let-sunny/canicode/issues/548)) bundled into a
73
+ single SKILL prose update — one batch per message pacing, output
74
+ language detection, grade-movement attribution, and Step 1.5 doctor
75
+ inconclusive handling on screen-scope FRAME URLs.
76
+
77
+ ## [0.12.1] - 2026-04-28
78
+
79
+ ### Added
80
+
81
+ - `canicode doctor --figma-url <url>` publish-status pre-check
82
+ ([#532](https://github.com/let-sunny/canicode/issues/532)) — verifies
83
+ the target Figma component is published in a library before the
84
+ roundtrip's closing Code Connect mapping step asks the satisfaction
85
+ prompt. Surfaces inconclusive when `FIGMA_TOKEN` is not configured or
86
+ the URL has no node-id; failure cases print remediation hints.
87
+ - `unmapped-component` v1.5 — parser-driven main check
88
+ ([#526](https://github.com/let-sunny/canicode/issues/526)) and
89
+ acknowledgment-channel opt-out write path
90
+ ([#543](https://github.com/let-sunny/canicode/issues/543)) so designers
91
+ can mark a component "intentionally unmapped" and have the rule
92
+ silently skip it on subsequent roundtrips.
93
+ - ADR-022 — `unmapped-component` opt-out via the existing acknowledgment
94
+ channel + the REST private-beta annotations field gating that limits
95
+ standalone analyze visibility.
96
+
97
+ ### Fixed
98
+
99
+ - Bridge `CanICodeRoundtrip` onto `globalThis` inside `eval`
100
+ ([#533](https://github.com/let-sunny/canicode/issues/533)) so the
101
+ bundled IIFE survives Plugin API hosts that wrap the script in a
102
+ shadowed scope.
103
+
104
+ ### Documentation
105
+
106
+ - SKILL Step 4 inline-staging guidance + Step 7d single-mapping path
107
+ ([#531](https://github.com/let-sunny/canicode/issues/531),
108
+ [#534](https://github.com/let-sunny/canicode/issues/534)).
109
+
110
+ ## [0.12.0] - 2026-04-27
111
+
112
+ ### Added
113
+
114
+ - **Phase 1 — component-to-code mapping** roundtrip closing step
115
+ ([#515](https://github.com/let-sunny/canicode/issues/515)): after
116
+ `figma-implement-design` finishes, the roundtrip prompts for
117
+ satisfaction and registers a Code Connect mapping
118
+ (`add_code_connect_map` + `send_code_connect_mappings`) so future
119
+ roundtrips on screens containing the same component reuse the
120
+ generated code instead of regenerating markup.
121
+ - `canicode doctor` — verifies `@figma/code-connect` install and
122
+ `figma.config.json` presence in the user's repo before the roundtrip
123
+ reaches the mapping step
124
+ ([#512](https://github.com/let-sunny/canicode/issues/512)).
125
+ - `unmapped-component` analyze rule
126
+ ([#520](https://github.com/let-sunny/canicode/issues/520)) — flags
127
+ Figma components that have no Code Connect mapping yet, with the
128
+ optional Workflow 1 onboarding pointer.
129
+ - New `note` severity tier (zero score impact) for annotation-primary
130
+ rules whose value is the nudge, not the score
131
+ ([#519](https://github.com/let-sunny/canicode/issues/519)). Backfills
132
+ `missing-prototype`, `missing-interaction-state`,
133
+ `missing-size-constraint`, `unmapped-component`.
134
+ - ADR-021 — handoff carriers and conflict resolution between
135
+ `canicode-roundtrip` and `figma-implement-design`.
136
+ - WORKFLOWS.md — new 3-phase canicode bootstrap roadmap document
137
+ ([#511](https://github.com/let-sunny/canicode/issues/511)).
138
+
139
+ ### Changed
140
+
141
+ - `canicode init` split into interactive setup + a separate
142
+ `canicode config set-token` subcommand
143
+ ([#506](https://github.com/let-sunny/canicode/issues/506)). Existing
144
+ users who relied on the old combined flow should move the token-only
145
+ step to `config set-token`.
146
+ - Stripped internal issue / ADR refs from user-facing surfaces
147
+ ([#504](https://github.com/let-sunny/canicode/issues/504)) so the
148
+ README and SKILL prose read cleanly to a first-time user.
149
+
150
+ ## [0.11.5] - 2026-04-25
151
+
152
+ ### Added
153
+
154
+ - Claude Code marketplace manifest
155
+ ([#497](https://github.com/let-sunny/canicode/issues/497)) so canicode
156
+ appears in Claude Code's plugin/skill catalogue.
157
+ - "Other agents" install guide for non-Claude/Cursor hosts
158
+ ([#502](https://github.com/let-sunny/canicode/issues/502)) — manual
159
+ skill copy instructions for hosts that do not auto-discover skills.
160
+
161
+ ### Documentation
162
+
163
+ - Polished `canicode init` path docs
164
+ ([#499](https://github.com/let-sunny/canicode/issues/499)) following
165
+ user-flow testing.
166
+
167
+ [Unreleased]: https://github.com/let-sunny/canicode/compare/v0.12.3...HEAD
168
+ [0.12.3]: https://github.com/let-sunny/canicode/compare/v0.12.2...v0.12.3
169
+ [0.12.2]: https://github.com/let-sunny/canicode/compare/v0.12.1...v0.12.2
170
+ [0.12.1]: https://github.com/let-sunny/canicode/compare/v0.12.0...v0.12.1
171
+ [0.12.0]: https://github.com/let-sunny/canicode/compare/v0.11.5...v0.12.0
172
+ [0.11.5]: https://github.com/let-sunny/canicode/compare/v0.11.4...v0.11.5
package/README.md CHANGED
@@ -6,11 +6,12 @@
6
6
 
7
7
  <p align="center">
8
8
  <a href="https://www.npmjs.com/package/canicode"><img src="https://img.shields.io/npm/v/canicode.svg" alt="npm version"></a>
9
+ <a href="https://www.figma.com/community/plugin/1617144221046795292/can-i-code"><img src="https://img.shields.io/badge/Figma-Plugin-F24E1E?logo=figma&logoColor=white" alt="Figma Plugin"></a>
9
10
  <a href="https://github.com/let-sunny/canicode/actions/workflows/ci.yml"><img src="https://github.com/let-sunny/canicode/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
10
11
  <a href="https://github.com/let-sunny/canicode/actions/workflows/release.yml"><img src="https://github.com/let-sunny/canicode/actions/workflows/release.yml/badge.svg" alt="Release"></a>
11
12
  </p>
12
13
 
13
- <p align="center"><strong>Make your Figma file information-complete so AI generates code that actually works.</strong></p>
14
+ <p align="center"><strong>Ship your Figma design exactly as you intended without learning CSS.</strong></p>
14
15
 
15
16
  <p align="center">
16
17
  <strong><a href="https://let-sunny.github.io/canicode/">Try it in your browser</a></strong> — no install needed.
@@ -22,19 +23,41 @@
22
23
 
23
24
  ---
24
25
 
25
- ## How it works
26
+ ## What canicode does
26
27
 
27
- AI can turn Figma designs into code but the quality depends heavily on **how the design is structured**. CanICode runs a **roundtrip** over your Figma file: analyze the design, surface the gotchas it can't answer on its own, apply fixes back to Figma, re-analyze until the design is clean, then hand off to Figma's `figma-implement-design` skill for code generation. canicode does the design augmentation; code generation lives downstream.
28
+ You designed it in Figma. You know exactly how it should look and behave but the AI codegen guesses, and half the time it guesses wrong. The cards don't stack on mobile. The hover state disappears. The padding gets normalised. *That's not what I designed.*
28
29
 
29
- ![Role diagram: gotchas (memo) vs roundtrip (canvas) vs code-gen](docs/images/roles.svg)
30
+ canicode is the **pre-implementation interview** between your Figma file and AI codegen. It asks the questions a developer would ask before they implement — in plain language you can answer:
30
31
 
31
- ### How the analyzer knows what to fix
32
+ > *"The three cards are side-by-side at desktop width — what should happen when the screen is narrow?"*
32
33
 
33
- - **16 rules** across 6 categories: Pixel Critical, Responsive Critical, Code Quality, Token Management, Interaction, Semantic
34
- - **Deterministic** — no AI tokens consumed per analysis, runs in milliseconds
35
- - **Ablation-validated** — [experiments](https://github.com/let-sunny/canicode/wiki) confirmed the curated design-tree achieves 94% pixel accuracy with 5× fewer tokens than raw JSON
34
+ You answer in plain words ("좁아지면 줄에 하나씩" / "stack them vertically"). canicode translates that into the exact technical spec the codegen needs, and writes it back into your Figma file as a structured annotation so the next run no longer has to guess.
36
35
 
37
- Rule scores aren't guesswork. The calibration pipeline converts real Figma designs to HTML, measures pixel-level similarity (via `visual-compare`), and adjusts scores based on actual implementation difficulty — hard-to-implement patterns get a higher penalty, easy ones get demoted. The pipeline runs on community fixtures, not on every analysis. See the [Calibration wiki](https://github.com/let-sunny/canicode/wiki/Calibration).
36
+ You stay in Figma. You never type a CSS selector. The intent ships.
37
+
38
+ → Full thesis: [docs/POSITIONING.md](docs/POSITIONING.md)
39
+
40
+ ### The three layers
41
+
42
+ 1. **Linter** — 17 rules surface the gaps your craft instinct senses but can't name. *"Something's off with this spacing."* → it finds the one card with 14px padding while the rest are 16px.
43
+ 2. **Q&A scaffolding** — for every gap, canicode asks in your vocabulary, not the developer's. You answer, it spec'ifies.
44
+ 3. **Roundtrip persistence** — answers live in the Figma file as Dev Mode annotations. Next run, next person, next codegen — they all see the intent.
45
+
46
+ The linter is deterministic (no LLM tokens consumed for the analysis itself), [ablation-validated](https://github.com/let-sunny/canicode/wiki) at 94% pixel accuracy with 5× fewer tokens than raw JSON, and [calibrated](https://github.com/let-sunny/canicode/wiki/Calibration) against real Figma designs.
47
+
48
+ ---
49
+
50
+ ## Why canicode (vs Claude Design)
51
+
52
+ [Claude Design](https://www.anthropic.com/news/claude-design) launched in April 2026 and covers the **prompt → app** path beautifully — AI designs from your prompt and hands the result to Claude Code. canicode covers a different workflow:
53
+
54
+ | | Claude Design | canicode |
55
+ |---|---|---|
56
+ | Who designs | AI (from a prompt) | The designer (in Figma) |
57
+
58
+ If you want the AI to design for you, use Claude Design. If you already designed it and want it implemented exactly the way you intended, canicode is for you. The two are not in competition — they assume different things about who owns the design.
59
+
60
+ See [docs/POSITIONING.md](docs/POSITIONING.md) for the full thesis (target persona, the workflow assumption, and what canicode is **not** for).
38
61
 
39
62
  ---
40
63
 
@@ -42,7 +65,7 @@ Rule scores aren't guesswork. The calibration pipeline converts real Figma desig
42
65
 
43
66
  **Skills:** **`canicode-gotchas`** = survey answers saved **locally** in SKILL.md only (memo). **`canicode-roundtrip`** = same analysis path plus **writes to Figma** (annotations / properties). Pick gotchas for capture-only; pick roundtrip when the design file should change.
44
67
 
45
- 1. **Analyze** — run the 16 rules against the Figma design (report includes grade).
68
+ 1. **Analyze** — run the 17 rules against the Figma design (report includes grade).
46
69
  2. **Surface gotchas** — the analyzer emits questions for design information it can't infer (missing states, unclear variants, responsive intent).
47
70
  3. **Apply fixes to Figma** — the `/canicode-roundtrip` skill writes answers back via `use_figma`. Each write shows up in the summary with one of three outcome markers:
48
71
  - ✅ **scene write succeeded** — the property was written directly to the scene node or instance override.
@@ -54,71 +77,27 @@ Rule scores aren't guesswork. The calibration pipeline converts real Figma desig
54
77
 
55
78
  ---
56
79
 
57
- ## Getting Started
58
-
59
- > **Token safety:** Do **not** paste your Figma token into Claude, Cursor, or other agent chats — session logs can retain it. Use `FIGMA_TOKEN=figd_… npx canicode init`, or run `npx canicode init` and enter the token **only** at the CLI prompt.
80
+ ## Try it in 30 seconds
60
81
 
61
- **Quickest way:** **[Open the web app](https://let-sunny.github.io/canicode/)** — paste a Figma URL, get a report.
82
+ 1. **[Open the web app](https://let-sunny.github.io/canicode/)** — paste any Figma URL, get a report. No install, no token, no AI cost. This is the linter view (Layer 1) — useful by itself.
83
+ 2. Then if you want the full Q&A roundtrip, install the Claude Code skill below.
62
84
 
63
- **Design-to-code in Claude Code (recommended):**
85
+ ## Run the full roundtrip (Claude Code)
64
86
 
65
87
  ```bash
66
- # 1. Save your Figma token AND install the /canicode-roundtrip skill
67
- # Interactive (TTY): npx canicode init — prompts for the token
68
- # Non-interactive: npx canicode init --token figd_xxxxxxxxxxxxx
69
- # (never paste the token into chat — use env var, the prompt, or --token only)
70
88
  npx canicode init
89
+ # saves your Figma token + installs /canicode-roundtrip
90
+ # (the prompt asks for the token; never paste it into agent chat)
71
91
 
72
- # 2. Run the roundtrip on a Figma URL
73
92
  /canicode-roundtrip https://www.figma.com/design/ABC123/MyDesign?node-id=1-234
93
+ # in your Claude Code session
74
94
  ```
75
95
 
76
- > **Prerequisite:** the roundtrip skill calls the Figma MCP server to read and write the design. Install it once with `claude mcp add -s project -t http figma https://mcp.figma.com/mcp` see the **MCP Server** install section below.
96
+ The roundtrip calls the **Figma MCP server** to read and write your design install it once with `claude mcp add -s project -t http figma https://mcp.figma.com/mcp` and restart Claude Code.
77
97
 
78
- > **Optional Code Connect (for the closing Step 6 mapping):** install `@figma/code-connect` (`pnpm add -D @figma/code-connect` or npm/yarn equivalent) and create `figma.config.json` at your repo root per [Figma's setup guide](https://www.figma.com/code-connect-docs/). Then run `canicode doctor` to confirm both prerequisites are in place. If you skip this, the roundtrip still generates code but will not register a Code Connect mapping — it tells you up front so you can decide.
98
+ > **Cursor / Claude Desktop / other MCP host:** also supported. CLI install path (`npx canicode init --cursor-skills`), bare MCP server install, GitHub Action for CI gates, and other channels are listed in the **Installation** matrix below.
79
99
 
80
- **CanICode in Cursor (no Claude Code required):**
81
-
82
- 1. Add **canicode** and **Figma** MCPs — [Cursor MCP](docs/CUSTOMIZATION.md#cursor-mcp-canicode) for `npx` → `canicode-mcp` (use **`.cursor/mcp.json`** or **`~/.cursor/mcp.json`** in Cursor, not repo-root `.mcp.json`; [why](docs/CUSTOMIZATION.md#which-mcp-file-affects-which-host)); Figma MCP is required for **`use_figma`** if you run **roundtrip** (design writes), not for analyze-only.
83
- 2. `npx canicode init --token figd_xxxxxxxxxxxxx --cursor-skills` (use `FIGMA_TOKEN=…` or the CLI prompt — not chat) — installs the same three skills as Claude under `.cursor/skills/` (`canicode`, `canicode-gotchas`, `canicode-roundtrip` + `helpers.js`) plus the shared `.claude/skills/canicode-gotchas/SKILL.md` answer file when needed.
84
- 3. In Agent chat, @-mention **canicode-gotchas** (survey) or **canicode-roundtrip** (full roundtrip) and pass a Figma URL — same tool names and JSON as Claude Code (`gotcha-survey`, `analyze`, etc.).
85
-
86
- **If you only want analysis (no writes back to Figma):**
87
-
88
- ```bash
89
- # CLI — one command
90
- npx canicode analyze "https://www.figma.com/design/ABC123/MyDesign?node-id=1-234"
91
-
92
- # MCP Server — works with Claude Code, Cursor, Claude Desktop
93
- claude mcp add canicode -- npx --yes --package=canicode canicode-mcp
94
- ```
95
-
96
- Restart Claude Code or reload MCP (Cursor) so canicode tools (`analyze`, `gotcha-survey`, …) load — same cold-session requirement as the Figma MCP.
97
-
98
- **Smoke test (no Figma token needed):**
99
-
100
- ```bash
101
- git clone https://github.com/let-sunny/canicode.git
102
- cd canicode && pnpm install && pnpm build
103
- canicode analyze ./fixtures/done/desktop-home-page
104
- ```
105
-
106
- Loads a bundled fixture (no Figma API call, no token), opens the HTML report in a browser (pass `--no-open` to skip auto-launch). Use any directory under `fixtures/done/` — `desktop-*` are screen-scale, `mobile-*` are mobile viewports.
107
-
108
- <details>
109
- <summary><strong>All channels</strong></summary>
110
-
111
- | Channel | Best for |
112
- |---------|----------|
113
- | **[Web App](https://let-sunny.github.io/canicode/)** | Quick check, no install |
114
- | **[Figma Plugin](https://www.figma.com/community/plugin/1617144221046795292/canicode)** | Analyze inside Figma (under review) |
115
- | **MCP Server** | Claude Code / Cursor / Claude Desktop integration |
116
- | **`/canicode-roundtrip` Skill** | Full design-to-code roundtrip via Claude Code (analyze → fix → re-analyze → handoff) |
117
- | **`/canicode` Skill** | Lightweight analyze-only skill, no MCP install |
118
- | **CLI** | Full control, CI/CD, offline analysis |
119
- | **[GitHub Action](https://github.com/marketplace/actions/canicode-action)** | PR gate with score threshold |
120
-
121
- </details>
100
+ > **Optional Code Connect** (for the closing Step 6 mapping): install `@figma/code-connect` and create `figma.config.json` at your repo root per [Figma's setup guide](https://www.figma.com/code-connect-docs/), then run `canicode doctor`. If you skip this, the roundtrip still works — it just does not register the mapping. It warns you up front so you can decide.
122
101
 
123
102
  ---
124
103
 
@@ -128,7 +107,7 @@ Loads a bundled fixture (no Figma API call, no token), opens the HTML report in
128
107
  |----------|:-----:|------------------|
129
108
  | **Pixel Critical** | 3 | Can AI read the layout? (Auto Layout, absolute positioning, groups) |
130
109
  | **Responsive Critical** | 2 | Will it work at different viewports? (fixed sizing, size constraints) |
131
- | **Code Quality** | 4 | Is the design efficient for AI context? (components, variants, nesting) |
110
+ | **Code Quality** | 5 | Is the design efficient for AI context? (components, variants, nesting, Code Connect coverage) |
132
111
  | **Token Management** | 2 | Can AI reproduce exact values? (raw values, spacing grid) |
133
112
  | **Interaction** | 2 | Can AI know what happens? (state variants, prototypes) |
134
113
  | **Semantic** | 3 | Can AI infer meaning? (semantic names, conventions) |
@@ -139,6 +118,8 @@ Each issue is classified: **Blocking** > **Risk** > **Missing Info** > **Suggest
139
118
 
140
119
  ## Installation — pick one
141
120
 
121
+ > **Token safety:** never paste your Figma token into Claude / Cursor / agent chat — session logs can retain it. Use the CLI prompt (`npx canicode init`), `--token`, or the `FIGMA_TOKEN=…` env var.
122
+
142
123
  Each row below is a **complete** install. Don't run more than one — they cover overlapping use cases.
143
124
 
144
125
  | If you use… | Install |
package/dist/cli/index.js CHANGED
@@ -2959,6 +2959,10 @@ defineRule({
2959
2959
  function isOnGrid(value, gridBase) {
2960
2960
  return value % gridBase === 0;
2961
2961
  }
2962
+ function hasOwnBoundVariables(obj) {
2963
+ const bv = obj["boundVariables"];
2964
+ return bv !== void 0 && bv !== null && Object.keys(bv).length > 0;
2965
+ }
2962
2966
  var rawValueDef = {
2963
2967
  id: "raw-value",
2964
2968
  name: "Raw Value",
@@ -2973,6 +2977,7 @@ var rawValueCheck = (node, context) => {
2973
2977
  if (!hasStyleReference(node, "fill") && !hasBoundVariable(node, "fills")) {
2974
2978
  for (const fill of node.fills) {
2975
2979
  const fillObj = fill;
2980
+ if (hasOwnBoundVariables(fillObj)) continue;
2976
2981
  if (fillObj["type"] === "SOLID" && fillObj["color"]) {
2977
2982
  const c = fillObj["color"];
2978
2983
  const hex = `#${Math.round((c["r"] ?? 0) * 255).toString(16).padStart(2, "0")}${Math.round((c["g"] ?? 0) * 255).toString(16).padStart(2, "0")}${Math.round((c["b"] ?? 0) * 255).toString(16).padStart(2, "0")}`.toUpperCase();
@@ -3007,9 +3012,10 @@ var rawValueCheck = (node, context) => {
3007
3012
  }
3008
3013
  }
3009
3014
  if (node.effects && Array.isArray(node.effects) && node.effects.length > 0) {
3010
- if (!hasStyleReference(node, "effect")) {
3015
+ if (!hasStyleReference(node, "effect") && !hasBoundVariable(node, "effects")) {
3011
3016
  for (const effect of node.effects) {
3012
3017
  const effectObj = effect;
3018
+ if (hasOwnBoundVariables(effectObj)) continue;
3013
3019
  if (effectObj["type"] === "DROP_SHADOW" || effectObj["type"] === "INNER_SHADOW") {
3014
3020
  const shadowType = effectObj["type"] === "DROP_SHADOW" ? "drop shadow" : "inner shadow";
3015
3021
  const offset = effectObj["offset"];
@@ -3040,6 +3046,7 @@ var rawValueCheck = (node, context) => {
3040
3046
  }
3041
3047
  const spacingKeys = ["paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "itemSpacing"];
3042
3048
  for (const key of spacingKeys) {
3049
+ if (key === "itemSpacing" && node.primaryAxisAlignItems === "SPACE_BETWEEN") continue;
3043
3050
  const value = node[key];
3044
3051
  if (value !== void 0 && value > 0 && !hasBoundVariable(node, key)) {
3045
3052
  const label = key === "itemSpacing" ? "gap" : key.replace("padding", "padding-").toLowerCase();
@@ -3072,14 +3079,15 @@ var irregularSpacingCheck = (node, context, options) => {
3072
3079
  const spacingEntries = [];
3073
3080
  for (const key of ["paddingLeft", "paddingRight", "paddingTop", "paddingBottom"]) {
3074
3081
  const v = node[key];
3075
- if (v !== void 0 && v > 0) spacingEntries.push({ value: v, subType: "padding" });
3082
+ if (v !== void 0 && v > 0) spacingEntries.push({ key, value: v, subType: "padding" });
3076
3083
  }
3077
- if (node.itemSpacing !== void 0 && node.itemSpacing > 0) {
3078
- spacingEntries.push({ value: node.itemSpacing, subType: "gap" });
3084
+ if (node.itemSpacing !== void 0 && node.itemSpacing > 0 && node.primaryAxisAlignItems !== "SPACE_BETWEEN") {
3085
+ spacingEntries.push({ key: "itemSpacing", value: node.itemSpacing, subType: "gap" });
3079
3086
  }
3080
3087
  const commonValues = [1, 2];
3081
3088
  for (const entry of spacingEntries) {
3082
3089
  if (commonValues.includes(entry.value)) continue;
3090
+ if (hasBoundVariable(node, entry.key)) continue;
3083
3091
  if (!isOnGrid(entry.value, gridBase)) {
3084
3092
  return {
3085
3093
  ruleId: irregularSpacingDef.id,
@@ -3507,17 +3515,28 @@ defineRule({
3507
3515
  var CODE_CONNECT_SETUP_KEY = "unmapped-component:setup-detected";
3508
3516
  var CODE_CONNECT_MAPPINGS_KEY = "unmapped-component:mappings";
3509
3517
  var SEEN_MAIN_IDS_KEY = "unmapped-component:seen-main-ids";
3518
+ function browserCwd() {
3519
+ return typeof process !== "undefined" && typeof process.cwd === "function" ? process.cwd() : null;
3520
+ }
3510
3521
  function codeConnectIsSetUp(context) {
3511
3522
  return getAnalysisState(context, CODE_CONNECT_SETUP_KEY, () => {
3512
- return existsSync(join(process.cwd(), "figma.config.json"));
3523
+ const cwd = browserCwd();
3524
+ if (cwd === null) return false;
3525
+ return existsSync(join(cwd, "figma.config.json"));
3513
3526
  });
3514
3527
  }
3515
3528
  function codeConnectMappings(context) {
3516
- return getAnalysisState(
3517
- context,
3518
- CODE_CONNECT_MAPPINGS_KEY,
3519
- () => parseCodeConnectMappings(process.cwd())
3520
- );
3529
+ return getAnalysisState(context, CODE_CONNECT_MAPPINGS_KEY, () => {
3530
+ const cwd = browserCwd();
3531
+ if (cwd === null) {
3532
+ return {
3533
+ mappedNodeIds: /* @__PURE__ */ new Set(),
3534
+ scannedFiles: [],
3535
+ skipReason: "no-config"
3536
+ };
3537
+ }
3538
+ return parseCodeConnectMappings(cwd);
3539
+ });
3521
3540
  }
3522
3541
  function seenMainIds(context) {
3523
3542
  return getAnalysisState(context, SEEN_MAIN_IDS_KEY, () => /* @__PURE__ */ new Set());
@@ -4577,7 +4596,7 @@ function computeApplyContext(violation, instanceContext) {
4577
4596
  }
4578
4597
 
4579
4598
  // package.json
4580
- var version2 = "0.12.3";
4599
+ var version2 = "0.12.4";
4581
4600
 
4582
4601
  // src/core/engine/scoring.ts
4583
4602
  var GRADE_ORDER = ["S", "A+", "A", "B+", "B", "C+", "C", "D", "F"];
@@ -5888,6 +5907,22 @@ body {
5888
5907
  border: 1px solid rgba(239,68,68,0.15);
5889
5908
  }
5890
5909
 
5910
+ /* ---- Warning (skipped nodes, etc.) ---- */
5911
+ .warning-msg {
5912
+ background: var(--amber-bg);
5913
+ color: #92400e;
5914
+ padding: 10px 12px;
5915
+ border-radius: var(--radius-sm);
5916
+ font-size: 12px;
5917
+ margin-bottom: 16px;
5918
+ border: 1px solid rgba(245,158,11,0.2);
5919
+ }
5920
+ .warning-msg details { margin-top: 8px; }
5921
+ .warning-msg summary { cursor: pointer; font-weight: 500; }
5922
+ .warning-msg ul { margin: 8px 0 0 16px; padding: 0; }
5923
+ .warning-msg li { margin: 4px 0; }
5924
+ .warning-msg a { color: inherit; text-decoration: underline; }
5925
+
5891
5926
  /* ---- Layout (plugin) ---- */
5892
5927
  .container { padding: 16px; }
5893
5928
  .header {