{"id":"76796ec1-1db6-4e82-8e5d-76f680ed9ab5","entityType":"agent","slug":"clawhub-timeaground-figma-to-mobile","name":"figma-to-mobile","canonicalUrl":"https://www.xpersona.co/agent/clawhub-timeaground-figma-to-mobile","canonicalPath":"/agent/clawhub-timeaground-figma-to-mobile","generatedAt":"2026-10-09T19:36:29.419Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-09T16:51:27.085Z","emptyReason":null},"description":"Convert Figma designs to mobile UI code (Compose/XML/SwiftUI/UIKit/Flutter) via the Figma REST API with local resource scanning, multi-frame comparison, and feedback-log corrections. Activate when a user provides a Figma link and asks for mobile layout code. Skill: figma-to-mobile Owner: timeaground Summary: Convert Figma designs to mobile UI code (Compose/XML/SwiftUI/UIKit/Flutter) via the Figma REST API with local resource scanning, multi-frame comparison, and feedback-log corrections. Activate when a user provides a Figma link and asks for mobile layout code. Tags: latest:2.3.0 Version history: v2.3.0 | 2026-08-06T15:45:12.237Z | auto - Adds new references for error h","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 2.3K downloads reported by the source. Last updated 10/9/2026.","installCommand":"clawhub skill install s1768nwd588za5fw47dcmmxwwh843t26:figma-to-mobile","sourceUrl":"https://clawhub.ai/timeaground/figma-to-mobile","homepage":"https://clawhub.ai/timeaground/skills/figma-to-mobile","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/timeaground/figma-to-mobile","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/timeaground/skills/figma-to-mobile","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":42,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Convert Figma designs to mobile UI code (Compose/XML/SwiftUI/UIKit/Flutter) via the Figma REST API with local resource scanning, multi-frame comparison, and fee"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-09T16:51:27.085Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T16:51:27.085Z","emptyReason":null},"stars":null,"forks":null,"downloads":2270,"packageName":null,"latestVersion":"2.3.0","tractionLabel":"2.3K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T16:51:27.084Z","emptyReason":null},"lastUpdatedAt":"2026-10-09T16:51:27.085Z","lastCrawledAt":"2026-10-09T16:51:27.084Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-10T16:51:27.085Z","lastVerifiedAt":null,"highlights":[{"version":"2.3.0","createdAt":"2026-08-06T15:45:12.237Z","changelog":"- Adds new references for error handling, feedback logging, and multi-frame support - Updates documentation with clearer workflow, deliverables, and usage restrictions - Expands details for multi-frame analysis and project resource scanning - Removes outdated documentation (skill-card.md) - Introduces a CHANGELOG.md and structured metadata for resource usage and permissions","fileCount":39,"zipByteSize":108948},{"version":"2.2.5","createdAt":"2026-07-25T13:11:24.222Z","changelog":"- Documentation updated: README.md revised with latest instructions and clarifications. - Developer documentation streamlined: skill-card.md file removed.","fileCount":34,"zipByteSize":103668},{"version":"2.2.4","createdAt":"2026-07-25T11:09:18.743Z","changelog":"- Clarified and tightened skill trigger: now only activates when a user explicitly requests Figma-to-mobile code conversion (won’t run on casual/pasted links). - Updated documentation to reflect the above and improve wording for accuracy and brevity. - Removed unused/legacy documentation file (skill-card.md). - No functional/code changes—documentation only.","fileCount":34,"zipByteSize":103546},{"version":"2.2.3","createdAt":"2026-07-24T14:07:39.196Z","changelog":"Version 2.2.3 - Added project-aware scanning: can read local resources (colors, strings, components) for improved code reuse. - Updated permissions: now uses network access and limited file system read/write (with user consent). - Now supports export to Flutter in addition to Android (Compose/XML) and iOS (SwiftUI/UIKit). - Multi-frame comparison and SVG export are supported. - Introduced feedback-log for capturing manual corrections, with user confirmation. - Improved documentation clarity and removed skill-card.md file.","fileCount":34,"zipByteSize":103659},{"version":"2.2.2","createdAt":"2026-07-24T13:20:06.986Z","changelog":"- Added LICENSE file to provide clear open source/legal information. - Updated SKILL.md with a security note about Figma token usage. - Removed obsolete skill-card.md file.","fileCount":34,"zipByteSize":103094},{"version":"2.2.1","createdAt":"2026-07-23T16:08:14.979Z","changelog":"Refactor scanner pipeline, add Flutter support, add semantic labels, add scan consent dialog","fileCount":33,"zipByteSize":102495},{"version":"2.2.0","createdAt":"2026-07-23T15:19:30.710Z","changelog":"**Figma-to-mobile 2.2.0 introduces Flutter code scanning support and project scan improvements.** - Added Flutter platform detection and scanner modules. - Updated Android and iOS scanners for better modularity and extendibility. - Enhanced project resource scanning instructions and output presentation. - Always require user consent before scanning project directories. - Improved documentation (README and SKILL.md) for multilingual support, clear triggers, and streamlined workflow. - Removed obsolete skill-card.md.","fileCount":33,"zipByteSize":102092},{"version":"2.1.1","createdAt":"2026-07-13T13:14:45.942Z","changelog":"Fix 4 remaining security audit issue categories (16 findings): - README.md: remove 'agent saves token to .env' description — now matches SKILL.md (user env var only) - Trigger: restrict to explicit code conversion requests, not any Figma link - Feedback logging: change from automatic/forced to opt-in with user consent","fileCount":31,"zipByteSize":92971}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s1768nwd588za5fw47dcmmxwwh843t26:figma-to-mobile","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-timeaground-figma-to-mobile/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-timeaground-figma-to-mobile/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-timeaground-figma-to-mobile/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-timeaground-figma-to-mobile/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-timeaground-figma-to-mobile/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-timeaground-figma-to-mobile/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-09T19:36:29.416Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-timeaground-figma-to-mobile/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-timeaground-figma-to-mobile/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-timeaground-figma-to-mobile/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-timeaground-figma-to-mobile/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-09T16:51:27.085Z","emptyReason":null},"readme":"Skill: figma-to-mobile\n\nOwner: timeaground\n\nSummary: Convert Figma designs to mobile UI code (Compose/XML/SwiftUI/UIKit/Flutter) via the Figma REST API with local resource scanning, multi-frame comparison, and feedback-log corrections. Activate when a user provides a Figma link and asks for mobile layout code.\n\nTags: latest:2.3.0\n\nVersion history:\n\nv2.3.0 | 2026-08-06T15:45:12.237Z | auto\n\n- Adds new references for error handling, feedback logging, and multi-frame support\n- Updates documentation with clearer workflow, deliverables, and usage restrictions\n- Expands details for multi-frame analysis and project resource scanning\n- Removes outdated documentation (skill-card.md)\n- Introduces a CHANGELOG.md and structured metadata for resource usage and permissions\n\nv2.2.5 | 2026-07-25T13:11:24.222Z | auto\n\n- Documentation updated: README.md revised with latest instructions and clarifications.\n- Developer documentation streamlined: skill-card.md file removed.\n\nv2.2.4 | 2026-07-25T11:09:18.743Z | auto\n\n- Clarified and tightened skill trigger: now only activates when a user explicitly requests Figma-to-mobile code conversion (won’t run on casual/pasted links).\n- Updated documentation to reflect the above and improve wording for accuracy and brevity.\n- Removed unused/legacy documentation file (skill-card.md).\n- No functional/code changes—documentation only.\n\nv2.2.3 | 2026-07-24T14:07:39.196Z | auto\n\nVersion 2.2.3\n\n- Added project-aware scanning: can read local resources (colors, strings, components) for improved code reuse.\n- Updated permissions: now uses network access and limited file system read/write (with user consent).\n- Now supports export to Flutter in addition to Android (Compose/XML) and iOS (SwiftUI/UIKit).\n- Multi-frame comparison and SVG export are supported.\n- Introduced feedback-log for capturing manual corrections, with user confirmation.\n- Improved documentation clarity and removed skill-card.md file.\n\nv2.2.2 | 2026-07-24T13:20:06.986Z | auto\n\n- Added LICENSE file to provide clear open source/legal information.\n- Updated SKILL.md with a security note about Figma token usage.\n- Removed obsolete skill-card.md file.\n\nv2.2.1 | 2026-07-23T16:08:14.979Z | user\n\nRefactor scanner pipeline, add Flutter support, add semantic labels, add scan consent dialog\n\nv2.2.0 | 2026-07-23T15:19:30.710Z | auto\n\n**Figma-to-mobile 2.2.0 introduces Flutter code scanning support and project scan improvements.**\n\n- Added Flutter platform detection and scanner modules.\n- Updated Android and iOS scanners for better modularity and extendibility.\n- Enhanced project resource scanning instructions and output presentation.\n- Always require user consent before scanning project directories.\n- Improved documentation (README and SKILL.md) for multilingual support, clear triggers, and streamlined workflow.\n- Removed obsolete skill-card.md.\n\nv2.1.1 | 2026-07-13T13:14:45.942Z | user\n\nFix 4 remaining security audit issue categories (16 findings):\n\n- README.md: remove 'agent saves token to .env' description — now matches SKILL.md (user env var only)\n- Trigger: restrict to explicit code conversion requests, not any Figma link\n- Feedback logging: change from automatic/forced to opt-in with user consent\n\nv2.1.0 | 2026-07-11T12:44:03.375Z | user\n\nFix security audit findings (3):\n\n- Add consent step before project scan (Step 2.5)\n- Add write boundary rules (output to conversation first)\n- Remove .env fallback, token via user env var only\n- Update description to reflect optional project inspection\n\nv2.0.4 | 2026-06-06T02:34:04.635Z | user\n\n- Added guidelines for secure FIGMA_TOKEN setup, including platform-specific instructions and a note to avoid sharing tokens in chat.\n- Updated Python version prerequisite to Python 3.10+.\n- Added .env usage caution (edit locally, ignore with git).\n- Included new metadata files: _meta.json and evals/evals.json.\n- Removed deprecated documentation: skill-card.md.\n\nv2.0.3 | 2026-05-09T14:10:00.592Z | user\n\ndocs: README updated to reflect patch-only iteration behavior\n\nv2.0.2 | 2026-05-09T14:07:43.396Z | user\n\nIteration output optimization: only patch changed lines when file exists on disk, skip full file regeneration unless explicitly requested\n\nv2.0.1 | 2026-05-09T14:05:27.145Z | user\n\nBug fixes and improvements\n\nv2.0.0 | 2026-05-08T07:13:26.914Z | user\n\nIteration patch rule: agent now outputs minimal diff instead of regenerating full file on each iteration\n\nv1.3.0 | 2026-04-08T13:59:15.553Z | user\n\nSimplify link handling: one link = one frame, no more file overview mode. Add Figma seat/plan diagnostics on 429 rate limit. Cap Retry-After to prevent infinite waits.\n\nv1.2.0 | 2026-04-07T03:13:03.015Z | user\n\nAdd demo comparison image to README, expanded install/usage docs, SKILL.md refactored with modular references, new scanners for drawables and text styles\n\nv1.1.0 | 2026-04-04T05:56:03.358Z | user\n\nv1.1.0: Figma数据补全(textAutoResize/layoutAlign/variantProperties/styleRefs) + Compose patterns补全(6章节) + Drawable shape扫描匹配 + TextAppearance索引 + 结构摘要输出 + 资源建议块 + 多状态batch compare + SKILL.md模块化拆分\n\nv1.0.2 | 2026-04-03T01:50:47.928Z | user\n\nFix metadata: declare FIGMA_TOKEN env var + python3 dependency\n\n- Added requires.env: FIGMA_TOKEN to package metadata\n- Added requires.bins: python3\n- Added primaryEnv: FIGMA_TOKEN\n- Fixes ClawHub security scan flagging metadata inconsistency\n\nv1.0.1 | 2026-04-03T01:43:01.425Z | user\n\nMulti-platform architecture + iOS support + performance optimization\n\n- Unified scan schema (base.py) with platform-agnostic ScanReport\n- iOS scanners: color (xcassets + Swift code), string (JSON i18n + .strings), imageset, custom View (UIKit + SwiftUI)\n- Single-pass Swift file scanning (24-50% faster)\n- Scan levels: --level resources (fast) vs full (default)\n- Android: module dependency graph + resource visibility filtering\n- Android: layout XML analysis (View usage, text styles, dimension patterns)\n- Updated xml-patterns.md: ConstraintLayout sizing rules, divider pattern, ImageView dark mode trend\n- Updated uikit/swiftui-patterns.md: dark mode inference rules\n- SKILL.md: new Step 2.5 Project Scan workflow\n\nv1.0.0 | 2026-04-02T10:57:39.743Z | user\n\nInitial release: Figma design to mobile UI code (Android XML, Compose, SwiftUI, UIKit)\n\nArchive index:\n\nArchive v2.3.0: 39 files, 108948 bytes\n\nFiles: CHANGELOG.md (1504b), eval/evals.json (3905b), LICENSE (1084b), README-zh.md (7193b), README.md (3608b), references/compose-patterns.md (23941b), references/error-handling.md (1131b), references/feedback-log.md (1627b), references/figma-interpretation.md (8637b), references/generation-rules.md (5275b), references/multi-frame.md (1520b), references/scan-usage.md (4496b), references/swiftui-patterns.md (12454b), references/uikit-patterns.md (14135b), references/xml-patterns.md (24007b), scripts/feedback_analyze.py (6420b), scripts/figma_fetch.py (26437b), scripts/project_scan.py (7850b), scripts/scanners/__init__.py (1504b), scripts/scanners/android_deps.py (2485b), scripts/scanners/android_drawables.py (13181b), scripts/scanners/android_layouts.py (5030b), scripts/scanners/android_modules.py (4061b), scripts/scanners/android_resources.py (11356b), scripts/scanners/android_scanner.py (19508b), scripts/scanners/android_views.py (6321b), scripts/scanners/base.py (12022b), scripts/scanners/flutter_detector.py (452b), scripts/scanners/flutter_scanner.py (15776b), scripts/scanners/ios_assets.py (1858b), scripts/scanners/ios_detector.py (815b), scripts/scanners/ios_resources.py (5804b), scripts/scanners/ios_scanner.py (8028b), scripts/scanners/ios_swift_scan.py (4962b), scripts/scanners/ios_views.py (3148b), skill-card.md (3060b), SKILL.md (12314b), tests/test_project_scan.py (15007b), _meta.json (134b)\n\nFile v2.3.0:SKILL.md\n\n---\r\nname: figma-to-mobile\r\nversion: 2.3.0\r\nrequires: [Bash, Read, Write, Grep, Glob]\r\ndescription: >-\r\n  Convert Figma designs to mobile UI code (Compose/XML/SwiftUI/UIKit/Flutter)\r\n  via the Figma REST API with local resource scanning, multi-frame comparison,\r\n  and feedback-log corrections. Activate when a user provides a Figma link and\r\n  asks for mobile layout code.\r\nmetadata:\r\n  {\r\n    \"openclaw\":\r\n      {\r\n        \"requires\": { \"bins\": [\"python3\"], \"env\": [\"FIGMA_TOKEN\"] },\r\n        \"primaryEnv\": \"FIGMA_TOKEN\",\r\n        \"permissions\":\r\n          {\r\n            \"network\": [ \"api.figma.com\" ],\r\n            \"fs\":\r\n              {\r\n                \"read\": [ \"project root — for resource scanning (colors, strings, components)\" ],\r\n                \"write\": [\r\n                  \"project root — for generated UI code files\",\r\n                  \"project root — for feedback-log.md (user consent required)\"\r\n                ]\r\n              }\r\n          },\r\n        \"install\":\r\n          [\r\n            {\r\n              \"id\": \"python-requests\",\r\n              \"kind\": \"shell\",\r\n              \"command\": \"pip3 install requests\",\r\n              \"label\": \"Install Python requests package\",\r\n            },\r\n          ],\r\n      },\r\n    \"resourceManifest\":\r\n      {\r\n        \"cpu\": \"low — short-lived CLI scripts\",\r\n        \"memory\": \"low (< 512 MB)\",\r\n        \"timeout\": \"30s per API request; 3 retries; ~2s min interval between requests\",\r\n        \"network\": [ \"api.figma.com\" ],\r\n      },\r\n  }\r\n---\r\n\r\n# Figma to Mobile\r\n\r\nConvert Figma designs to mobile UI code with interactive clarification.\r\n\r\nSupported: Android Compose, Android XML, iOS SwiftUI, iOS UIKit.\r\n\r\n## Prerequisites\r\n\r\n- `FIGMA_TOKEN` environment variable set (Figma > Settings > Personal Access Tokens)\r\n  ⚠️ **Your Figma token is sensitive** — treat it like a password. Never paste it into chat\r\n    messages (they may be logged). Set it via your shell rc file or OpenClaw env config.\r\n- Python 3.8+ with `requests` package\r\n\r\n## Trigger & Input\r\n\r\nThis skill activates when a user **explicitly asks to convert a Figma design to mobile UI code** and provides a Figma link. It does NOT activate on casual mentions of Figma links, pasted URLs in logs, or design references without a conversion request.\r\n\r\nThe user may also include **inline hints** alongside the link, such as:\r\n- Target platform: \"Android XML\", \"Compose\", \"SwiftUI\", \"UIKit\"\r\n- Layout preferences: \"use ConstraintLayout\", \"prefer StackView\"\r\n- Component notes: \"the switch is our custom CompactSwitch\", \"this is a dynamic list\"\r\n- Any other context about the design\r\n\r\n**If the user provides hints, respect them and skip the corresponding questions.**\r\nFor example, if the user says \"Android XML, the 3 cards are a RecyclerView list\", do NOT ask about output format or whether the cards are dynamic/static.\r\n\r\n## Not Applicable\r\n\r\nThis skill is NOT for:\r\n\r\n- **Screenshot / image → code**: it works only with Figma design links via the Figma REST API, not with screenshots or image files.\r\n- **Layout code without a Figma link**: generic \"write me a login screen\" requests have no design to interpret.\r\n- **Casual mentions or pasted URLs without a conversion request** — see Trigger & Input above.\r\n- **Pure design discussion or Figma feature questions**: no code is generated.\r\n\r\n## Deliverables\r\n\r\nAfter a successful conversion, the user receives:\r\n\r\n- **Generated UI code files** — platform-idiomatic (Compose / XML / SwiftUI / UIKit / Flutter). Multiple files are presented with a clear filename header (see Step 3).\r\n- **`scan-report.json`** — project resource scan results, only when the user agrees to a project scan (Step 2.5).\r\n- **`feedback-log.md`** — correction log written to the project root, only with user consent (Step 4).\r\n\r\n## Workflow\r\n\r\n### Step 1: Fetch & Analyze\r\n\r\nWhen user provides Figma link(s):\r\n\r\n1. **Determine the input type:**\r\n\r\n   **A. Link without specific node-id** (no `node-id`, or `node-id=0-1`):\r\n   This link points to the entire page, not a specific frame. Tell the user:\r\n   > This link points to the whole page. Please select the frame you want in Figma, right-click it, and choose \"Copy link to selection\", then send that link.\r\n   > If you want to convert multiple frames, send multiple links.\r\n\r\n   **B. Single frame link** (has specific `node-id`):\r\n   ```bash\r\n   python scripts/figma_fetch.py \"https://www.figma.com/design/<fileKey>/<name>?node-id=<id>\"\r\n   ```\r\n   → returns that frame's design data. Proceed to analysis.\r\n\r\n   **C. Multiple links** (user sends 2+ URLs):\r\n   Determine the relationship by frame names and user context: same-page\r\n   states → `--compare` mode (multi-state code); parent+overlay → independent\r\n   layout files; independent pages → fetch one at a time, ask which to convert\r\n   first. If unsure, ask the user.\r\n\r\n   **Detailed multi-frame rules**: Read `references/multi-frame.md`\r\n\r\n2. **If the link has no specific node-id**, ask the user to re-copy from the specific frame (see A above). Do NOT call the API.\r\n\r\n3. Analyze the structure: identify sections, repeated patterns, component types\r\n4. Note INSTANCE nodes — they indicate reusable components. Check `variantProperties` for component state (e.g. State=Default, Size=Large) — these map to multi-state code\r\n5. Note gradient/shadow data — flag for the user if complex\r\n6. Apply Figma node interpretation rules before generating code\r\n\r\n**Detailed interpretation rules**: Read `references/figma-interpretation.md`\r\n\r\n### Step 1.5: Structure Summary\r\n\r\nBefore asking any questions, present a brief **structure summary** to the user so they can confirm your understanding:\r\n\r\n> I see: [navigation bar with back button + title] → [2 content sections: user profile card, settings list (8 items)] → [bottom action button]. Total ~25 nodes.\r\n\r\nKeep it to 2-3 lines. Mention:\r\n- Major sections identified (nav bar, content areas, footer)\r\n- Repeated patterns (\"8 similar list items\", \"3 tab labels\")\r\n- Notable elements (gradients, complex illustrations, stacked cards)\r\n\r\nIf the user says \"that's wrong\" or corrects the structure, adjust understanding before proceeding to Step 2.\r\n\r\nIf the design has ≤10 leaf nodes (visible elements that map to actual views), skip this step — the structure is simple enough to proceed directly.\r\n\r\n### Step 2: Confirm & Clarify\r\n\r\n**Question priority (strict order — ask earlier questions first):**\r\n\r\n1. **Output format** (MUST ask first unless user already specified)\r\n   → Android XML / Compose / SwiftUI / UIKit\r\n   This determines all subsequent analysis phrasing and code output.\r\n\r\n2. **Structural ambiguities** (only ask what you're genuinely unsure about)\r\n   → \"These N items look similar — dynamic list or fixed layout?\"\r\n   → \"This area: single image asset or icon-on-background combo?\"\r\n\r\n3. **Component choices** (only if platform-relevant)\r\n   → \"Any custom components to use? (otherwise I'll use platform defaults)\"\r\n\r\n**Rules for questions:**\r\n- Skip any question the user already answered via inline hints\r\n- Max 3-5 questions total, fewer is better\r\n- Each question gives concrete options with one-line pros/cons\r\n- Every question includes an open option: \"or tell me more about this\"\r\n- Use natural language, no JSON or technical dumps\r\n- If everything is clear (user gave full context + simple structure), skip Step 2 entirely\r\n\r\n**Confidence guide — when to ask vs. when to just generate:**\r\n- ≥3 sibling nodes with similar structure → likely a list → ASK (dynamic vs static)\r\n- INSTANCE nodes sharing same componentId → reusable component → MENTION but can default\r\n- Single clear hierarchy, no ambiguity → high confidence → SKIP questions, go to Step 3\r\n- Gradient/complex shadow in design → MENTION in summary (\"I see a gradient here, I'll approximate it as X\")\r\n\r\n### Step 2.5: Project Scan — Ask First\r\n\r\n**⚠️ Always ask the user before scanning their project.** Scanning reads local files;\r\nthe user should know and agree.\r\n\r\n> \"你的项目在 /path/to/project 对吗？要不要我先扫描一下项目里已有的资源\r\n>（颜色、文案、图片、自定义组件），这样生成代码时可以直接复用已有的东西？\"\r\n\r\nIf the user agrees:\r\n\r\n```bash\r\npython scripts/project_scan.py /path/to/project --json --output scan-report.json\r\n```\r\n\r\nThen read `scan-report.json` and `references/scan-usage.md`.\r\n\r\nIf the user declines: proceed with hardcoded values per generation rules.\r\n\r\n**How to present scan results** (with sample phrasing): Read `references/scan-usage.md`\r\n\r\n**If no project path is known yet, don't scan.** Proceed with hardcoded generation.\r\n\r\n### Step 3: Generate Code\r\n\r\nAfter user confirms (or if no questions needed), generate code files.\r\n\r\n**Detailed generation rules**: Read `references/generation-rules.md`\r\n\r\nIf multiple files are needed, output each with a clear filename header:\r\n```\r\n📄 activity_notification_settings.xml\r\n[code]\r\n\r\n📄 item_expert_notification.xml\r\n[code]\r\n```\r\n\r\n### Step 4: Iterate & Capture Feedback\r\n\r\nAfter showing code, ask briefly:\r\n> Matches the design? Any adjustments?\r\n\r\n**The user can then give feedback to refine the output.** Common iterations:\r\n- \"间距大了\" → adjust specific spacing\r\n- \"Switch 换成我们的 CustomSwitch\" → swap component\r\n- \"把标题栏去掉\" → remove section\r\n- \"换成 Compose 版本\" → regenerate in different format\r\n- \"颜色不对，这里应该是 #333333\" → fix specific values\r\n\r\nContinue iterating until the user is satisfied.\r\n\r\n**Iteration output rule:**\r\n- If the file has already been written to disk → read the current file, apply only the minimal patch, output just the changed lines with clear context (file path + line range). Do NOT regenerate the whole file.\r\n- If the code only exists in the conversation (not written to disk) → output only the changed snippet with a comment indicating where it replaces (e.g., `// replaces lines 12-18 in activity_main.xml`). Do NOT repeat the entire file.\r\n- Only regenerate the full file if the user explicitly asks (e.g., \"重新生成完整文件\", \"show me the full file\").\r\n\r\n**⚠️ Before logging any feedback, tell the user:**\r\n  > \"I'll save this correction locally to `feedback-log.md` to improve future output.\r\n  > It stores before/after snippets — is that OK?\"\r\n  Only proceed if the user agrees.\r\n\r\n**⚠️ IMPORTANT: Every time the user corrects your output (layout issue, wrong component, spacing problem, etc.), you MUST log it to `feedback-log.md` before proceeding with the fix (after user consent). Do not skip this step — the log is how the skill learns and improves over time.**\r\n\r\n**Feedback format, logging rules, and analysis**: Read `references/feedback-log.md`\r\n\r\n## API Request Limits\r\n\r\nThe bundled `scripts/figma_fetch.py` enforces these limits automatically:\r\n\r\n- **Timeout**: 30s per request\r\n- **Retries**: up to 3 attempts on connection/SSL errors (backoff 5s/10s/15s)\r\n- **Rate limit**: ≥2s between requests; on HTTP 429, wait per `Retry-After` header (cap 30s)\r\n- **Adaptive depth**: refetches with deeper depth (up to 15) when children look truncated\r\n\r\nWhen a rate limit is exceeded, the script reports:\r\n\r\n```json\r\n{\r\n  \"status\": \"error\",\r\n  \"error\": \"RATE_LIMIT_EXCEEDED\",\r\n  \"message\": \"Figma API rate limit exceeded\",\r\n  \"retry_after_seconds\": 30,\r\n  \"plan_tier\": \"FREE\",\r\n  \"limit_type\": \"requests_per_minute\"\r\n}\r\n```\r\n\r\n## Error Handling\r\n\r\nQuick reference — full details in `references/error-handling.md`:\r\n\r\n- **FIGMA_TOKEN not set** (`FIGMA_TOKEN_NOT_SET`) → guide user to set env var; never paste token into chat\r\n- **403/401** → token expired/revoked; regenerate in Figma Settings → Security\r\n- **Invalid URL** → show valid format: `https://www.figma.com/design/<fileKey>/<name>?node-id=<id>`\r\n- **API error / node too large / depth auto-increased** → see `references/error-handling.md`\r\n\r\n## Tips\r\n\r\n- **Token safety**: never paste `FIGMA_TOKEN` into chat — set it as an environment variable (see Prerequisites).\r\n- **Rate limits**: keep ≥2s between Figma API calls; the script enforces this, so avoid parallel fetches to save time.\r\n- **Multi-frame**: compare shared components across frames before generating code to avoid duplication.\n\nFile v2.3.0:README.md\n\n# figma-to-mobile\r\n\r\nConvert Figma designs to production-ready mobile UI code using AI.\r\n\r\nSupports: **Jetpack Compose** · **Android XML** · **SwiftUI** · **UIKit**\r\n\r\n## Demo\r\n\r\nHere's a side-by-side comparison using a [Material Design 3 Messaging App](https://www.figma.com/community/file/1169726503071187057/) from Figma Community:\r\n\r\n![Figma to Compose comparison](assets/demo-comparison.png)\r\n\r\n**Left:** Figma design · **Right:** Generated Jetpack Compose code running in Android Studio\r\n\r\nThe tool reads the Figma design tree (auto-layout, style refs, variants) and generates idiomatic code — not pixel-positioned boxes.\r\n\r\n## How It Works\r\n\r\n1. **Fetch** — Python script calls Figma API to extract the node tree\r\n2. **Interpret** — AI analyzes layout semantics: \"6 similar rows → `LazyColumn`\", \"horizontal stack → `Row`\"\r\n3. **Generate** — Outputs platform-idiomatic code with proper theming (Material3, SF Symbols, etc.)\r\n4. **Iterate** — Refine through natural language: \"make the header sticky\", \"switch to dark theme\"\r\n\r\n## Install\r\n\r\n### OpenClaw\r\n\r\n```bash\r\nclawhub install figma-to-mobile\r\n```\r\n\r\n### Claude Code\r\n\r\nCopy the `figma-to-mobile` folder into your project:\r\n```\r\nyour-project/.claude/skills/figma-to-mobile/\r\n```\r\n\r\n### GitHub Copilot\r\n\r\nCopy the `figma-to-mobile` folder into your project:\r\n```\r\nyour-project/.agents/skills/figma-to-mobile/\r\n```\r\n\r\n## Setup\r\n\r\n> ⚠️ **Figma Token** — Set the `FIGMA_TOKEN` environment variable.\r\n> Generate one at Figma → Avatar → Settings → Security → Personal Access Tokens.\r\n>\r\n> **Do not paste your token into chat** — chat messages may be logged.\r\n> Set it via your shell profile or system environment variables:\r\n>\r\n> ```bash\r\n> # macOS/Linux\r\n> export FIGMA_TOKEN=\"figd_your_token_here\"\r\n>\r\n> # Windows PowerShell\r\n> $env:FIGMA_TOKEN = \"figd_your_token_here\"\r\n> ```\r\n\r\n## Usage\r\n\r\nPaste a Figma design link in your AI assistant's chat:\r\n\r\n> Convert this to Jetpack Compose: https://www.figma.com/design/xxx/Project?node-id=100-200\r\n\r\nThe agent will:\r\n1. Fetch the design data from Figma API\r\n2. Ask clarifying questions (platform, list vs static, etc.)\r\n3. Generate production-ready code files\r\n4. Iterate based on your feedback — only patches the changed lines, never regenerates the full file unless you ask\r\n\r\n## What Makes It Different\r\n\r\n| Feature | Screenshot-based tools | figma-to-mobile |\r\n|---------|----------------------|-----------------|\r\n| Input | Screenshot/image | Figma API (design tree) |\r\n| Layout understanding | Pixel positions | Auto-layout semantics |\r\n| Output quality | Absolute positioning | Idiomatic code (LazyColumn, VStack, etc.) |\r\n| Iteration | Re-screenshot | Natural language refinement |\r\n| Cost | Paid subscription | Free & open source |\r\n\r\n## What's in the Box\r\n\r\n```\r\nfigma-to-mobile/\r\n├── SKILL.md              # Agent instructions (the brain)\r\n├── scripts/\r\n│   └── figma_fetch.py    # Figma API data fetcher\r\n├── references/\r\n│   ├── compose-patterns.md   # Jetpack Compose mapping rules\r\n│   ├── xml-patterns.md       # Android XML mapping rules\r\n│   ├── swiftui-patterns.md   # SwiftUI mapping rules\r\n│   └── uikit-patterns.md     # UIKit mapping rules\r\n└── assets/\r\n    └── demo-comparison.png   # Demo comparison image\r\n```\r\n\r\n## Requirements\r\n\r\n- Python 3.8+ with `requests` package\r\n- Figma Personal Access Token (free)\r\n\r\n## Feedback & Issues\r\n\r\nFound a bug or have a suggestion?\r\n[Open an issue on GitHub](https://github.com/TimeAground/figma-to-mobile/issues)\r\n\r\n## License\r\n\r\nMIT\n\nFile v2.3.0:_meta.json\n\n{\n  \"ownerId\": \"kn7crx9z6m9cw8gpcwbhp8na31843hnr\",\n  \"slug\": \"figma-to-mobile\",\n  \"version\": \"2.3.0\",\n  \"publishedAt\": 1786031112237\n}\n\nFile v2.3.0:references/compose-patterns.md\n\n# Compose Patterns — Figma to Jetpack Compose Mapping\r\n\r\n> Purpose: Map Figma properties to Jetpack Compose code.\r\n> This is a **mapping reference**, not a Compose tutorial — the agent already knows Compose conventions.\r\n\r\n## Layout Selection Guide\r\n\r\n| Figma Structure | Recommended Composable |\r\n|---|---|\r\n| Vertical stack | Column |\r\n| Horizontal stack | Row |\r\n| Overlapping / z-stacking | Box |\r\n| Repeating similar items (≥3) | LazyColumn / LazyRow |\r\n| Page structure with top/bottom bars | Scaffold |\r\n| Complex relative positioning | Box with Modifier.align / offset |\r\n\r\n## Auto-layout Mapping\r\n\r\n| Figma Property | Compose Equivalent |\r\n|---|---|\r\n| layoutMode: VERTICAL | Column |\r\n| layoutMode: HORIZONTAL | Row |\r\n| itemSpacing | Arrangement.spacedBy(X.dp) |\r\n| padding* | Modifier.padding() |\r\n| primaryAxisAlignItems: CENTER | verticalArrangement = Arrangement.Center |\r\n| counterAxisAlignItems: CENTER | horizontalAlignment = Alignment.CenterHorizontally |\r\n| layoutGrow: 1 | Modifier.weight(1f) |\r\n| primaryAxisSizingMode: FIXED | Modifier.height/width(X.dp) |\r\n| counterAxisSizingMode: AUTO | wrapContentWidth/Height |\r\n\r\n## Size Conversion\r\n\r\n- Figma px → Compose .dp (1:1)\r\n- Figma font px → Compose .sp (1:1)\r\n\r\n## Shadow Mapping\r\n\r\n```kotlin\r\n// Elevation shadow\r\nCard(elevation = CardDefaults.cardElevation(defaultElevation = 4.dp))\r\n\r\n// Custom shadow (Compose 1.6+)\r\nModifier.shadow(\r\n    elevation = 4.dp,\r\n    shape = RoundedCornerShape(12.dp),\r\n    ambientColor = Color(0x1A000000),\r\n    spotColor = Color(0x33000000)\r\n)\r\n```\r\n\r\n## Gradient Mapping\r\n\r\n```kotlin\r\n// Linear gradient\r\nModifier.background(\r\n    Brush.linearGradient(\r\n        colors = listOf(Color(0xFFFF6B6B), Color(0xFF4ECDC4)),\r\n        start = Offset(0f, 0f),\r\n        end = Offset(0f, Float.POSITIVE_INFINITY)\r\n    )\r\n)\r\n```\r\n\r\n## Per-corner Radius\r\n\r\n```kotlin\r\nRoundedCornerShape(\r\n    topStart = 12.dp,\r\n    topEnd = 12.dp,\r\n    bottomEnd = 0.dp,\r\n    bottomStart = 0.dp\r\n)\r\n```\r\n\r\n## Page Architecture Patterns\r\n\r\nThese patterns reflect how Android apps are **actually structured** in production with Compose.\r\n\r\n### Multi-Tab Pages\r\nWhen a design shows **multiple tabs** (≥2 text labels acting as navigation):\r\n- Use `TabRow` + `HorizontalPager` (accompanist or foundation) for top tabs\r\n- Use `NavigationBar` for bottom navigation\r\n- Do NOT use plain `Text` composables for tabs — they lack selection state, indicators, and swipe support\r\n- Each tab's content should be a separate `@Composable` function\r\n\r\n```kotlin\r\n@Composable\r\nfun TabScreen() {\r\n    val pagerState = rememberPagerState(pageCount = { 3 })\r\n    val scope = rememberCoroutineScope()\r\n    val tabs = listOf(\"关注\", \"推荐\", \"热榜\")\r\n\r\n    Column {\r\n        TabRow(\r\n            selectedTabIndex = pagerState.currentPage,\r\n            containerColor = Color.White,\r\n            contentColor = Color(0xFF0F0F0F),\r\n            indicator = { tabPositions ->\r\n                TabRowDefaults.SecondaryIndicator(\r\n                    modifier = Modifier.tabIndicatorOffset(tabPositions[pagerState.currentPage]),\r\n                    color = Color(0xFF0F0F0F)\r\n                )\r\n            }\r\n        ) {\r\n            tabs.forEachIndexed { index, title ->\r\n                Tab(\r\n                    selected = pagerState.currentPage == index,\r\n                    onClick = { scope.launch { pagerState.animateScrollToPage(index) } },\r\n                    text = {\r\n                        Text(\r\n                            title,\r\n                            fontWeight = if (pagerState.currentPage == index) FontWeight.Bold else FontWeight.Normal,\r\n                            color = if (pagerState.currentPage == index) Color(0xFF0F0F0F) else Color(0xFF858A99)\r\n                        )\r\n                    }\r\n                )\r\n            }\r\n        }\r\n        HorizontalPager(state = pagerState) { page ->\r\n            when (page) {\r\n                0 -> FollowingScreen()\r\n                1 -> RecommendScreen()\r\n                2 -> HotListScreen()\r\n            }\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n### Navigation Bar — Treat as a Single Unit\r\nThe navigation bar (back button + title, possibly + right action) is **one logical container**.\r\n- Use `TopAppBar` / `CenterAlignedTopAppBar` for standard Compose nav bars\r\n- For custom nav bars, use `Row` as a single container, constrain content below it\r\n\r\n```kotlin\r\n@Composable\r\nfun CustomNavBar(onBack: () -> Unit, title: String) {\r\n    Row(\r\n        modifier = Modifier\r\n            .fillMaxWidth()\r\n            .padding(horizontal = 20.dp, vertical = 8.dp),\r\n        verticalAlignment = Alignment.CenterVertically\r\n    ) {\r\n        IconButton(\r\n            onClick = onBack,\r\n            modifier = Modifier\r\n                .size(32.dp)\r\n                .background(Color.Black, CircleShape)\r\n        ) {\r\n            Icon(\r\n                imageVector = Icons.AutoMirrored.Filled.ArrowBack,\r\n                contentDescription = \"返回\",\r\n                tint = Color.White,\r\n                modifier = Modifier.size(18.dp)\r\n            )\r\n        }\r\n        Spacer(Modifier.weight(1f))\r\n        Text(title, fontSize = 17.sp, fontWeight = FontWeight.Bold)\r\n        Spacer(Modifier.weight(1f))\r\n        Spacer(Modifier.size(32.dp)) // Balance spacer\r\n    }\r\n}\r\n```\r\n\r\n### Buttons with Icon + Text\r\nPrefer `Row` inside `Button` or a clickable `Row` for reliable icon+text buttons:\r\n\r\n```kotlin\r\n// Outlined button with icon\r\nOutlinedButton(\r\n    onClick = {},\r\n    shape = RoundedCornerShape(12.dp),\r\n    border = BorderStroke(1.dp, Color(0xFFDCDCDC)),\r\n    modifier = Modifier.fillMaxWidth().height(40.dp)\r\n) {\r\n    Icon(painter = painterResource(R.drawable.ic_video), contentDescription = null, modifier = Modifier.size(20.dp))\r\n    Spacer(Modifier.width(6.dp))\r\n    Text(\"查看视频\", fontSize = 15.sp, fontWeight = FontWeight.Bold, color = Color(0xFF0F0F0F))\r\n}\r\n\r\n// Solid filled button\r\nButton(\r\n    onClick = {},\r\n    shape = RoundedCornerShape(12.dp),\r\n    colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF0158FF)),\r\n    modifier = Modifier.fillMaxWidth().height(40.dp)\r\n) {\r\n    Text(\"查看报告\", fontSize = 15.sp, fontWeight = FontWeight.Bold)\r\n}\r\n```\r\n\r\n### Switch / Toggle\r\n- Use `Switch` from Material3 — standard, reliable\r\n- Custom colors via `SwitchDefaults.colors()`\r\n\r\n```kotlin\r\nvar checked by remember { mutableStateOf(true) }\r\nSwitch(\r\n    checked = checked,\r\n    onCheckedChange = { checked = it },\r\n    colors = SwitchDefaults.colors(checkedTrackColor = Color(0xFF0158FF))\r\n)\r\n```\r\n\r\n### Input Fields vs Display Fields\r\nFigma cannot distinguish between `TextField` and `Text` — both appear as RECTANGLE + TEXT.\r\n- **Placeholder-like text** with input styling → `TextField` / `OutlinedTextField`\r\n- **Static display text** → `Text`\r\n- **ASK the user if unsure**\r\n\r\n```kotlin\r\nvar text by remember { mutableStateOf(\"\") }\r\nTextField(\r\n    value = text,\r\n    onValueChange = { text = it },\r\n    placeholder = { Text(\"请输入昵称\", color = Color(0xFFB8B8B8)) },\r\n    modifier = Modifier.width(295.dp).height(48.dp),\r\n    shape = RoundedCornerShape(8.dp),\r\n    colors = TextFieldDefaults.colors(\r\n        unfocusedContainerColor = Color.White,\r\n        focusedContainerColor = Color.White,\r\n        unfocusedIndicatorColor = Color.Transparent,\r\n        focusedIndicatorColor = Color.Transparent\r\n    ),\r\n    textStyle = TextStyle(fontSize = 15.sp, color = Color(0xFF0F0F0F)),\r\n    singleLine = true\r\n)\r\n```\r\n\r\n## Width Strategy — Fixed vs Flexible\r\n\r\nFigma designs are typically based on a 375px canvas. Width values in Figma are **calculated results**, not design intent. Reverse-engineer the intent to decide Compose properties.\r\n\r\nCore question: **Is this element a \"fixed width\" or \"fill remaining space\"?**\r\n\r\n### Rule 1: Single Element Fills Screen Width\r\nElement width + left/right offset ≈ screen width (375), and left/right margins are symmetric or near-symmetric → `Modifier.fillMaxWidth()` + `padding(horizontal = X.dp)`\r\n\r\n- Example: width 335 + left 20 + right 20 = 375 → `Modifier.fillMaxWidth().padding(horizontal = 20.dp)`\r\n- **Heuristic**: width >85% of screen and symmetric left/right margins → prefer `fillMaxWidth()` + padding\r\n- Adapts automatically to different screen widths\r\n\r\n### Rule 2: Side-by-Side Elements — Identify the Flexible Side\r\nWhen multiple elements are arranged horizontally, determine each element as **fixed** or **flexible**:\r\n\r\n- **Fixed side**: elements with clear visual dimensions — avatars, icons, buttons, fixed-width labels. Use fixed `dp` values.\r\n- **Flexible side**: width = screen width - fixed widths - gaps. Usually text, descriptions, content areas. Use `Modifier.weight(1f)` to fill remaining space.\r\n\r\nValidation: `fixed_width + flexible_width + all_gaps ≈ screen_width`\r\n\r\n```kotlin\r\n// Example: Avatar (56dp) + gap (16dp) + Text (flexible) + margin-right (20dp) + margin-left (20dp) = 375\r\nRow(\r\n    modifier = Modifier\r\n        .fillMaxWidth()\r\n        .padding(horizontal = 20.dp),\r\n    horizontalArrangement = Arrangement.spacedBy(16.dp),\r\n    verticalAlignment = Alignment.CenterVertically\r\n) {\r\n    Image(\r\n        painter = painterResource(R.drawable.ic_avatar),\r\n        contentDescription = \"Avatar\",\r\n        modifier = Modifier\r\n            .size(56.dp)\r\n            .clip(CircleShape)\r\n    )\r\n    Text(\r\n        text = \"User Name\",\r\n        modifier = Modifier.weight(1f),  // Flexible side\r\n        fontSize = 15.sp\r\n    )\r\n}\r\n```\r\n\r\n### Rule 3: Fixed Width + Center\r\nElement is visibly narrower than screen and centered, not edge-aligned → fixed width + center alignment in parent\r\n\r\n- Example: width 295 centered in 375 → `Modifier.width(295.dp)` + parent `horizontalAlignment = Alignment.CenterHorizontally`\r\n- Common for input fields, centered cards\r\n\r\n### LazyColumn Item Width\r\nItems in `LazyColumn` always use `Modifier.fillMaxWidth()`. The LazyColumn's own width is determined by parent constraints.\r\n\r\n```kotlin\r\nLazyColumn(\r\n    modifier = Modifier.fillMaxWidth()\r\n) {\r\n    items(count = 20, key = { it }) { index ->\r\n        Text(\r\n            text = \"Item $index\",\r\n            modifier = Modifier\r\n                .fillMaxWidth()\r\n                .padding(16.dp)\r\n        )\r\n    }\r\n}\r\n```\r\n\r\n## Multi-State Views\r\n\r\nCompose uses **state** to drive conditional UI changes. Each state change affects Modifier chains and child rendering.\r\n\r\n### Selected / Unselected State\r\nUse `remember { mutableStateOf() }` with conditional Modifiers:\r\n\r\n```kotlin\r\nvar isSelected by remember { mutableStateOf(false) }\r\n\r\nCard(\r\n    modifier = Modifier\r\n        .fillMaxWidth()\r\n        .height(120.dp)\r\n        .background(\r\n            color = if (isSelected) Color(0xFF0158FF) else Color.White,\r\n            shape = RoundedCornerShape(12.dp)\r\n        )\r\n        .border(\r\n            width = 2.dp,\r\n            color = if (isSelected) Color(0xFF0158FF) else Color(0xFFDCDCDC),\r\n            shape = RoundedCornerShape(12.dp)\r\n        )\r\n        .clickable { isSelected = !isSelected },\r\n    colors = CardDefaults.cardColors(containerColor = Color.Transparent),\r\n    shape = RoundedCornerShape(12.dp)\r\n) {\r\n    Box(\r\n        modifier = Modifier.fillMaxSize(),\r\n        contentAlignment = Alignment.Center\r\n    ) {\r\n        Text(\r\n            text = if (isSelected) \"✓ Selected\" else \"选择\",\r\n            color = if (isSelected) Color.White else Color(0xFF858A99),\r\n            fontSize = 16.sp,\r\n            fontWeight = FontWeight.Bold\r\n        )\r\n    }\r\n}\r\n```\r\n\r\nExample: Gender selection card\r\n```kotlin\r\n@Composable\r\nfun GenderSelector() {\r\n    var selectedGender by remember { mutableStateOf<String?>(null) }\r\n    \r\n    Row(\r\n        modifier = Modifier\r\n            .fillMaxWidth()\r\n            .padding(16.dp),\r\n        horizontalArrangement = Arrangement.spacedBy(12.dp)\r\n    ) {\r\n        listOf(\"Male\" to \"男\", \"Female\" to \"女\").forEach { (key, label) ->\r\n            Card(\r\n                modifier = Modifier\r\n                    .weight(1f)\r\n                    .height(80.dp)\r\n                    .background(\r\n                        color = if (selectedGender == key) Color(0xFF0158FF) else Color.White,\r\n                        shape = RoundedCornerShape(12.dp)\r\n                    )\r\n                    .border(\r\n                        width = 1.dp,\r\n                        color = if (selectedGender == key) Color(0xFF0158FF) else Color(0xFFDCDCDC),\r\n                        shape = RoundedCornerShape(12.dp)\r\n                    )\r\n                    .clickable { selectedGender = key },\r\n                colors = CardDefaults.cardColors(containerColor = Color.Transparent)\r\n            ) {\r\n                Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {\r\n                    Text(\r\n                        text = label,\r\n                        color = if (selectedGender == key) Color.White else Color(0xFF0F0F0F),\r\n                        fontWeight = FontWeight.Bold\r\n                    )\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n### Disabled / Enabled State via Alpha\r\nUse `Modifier.alpha()` combined with `.clickable(enabled = boolean)`:\r\n\r\n```kotlin\r\nvar isValid by remember { mutableStateOf(false) }\r\n\r\nButton(\r\n    onClick = { /* handle */ },\r\n    enabled = isValid,\r\n    modifier = Modifier\r\n        .fillMaxWidth()\r\n        .height(40.dp)\r\n        .alpha(if (isValid) 1f else 0.3f),\r\n    colors = ButtonDefaults.buttonColors(\r\n        containerColor = Color(0xFF0158FF),\r\n        disabledContainerColor = Color(0xFF0158FF)\r\n    )\r\n) {\r\n    Text(\"提交\", color = Color.White, fontWeight = FontWeight.Bold)\r\n}\r\n```\r\n\r\n### Stacked / Overlapping Cards\r\nWhen the design shows **stacked or overlapping cards** with interaction (swipe, drag, peek), **do NOT generate static nested layouts**. Instead:\r\n- Ask the user about the intended interaction: swipe between cards (→ `HorizontalPager`), drag gestures (→ custom `Modifier.pointerInput()`), or just visual overlap?\r\n- Provide a template based on the answer\r\n- For now, suggest the most likely pattern based on context\r\n\r\n```kotlin\r\n// If swipeable between cards: use HorizontalPager\r\nval pagerState = rememberPagerState(pageCount = { 3 })\r\nHorizontalPager(\r\n    state = pagerState,\r\n    modifier = Modifier\r\n        .fillMaxWidth()\r\n        .height(300.dp)\r\n) { page ->\r\n    Card(\r\n        modifier = Modifier\r\n            .fillMaxWidth()\r\n            .padding(16.dp),\r\n        elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)\r\n    ) {\r\n        Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {\r\n            Text(\"Card ${page + 1}\")\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n## Dark Mode Patterns\r\n\r\nCompose integrates dark mode through `MaterialTheme` and system state. Choose the approach based on project configuration.\r\n\r\n### Using MaterialTheme Colors (Recommended)\r\nWhen the project has a `Theme.kt` file, all colors automatically follow the theme:\r\n\r\n```kotlin\r\n@Composable\r\nfun MyCard() {\r\n    Card(\r\n        modifier = Modifier\r\n            .fillMaxWidth()\r\n            .padding(16.dp),\r\n        colors = CardDefaults.cardColors(\r\n            containerColor = MaterialTheme.colorScheme.surface,\r\n            contentColor = MaterialTheme.colorScheme.onSurface\r\n        )\r\n    ) {\r\n        Text(\r\n            text = \"Content\",\r\n            color = MaterialTheme.colorScheme.primary,\r\n            style = MaterialTheme.typography.bodyMedium\r\n        )\r\n    }\r\n}\r\n```\r\n\r\n### Manual Dark Mode Detection\r\nUse `isSystemInDarkTheme()` when you need to apply specific logic:\r\n\r\n```kotlin\r\nval isDark = isSystemInDarkTheme()\r\nval backgroundColor = if (isDark) Color(0xFF1A1A1A) else Color.White\r\nval textColor = if (isDark) Color.White else Color(0xFF0F0F0F)\r\n\r\nBox(\r\n    modifier = Modifier\r\n        .fillMaxSize()\r\n        .background(backgroundColor),\r\n    contentAlignment = Alignment.Center\r\n) {\r\n    Text(\"Dark mode aware\", color = textColor)\r\n}\r\n```\r\n\r\n### Dynamic Colors (Android 12+)\r\nAndroid 12+ supports Material You dynamic colors based on system theme:\r\n\r\n```kotlin\r\n@Composable\r\nfun DynamicThemeExample() {\r\n    val colorScheme = when {\r\n        isSystemInDarkTheme() -> dynamicDarkColorScheme(LocalContext.current)\r\n        else -> dynamicLightColorScheme(LocalContext.current)\r\n    }\r\n    \r\n    MaterialTheme(colorScheme = colorScheme) {\r\n        // App content\r\n    }\r\n}\r\n```\r\n\r\n### Rules\r\n- If the project already has a `Theme.kt` with `colorScheme` defined, **always use `MaterialTheme.colorScheme.*`** for colors\r\n- If the project has **no dark mode support** configured, **do not add it** — output light-mode-only code\r\n- Never hardcode colors when `MaterialTheme` is available\r\n\r\n## LazyColumn / LazyRow Item Patterns\r\n\r\n### Basic Item Sizing\r\n- Items should use `Modifier.fillMaxWidth()` by default\r\n- LazyColumn/LazyRow width is set on the composable itself, items fill that width\r\n\r\n### Using `key` for Performance\r\nWhen list content can change, add `key` to prevent recomposition issues:\r\n\r\n```kotlin\r\nLazyColumn(\r\n    modifier = Modifier.fillMaxSize()\r\n) {\r\n    items(\r\n        count = items.size,\r\n        key = { index -> items[index].id },  // Stable identifier\r\n        contentType = { \"item\" }\r\n    ) { index ->\r\n        ListItem(\r\n            modifier = Modifier.fillMaxWidth(),\r\n            item = items[index]\r\n        )\r\n    }\r\n}\r\n```\r\n\r\n### Mixed Content Types with `contentType`\r\nWhen a list mixes different item types (headers, regular items, footers), use `contentType` to help Compose recycle properly:\r\n\r\n```kotlin\r\ndata class ListSection(\r\n    val type: String,  // \"header\", \"item\", \"footer\"\r\n    val content: Any\r\n)\r\n\r\nLazyColumn {\r\n    items(\r\n        count = sections.size,\r\n        key = { index -> sections[index].content.hashCode() },\r\n        contentType = { index -> sections[index].type }\r\n    ) { index ->\r\n        val section = sections[index]\r\n        when (section.type) {\r\n            \"header\" -> HeaderItem(section.content as String)\r\n            \"item\" -> RegularItem(section.content as Item)\r\n            \"footer\" -> FooterItem(section.content as String)\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n### Example: List with Header + Items\r\n```kotlin\r\n@Composable\r\nfun UserListWithHeader(users: List<User>) {\r\n    LazyColumn(\r\n        modifier = Modifier.fillMaxSize()\r\n    ) {\r\n        item {\r\n            Text(\r\n                text = \"用户列表\",\r\n                modifier = Modifier\r\n                    .fillMaxWidth()\r\n                    .padding(16.dp),\r\n                fontSize = 18.sp,\r\n                fontWeight = FontWeight.Bold,\r\n                color = MaterialTheme.colorScheme.primary\r\n            )\r\n        }\r\n        items(\r\n            count = users.size,\r\n            key = { index -> users[index].id }\r\n        ) { index ->\r\n            Row(\r\n                modifier = Modifier\r\n                    .fillMaxWidth()\r\n                    .padding(vertical = 8.dp, horizontal = 16.dp),\r\n                verticalAlignment = Alignment.CenterVertically,\r\n                horizontalArrangement = Arrangement.spacedBy(12.dp)\r\n            ) {\r\n                Image(\r\n                    painter = painterResource(R.drawable.ic_avatar),\r\n                    contentDescription = \"Avatar\",\r\n                    modifier = Modifier\r\n                        .size(48.dp)\r\n                        .clip(CircleShape)\r\n                )\r\n                Text(\r\n                    text = users[index].name,\r\n                    modifier = Modifier.weight(1f),\r\n                    fontSize = 15.sp\r\n                )\r\n                Text(\r\n                    text = users[index].status,\r\n                    fontSize = 13.sp,\r\n                    color = Color(0xFF858A99)\r\n                )\r\n            }\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n## Divider Pattern\r\n\r\nCompose Material3 provides a native `HorizontalDivider` composable. Use it for clean line separators.\r\n\r\n### Basic Divider\r\n```kotlin\r\nHorizontalDivider(\r\n    thickness = 1.dp,\r\n    color = Color(0xFFEEEEEE)\r\n)\r\n```\r\n\r\n### Divider with Custom Spacing\r\n```kotlin\r\nColumn {\r\n    Text(\"Item 1\")\r\n    HorizontalDivider(\r\n        modifier = Modifier\r\n            .fillMaxWidth()\r\n            .padding(vertical = 16.dp),\r\n        thickness = 1.dp,\r\n        color = Color(0xFFDCDCDC)\r\n    )\r\n    Text(\"Item 2\")\r\n}\r\n```\r\n\r\n### Divider in List Items\r\nPlace dividers between list items for visual separation:\r\n\r\n```kotlin\r\nLazyColumn {\r\n    items(items.size) { index ->\r\n        ListItemContent(items[index])\r\n        if (index < items.size - 1) {\r\n            HorizontalDivider(\r\n                thickness = 1.dp,\r\n                color = Color(0xFFEEEEEE),\r\n                modifier = Modifier.padding(horizontal = 16.dp)\r\n            )\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n## Figma Node Interpretation (Compose-specific notes)\r\n\r\n### Container + Icon = Single Image Composable\r\nWhen Figma shows a **FRAME** (with background color + cornerRadius) containing a single **INSTANCE** or **VECTOR** child that is clearly an icon:\r\n- This is one `Image` composable in code, not nested layouts\r\n- `.background()` modifier = the container shape (circle, rounded rect, etc.)\r\n- `painter` parameter = the icon drawable\r\n- Typical signal: outer FRAME has cornerRadius ≥ 50% of size (circular), inner child is much smaller\r\n\r\n```kotlin\r\n// Figma: FRAME(32×32, #000000, cornerRadius=16) → INSTANCE(18×18) = one Image\r\nImage(\r\n    painter = painterResource(R.drawable.ic_arrow),\r\n    contentDescription = \"Arrow\",\r\n    modifier = Modifier\r\n        .size(32.dp)\r\n        .background(Color.Black, RoundedCornerShape(16.dp))\r\n        .padding(7.dp),  // Center the 18dp icon in 32dp container\r\n    colorFilter = ColorFilter.tint(Color.White)\r\n)\r\n```\r\n\r\n### RECTANGLE as Background\r\nWhen a GROUP's **first child** is a RECTANGLE with the **same dimensions** as the GROUP:\r\n- The RECTANGLE is a background shape, not an independent composable\r\n- Map it to `.background()` modifier on the parent container\r\n- Signal: RECTANGLE is first child, width≈GROUP width, height≈GROUP height\r\n\r\n```kotlin\r\n// Figma: GROUP(150×60) → RECTANGLE(150×60, #F3F3F4, cornerRadius=8) → TEXT\r\nBox(\r\n    modifier = Modifier\r\n        .width(150.dp)\r\n        .height(60.dp)\r\n        .background(Color(0xFFF3F3F4), RoundedCornerShape(8.dp)),\r\n    contentAlignment = Alignment.Center\r\n) {\r\n    Text(\"Button\")\r\n}\r\n```\r\n\r\n### FRAME with layoutMode vs GROUP without layoutMode\r\n- **FRAME with `layoutMode`**: has Auto-layout → map to `Column` or `Row`\r\n- **GROUP without `layoutMode`**: no Auto-layout → children positioned by coordinates → use `Box` with explicit `Modifier.offset()` or nested `Box` layouts\r\n\r\n```kotlin\r\n// FRAME with layoutMode: VERTICAL → Column\r\nColumn(\r\n    modifier = Modifier\r\n        .fillMaxWidth()\r\n        .padding(16.dp),\r\n    verticalArrangement = Arrangement.spacedBy(12.dp)\r\n) {\r\n    Text(\"Item 1\")\r\n    Text(\"Item 2\")\r\n}\r\n\r\n// GROUP without layoutMode → use Box with offset\r\nBox(\r\n    modifier = Modifier\r\n        .width(200.dp)\r\n        .height(200.dp)\r\n) {\r\n    Text(\r\n        \"Top-left text\",\r\n        modifier = Modifier.offset(x = 10.dp, y = 20.dp)\r\n    )\r\n    Image(\r\n        painter = painterResource(R.drawable.ic_icon),\r\n        contentDescription = null,\r\n        modifier = Modifier\r\n            .size(48.dp)\r\n            .offset(x = 100.dp, y = 100.dp)\r\n    )\r\n}\r\n```\r\n\r\n### Numeric Precision\r\nFigma values often have excessive decimal places. Round appropriately for Compose:\r\n- **`dp` values** (layout, padding, margins): round to **nearest integer**\r\n  - Example: 127.86dp → 128.dp, 7.63dp → 8.dp\r\n- **`sp` values** (font sizes): round to **nearest 0.5**\r\n  - Example: 15.27sp → 15.5sp, 14.99sp → 15sp\r\n- **Exception**: If the exact value maps to a standard size, snap to it\r\n  - Example: 47.99dp → 48.dp (standard Material touch target)\r\n```\n\nFile v2.3.0:references/error-handling.md\n\n# Error Handling\r\n\r\n## FIGMA_TOKEN not set (script outputs `FIGMA_TOKEN_NOT_SET`)\r\n\r\nTell the user:\r\n\r\n> I need a Figma Personal Access Token to fetch the design.\r\n> ⚠️ **Do not paste it into this chat** — chat messages may be logged.\r\n> Set it as an environment variable and restart.\r\n> Get one at: Figma → avatar → Settings → Security → Personal Access Tokens\r\n>\r\n>   Windows: `setx FIGMA_TOKEN \"figd_xxx\"`\r\n>   macOS/Linux: add `export FIGMA_TOKEN=\"figd_xxx\"` to ~/.zshrc\r\n\r\n## FIGMA_TOKEN invalid (API returns 403/401)\r\n\r\nTell the user the token may have expired or been revoked. Direct them to\r\nregenerate from Figma Settings → Security → Personal Access Tokens.\r\n\r\n## Invalid URL\r\n\r\nShow valid URL example: `https://www.figma.com/design/<fileKey>/<name>?node-id=<id>`\r\n\r\n## API error\r\n\r\nShow the error message, suggest checking network/proxy.\r\n\r\n## Node too large (>200 children)\r\n\r\nSuggest selecting a smaller frame.\r\n\r\n## Depth auto-increased\r\n\r\nThe script auto-retries with deeper depth if it detects truncated children.\r\nInform the user if this happens (\"I needed to fetch deeper to get all details\").\n\nFile v2.3.0:references/feedback-log.md\n\n# Feedback Log Format\r\n\r\n**⚠️ Every time the user corrects your output (layout issue, wrong component,\r\nspacing problem, etc.), you MUST log it to `feedback-log.md` before proceeding\r\nwith the fix (after user consent). Do not skip this step — the log is how the\r\nskill learns and improves over time.**\r\n\r\nWhenever the user corrects your generated output (with consent), log the\r\ncorrection to `feedback-log.md` in the project root (create if it doesn't\r\nexist). Each entry follows this format:\r\n\r\n```\r\n## YYYY-MM-DD HH:MM\r\n- **Platform**: Android XML / Compose / SwiftUI / UIKit\r\n- **Figma node type**: (e.g., FRAME with icon, Tab bar, Button group)\r\n- **Issue**: Brief description of what was wrong\r\n- **Before**: What the agent generated (snippet or description)\r\n- **After**: What the user wanted (snippet or description)\r\n- **Rule candidate**: (optional) If this correction suggests a general pattern rule, note it here\r\n```\r\n\r\nLog entries should be:\r\n\r\n- **Concise** — only the relevant diff, not entire files\r\n- **Categorized** — always include platform and Figma node type for later analysis\r\n- **Actionable** — focus on the mapping error, not cosmetic preferences (e.g., \"user prefers 16dp\" is not a rule; \"VECTOR compositions should be single ImageView\" is)\r\n\r\nDo NOT log:\r\n\r\n- One-off personal preferences (specific color choices, naming conventions)\r\n- Corrections to non-mapping issues (typos, import statements)\r\n- Feedback the user explicitly says is project-specific, not general\r\n\r\nPeriodically (or when asked), run `scripts/feedback_analyze.py` to identify\r\npatterns and generate rule candidates.\n\nFile v2.3.0:references/figma-interpretation.md\n\n# Figma Node Interpretation Rules\r\n> Referenced by SKILL.md Step 1. Read this file when analyzing Figma fetch results.\r\n\r\n## Style References and Text Sizing\r\n\r\n- Note `styleRefs` on nodes — these reference Figma shared styles (design tokens). Nodes sharing the same style ID should use the same code-level token/resource\r\n- Note `textAutoResize` on TEXT nodes — `WIDTH_AND_HEIGHT` means auto-size (wrap_content), `HEIGHT` means fixed width + auto height, absent/NONE means fixed size\r\n\r\n## Figma Node Interpretation (apply before generating any platform code)\r\n\r\n- **Skip system chrome**: StatusBar, HomeIndicator, NavigationBar are iOS design placeholders — don't generate code for them. Also skip duplicate nodes at the same position (Figma artifacts)\r\n- **Skip invisible nodes**: VECTOR/RECTANGLE with empty fills and all strokes `visible: false`, or `absoluteRenderBounds: null` — these are leftover design artifacts that render nothing\r\n- **layoutAlign=STRETCH**: child fills the cross-axis of its auto-layout parent → `match_parent` on the cross-axis / `.frame(maxWidth: .infinity)`. Only present when it differs from INHERIT\r\n- **layoutPositioning=ABSOLUTE**: child is absolutely positioned within an auto-layout parent → use explicit x/y offset constraints instead of flow layout\r\n- **Container + icon = single view**: A FRAME (with background/cornerRadius) wrapping a small VECTOR/INSTANCE is one ImageView/Image, not nested layouts\r\n- **VECTOR/ELLIPSE compositions = single asset**: Multiple small VECTOR/ELLIPSE siblings inside a FRAME are pieces of one icon — output as a single image reference, not separate views\r\n- **RECTANGLE as background**: When a GROUP's first child is a RECTANGLE matching the GROUP's dimensions, it's a background shape, not a separate view\r\n- **GROUP vs FRAME**: FRAME with `layoutMode` maps to structured layouts (LinearLayout, HStack, etc.); GROUP without `layoutMode` uses absolute positioning — map to ConstraintLayout constraints or explicit offsets\r\n- **Round Figma decimals**: Round dp to nearest integer, sp to nearest 0.5. Snap near-standard values (e.g., 47.99 → 48dp)\r\n- **Width strategy**: Don't blindly copy Figma width values — infer design intent. Elements spanning near-full screen width → `match_parent` + `marginHorizontal`. In side-by-side layouts, identify the \"flexible\" element (text/content) vs \"fixed\" element (icon/avatar) and use `0dp` + constraints for the flexible one. See xml-patterns.md \"Width Strategy\" for full rules.\r\n\r\n## Page Architecture Analysis (all platforms)\r\n\r\nThese rules apply to ALL output platforms (Android XML, Compose, SwiftUI, UIKit). The examples below use Android XML, but the principles are universal — each platform's patterns file has the equivalent implementation.\r\n\r\n- Multiple tab labels → likely `TabLayout` + `ViewPager2`, content in Fragment layouts (strong signal, not absolute — ask if unsure)\r\n- Tab color differences between items → selected/unselected state, use `tabSelectedTextColor` / `tabTextColor`, not hardcoded per-tab colors\r\n- Navigation bar with back/close icon → `ImageView` (src + background), not FrameLayout wrapper\r\n- Buttons with icon + text → **always** use `LinearLayout` + `ImageView` + `TextView`. Never use `MaterialButton` with `app:icon` (rendering issues) or `TextView` with `drawableStart`/`drawableEnd` (icon centering issues)\r\n- List item with left sidebar + right content → observe multiple items to judge if equal-height or independent\r\n- **Stacked/overlapping cards** with similar structure (same shape, offset position) → likely a card-switching interaction (swipe, stack, flip). Do NOT generate as separate static Views. Instead, ask the user: \"These cards appear stacked — is this a swipe/switch interaction? If so, what's the switching behavior (left-right swipe, tap to flip, auto-play)?\" The implementation (custom View, ViewPager2, third-party CardStackView, etc.) depends on the answer.\r\n\r\n### Component Variants → Multi-State Code\r\n\r\nWhen INSTANCE nodes have `variantProperties`, they represent different states of the same component.\r\n\r\n**How to use:**\r\n1. If the design contains multiple INSTANCE nodes with the same `componentId` but different `variantProperties` (e.g. State=Default, State=Pressed, State=Disabled), these are states of one component\r\n2. Generate **one** view/composable with state handling, not multiple separate views\r\n3. Map common variant properties to platform state mechanisms:\r\n\r\n| Variant Property | Android XML | Compose | iOS |\r\n|---|---|---|---|\r\n| State=Default/Pressed/Disabled | `selector` drawable + `state_pressed`/`state_enabled` | `Modifier.clickable` + conditional styling | `UIControl` states / `.disabled()` |\r\n| State=Selected/Unselected | `state_selected` + `duplicateParentState` | `var isSelected by remember` + conditional | `isSelected` property |\r\n| State=Active/Inactive | `state_activated` | boolean state + conditional | custom state |\r\n| Size=Small/Medium/Large | Different dimension values in same layout | Parameterized composable with size enum | Parameterized view |\r\n| Type=Primary/Secondary/Outline | Different style resources | Different color/border params | Different configuration |\r\n\r\n4. If only **one state** is present in the design (e.g. only State=Default), still note the variant property — mention to the user that other states exist and ask if they want state handling generated\r\n5. Size and Type variants → generate a parameterized component with enum/sealed class, not hardcoded values\r\n\r\n**Example — Button with State variants:**\r\nIf Figma shows a button with variantProperties `{\"State\": \"Default\"}`, and you know (from componentId) this component also has Pressed and Disabled states:\r\n- Android XML: generate the default state layout + mention selector drawable needed for other states\r\n- Compose: generate a composable with `enabled` parameter + conditional colors/alpha\r\n\r\n### Figma Style References → Consistent Token Usage\r\n\r\nWhen nodes have `styleRefs`, they reference Figma's shared styles (design tokens).\r\n\r\n**How to use:**\r\n1. Nodes sharing the **same style ID** (e.g. same `styleRefs.fill` value) should use the **same code-level resource/token**, even if the actual hex values happen to be identical\r\n2. This helps identify the designer's semantic intent — two texts with the same `styleRefs.text` ID are meant to use the same text style, even if their fontSize happens to differ slightly (designer oversight)\r\n3. When project scan is available: try to match Figma style semantic names to project resources. For example, if multiple nodes share a fill style and the color is `#0158FF` → likely the project's `@color/primary` or `colorScheme.primary`\r\n4. When generating code, **group by styleRef first, then by value**:\r\n   - Same styleRef → must use same resource reference\r\n   - Same value but different styleRef → can use same resource, but note the discrepancy\r\n   - Different value and different styleRef → different resources\r\n\r\n**Practical application:**\r\n- You don't need to resolve the Figma style ID to a name (that requires an extra API call)\r\n- Just use it as a **grouping key**: \"these 5 text nodes all share styleRef.text = S:def456, so they should all use the same text style in code\"\r\n- If you notice inconsistency (same styleRef but different rendered values), flag it to the user as a potential design inconsistency\r\n\r\n## Multi-State Batch Compare\r\n\r\nWhen the user provides multiple Figma frames representing different states of the same page:\r\n\r\n1. Use `figma_fetch.py --compare` to get all states + diff\r\n2. The diff output shows exactly what changes between states (color, text, opacity, visibility, etc.)\r\n3. Use diff results to generate appropriate state handling:\r\n   - Color changes → selector drawable / conditional color\r\n   - Opacity changes → alpha animation / enabled state\r\n   - Visibility changes → View.GONE / if-else block\r\n   - Text changes → dynamic text binding\r\n4. Output a state change summary table after the code\r\n\r\n**Usage:**\r\n```bash\r\n# Two separate Figma URLs (different frames)\r\npython figma_fetch.py \"<url1>\" \"<url2>\" --compare\r\n\r\n# Multiple node-ids from the same file\r\npython figma_fetch.py \"<base_url>\" --nodes \"100:200,100:300\" --compare\r\n```\r\n\r\nThe output JSON contains a `nodes` array (one entry per state with its full simplified tree)\r\nand a `diff` object with `changed`, `added`, and `removed` lists.\r\nBase is always the first node; each subsequent node is diffed against it.\r\n`label` is taken from the Figma node name (e.g. the frame name), or falls back to \"State 1\", \"State 2\".\n\nFile v2.3.0:references/generation-rules.md\n\n# Code Generation Rules\r\n> Referenced by SKILL.md Step 3. Read this file before generating code.\r\n\r\n## Output Rules (absolute — never break these)\r\n\r\n**Write boundary — generated code stays in conversation until confirmed:**\r\n- Output all generated code in the conversation first, as text with filename headers (e.g. `📄 activity_main.xml`).\r\n- Only write files to disk after the user sees the output, confirms it's correct, and explicitly specifies where to put them.\r\n- When writing files, always ask: \"Where should I write this?\" Wait for the user to specify a path.\r\n- Never write files into an existing project module without explicit user instruction to do so.\r\n\r\n**Project scan availability check:**\r\n- If a project scan report is available (Step 2.5 was run): match Figma values against project resources first, then hardcode unmatched values.\r\n- If NO project scan was run: hardcode ALL values directly (hex colors, literal strings, dp dimensions). Do not use `@color/placeholder` or `@string/placeholder` — write real values for instant visual preview.\r\n\r\n- **Colors**: Before hardcoding, search `res/values/colors.xml` (and `res/values/colors_*.xml` if present) for a matching hex value. If found, use the resource reference (e.g. `@color/primary`). If not found, write hex directly (`android:textColor=\"#0F0F0F\"` / `Color(0xFF0F0F0F)`).\r\n- **Strings**: Before hardcoding, search `res/values/strings.xml` for matching text content. If found, use the resource reference (e.g. `@string/notification_settings`). If not found, write text directly (`android:text=\"通知设置\"`).\r\n- **Dimensions**: write values directly (`android:textSize=\"17sp\"`). Dimension resources are rarely worth matching.\r\n- **Lists**: output main layout + separate item layout file. Do NOT generate Adapter/ViewHolder.\r\n- **Resource matching priority**: Use project-defined `@color/` and `@string/` when an exact match exists. Hardcode everything else for instant preview. Never create new resource definitions — only reference existing ones.\r\n\r\n## Drawable Resources — Generate, Don't Placeholder\r\n\r\n- **Shape drawables** (backgrounds, outlines, tracks): Generate the actual XML shape drawable code based on Figma data (color, cornerRadius, stroke, gradient). Output each as a separate file with `📄 drawable/filename.xml` header.\r\n- **Icons/vectors**: Use `figma_fetch.py --export-svg <node-ids>` to export SVG from Figma API, then convert to Android Vector Drawable XML. The simplified JSON includes an `\"id\"` field on every node — use these IDs for export. Output each as `📄 drawable/ic_name.xml`.\r\n- **Photos/bitmaps**: These cannot be generated — use `@drawable/placeholder` and note what image is needed.\r\n- **Goal**: The generated code should be copy-pasteable and immediately render a close approximation of the design, not a blank screen with placeholders.\r\n\r\n## Unmatched Resource Suggestions (when project scan is available)\r\n\r\nAfter all code output, if any colors or strings were hardcoded because they didn't match existing project resources, append a **\"Suggested Resources\"** block. This helps the user add them to the project in one go:\r\n\r\n```\r\n📝 Suggested Resources (unmatched — copy to your project if needed)\r\n\r\n// colors.xml\r\n<color name=\"text_primary\">#0F0F0F</color>\r\n<color name=\"bg_card\">#F7F7F7</color>\r\n\r\n// strings.xml\r\n<string name=\"btn_submit\">提交</string>\r\n<string name=\"hint_nickname\">请输入昵称</string>\r\n```\r\n\r\nRules for this block:\r\n- Only include when project scan was used and there are unmatched resources\r\n- Name suggestions should follow the project's existing naming convention (observe scan results for patterns like `color_xxx` vs `xxx_color` vs `colorXxx`)\r\n- Group by resource type (colors first, then strings)\r\n- Skip dimensions — they're rarely worth extracting into resources\r\n- This is a **suggestion**, not auto-creation. The user decides whether to add them\r\n\r\n## Platform Guidelines\r\n\r\nThe agent already knows these — this is a reminder to follow them strictly:\r\n\r\n- **Android XML**: Material Design 3. ConstraintLayout as default for any non-trivial layout. 8dp grid. Min touch target 48dp. MaterialCardView/MaterialSwitch over legacy.\r\n- **Android Compose**: Material3 composables. Modifier chains. LazyColumn for lists. Scaffold for pages.\r\n- **iOS SwiftUI**: Apple HIG. NavigationStack, List, VStack/HStack/ZStack. Safe areas. System fonts.\r\n- **iOS UIKit**: Apple HIG. AutoLayout (NSLayoutConstraint or StackView). UITableView/UICollectionView for lists. Safe areas.\r\n\r\n## Handling Special Visual Properties\r\n\r\n- **Gradients**: generate platform-appropriate gradient code (GradientDrawable / Brush.linearGradient / LinearGradient / CAGradientLayer). If gradient is complex, add a comment noting it may need visual tuning.\r\n- **Shadows**: use platform shadow APIs. Note if the design shadow differs from default elevation shadow.\r\n- **Per-corner radius**: use platform-specific per-corner APIs when radii differ.\r\n\r\n## Platform-Specific Mapping Details\r\n\r\nRead platform-specific mapping details from:\r\n- Android Compose → references/compose-patterns.md\r\n- Android XML → references/xml-patterns.md\r\n- iOS SwiftUI → references/swiftui-patterns.md\r\n- iOS UIKit → references/uikit-patterns.md\n\nFile v2.3.0:references/multi-frame.md\n\n# Multi-frame & Multiple Links Handling\r\n\r\nWhen the user provides 2+ Figma links, determine the relationship by examining\r\nframe names and user context:\r\n\r\n## Same page, different visual states\r\n\r\n(e.g. \"首页-有banner\" and \"首页-无banner\")\r\n\r\nUse `--compare` mode to fetch all and get a diff summary. Generate multi-state\r\ncode (conditional visibility, state switching).\r\n\r\n## Parent page + overlay/drawer\r\n\r\n(e.g. \"首页\" + \"首页-抽屉-xxx\")\r\n\r\nGenerate each as an **independent layout file**. Then tell the user the\r\nrelationship:\r\n\r\n> Frame 1 (\"首页\") and Frame 2 (\"首页-抽屉\") look like a main page + side drawer.\r\n> I've generated two separate layout files. How you wire them together\r\n> (DrawerLayout, Navigation, etc.) depends on your project architecture.\r\n\r\nThe skill's job is generating UI layout code, not deciding architecture\r\n(Activity vs Fragment vs Navigation).\r\n\r\n## Different independent pages\r\n\r\n(e.g. \"首页\" + \"设置页\" + \"个人中心\")\r\n\r\nProcess each independently. Fetch them **one at a time** with a pause between\r\nrequests to avoid rate limiting. Present a summary of all pages, then ask which\r\nto convert first (or convert all sequentially).\r\n\r\n## Relationship unclear\r\n\r\nAsk the user — \"These frames look related but I'm not sure how. Are they\r\ndifferent states of the same page, a page with an overlay, or independent pages?\"\r\n\r\n## Rate limit protection\r\n\r\nWhen fetching multiple nodes, wait 2-3 seconds between requests. Never fire\r\nmore than 2 requests in parallel.\n\nFile v2.3.0:references/scan-usage.md\n\n# Project Scan Usage Guide\r\n> Referenced by SKILL.md Step 2.5. Read this file when a project scan is available.\r\n\r\n## Running the Scan\r\n\r\n```bash\r\npython scripts/project_scan.py /path/to/project --json --output scan-report.json\r\n```\r\n\r\nThe scan auto-detects Android or iOS, and produces a report with:\r\n- All colors, strings, images, custom views in the project\r\n- Lookup indices for fast matching (hex → color resource, text → string resource)\r\n- Semantic labels: which resources map to design system roles (primary, surface, body, etc.)\r\n\r\n## How to Use Scan Results in Code Generation (Step 3)\r\n\r\n### Color matching (with semantic labels)\r\n\r\nThe scan report includes `semantic_labels` — annotations that tell you which\r\nresources play which role in the design system:\r\n\r\n```json\r\n{\r\n  \"semantic_labels\": [\r\n    {\"name\": \"#FF6200EE\", \"resource_type\": \"color\", \"semantic_role\": \"primary\",\r\n     \"source\": \"theme_mapping\", \"confidence\": \"high\"},\r\n    {\"name\": \"#FF1C1B1F\", \"resource_type\": \"color\", \"semantic_role\": \"on_primary\",\r\n     \"source\": \"theme_mapping\", \"confidence\": \"high\"}\r\n  ]\r\n}\r\n```\r\n\r\n**Process:**\r\n1. Extract hex from Figma node → normalize to `#RRGGBB`\r\n2. Look up in `indices.colors` → if hit, use project reference\r\n3. Also check `semantic_labels` for role → if Figma has a color labeled\r\n   \"Primary\" and the project has one, map them even if hex differs\r\n4. For iOS dynamic colors: Figma is light mode. A scanned `light:#2965FF dark:#4D88FF`\r\n   matches Figma `#2965FF`\r\n5. No match → hardcode hex, but comment `// TODO: no matching project color`\r\n\r\n**When to use semantic labels over exact hex match:**\r\n- Figma uses a design system color (primary, secondary, surface) → prefer semantic\r\n  label mapping even if hex values differ slightly\r\n- Project has theme variables (`?attr/colorPrimary`) → use theme reference,\r\n  not the resolved hex\r\n\r\n### String matching\r\n\r\n1. Extract text from Figma TEXT node\r\n2. Look up in `indices.strings` → if hit, use i18n reference\r\n   (Android: `@string/key`, iOS: depends on project i18n format)\r\n3. No match → hardcode text, but comment `// TODO: not in i18n`\r\n\r\n### Text style matching\r\n\r\n1. Extract fontSize + fontWeight from Figma TEXT node\r\n2. Build lookup key: \"{fontSize}sp_{weight}\" (e.g. \"16sp_bold\")\r\n3. Look up in `indices.text_styles` → if hit, use style reference\r\n   (Android: `style=\"@style/TextAppearance.App.Body\"`)\r\n4. Also check `semantic_labels` for text style roles — if Figma labels a text\r\n   as \"Body\" and the project has a matching style, use it\r\n5. No match → use inline attributes (android:textSize, android:textStyle, etc.)\r\n\r\nfontWeight mapping (Figma numeric → Android key):\r\n- 400 or below → \"normal\"\r\n- 500 → \"medium\"\r\n- 600 → \"semibold\"\r\n- 700+ → \"bold\"\r\n\r\n### Image matching\r\n\r\n1. Icon elements → search scan images by semantic name\r\n   (Figma `icon/back` → `icon_back`)\r\n2. If matched: `UIImage(named:)` / `@drawable/name`\r\n3. If not matched: export from Figma API\r\n\r\n### Base class detection (iOS)\r\n\r\n- Scan reveals `BaseViewController`, `BaseTableViewCell`, etc.\r\n  → use as parent class instead of raw UIKit classes\r\n\r\n## Semantic Labels Reference\r\n\r\nThe scanner produces three levels of semantic annotation:\r\n\r\n| Source | Confidence | Example |\r\n|--------|-----------|---------|\r\n| **Theme mapping** (Android themes.xml) | high | `colorPrimary` → primary |\r\n| **Asset naming** (iOS .colorset names) | high | `AccentColor` → primary |\r\n| **Resource naming** (color names like \"primary\") | medium | `brand_primary_color` → primary |\r\n| **Dimen naming** (spacing_*) | medium | `spacing_16` → spacing_md |\r\n| **Text style naming** (style name heuristics) | medium | `TextAppearance.Body1` → body |\r\n\r\nUse high-confidence labels first. Medium-confidence labels are hints —\r\nvalidate against the Figma design before making assumptions.\r\n\r\n## Fallback (No Scan Available)\r\n\r\nIf no scan report is available, fall back to the hardcoded resource matching\r\ndescribed in Step 3.\r\n\r\n## Presenting Scan Results\r\n\r\nKeep it brief and useful — not a JSON dump:\r\n\r\n> ✓ 扫描完成：找到 3 个模块、24 个颜色、18 条文案。\r\n> 其中有 6 个颜色映射到了主题色（primary、surface 等），生成代码时会用项目资源引用。\r\n\r\nIf scan found issues, tell the user naturally:\r\n\r\n> 扫描完了，找到 N 个资源。不过 [具体问题，如某个模块没找到资源文件]，\r\n> 你项目里这部分是怎么组织的？\n\nFile v2.3.0:references/swiftui-patterns.md\n\n# SwiftUI Patterns — Figma to SwiftUI Mapping\r\n\r\n> Purpose: Map Figma properties to SwiftUI code.\r\n> Includes both basic mapping and **production patterns** derived from real-project experience.\r\n\r\n## Layout Selection Guide\r\n\r\n| Figma Structure | Recommended View |\r\n|---|---|\r\n| Vertical stack | VStack |\r\n| Horizontal stack | HStack |\r\n| Overlapping / z-stacking | ZStack |\r\n| Repeating similar items (≥3) | List / LazyVStack / LazyHStack |\r\n| Page with navigation bar | NavigationStack + .navigationTitle |\r\n| Scrollable content | ScrollView |\r\n\r\n## Auto-layout Mapping\r\n\r\n| Figma Property | SwiftUI Equivalent |\r\n|---|---|\r\n| layoutMode: VERTICAL | VStack |\r\n| layoutMode: HORIZONTAL | HStack |\r\n| itemSpacing | spacing: parameter |\r\n| padding* | .padding() modifiers |\r\n| primaryAxisAlignItems: CENTER | alignment parameter + Spacer() |\r\n| counterAxisAlignItems: CENTER | alignment: .center |\r\n| layoutGrow: 1 | .frame(maxWidth: .infinity) or Spacer() |\r\n| primaryAxisSizingMode: FIXED | .frame(height/width: X) |\r\n\r\n## Size Conversion\r\n\r\n- Figma px → SwiftUI pt (1:1)\r\n- Font sizes: .system(size:) with pt values\r\n\r\n## Page Architecture Patterns\r\n\r\nThese patterns reflect how iOS apps are **actually structured** in production.\r\nWhen generating code, think about the **page-level architecture**, not just individual views.\r\n\r\n### Multi-Tab Pages\r\nWhen a design shows **multiple tabs** (≥2 text labels acting as navigation):\r\n- Use `TabView` with `.tabViewStyle(.page)` for swipeable tabs, or custom `Picker`/segmented control for top tabs\r\n- For bottom tab bars, use `TabView` with default style (system tab bar)\r\n- Do NOT use plain `Text` views for tabs — they lack selection state, indicators, and swipe support\r\n- Each tab’s content should be a separate `View` struct\r\n- Output: main view (with TabView/segmented control) + separate content views\r\n\r\n```swift\r\n// Top tab bar with underline indicator (custom)\r\nstruct ContentView: View {\r\n    @State private var selectedTab = 0\r\n    let tabs = [\"关注\", \"推荐\", \"热榜\"]\r\n\r\n    var body: some View {\r\n        VStack(spacing: 0) {\r\n            // Tab bar\r\n            HStack(spacing: 24) {\r\n                ForEach(tabs.indices, id: \\.self) { index in\r\n                    VStack(spacing: 4) {\r\n                        Text(tabs[index])\r\n                            .font(.system(size: 16, weight: selectedTab == index ? .bold : .regular))\r\n                            .foregroundColor(selectedTab == index ? Color(hex: \"0F0F0F\") : Color(hex: \"858A99\"))\r\n                        Rectangle()\r\n                            .fill(selectedTab == index ? Color(hex: \"0F0F0F\") : .clear)\r\n                            .frame(height: 2)\r\n                    }\r\n                    .onTapGesture { selectedTab = index }\r\n                }\r\n            }\r\n            .padding(.horizontal, 20)\r\n\r\n            // Content\r\n            TabView(selection: $selectedTab) {\r\n                FollowingView().tag(0)\r\n                RecommendView().tag(1)\r\n                HotListView().tag(2)\r\n            }\r\n            .tabViewStyle(.page(indexDisplayMode: .never))\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n### Navigation Bar — Treat as a Single Unit\r\nThe navigation bar (back button + title, possibly + right action) is **one logical container**.\r\n- Use `NavigationStack` with `.toolbar` for standard nav bars\r\n- For custom nav bars (non-standard styling), use `HStack` as a single container, then place content below with proper spacing\r\n- Content below should reference the navbar as one unit, not individual children within it\r\n\r\n```swift\r\n// Custom navigation bar\r\nHStack {\r\n    Button(action: { dismiss() }) {\r\n        Image(systemName: \"chevron.left\")\r\n            .frame(width: 32, height: 32)\r\n            .background(Circle().fill(Color(hex: \"000000\")))\r\n            .foregroundColor(.white)\r\n    }\r\n    Spacer()\r\n    Text(\"设置\")\r\n        .font(.system(size: 17, weight: .bold))\r\n    Spacer()\r\n    Color.clear.frame(width: 32, height: 32) // Balance spacer\r\n}\r\n.padding(.horizontal, 20)\r\n```\r\n\r\n### Navigation Bar Buttons\r\n- **Back/close buttons**: Use `Button` with `Image` content — simple, reliable\r\n  - Circular background: `.background(Circle().fill(...))` on the Image\r\n  - Icon: `Image(systemName:)` for SF Symbols or `Image(\"custom_icon\")`\r\n  - Do NOT wrap in extra containers for simple icon buttons\r\n\r\n### Buttons with Icon + Text\r\nPrefer `HStack` with `Image` + `Text` inside a `Button` for reliable icon+text buttons.\r\n`Label` is an alternative but gives less layout control.\r\n\r\n```swift\r\n// Outlined button with icon\r\nButton(action: {}) {\r\n    HStack(spacing: 6) {\r\n        Image(\"ic_video\")\r\n            .resizable()\r\n            .frame(width: 20, height: 20)\r\n        Text(\"查看视频\")\r\n            .font(.system(size: 15, weight: .bold))\r\n            .foregroundColor(Color(hex: \"0F0F0F\"))\r\n    }\r\n    .frame(maxWidth: .infinity)\r\n    .frame(height: 40)\r\n    .background(\r\n        RoundedRectangle(cornerRadius: 12)\r\n            .stroke(Color(hex: \"DCDCDC\"), lineWidth: 1)\r\n    )\r\n}\r\n\r\n// Solid filled button\r\nButton(action: {}) {\r\n    Text(\"查看报告\")\r\n        .font(.system(size: 15, weight: .bold))\r\n        .foregroundColor(.white)\r\n        .frame(maxWidth: .infinity)\r\n        .frame(height: 40)\r\n        .background(RoundedRectangle(cornerRadius: 12).fill(Color(hex: \"0158FF\")))\r\n}\r\n```\r\n\r\n### Switch / Toggle\r\n- Use `Toggle` — it’s the standard SwiftUI control, reliable across all iOS versions\r\n- For custom styling (non-standard colors/shape), use `.toggleStyle` with a custom style\r\n- For tint color changes: `.tint(Color(...))` (iOS 15+)\r\n\r\n```swift\r\nToggle(\"通知\", isOn: $isEnabled)\r\n    .tint(Color(hex: \"0158FF\"))\r\n```\r\n\r\n### Input Fields vs Display Fields\r\nFigma cannot distinguish between input and display — both appear as RECTANGLE + TEXT.\r\n- **Placeholder-like text** (\"选择你的生日\", \"请输入姓名\") with input styling → `TextField`\r\n- **Static display text** → `Text`\r\n- **ASK the user if unsure** — this is a functional decision\r\n\r\n```swift\r\n// Input field\r\nTextField(\"请输入昵称\", text: $nickname)\r\n    .font(.system(size: 15))\r\n    .foregroundColor(Color(hex: \"0F0F0F\"))\r\n    .padding(.horizontal, 12)\r\n    .frame(width: 295, height: 48)\r\n    .background(\r\n        RoundedRectangle(cornerRadius: 8)\r\n            .stroke(Color(hex: \"E5E5E5\"), lineWidth: 1)\r\n    )\r\n```\r\n\r\n## Width Strategy — Fixed vs Flexible\r\n\r\nFigma designs are usually based on a fixed canvas (e.g. 375px or 390px). Figma widths are **computed results**, not design intent. Infer intent to decide SwiftUI sizing.\r\n\r\nCore question: **Is this element’s width \"fixed\" or \"fill remaining space\"?**\r\n\r\n### Rule 1: Single element spanning full width\r\nElement width + left/right offsets ≈ screen width, with symmetric margins → `.frame(maxWidth: .infinity)` + `.padding(.horizontal, X)`\r\n\r\n```swift\r\nText(\"标题\")\r\n    .frame(maxWidth: .infinity, alignment: .leading)\r\n    .padding(.horizontal, 20)\r\n```\r\n\r\n### Rule 2: Identify the \"flexible\" element in side-by-side layouts\r\nMultiple elements arranged horizontally — determine which is **fixed** and which is **flexible**:\r\n- **Fixed**: elements with inherent size — avatars, icons, buttons. Use explicit `.frame(width:)`\r\n- **Flexible**: text/content areas that fill remaining space. Use `Spacer()` or let natural layout expand\r\n\r\n```swift\r\n// Avatar (fixed) + text (flexible)\r\nHStack(spacing: 16) {\r\n    Image(\"avatar\")\r\n        .resizable()\r\n        .frame(width: 56, height: 56)\r\n        .clipShape(Circle())\r\n    VStack(alignment: .leading, spacing: 4) {\r\n        Text(\"用户名\").font(.system(size: 16, weight: .bold))\r\n        Text(\"描述文字\").font(.system(size: 14)).foregroundColor(.gray)\r\n    }\r\n    Spacer() // text side is flexible\r\n}\r\n.padding(.horizontal, 20)\r\n```\r\n\r\n### Rule 3: Fixed width + centered\r\nElement clearly narrower than screen, centered → explicit `.frame(width:)` with centered parent\r\n\r\n```swift\r\nTextField(\"请输入昵称\", text: $nickname)\r\n    .frame(width: 295, height: 48)\r\n```\r\n\r\n### List Item Width\r\n- Item views should use `.frame(maxWidth: .infinity)` — List/LazyVStack controls the width\r\n- The List itself follows the above rules for its own sizing\r\n\r\n## Multi-State Views\r\n\r\nWhen the same View has two or more visual states (selected/unselected, enabled/disabled):\r\n\r\n### Selected/Unselected State\r\nUse conditional modifiers based on state:\r\n\r\n```swift\r\n// Gender selection card\r\nstruct GenderCard: View {\r\n    let title: String\r\n    let icon: String\r\n    let isSelected: Bool\r\n\r\n    var body: some View {\r\n        VStack(spacing: 8) {\r\n            Image(icon)\r\n                .resizable()\r\n                .frame(width: 48, height: 48)\r\n            Text(title)\r\n                .font(.system(size: 15, weight: .medium))\r\n        }\r\n        .frame(width: 140, height: 120)\r\n        .background(\r\n            RoundedRectangle(cornerRadius: 16)\r\n                .fill(isSelected ? Color(hex: \"E8F0FF\") : Color(hex: \"F5F5F5\"))\r\n        )\r\n        .overlay(\r\n            RoundedRectangle(cornerRadius: 16)\r\n                .stroke(isSelected ? Color(hex: \"0158FF\") : .clear, lineWidth: 2)\r\n        )\r\n    }\r\n}\r\n```\r\n\r\n### Disabled/Enabled State via Opacity\r\nWhen a View appears in two states where one has reduced opacity:\r\n- Use `.opacity()` + `.disabled()` modifiers\r\n- Do NOT create separate views for each state\r\n\r\n```swift\r\nButton(action: { submit() }) {\r\n    Text(\"提交\")\r\n        .frame(maxWidth: .infinity)\r\n        .frame(height: 48)\r\n        .background(RoundedRectangle(cornerRadius: 12).fill(Color(hex: \"0158FF\")))\r\n        .foregroundColor(.white)\r\n}\r\n.opacity(isValid ? 1.0 : 0.3)\r\n.disabled(!isValid)\r\n```\r\n\r\n### Stacked/Overlapping Cards\r\nStacked cards with similar structure (same shape, offset position) → likely a card-switching interaction.\r\nDo NOT generate as separate static Views. Ask the user about the interaction pattern.\r\n- SwiftUI options: `TabView(.page)`, custom `DragGesture` + offset, or third-party card stack library\r\n\r\n## Visual Properties\r\n\r\n### Color Hex Extension (include once in generated code)\r\n\r\n```swift\r\nextension Color {\r\n    init(hex: String) {\r\n        let scanner = Scanner(string: hex)\r\n        var rgb: UInt64 = 0\r\n        scanner.scanHexInt64(&rgb)\r\n        self.init(\r\n            red: Double((rgb >> 16) & 0xFF) / 255.0,\r\n            green: Double((rgb >> 8) & 0xFF) / 255.0,\r\n            blue: Double(rgb & 0xFF) / 255.0\r\n        )\r\n    }\r\n}\r\n```\r\n\r\n### Shadow Mapping\r\n\r\n```swift\r\n.shadow(color: Color(hex: \"000000\").opacity(0.1), radius: 4, x: 0, y: 2)\r\n```\r\n\r\n### Gradient Mapping\r\n\r\n```swift\r\nLinearGradient(\r\n    colors: [Color(hex: \"FF6B6B\"), Color(hex: \"4ECDC4\")],\r\n    startPoint: .top,\r\n    endPoint: .bottom\r\n)\r\n```\r\n\r\n### Per-corner Radius (iOS 16+)\r\n\r\n```swift\r\n.clipShape(UnevenRoundedRectangle(\r\n    topLeadingRadius: 12,\r\n    topTrailingRadius: 12,\r\n    bottomLeadingRadius: 0,\r\n    bottomTrailingRadius: 0\r\n))\r\n```\r\n\r\n### Complex Illustrations — Export as Bitmap\r\nWhen a Figma node contains gradients + boolean operations + multiple overlapping shapes:\r\n- Do NOT attempt to recreate in SwiftUI code\r\n- Export from Figma API as PNG/WebP at 2x/3x\r\n- Use `Image(\"illustration\")` with `.resizable()` and `.aspectRatio(contentMode: .fit)`\r\n\r\n## Dark Mode Patterns\r\n\r\n```swift\r\n// ✅ Using Color asset with light/dark variants\r\nColor(\"primaryText\") // defined in Assets.xcassets with Appearances\r\n\r\n// ✅ Using Environment\r\n@Environment(\\.colorScheme) var colorScheme\r\nlet textColor = colorScheme == .dark ? Color(hex: \"F0F0F0\") : Color(hex: \"0F0F0F\")\r\n\r\n// ✅ Dynamic UIColor bridge\r\nColor(UIColor.dynamic(light: UIColor(hex: \"0F0F0F\"), dark: UIColor(hex: \"F0F0F0\")))\r\n```\r\n\r\n## Figma Node Interpretation (SwiftUI-specific notes)\r\n\r\nThe general Figma node interpretation rules in SKILL.md apply to all platforms.\r\nHere are SwiftUI-specific mappings:\r\n\r\n- **Container + icon = single Image**: FRAME(background + cornerRadius) wrapping VECTOR/INSTANCE → one `Image` with `.background(Circle/RoundedRectangle)`, not nested views\r\n- **RECTANGLE as background**: GROUP’s first child RECTANGLE matching parent size → `.background()` modifier on the container\r\n- **GROUP vs FRAME**: FRAME with `layoutMode` → VStack/HStack; GROUP without `layoutMode` → ZStack with explicit `.offset()` or `.position()`\r\n- **Rounding**: Round pt to nearest integer, sp to nearest 0.5\n\nFile v2.3.0:references/uikit-patterns.md\n\n# UIKit Patterns — Figma to UIKit Mapping\r\n\r\n> Purpose: Map Figma properties to UIKit code.\r\n> Includes both basic mapping and **production patterns** derived from real-project experience.\r\n\r\n## Layout Selection Guide\r\n\r\n| Figma Structure | Recommended Approach |\r\n|---|---|\r\n| Simple vertical/horizontal stack | UIStackView |\r\n| Complex / relative positioning | Auto Layout (NSLayoutConstraint) |\r\n| Repeating similar items (≥3) | UITableView / UICollectionView |\r\n| Scrollable content | UIScrollView |\r\n| Overlapping views | UIView hierarchy with constraints |\r\n\r\n## Auto-layout Mapping\r\n\r\n| Figma Property | UIKit Equivalent |\r\n|---|---|\r\n| layoutMode: VERTICAL | UIStackView axis=.vertical |\r\n| layoutMode: HORIZONTAL | UIStackView axis=.horizontal |\r\n| itemSpacing | stackView.spacing |\r\n| padding | layoutMargins + isLayoutMarginsRelativeArrangement |\r\n| primaryAxisAlignItems: CENTER | distribution = .equalCentering |\r\n| counterAxisAlignItems: CENTER | alignment = .center |\r\n| layoutGrow: 1 | setContentHuggingPriority(.defaultLow) |\r\n| primaryAxisSizingMode: FIXED | heightAnchor/widthAnchor constraint |\r\n\r\n## Size Conversion\r\n\r\n- Figma px → UIKit pt (1:1)\r\n- Font sizes: .systemFont(ofSize:) with pt values\r\n\r\n## Page Architecture Patterns\r\n\r\nThese patterns reflect how iOS apps are **actually structured** in production.\r\n\r\n### Multi-Tab Pages\r\nWhen a design shows **multiple tabs** (≥2 text labels acting as navigation):\r\n- Use `UITabBarController` for bottom tab bars\r\n- For top tabs (segmented switching), use a **custom tab bar** + `UIPageViewController` or child `UIViewController` swapping\r\n- Do NOT use plain `UILabel` views for tabs — they lack selection state, indicators, and swipe support\r\n- Each tab’s content should be a separate `UIViewController`\r\n- Output: main controller (with tab bar + container) + separate content view controllers\r\n\r\n```swift\r\n// Custom top tab bar with UIPageViewController\r\nclass TabContainerViewController: UIViewController {\r\n    private let tabBar = UIStackView()\r\n    private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal)\r\n    private var tabs: [UIButton] = []\r\n    private var viewControllers: [UIViewController] = []\r\n    private var selectedIndex = 0\r\n\r\n    private func setupTabBar() {\r\n        tabBar.axis = .horizontal\r\n        tabBar.spacing = 24\r\n        tabBar.alignment = .center\r\n\r\n        let titles = [\"关注\", \"推荐\", \"热榜\"]\r\n        for (index, title) in titles.enumerated() {\r\n            let button = UIButton(type: .system)\r\n            button.setTitle(title, for: .normal)\r\n            button.titleLabel?.font = .systemFont(ofSize: 16, weight: index == 0 ? .bold : .regular)\r\n            button.setTitleColor(index == 0 ? UIColor(hex: \"0F0F0F\") : UIColor(hex: \"858A99\"), for: .normal)\r\n            button.tag = index\r\n            button.addTarget(self, action: #selector(tabTapped(_:)), for: .touchUpInside)\r\n            tabs.append(button)\r\n            tabBar.addArrangedSubview(button)\r\n        }\r\n    }\r\n\r\n    @objc private func tabTapped(_ sender: UIButton) {\r\n        selectTab(at: sender.tag)\r\n    }\r\n}\r\n```\r\n\r\n### Navigation Bar — Treat as a Single Unit\r\nThe navigation bar is **one logical container**. Content below should reference it as a whole.\r\n- Use `UINavigationController` with `UINavigationBar` for standard nav bars\r\n- For custom nav bars, use a `UIView` container with subviews, then constrain content below to the container’s bottomAnchor\r\n\r\n```swift\r\n// Custom navigation bar\r\nlet navContainer = UIView()\r\nnavContainer.translatesAutoresizingMaskIntoConstraints = false\r\nview.addSubview(navContainer)\r\n\r\nlet backButton = UIButton(type: .system)\r\nbackButton.setImage(UIImage(named: \"ic_back\"), for: .normal)\r\nbackButton.backgroundColor = UIColor(hex: \"000000\")\r\nbackButton.layer.cornerRadius = 16\r\nbackButton.translatesAutoresizingMaskIntoConstraints = false\r\n\r\nlet titleLabel = UILabel()\r\ntitleLabel.text = \"设置\"\r\ntitleLabel.font = .systemFont(ofSize: 17, weight: .bold)\r\ntitleLabel.translatesAutoresizingMaskIntoConstraints = false\r\n\r\nnavContainer.addSubview(backButton)\r\nnavContainer.addSubview(titleLabel)\r\n\r\n// Content constrained to nav container bottom\r\ncontentView.topAnchor.constraint(equalTo: navContainer.bottomAnchor, constant: 12).isActive = true\r\n```\r\n\r\n### Navigation Bar Buttons\r\n- **Back/close buttons**: Use `UIButton` — simple, reliable\r\n  - Circular background: `button.layer.cornerRadius = size/2` + `clipsToBounds`\r\n  - Icon: `setImage(UIImage(named:), for: .normal)`\r\n  - Do NOT wrap in extra container views for simple icon buttons\r\n\r\n### Buttons with Icon + Text\r\nPrefer `UIStackView` with `UIImageView` + `UILabel` inside a `UIView` container, or use `UIButton.Configuration` (iOS 15+).\r\n\r\n```swift\r\n// UIButton.Configuration approach (iOS 15+)\r\nvar config = UIButton.Configuration.plain()\r\nconfig.image = UIImage(named: \"ic_video\")\r\nconfig.title = \"查看视频\"\r\nconfig.imagePadding = 6\r\nconfig.baseForegroundColor = UIColor(hex: \"0F0F0F\")\r\nconfig.background.strokeColor = UIColor(hex: \"DCDCDC\")\r\nconfig.background.strokeWidth = 1\r\nconfig.background.cornerRadius = 12\r\nlet button = UIButton(configuration: config)\r\n\r\n// StackView approach (backward compatible)\r\nlet container = UIView()\r\ncontainer.layer.cornerRadius = 12\r\ncontainer.layer.borderWidth = 1\r\ncontainer.layer.borderColor = UIColor(hex: \"DCDCDC\").cgColor\r\n\r\nlet stack = UIStackView()\r\nstack.axis = .horizontal\r\nstack.spacing = 6\r\nstack.alignment = .center\r\n\r\nlet iconView = UIImageView(image: UIImage(named: \"ic_video\"))\r\niconView.widthAnchor.constraint(equalToConstant: 20).isActive = true\r\niconView.heightAnchor.constraint(equalToConstant: 20).isActive = true\r\n\r\nlet label = UILabel()\r\nlabel.text = \"查看视频\"\r\nlabel.font = .systemFont(ofSize: 15, weight: .bold)\r\nlabel.textColor = UIColor(hex: \"0F0F0F\")\r\n\r\nstack.addArrangedSubview(iconView)\r\nstack.addArrangedSubview(label)\r\ncontainer.addSubview(stack)\r\n```\r\n\r\n### Switch / Toggle\r\n- Use `UISwitch` — standard, reliable across all iOS versions\r\n- For custom tint: `onTintColor` property\r\n- For fully custom appearance (non-standard shape/size), consider a custom `UIControl` subclass\r\n\r\n```swift\r\nlet toggle = UISwitch()\r\ntoggle.onTintColor = UIColor(hex: \"0158FF\")\r\ntoggle.isOn = true\r\n```\r\n\r\n### Input Fields vs Display Fields\r\nFigma cannot distinguish between `UITextField` and `UILabel` — both appear as RECTANGLE + TEXT.\r\n- **Placeholder-like text** with input styling → `UITextField`\r\n- **Static display text** → `UILabel`\r\n- **ASK the user if unsure**\r\n\r\n```swift\r\nlet textField = UITextField()\r\ntextField.placeholder = \"请输入昵称\"\r\ntextField.font = .systemFont(ofSize: 15)\r\ntextField.textColor = UIColor(hex: \"0F0F0F\")\r\ntextField.layer.cornerRadius = 8\r\ntextField.layer.borderWidth = 1\r\ntextField.layer.borderColor = UIColor(hex: \"E5E5E5\").cgColor\r\ntextField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 12, height: 0))\r\ntextField.leftViewMode = .always\r\n```\r\n\r\n## Width Strategy — Fixed vs Flexible\r\n\r\nFigma widths are **computed results**, not design intent. Infer intent to decide Auto Layout constraints.\r\n\r\n### Rule 1: Single element spanning full width\r\nElement width + left/right offsets ≈ screen width, with symmetric margins → pin leading + trailing to superview with margin constants.\r\n\r\n```swift\r\nNSLayoutConstraint.activate([\r\n    titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),\r\n    titleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)\r\n])\r\n```\r\n\r\n### Rule 2: Identify the \"flexible\" element in side-by-side layouts\r\n- **Fixed**: elements with inherent size (avatars, icons, buttons). Use explicit `widthAnchor`.\r\n- **Flexible**: text/content areas. Do NOT set `widthAnchor` — let leading+trailing constraints determine width.\r\n- Use `contentHuggingPriority` and `contentCompressionResistancePriority` to control which view stretches.\r\n\r\n```swift\r\n// Avatar (fixed) + label (flexible)\r\nNSLayoutConstraint.activate([\r\n    avatarView.widthAnchor.constraint(equalToConstant: 56),\r\n    avatarView.heightAnchor.constraint(equalToConstant: 56),\r\n    avatarView.leadingAnchor.constraint(equalTo: cell.leadingAnchor, constant: 20),\r\n\r\n    nameLabel.leadingAnchor.constraint(equalTo: avatarView.trailingAnchor, constant: 16),\r\n    nameLabel.trailingAnchor.constraint(equalTo: cell.trailingAnchor, constant: -20),\r\n    // nameLabel width is determined by constraints, not explicit value\r\n])\r\n```\r\n\r\n### Rule 3: Fixed width + centered\r\nElement clearly narrower than screen, centered → explicit `widthAnchor` + `centerXAnchor`.\r\n\r\n```swift\r\nNSLayoutConstraint.activate([\r\n    textField.widthAnchor.constraint(equalToConstant: 295),\r\n    textField.heightAnchor.constraint(equalToConstant: 48),\r\n    textField.centerXAnchor.constraint(equalTo: view.centerXAnchor)\r\n])\r\n```\r\n\r\n### Table/Collection View Cell Width\r\n- Cell content should use leading/trailing constraints to superview (the cell’s contentView)\r\n- Never hardcode cell width — let the table/collection view control it\r\n\r\n## Multi-State Views\r\n\r\n### Selected/Unselected State\r\nUse `isSelected` property with custom state rendering:\r\n\r\n```swift\r\nclass GenderCardView: UIControl {\r\n    override var isSelected: Bool {\r\n        didSet { updateAppearance() }\r\n    }\r\n\r\n    private func updateAppearance() {\r\n        backgroundColor = isSelected ? UIColor(hex: \"E8F0FF\") : UIColor(hex: \"F5F5F5\")\r\n        layer.borderColor = isSelected ? UIColor(hex: \"0158FF\").cgColor : UIColor.clear.cgColor\r\n        layer.borderWidth = isSelected ? 2 : 0\r\n    }\r\n}\r\n```\r\n\r\nFor simpler cases, use `UIButton` with different configurations per state:\r\n```swift\r\nbutton.setTitleColor(UIColor(hex: \"0F0F0F\"), for: .selected)\r\nbutton.setTitleColor(UIColor(hex: \"858A99\"), for: .normal)\r\n```\r\n\r\n### Disabled/Enabled State via Opacity\r\n- Use `alpha` + `isEnabled` / `isUserInteractionEnabled`\r\n- Do NOT create separate views for each state\r\n\r\n```swift\r\nsubmitButton.alpha = isValid ? 1.0 : 0.3\r\nsubmitButton.isEnabled = isValid\r\n```\r\n\r\n### Stacked/Overlapping Cards\r\nStacked cards with similar structure → likely a card-switching interaction.\r\nDo NOT generate as separate static views. Ask the user about the interaction pattern.\r\n- UIKit options: `UIPageViewController`, custom pan gesture + transform, or third-party card stack library\r\n\r\n## Visual Properties\r\n\r\n### Color Hex Extension (include once in generated code)\r\n\r\n```swift\r\nextension UIColor {\r\n    convenience init(hex: String) {\r\n        let scanner = Scanner(string: hex)\r\n        var rgb: UInt64 = 0\r\n        scanner.scanHexInt64(&rgb)\r\n        self.init(\r\n            red: CGFloat((rgb >> 16) & 0xFF) / 255.0,\r\n            green: CGFloat((rgb >> 8) & 0xFF) / 255.0,\r\n            blue: CGFloat(rgb & 0xFF) / 255.0,\r\n            alpha: 1.0\r\n        )\r\n    }\r\n}\r\n```\r\n\r\n### Layout Approach\r\n\r\n1. Set `translatesAutoresizingMaskIntoConstraints = false`\r\n2. Use `NSLayoutConstraint.activate([])`\r\n3. Prefer UIStackView for linear layouts (reduces constraint count)\r\n4. Use direct constraints for complex/absolute positioning\r\n\r\n### Shadow Mapping\r\n\r\n```swift\r\nview.layer.shadowColor = UIColor(hex: \"000000\").cgColor\r\nview.layer.shadowOpacity = 0.1\r\nview.layer.shadowRadius = 4\r\nview.layer.shadowOffset = CGSize(width: 0, height: 2)\r\n```\r\n\r\n### Gradient Mapping\r\n\r\n```swift\r\nlet gradientLayer = CAGradientLayer()\r\ngradientLayer.colors = [UIColor(hex: \"FF6B6B\").cgColor, UIColor(hex: \"4ECDC4\").cgColor]\r\ngradientLayer.startPoint = CGPoint(x: 0.5, y: 0)\r\ngradientLayer.endPoint = CGPoint(x: 0.5, y: 1)\r\ngradientLayer.frame = view.bounds\r\nview.layer.insertSublayer(gradientLayer, at: 0)\r\n```\r\n\r\n### Per-corner Radius\r\n\r\n```swift\r\nview.layer.cornerRadius = 12\r\nview.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]  // top only\r\n```\r\n\r\n### Complex Illustrations — Export as Bitmap\r\nWhen a Figma node contains gradients + boolean operations + multiple overlapping shapes:\r\n- Do NOT attempt to recreate programmatically\r\n- Export from Figma API as PNG/WebP at 2x/3x\r\n- Use `UIImageView` with the exported image\r\n\r\n## Dark Mode Patterns\r\n\r\n### UIColor.dynamic Pattern\r\nMost iOS projects use `UIColor.dynamic(light:dark:)` or similar wrapper. When generating code:\r\n\r\n```swift\r\n// ✅ Correct: use dynamic color for dark mode support\r\nlet textColor = UIColor.dynamic(\r\n    light: UIColor(hex: \"0F0F0F\"),\r\n    dark: UIColor(hex: \"F0F0F0\")\r\n)\r\n\r\n// ❌ Wrong: hardcoded single color (no dark mode)\r\nlet textColor = UIColor(hex: \"0F0F0F\")\r\n```\r\n\r\n### Dynamic Color Inference\r\nFigma designs are typically light mode only. When generating dark mode counterparts:\r\n- **Text**: light `#0F0F0F` → dark `#F0F0F0` (invert on gray scale)\r\n- **Background**: light `#FFFFFF` → dark `#1C1C1E` (system dark background)\r\n- **Card/surface**: light `#F7F7F7` → dark `#2C2C2E`\r\n- **Dividers**: light `#EEEEEE` → dark `#3A3A3C`\r\n- **Brand colors**: keep same or slightly lighten for dark (e.g., `#2965FF` → `#4D88FF`)\r\n- **Always check the project first** — if the project already defines light/dark color pairs, use those\r\n\r\n### When NOT to Infer Dark Mode\r\n- If the project has NO dynamic colors (no `UIColor.dynamic` / no colorset with Appearances) → project likely doesn't support dark mode, don't add it\r\n- If user explicitly says \"light mode only\" → use plain `UIColor(hex:)`\r\n\r\n## Figma Node Interpretation (UIKit-specific notes)\r\n\r\nThe general Figma node interpretation rules in SKILL.md apply to all platforms.\r\nHere are UIKit-specific mappings:\r\n\r\n- **Container + icon = single UIImageView**: FRAME(background + cornerRadius) wrapping VECTOR/INSTANCE → one `UIImageView` with `layer.cornerRadius` + `backgroundColor` + `image`\r\n- **RECTANGLE as background**: GROUP’s first child RECTANGLE matching parent size → `backgroundColor` or a background sublayer on the container UIView\r\n- **GROUP vs FRAME**: FRAME with `layoutMode` → UIStackView; GROUP without `layoutMode` → explicit frame/constraints based on x/y positions\r\n- **Rounding**: Round pt to nearest integer, font size to nearest 0.5\n\nArchive v2.2.5: 34 files, 103668 bytes\n\nFiles: LICENSE (1084b), README-zh.md (7193b), README.md (3608b), references/compose-patterns.md (23941b), references/figma-interpretation.md (8637b), references/generation-rules.md (5275b), references/scan-usage.md (4022b), references/swiftui-patterns.md (12454b), references/uikit-patterns.md (14135b), references/xml-patterns.md (24007b), scripts/feedback_analyze.py (6420b), scripts/figma_fetch.py (26437b), scripts/project_scan.py (7850b), scripts/scanners/__init__.py (1504b), scripts/scanners/android_deps.py (2485b), scripts/scanners/android_drawables.py (13181b), scripts/scanners/android_layouts.py (5030b), scripts/scanners/android_modules.py (4061b), scripts/scanners/android_resources.py (11356b), scripts/scanners/android_scanner.py (19508b), scripts/scanners/android_views.py (6321b), scripts/scanners/base.py (12022b), scripts/scanners/flutter_detector.py (452b), scripts/scanners/flutter_scanner.py (15776b), scripts/scanners/ios_assets.py (1858b), scripts/scanners/ios_detector.py (815b), scripts/scanners/ios_resources.py (5804b), scripts/scanners/ios_scanner.py (8028b), scripts/scanners/ios_swift_scan.py (4962b), scripts/scanners/ios_views.py (3148b), skill-card.md (3051b), SKILL.md (13603b), tests/test_project_scan.py (15007b), _meta.json (134b)\n\nFile v2.2.5:SKILL.md\n\n---\r\nname: figma-to-mobile\r\ndescription: >\r\n  Convert Figma designs to mobile UI code with project-aware scanning.\r\n  Supports Android (Jetpack Compose, XML), iOS (SwiftUI, UIKit), and Flutter.\r\n  Use when a user provides a Figma link and asks for mobile layout code.\r\n  Extracts design tokens via Figma REST API (from api.figma.com), scans\r\n  local project resources for reuse, supports multi-frame comparison,\r\n  and captures manual corrections via feedback-log for improvement.\r\n  Bugs/feedback: https://github.com/TimeAground/figma-to-mobile/issues\r\nmetadata:\r\n  {\r\n    \"openclaw\":\r\n      {\r\n        \"requires\": { \"bins\": [\"python3\"], \"env\": [\"FIGMA_TOKEN\"] },\r\n        \"primaryEnv\": \"FIGMA_TOKEN\",\r\n        \"permissions\":\r\n          {\r\n            \"network\": [ \"api.figma.com\" ],\r\n            \"fs\":\r\n              {\r\n                \"read\": [ \"project root — for resource scanning (colors, strings, components)\" ],\r\n                \"write\": [\r\n                  \"project root — for generated UI code files\",\r\n                  \"project root — for feedback-log.md (user consent required)\"\r\n                ]\r\n              }\r\n          },\r\n        \"install\":\r\n          [\r\n            {\r\n              \"id\": \"python-requests\",\r\n              \"kind\": \"shell\",\r\n              \"command\": \"pip3 install requests\",\r\n              \"label\": \"Install Python requests package\",\r\n            },\r\n          ],\r\n      },\r\n  }\r\n---\r\n\r\n# Figma to Mobile\r\n\r\nConvert Figma designs to mobile UI code with interactive clarification.\r\n\r\nSupported: Android Compose, Android XML, iOS SwiftUI, iOS UIKit.\r\n\r\n## Prerequisites\r\n\r\n- `FIGMA_TOKEN` environment variable set (Figma > Settings > Personal Access Tokens)\r\n  ⚠️ **Your Figma token is sensitive** — treat it like a password. Never paste it into chat\r\n    messages (they may be logged). Set it via your shell rc file or OpenClaw env config.\r\n- Python 3.8+ with `requests` package\r\n\r\n## Trigger & Input\r\n\r\nThis skill activates when a user **explicitly asks to convert a Figma design to mobile UI code** and provides a Figma link. It does NOT activate on casual mentions of Figma links, pasted URLs in logs, or design references without a conversion request.\r\n\r\nThe user may also include **inline hints** alongside the link, such as:\r\n- Target platform: \"Android XML\", \"Compose\", \"SwiftUI\", \"UIKit\"\r\n- Layout preferences: \"use ConstraintLayout\", \"prefer StackView\"\r\n- Component notes: \"the switch is our custom CompactSwitch\", \"this is a dynamic list\"\r\n- Any other context about the design\r\n\r\n**If the user provides hints, respect them and skip the corresponding questions.**\r\nFor example, if the user says \"Android XML, the 3 cards are a RecyclerView list\", do NOT ask about output format or whether the cards are dynamic/static.\r\n\r\n## Workflow\r\n\r\n### Step 1: Fetch & Analyze\r\n\r\nWhen user provides Figma link(s):\r\n\r\n1. **Determine the input type:**\r\n\r\n   **A. Link without specific node-id** (no `node-id`, or `node-id=0-1`):\r\n   This link points to the entire page, not a specific frame. Tell the user:\r\n   > This link points to the whole page. Please select the frame you want in Figma, right-click it, and choose \"Copy link to selection\", then send that link.\r\n   > If you want to convert multiple frames, send multiple links.\r\n\r\n   **B. Single frame link** (has specific `node-id`):\r\n   Run `scripts/figma_fetch.py \"<url>\"` → returns that frame's design data.\r\n   Proceed to analysis.\r\n\r\n   **C. Multiple links** (user sends 2+ URLs):\r\n   First, determine the relationship by examining frame names and user context:\r\n\r\n   - **Same page, different visual states** (e.g. \"首页-有banner\" and \"首页-无banner\"): Use `--compare` mode to fetch all and get a diff summary. Generate multi-state code (conditional visibility, state switching).\r\n\r\n   - **Parent page + overlay/drawer** (e.g. \"首页\" + \"首页-抽屉-xxx\"): Generate each as an **independent layout file**. Then tell the user the relationship:\r\n     > Frame 1 (\"首页\") and Frame 2 (\"首页-抽屉\") look like a main page + side drawer.\r\n     > I've generated two separate layout files. How you wire them together (DrawerLayout, Navigation, etc.) depends on your project architecture.\r\n\r\n     The Skill's job is generating UI layout code, not deciding architecture (Activity vs Fragment vs Navigation).\r\n\r\n   - **Different independent pages** (e.g. \"首页\" + \"设置页\" + \"个人中心\"): Process each independently. Fetch them **one at a time** with a pause between requests to avoid rate limiting. Present a summary of all pages, then ask which to convert first (or convert all sequentially).\r\n\r\n   - **Not sure**: Ask the user — \"These frames look related but I'm not sure how. Are they different states of the same page, a page with an overlay, or independent pages?\"\r\n\r\n   **Rate limit protection for multiple links**: When fetching multiple nodes, wait 2-3 seconds between requests. Never fire more than 2 requests in parallel.\r\n\r\n2. **If the link has no specific node-id**, ask the user to re-copy from the specific frame (see A above). Do NOT call the API.\r\n\r\n3. Analyze the structure: identify sections, repeated patterns, component types\r\n4. Note INSTANCE nodes — they indicate reusable components. Check `variantProperties` for component state (e.g. State=Default, Size=Large) — these map to multi-state code\r\n5. Note gradient/shadow data — flag for the user if complex\r\n6. Apply Figma node interpretation rules before generating code\r\n\r\n**Detailed interpretation rules**: Read `references/figma-interpretation.md`\r\n\r\n### Step 1.5: Structure Summary\r\n\r\nBefore asking any questions, present a brief **structure summary** to the user so they can confirm your understanding:\r\n\r\n> I see: [navigation bar with back button + title] → [2 content sections: user profile card, settings list (8 items)] → [bottom action button]. Total ~25 nodes.\r\n\r\nKeep it to 2-3 lines. Mention:\r\n- Major sections identified (nav bar, content areas, footer)\r\n- Repeated patterns (\"8 similar list items\", \"3 tab labels\")\r\n- Notable elements (gradients, complex illustrations, stacked cards)\r\n\r\nIf the user says \"that's wrong\" or corrects the structure, adjust understanding before proceeding to Step 2.\r\n\r\nIf the design has ≤10 leaf nodes (visible elements that map to actual views), skip this step — the structure is simple enough to proceed directly.\r\n\r\n### Step 2: Confirm & Clarify\r\n\r\n**Question priority (strict order — ask earlier questions first):**\r\n\r\n1. **Output format** (MUST ask first unless user already specified)\r\n   → Android XML / Compose / SwiftUI / UIKit\r\n   This determines all subsequent analysis phrasing and code output.\r\n\r\n2. **Structural ambiguities** (only ask what you're genuinely unsure about)\r\n   → \"These N items look similar — dynamic list or fixed layout?\"\r\n   → \"This area: single image asset or icon-on-background combo?\"\r\n\r\n3. **Component choices** (only if platform-relevant)\r\n   → \"Any custom components to use? (otherwise I'll use platform defaults)\"\r\n\r\n**Rules for questions:**\r\n- Skip any question the user already answered via inline hints\r\n- Max 3-5 questions total, fewer is better\r\n- Each question gives concrete options with one-line pros/cons\r\n- Every question includes an open option: \"or tell me more about this\"\r\n- Use natural language, no JSON or technical dumps\r\n- If everything is clear (user gave full context + simple structure), skip Step 2 entirely\r\n\r\n**Confidence guide — when to ask vs. when to just generate:**\r\n- ≥3 sibling nodes with similar structure → likely a list → ASK (dynamic vs static)\r\n- INSTANCE nodes sharing same componentId → reusable component → MENTION but can default\r\n- Single clear hierarchy, no ambiguity → high confidence → SKIP questions, go to Step 3\r\n- Gradient/complex shadow in design → MENTION in summary (\"I see a gradient here, I'll approximate it as X\")\r\n\r\n### Step 2.5: Project Scan — Ask First\r\n\r\n**⚠️ Always ask the user before scanning their project.** Scanning reads local files;\r\nthe user should know and agree.\r\n\r\n> \"你的项目在 /path/to/project 对吗？要不要我先扫描一下项目里已有的资源\r\n>（颜色、文案、图片、自定义组件），这样生成代码时可以直接复用已有的东西？\"\r\n\r\nIf the user agrees:\r\n\r\n```bash\r\npython scripts/project_scan.py /path/to/project --json --output scan-report.json\r\n```\r\n\r\nThen read `scan-report.json` and `references/scan-usage.md`.\r\n\r\nIf the user declines: proceed with hardcoded values per generation rules.\r\n\r\n**How to present scan results:**\r\n\r\nKeep it brief and useful — not a JSON dump:\r\n\r\n> ✓ 扫描完成：找到 3 个模块、24 个颜色、18 条文案。\r\n> 其中有 6 个颜色映射到了主题色（primary、surface 等），生成代码时会用项目资源引用。\r\n\r\n**If scan found issues, tell the user naturally:**\r\n\r\n> 扫描完了，找到 N 个资源。不过 [具体问题，如某个模块没找到资源文件]，\r\n> 你项目里这部分是怎么组织的？\r\n\r\n**If no project path is known yet, don't scan.** Proceed with hardcoded generation.\r\n\r\n### Step 3: Generate Code\r\n\r\nAfter user confirms (or if no questions needed), generate code files.\r\n\r\n**Detailed generation rules**: Read `references/generation-rules.md`\r\n\r\nIf multiple files are needed, output each with a clear filename header:\r\n```\r\n📄 activity_notification_settings.xml\r\n[code]\r\n\r\n📄 item_expert_notification.xml\r\n[code]\r\n```\r\n\r\n### Step 4: Iterate & Capture Feedback\r\n\r\nAfter showing code, ask briefly:\r\n> Matches the design? Any adjustments?\r\n\r\n**The user can then give feedback to refine the output.** Common iterations:\r\n- \"间距大了\" → adjust specific spacing\r\n- \"Switch 换成我们的 CustomSwitch\" → swap component\r\n- \"把标题栏去掉\" → remove section\r\n- \"换成 Compose 版本\" → regenerate in different format\r\n- \"颜色不对，这里应该是 #333333\" → fix specific values\r\n\r\nContinue iterating until the user is satisfied.\r\n\r\n**Iteration output rule:**\r\n- If the file has already been written to disk → read the current file, apply only the minimal patch, output just the changed lines with clear context (file path + line range). Do NOT regenerate the whole file.\r\n- If the code only exists in the conversation (not written to disk) → output only the changed snippet with a comment indicating where it replaces (e.g., `// replaces lines 12-18 in activity_main.xml`). Do NOT repeat the entire file.\r\n- Only regenerate the full file if the user explicitly asks (e.g., \"重新生成完整文件\", \"show me the full file\").\r\n\r\n**⚠️ Before logging any feedback, tell the user:**\r\n  > \"I'll save this correction locally to `feedback-log.md` to improve future output.\r\n  > It stores before/after snippets — is that OK?\"\r\n  Only proceed if the user agrees.\r\n\r\n**⚠️ IMPORTANT: Every time the user corrects your output (layout issue, wrong component, spacing problem, etc.), you MUST log it to `feedback-log.md` before proceeding with the fix (after user consent). Do not skip this step — the log is how the skill learns and improves over time.**\r\n\r\n**Feedback capture:**\r\nWhenever the user corrects your generated output (with consent), log the correction to `feedback-log.md` in the project root (create if it doesn't exist). Each entry follows this format:\r\n\r\n```\r\n## YYYY-MM-DD HH:MM\r\n- **Platform**: Android XML / Compose / SwiftUI / UIKit\r\n- **Figma node type**: (e.g., FRAME with icon, Tab bar, Button group)\r\n- **Issue**: Brief description of what was wrong\r\n- **Before**: What the agent generated (snippet or description)\r\n- **After**: What the user wanted (snippet or description)\r\n- **Rule candidate**: (optional) If this correction suggests a general pattern rule, note it here\r\n```\r\n\r\nLog entries should be:\r\n- **Concise** — only the relevant diff, not entire files\r\n- **Categorized** — always include platform and Figma node type for later analysis\r\n- **Actionable** — focus on the mapping error, not cosmetic preferences (e.g., \"user prefers 16dp\" is not a rule; \"VECTOR compositions should be single ImageView\" is)\r\n\r\nDo NOT log:\r\n- One-off personal preferences (specific color choices, naming conventions)\r\n- Corrections to non-mapping issues (typos, import statements)\r\n- Feedback the user explicitly says is project-specific, not general\r\n\r\nPeriodically (or when asked), run `scripts/feedback_analyze.py` to identify patterns and generate rule candidates.\r\n\r\n## Error Handling\r\n\r\n- **FIGMA_TOKEN not set** (script outputs `FIGMA_TOKEN_NOT_SET`):\r\n  Tell the user:\r\n  > I need a Figma Personal Access Token to fetch the design.\r\n  > ⚠️ **Do not paste it into this chat** — chat messages may be logged.\r\n  > Set it as an environment variable and restart.\r\n  > Get one at: Figma → avatar → Settings → Security → Personal Access Tokens\r\n  >\r\n  >   Windows: `setx FIGMA_TOKEN \"figd_xxx\"`\r\n  >   macOS/Linux: add `export FIGMA_TOKEN=\"figd_xxx\"` to ~/.zshrc\r\n- **FIGMA_TOKEN invalid** (API returns 403/401) → tell user the token may have expired or been revoked.\r\n  Direct them to regenerate from Figma Settings → Security → Personal Access Tokens.\r\n- **Invalid URL** → show valid URL example: `https://www.figma.com/design/<fileKey>/<name>?node-id=<id>`\r\n- **API error** → show error message, suggest checking network/proxy\r\n- **Node too large (>200 children)** → suggest selecting a smaller frame\r\n- **Depth auto-increased** → the script auto-retries with deeper depth if it detects truncated children. Inform user if this happens (\"I needed to fetch deeper to get all details\").\n\nFile v2.2.5:README.md\n\n# figma-to-mobile\r\n\r\nConvert Figma designs to production-ready mobile UI code using AI.\r\n\r\nSupports: **Jetpack Compose** · **Android XML** · **SwiftUI** · **UIKit**\r\n\r\n## Demo\r\n\r\nHere's a side-by-side comparison using a [Material Design 3 Messaging App](https://www.figma.com/community/file/1169726503071187057/) from Figma Community:\r\n\r\n![Figma to Compose comparison](assets/demo-comparison.png)\r\n\r\n**Left:** Figma design · **Right:** Generated Jetpack Compose code running in Android Studio\r\n\r\nThe tool reads the Figma design tree (auto-layout, style refs, variants) and generates idiomatic code — not pixel-positioned boxes.\r\n\r\n## How It Works\r\n\r\n1. **Fetch** — Python script calls Figma API to extract the node tree\r\n2. **Interpret** — AI analyzes layout semantics: \"6 similar rows → `LazyColumn`\", \"horizontal stack → `Row`\"\r\n3. **Generate** — Outputs platform-idiomatic code with proper theming (Material3, SF Symbols, etc.)\r\n4. **Iterate** — Refine through natural language: \"make the header sticky\", \"switch to dark theme\"\r\n\r\n## Install\r\n\r\n### OpenClaw\r\n\r\n```bash\r\nclawhub install figma-to-mobile\r\n```\r\n\r\n### Claude Code\r\n\r\nCopy the `figma-to-mobile` folder into your project:\r\n```\r\nyour-project/.claude/skills/figma-to-mobile/\r\n```\r\n\r\n### GitHub Copilot\r\n\r\nCopy the `figma-to-mobile` folder into your project:\r\n```\r\nyour-project/.agents/skills/figma-to-mobile/\r\n```\r\n\r\n## Setup\r\n\r\n> ⚠️ **Figma Token** — Set the `FIGMA_TOKEN` environment variable.\r\n> Generate one at Figma → Avatar → Settings → Security → Personal Access Tokens.\r\n>\r\n> **Do not paste your token into chat** — chat messages may be logged.\r\n> Set it via your shell profile or system environment variables:\r\n>\r\n> ```bash\r\n> # macOS/Linux\r\n> export FIGMA_TOKEN=\"figd_your_token_here\"\r\n>\r\n> # Windows PowerShell\r\n> $env:FIGMA_TOKEN = \"figd_your_token_here\"\r\n> ```\r\n\r\n## Usage\r\n\r\nPaste a Figma design link in your AI assistant's chat:\r\n\r\n> Convert this to Jetpack Compose: https://www.figma.com/design/xxx/Project?node-id=100-200\r\n\r\nThe agent will:\r\n1. Fetch the design data from Figma API\r\n2. Ask clarifying questions (platform, list vs static, etc.)\r\n3. Generate production-ready code files\r\n4. Iterate based on your feedback — only patches the changed lines, never regenerates the full file unless you ask\r\n\r\n## What Makes It Different\r\n\r\n| Feature | Screenshot-based tools | figma-to-mobile |\r\n|---------|----------------------|-----------------|\r\n| Input | Screenshot/image | Figma API (design tree) |\r\n| Layout understanding | Pixel positions | Auto-layout semantics |\r\n| Output quality | Absolute positioning | Idiomatic code (LazyColumn, VStack, etc.) |\r\n| Iteration | Re-screenshot | Natural language refinement |\r\n| Cost | Paid subscription | Free & open source |\r\n\r\n## What's in the Box\r\n\r\n```\r\nfigma-to-mobile/\r\n├── SKILL.md              # Agent instructions (the brain)\r\n├── scripts/\r\n│   └── figma_fetch.py    # Figma API data fetcher\r\n├── references/\r\n│   ├── compose-patterns.md   # Jetpack Compose mapping rules\r\n│   ├── xml-patterns.md       # Android XML mapping rules\r\n│   ├── swiftui-patterns.md   # SwiftUI mapping rules\r\n│   └── uikit-patterns.md     # UIKit mapping rules\r\n└── assets/\r\n    └── demo-comparison.png   # Demo comparison image\r\n```\r\n\r\n## Requirements\r\n\r\n- Python 3.8+ with `requests` package\r\n- Figma Personal Access Token (free)\r\n\r\n## Feedback & Issues\r\n\r\nFound a bug or have a suggestion?\r\n[Open an issue on GitHub](https://github.com/TimeAground/figma-to-mobile/issues)\r\n\r\n## License\r\n\r\nMIT\n\nFile v2.2.5:_meta.json\n\n{\n  \"ownerId\": \"kn7crx9z6m9cw8gpcwbhp8na31843hnr\",\n  \"slug\": \"figma-to-mobile\",\n  \"version\": \"2.2.5\",\n  \"publishedAt\": 1784985084222\n}\n\nFile v2.2.5:references/compose-patterns.md\n\n# Compose Patterns — Figma to Jetpack Compose Mapping\r\n\r\n> Purpose: Map Figma properties to Jetpack Compose code.\r\n> This is a **mapping reference**, not a Compose tutorial — the agent already knows Compose conventions.\r\n\r\n## Layout Selection Guide\r\n\r\n| Figma Structure | Recommended Composable |\r\n|---|---|\r\n| Vertical stack | Column |\r\n| Horizontal stack | Row |\r\n| Overlapping / z-stacking | Box |\r\n| Repeating similar items (≥3) | LazyColumn / LazyRow |\r\n| Page structure with top/bottom bars | Scaffold |\r\n| Complex relative positioning | Box with Modifier.align / offset |\r\n\r\n## Auto-layout Mapping\r\n\r\n| Figma Property | Compose Equivalent |\r\n|---|---|\r\n| layoutMode: VERTICAL | Column |\r\n| layoutMode: HORIZONTAL | Row |\r\n| itemSpacing | Arrangement.spacedBy(X.dp) |\r\n| padding* | Modifier.padding() |\r\n| primaryAxisAlignItems: CENTER | verticalArrangement = Arrangement.Center |\r\n| counterAxisAlignItems: CENTER | horizontalAlignment = Alignment.CenterHorizontally |\r\n| layoutGrow: 1 | Modifier.weight(1f) |\r\n| primaryAxisSizingMode: FIXED | Modifier.height/width(X.dp) |\r\n| counterAxisSizingMode: AUTO | wrapContentWidth/Height |\r\n\r\n## Size Conversion\r\n\r\n- Figma px → Compose .dp (1:1)\r\n- Figma font px → Compose .sp (1:1)\r\n\r\n## Shadow Mapping\r\n\r\n```kotlin\r\n// Elevation shadow\r\nCard(elevation = CardDefaults.cardElevation(defaultElevation = 4.dp))\r\n\r\n// Custom shadow (Compose 1.6+)\r\nModifier.shadow(\r\n    elevation = 4.dp,\r\n    shape = RoundedCornerShape(12.dp),\r\n    ambientColor = Color(0x1A000000),\r\n    spotColor = Color(0x33000000)\r\n)\r\n```\r\n\r\n## Gradient Mapping\r\n\r\n```kotlin\r\n// Linear gradient\r\nModifier.background(\r\n    Brush.linearGradient(\r\n        colors = listOf(Color(0xFFFF6B6B), Color(0xFF4ECDC4)),\r\n        start = Offset(0f, 0f),\r\n        end = Offset(0f, Float.POSITIVE_INFINITY)\r\n    )\r\n)\r\n```\r\n\r\n## Per-corner Radius\r\n\r\n```kotlin\r\nRoundedCornerShape(\r\n    topStart = 12.dp,\r\n    topEnd = 12.dp,\r\n    bottomEnd = 0.dp,\r\n    bottomStart = 0.dp\r\n)\r\n```\r\n\r\n## Page Architecture Patterns\r\n\r\nThese patterns reflect how Android apps are **actually structured** in production with Compose.\r\n\r\n### Multi-Tab Pages\r\nWhen a design shows **multiple tabs** (≥2 text labels acting as navigation):\r\n- Use `TabRow` + `HorizontalPager` (accompanist or foundation) for top tabs\r\n- Use `NavigationBar` for bottom navigation\r\n- Do NOT use plain `Text` composables for tabs — they lack selection state, indicators, and swipe support\r\n- Each tab's content should be a separate `@Composable` function\r\n\r\n```kotlin\r\n@Composable\r\nfun TabScreen() {\r\n    val pagerState = rememberPagerState(pageCount = { 3 })\r\n    val scope = rememberCoroutineScope()\r\n    val tabs = listOf(\"关注\", \"推荐\", \"热榜\")\r\n\r\n    Column {\r\n        TabRow(\r\n            selectedTabIndex = pagerState.currentPage,\r\n            containerColor = Color.White,\r\n            contentColor = Color(0xFF0F0F0F),\r\n            indicator = { tabPositions ->\r\n                TabRowDefaults.SecondaryIndicator(\r\n                    modifier = Modifier.tabIndicatorOffset(tabPositions[pagerState.currentPage]),\r\n                    color = Color(0xFF0F0F0F)\r\n                )\r\n            }\r\n        ) {\r\n            tabs.forEachIndexed { index, title ->\r\n                Tab(\r\n                    selected = pagerState.currentPage == index,\r\n                    onClick = { scope.launch { pagerState.animateScrollToPage(index) } },\r\n                    text = {\r\n                        Text(\r\n                            title,\r\n                            fontWeight = if (pagerState.currentPage == index) FontWeight.Bold else FontWeight.Normal,\r\n                            color = if (pagerState.currentPage == index) Color(0xFF0F0F0F) else Color(0xFF858A99)\r\n                        )\r\n                    }\r\n                )\r\n            }\r\n        }\r\n        HorizontalPager(state = pagerState) { page ->\r\n            when (page) {\r\n                0 -> FollowingScreen()\r\n                1 -> RecommendScreen()\r\n                2 -> HotListScreen()\r\n            }\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n### Navigation Bar — Treat as a Single Unit\r\nThe navigation bar (back button + title, possibly + right action) is **one logical container**.\r\n- Use `TopAppBar` / `CenterAlignedTopAppBar` for standard Compose nav bars\r\n- For custom nav bars, use `Row` as a single container, constrain content below it\r\n\r\n```kotlin\r\n@Composable\r\nfun CustomNavBar(onBack: () -> Unit, title: String) {\r\n    Row(\r\n        modifier = Modifier\r\n            .fillMaxWidth()\r\n            .padding(horizontal = 20.dp, vertical = 8.dp),\r\n        verticalAlignment = Alignment.CenterVertically\r\n    ) {\r\n        IconButton(\r\n            onClick = onBack,\r\n            modifier = Modifier\r\n                .size(32.dp)\r\n                .background(Color.Black, CircleShape)\r\n        ) {\r\n            Icon(\r\n                imageVector = Icons.AutoMirrored.Filled.ArrowBack,\r\n                contentDescription = \"返回\",\r\n                tint = Color.White,\r\n                modifier = Modifier.size(18.dp)\r\n            )\r\n        }\r\n        Spacer(Modifier.weight(1f))\r\n        Text(title, fontSize = 17.sp, fontWeight = FontWeight.Bold)\r\n        Spacer(Modifier.weight(1f))\r\n        Spacer(Modifier.size(32.dp)) // Balance spacer\r\n    }\r\n}\r\n```\r\n\r\n### Buttons with Icon + Text\r\nPrefer `Row` inside `Button` or a clickable `Row` for reliable icon+text buttons:\r\n\r\n```kotlin\r\n// Outlined button with icon\r\nOutlinedButton(\r\n    onClick = {},\r\n    shape = RoundedCornerShape(12.dp),\r\n    border = BorderStroke(1.dp, Color(0xFFDCDCDC)),\r\n    modifier = Modifier.fillMaxWidth().height(40.dp)\r\n) {\r\n    Icon(painter = painterResource(R.drawable.ic_video), contentDescription = null, modifier = Modifier.size(20.dp))\r\n    Spacer(Modifier.width(6.dp))\r\n    Text(\"查看视频\", fontSize = 15.sp, fontWeight = FontWeight.Bold, color = Color(0xFF0F0F0F))\r\n}\r\n\r\n// Solid filled button\r\nButton(\r\n    onClick = {},\r\n    shape = RoundedCornerShape(12.dp),\r\n    colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF0158FF)),\r\n    modifier = Modifier.fillMaxWidth().height(40.dp)\r\n) {\r\n    Text(\"查看报告\", fontSize = 15.sp, fontWeight = FontWeight.Bold)\r\n}\r\n```\r\n\r\n### Switch / Toggle\r\n- Use `Switch` from Material3 — standard, reliable\r\n- Custom colors via `SwitchDefaults.colors()`\r\n\r\n```kotlin\r\nvar checked by remember { mutableStateOf(true) }\r\nSwitch(\r\n    checked = checked,\r\n    onCheckedChange = { checked = it },\r\n    colors = SwitchDefaults.colors(checkedTrackColor = Color(0xFF0158FF))\r\n)\r\n```\r\n\r\n### Input Fields vs Display Fields\r\nFigma cannot distinguish between `TextField` and `Text` — both appear as RECTANGLE + TEXT.\r\n- **Placeholder-like text** with input styling → `TextField` / `OutlinedTextField`\r\n- **Static display text** → `Text`\r\n- **ASK the user if unsure**\r\n\r\n```kotlin\r\nvar text by remember { mutableStateOf(\"\") }\r\nTextField(\r\n    value = text,\r\n    onValueChange = { text = it },\r\n    placeholder = { Text(\"请输入昵称\", color = Color(0xFFB8B8B8)) },\r\n    modifier = Modifier.width(295.dp).height(48.dp),\r\n    shape = RoundedCornerShape(8.dp),\r\n    colors = TextFieldDefaults.colors(\r\n        unfocusedContainerColor = Color.White,\r\n        focusedContainerColor = Color.White,\r\n        unfocusedIndicatorColor = Color.Transparent,\r\n        focusedIndicatorColor = Color.Transparent\r\n    ),\r\n    textStyle = TextStyle(fontSize = 15.sp, color = Color(0xFF0F0F0F)),\r\n    singleLine = true\r\n)\r\n```\r\n\r\n## Width Strategy — Fixed vs Flexible\r\n\r\nFigma designs are typically based on a 375px canvas. Width values in Figma are **calculated results**, not design intent. Reverse-engineer the intent to decide Compose properties.\r\n\r\nCore question: **Is this element a \"fixed width\" or \"fill remaining space\"?**\r\n\r\n### Rule 1: Single Element Fills Screen Width\r\nElement width + left/right offset ≈ screen width (375), and left/right margins are symmetric or near-symmetric → `Modifier.fillMaxWidth()` + `padding(horizontal = X.dp)`\r\n\r\n- Example: width 335 + left 20 + right 20 = 375 → `Modifier.fillMaxWidth().padding(horizontal = 20.dp)`\r\n- **Heuristic**: width >85% of screen and symmetric left/right margins → prefer `fillMaxWidth()` + padding\r\n- Adapts automatically to different screen widths\r\n\r\n### Rule 2: Side-by-Side Elements — Identify the Flexible Side\r\nWhen multiple elements are arranged horizontally, determine each element as **fixed** or **flexible**:\r\n\r\n- **Fixed side**: elements with clear visual dimensions — avatars, icons, buttons, fixed-width labels. Use fixed `dp` values.\r\n- **Flexible side**: width = screen width - fixed widths - gaps. Usually text, descriptions, content areas. Use `Modifier.weight(1f)` to fill remaining space.\r\n\r\nValidation: `fixed_width + flexible_width + all_gaps ≈ screen_width`\r\n\r\n```kotlin\r\n// Example: Avatar (56dp) + gap (16dp) + Text (flexible) + margin-right (20dp) + margin-left (20dp) = 375\r\nRow(\r\n    modifier = Modifier\r\n        .fillMaxWidth()\r\n        .padding(horizontal = 20.dp),\r\n    horizontalArrangement = Arrangement.spacedBy(16.dp),\r\n    verticalAlignment = Alignment.CenterVertically\r\n) {\r\n    Image(\r\n        painter = painterResource(R.drawable.ic_avatar),\r\n        contentDescription = \"Avatar\",\r\n        modifier = Modifier\r\n            .size(56.dp)\r\n            .clip(CircleShape)\r\n    )\r\n    Text(\r\n        text = \"User Name\",\r\n        modifier = Modifier.weight(1f),  // Flexible side\r\n        fontSize = 15.sp\r\n    )\r\n}\r\n```\r\n\r\n### Rule 3: Fixed Width + Center\r\nElement is visibly narrower than screen and centered, not edge-aligned → fixed width + center alignment in parent\r\n\r\n- Example: width 295 centered in 375 → `Modifier.width(295.dp)` + parent `horizontalAlignment = Alignment.CenterHorizontally`\r\n- Common for input fields, centered cards\r\n\r\n### LazyColumn Item Width\r\nItems in `LazyColumn` always use `Modifier.fillMaxWidth()`. The LazyColumn's own width is determined by parent constraints.\r\n\r\n```kotlin\r\nLazyColumn(\r\n    modifier = Modifier.fillMaxWidth()\r\n) {\r\n    items(count = 20, key = { it }) { index ->\r\n        Text(\r\n            text = \"Item $index\",\r\n            modifier = Modifier\r\n                .fillMaxWidth()\r\n                .padding(16.dp)\r\n        )\r\n    }\r\n}\r\n```\r\n\r\n## Multi-State Views\r\n\r\nCompose uses **state** to drive conditional UI changes. Each state change affects Modifier chains and child rendering.\r\n\r\n### Selected / Unselected State\r\nUse `remember { mutableStateOf() }` with conditional Modifiers:\r\n\r\n```kotlin\r\nvar isSelected by remember { mutableStateOf(false) }\r\n\r\nCard(\r\n    modifier = Modifier\r\n        .fillMaxWidth()\r\n        .height(120.dp)\r\n        .background(\r\n            color = if (isSelected) Color(0xFF0158FF) else Color.White,\r\n            shape = RoundedCornerShape(12.dp)\r\n        )\r\n        .border(\r\n            width = 2.dp,\r\n            color = if (isSelected) Color(0xFF0158FF) else Color(0xFFDCDCDC),\r\n            shape = RoundedCornerShape(12.dp)\r\n        )\r\n        .clickable { isSelected = !isSelected },\r\n    colors = CardDefaults.cardColors(containerColor = Color.Transparent),\r\n    shape = RoundedCornerShape(12.dp)\r\n) {\r\n    Box(\r\n        modifier = Modifier.fillMaxSize(),\r\n        contentAlignment = Alignment.Center\r\n    ) {\r\n        Text(\r\n            text = if (isSelected) \"✓ Selected\" else \"选择\",\r\n            color = if (isSelected) Color.White else Color(0xFF858A99),\r\n            fontSize = 16.sp,\r\n            fontWeight = FontWeight.Bold\r\n        )\r\n    }\r\n}\r\n```\r\n\r\nExample: Gender selection card\r\n```kotlin\r\n@Composable\r\nfun GenderSelector() {\r\n    var selectedGender by remember { mutableStateOf<String?>(null) }\r\n    \r\n    Row(\r\n        modifier = Modifier\r\n            .fillMaxWidth()\r\n            .padding(16.dp),\r\n        horizontalArrangement = Arrangement.spacedBy(12.dp)\r\n    ) {\r\n        listOf(\"Male\" to \"男\", \"Female\" to \"女\").forEach { (key, label) ->\r\n            Card(\r\n                modifier = Modifier\r\n                    .weight(1f)\r\n                    .height(80.dp)\r\n                    .background(\r\n                        color = if (selectedGender == key) Color(0xFF0158FF) else Color.White,\r\n                        shape = RoundedCornerShape(12.dp)\r\n                    )\r\n                    .border(\r\n                        width = 1.dp,\r\n                        color = if (selectedGender == key) Color(0xFF0158FF) else Color(0xFFDCDCDC),\r\n                        shape = RoundedCornerShape(12.dp)\r\n                    )\r\n                    .clickable { selectedGender = key },\r\n                colors = CardDefaults.cardColors(containerColor = Color.Transparent)\r\n            ) {\r\n                Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {\r\n                    Text(\r\n                        text = label,\r\n                        color = if (selectedGender == key) Color.White else Color(0xFF0F0F0F),\r\n                        fontWeight = FontWeight.Bold\r\n                    )\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n### Disabled / Enabled State via Alpha\r\nUse `Modifier.alpha()` combined with `.clickable(enabled = boolean)`:\r\n\r\n```kotlin\r\nvar isValid by remember { mutableStateOf(false) }\r\n\r\nButton(\r\n    onClick = { /* handle */ },\r\n    enabled = isValid,\r\n    modifier = Modifier\r\n        .fillMaxWidth()\r\n        .height(40.dp)\r\n        .alpha(if (isValid) 1f else 0.3f),\r\n    colors = ButtonDefaults.buttonColors(\r\n        containerColor = Color(0xFF0158FF),\r\n        disabledContainerColor = Color(0xFF0158FF)\r\n    )\r\n) {\r\n    Text(\"提交\", color = Color.White, fontWeight = FontWeight.Bold)\r\n}\r\n```\r\n\r\n### Stacked / Overlapping Cards\r\nWhen the design shows **stacked or overlapping cards** with interaction (swipe, drag, peek), **do NOT generate static nested layouts**. Instead:\r\n- Ask the user about the intended interaction: swipe between cards (→ `HorizontalPager`), drag gestures (→ custom `Modifier.pointerInput()`), or just visual overlap?\r\n- Provide a template based on the answer\r\n- For now, suggest the most likely pattern based on context\r\n\r\n```kotlin\r\n// If swipeable between cards: use HorizontalPager\r\nval pagerState = rememberPagerState(pageCount = { 3 })\r\nHorizontalPager(\r\n    state = pagerState,\r\n    modifier = Modifier\r\n        .fillMaxWidth()\r\n        .height(300.dp)\r\n) { page ->\r\n    Card(\r\n        modifier = Modifier\r\n            .fillMaxWidth()\r\n            .padding(16.dp),\r\n        elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)\r\n    ) {\r\n        Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {\r\n            Text(\"Card ${page + 1}\")\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n## Dark Mode Patterns\r\n\r\nCompose integrates dark mode through `MaterialTheme` and system state. Choose the approach based on project configuration.\r\n\r\n### Using MaterialTheme Colors (Recommended)\r\nWhen the project has a `Theme.kt` file, all colors automatically follow the theme:\r\n\r\n```kotlin\r\n@Composable\r\nfun MyCard() {\r\n    Card(\r\n        modifier = Modifier\r\n            .fillMaxWidth()\r\n            .padding(16.dp),\r\n        colors = CardDefaults.cardColors(\r\n            containerColor = MaterialTheme.colorScheme.surface,\r\n            contentColor = MaterialTheme.colorScheme.onSurface\r\n        )\r\n    ) {\r\n        Text(\r\n            text = \"Content\",\r\n            color = MaterialTheme.colorScheme.primary,\r\n            style = MaterialTheme.typography.bodyMedium\r\n        )\r\n    }\r\n}\r\n```\r\n\r\n### Manual Dark Mode Detection\r\nUse `isSystemInDarkTheme()` when you need to apply specific logic:\r\n\r\n```kotlin\r\nval isDark = isSystemInDarkTheme()\r\nval backgroundColor = if (isDark) Color(0xFF1A1A1A) else Color.White\r\nval textColor = if (isDark) Color.White else Color(0xFF0F0F0F)\r\n\r\nBox(\r\n    modifier = Modifier\r\n        .fillMaxSize()\r\n        .background(backgroundColor),\r\n    contentAlignment = Alignment.Center\r\n) {\r\n    Text(\"Dark mode aware\", color = textColor)\r\n}\r\n```\r\n\r\n### Dynamic Colors (Android 12+)\r\nAndroid 12+ supports Material You dynamic colors based on system theme:\r\n\r\n```kotlin\r\n@Composable\r\nfun DynamicThemeExample() {\r\n    val colorScheme = when {\r\n        isSystemInDarkTheme() -> dynamicDarkColorScheme(LocalContext.current)\r\n        else -> dynamicLightColorScheme(LocalContext.current)\r\n    }\r\n    \r\n    MaterialTheme(colorScheme = colorScheme) {\r\n        // App content\r\n    }\r\n}\r\n```\r\n\r\n### Rules\r\n- If the project already has a `Theme.kt` with `colorScheme` defined, **always use `MaterialTheme.colorScheme.*`** for colors\r\n- If the project has **no dark mode support** configured, **do not add it** — output light-mode-only code\r\n- Never hardcode colors when `MaterialTheme` is available\r\n\r\n## LazyColumn / LazyRow Item Patterns\r\n\r\n### Basic Item Sizing\r\n- Items should use `Modifier.fillMaxWidth()` by default\r\n- LazyColumn/LazyRow width is set on the composable itself, items fill that width\r\n\r\n### Using `key` for Performance\r\nWhen list content can change, add `key` to prevent recomposition issues:\r\n\r\n```kotlin\r\nLazyColumn(\r\n    modifier = Modifier.fillMaxSize()\r\n) {\r\n    items(\r\n        count = items.size,\r\n        key = { index -> items[index].id },  // Stable identifier\r\n        contentType = { \"item\" }\r\n    ) { index ->\r\n        ListItem(\r\n            modifier = Modifier.fillMaxWidth(),\r\n            item = items[index]\r\n        )\r\n    }\r\n}\r\n```\r\n\r\n### Mixed Content Types with `contentType`\r\nWhen a list mixes different item types (headers, regular items, footers), use `contentType` to help Compose recycle properly:\r\n\r\n```kotlin\r\ndata class ListSection(\r\n    val type: String,  // \"header\", \"item\", \"footer\"\r\n    val content: Any\r\n)\r\n\r\nLazyColumn {\r\n    items(\r\n        count = sections.size,\r\n        key = { index -> sections[index].content.hashCode() },\r\n        contentType = { index -> sections[index].type }\r\n    ) { index ->\r\n        val section = sections[index]\r\n        when (section.type) {\r\n            \"header\" -> HeaderItem(section.content as String)\r\n            \"item\" -> RegularItem(section.content as Item)\r\n            \"footer\" -> FooterItem(section.content as String)\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n### Example: List with Header + Items\r\n```kotlin\r\n@Composable\r\nfun UserListWithHeader(users: List<User>) {\r\n    LazyColumn(\r\n        modifier = Modifier.fillMaxSize()\r\n    ) {\r\n        item {\r\n            Text(\r\n                text = \"用户列表\",\r\n                modifier = Modifier\r\n                    .fillMaxWidth()\r\n                    .padding(16.dp),\r\n                fontSize = 18.sp,\r\n                fontWeight = FontWeight.Bold,\r\n                color = MaterialTheme.colorScheme.primary\r\n            )\r\n        }\r\n        items(\r\n            count = users.size,\r\n            key = { index -> users[index].id }\r\n        ) { index ->\r\n            Row(\r\n                modifier = Modifier\r\n                    .fillMaxWidth()\r\n                    .padding(vertical = 8.dp, horizontal = 16.dp),\r\n                verticalAlignment = Alignment.CenterVertically,\r\n                horizontalArrangement = Arrangement.spacedBy(12.dp)\r\n            ) {\r\n                Image(\r\n                    painter = painterResource(R.drawable.ic_avatar),\r\n                    contentDescription = \"Avatar\",\r\n                    modifier = Modifier\r\n                        .size(48.dp)\r\n                        .clip(CircleShape)\r\n                )\r\n                Text(\r\n                    text = users[index].name,\r\n                    modifier = Modifier.weight(1f),\r\n                    fontSize = 15.sp\r\n                )\r\n                Text(\r\n                    text = users[index].status,\r\n                    fontSize = 13.sp,\r\n                    color = Color(0xFF858A99)\r\n                )\r\n            }\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n## Divider Pattern\r\n\r\nCompose Material3 provides a native `HorizontalDivider` composable. Use it for clean line separators.\r\n\r\n### Basic Divider\r\n```kotlin\r\nHorizontalDivider(\r\n    thickness = 1.dp,\r\n    color = Color(0xFFEEEEEE)\r\n)\r\n```\r\n\r\n### Divider with Custom Spacing\r\n```kotlin\r\nColumn {\r\n    Text(\"Item 1\")\r\n    HorizontalDivider(\r\n        modifier = Modifier\r\n            .fillMaxWidth()\r\n            .padding(vertical = 16.dp),\r\n        thickness = 1.dp,\r\n        color = Color(0xFFDCDCDC)\r\n    )\r\n    Text(\"Item 2\")\r\n}\r\n```\r\n\r\n### Divider in List Items\r\nPlace dividers between list items for visual separation:\r\n\r\n```kotlin\r\nLazyColumn {\r\n    items(items.size) { index ->\r\n        ListItemContent(items[index])\r\n        if (index < items.size - 1) {\r\n            HorizontalDivider(\r\n                thickness = 1.dp,\r\n                color = Color(0xFFEEEEEE),\r\n                modifier = Modifier.padding(horizontal = 16.dp)\r\n            )\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n## Figma Node Interpretation (Compose-specific notes)\r\n\r\n### Container + Icon = Single Image Composable\r\nWhen Figma shows a **FRAME** (with background color + cornerRadius) containing a single **INSTANCE** or **VECTOR** child that is clearly an icon:\r\n- This is one `Image` composable in code, not nested layouts\r\n- `.background()` modifier = the container shape (circle, rounded rect, etc.)\r\n- `painter` parameter = the icon drawable\r\n- Typical signal: outer FRAME has cornerRadius ≥ 50% of size (circular), inner child is much smaller\r\n\r\n```kotlin\r\n// Figma: FRAME(32×32, #000000, cornerRadius=16) → INSTANCE(18×18) = one Image\r\nImage(\r\n    painter = painterResource(R.drawable.ic_arrow),\r\n    contentDescription = \"Arrow\",\r\n    modifier = Modifier\r\n        .size(32.dp)\r\n        .background(Color.Black, RoundedCornerShape(16.dp))\r\n        .padding(7.dp),  // Center the 18dp icon in 32dp container\r\n    colorFilter = ColorFilter.tint(Color.White)\r\n)\r\n```\r\n\r\n### RECTANGLE as Background\r\nWhen a GROUP's **first child** is a RECTANGLE with the **same dimensions** as the GROUP:\r\n- The RECTANGLE is a background shape, not an independent composable\r\n- Map it to `.background()` modifier on the parent container\r\n- Signal: RECTANGLE is first child, width≈GROUP width, height≈GROUP height\r\n\r\n```kotlin\r\n// Figma: GROUP(150×60) → RECTANGLE(150×60, #F3F3F4, cornerRadius=8) → TEXT\r\nBox(\r\n    modifier = Modifier\r\n        .width(150.dp)\r\n        .height(60.dp)\r\n        .background(Color(0xFFF3F3F4), RoundedCornerShape(8.dp)),\r\n    contentAlignment = Alignment.Center\r\n) {\r\n    Text(\"Button\")\r\n}\r\n```\r\n\r\n### FRAME with layoutMode vs GROUP without layoutMode\r\n- **FRAME with `layoutMode`**: has Auto-layout → map to `Column` or `Row`\r\n- **GROUP without `layoutMode`**: no Auto-layout → children positioned by coordinates → use `Box` with explicit `Modifier.offset()` or nested `Box` layouts\r\n\r\n```kotlin\r\n// FRAME with layoutMode: VERTICAL → Column\r\nColumn(\r\n    modifier = Modifier\r\n        .fillMaxWidth()\r\n        .padding(16.dp),\r\n    verticalArrangement = Arrangement.spacedBy(12.dp)\r\n) {\r\n    Text(\"Item 1\")\r\n    Text(\"Item 2\")\r\n}\r\n\r\n// GROUP without layoutMode → use Box with offset\r\nBox(\r\n    modifier = Modifier\r\n        .width(200.dp)\r\n        .height(200.dp)\r\n) {\r\n    Text(\r\n        \"Top-left text\",\r\n        modifier = Modifier.offset(x = 10.dp, y = 20.dp)\r\n    )\r\n    Image(\r\n        painter = painterResource(R.drawable.ic_icon),\r\n        contentDescription = null,\r\n        modifier = Modifier\r\n            .size(48.dp)\r\n            .offset(x = 100.dp, y = 100.dp)\r\n    )\r\n}\r\n```\r\n\r\n### Numeric Precision\r\nFigma values often have excessive decimal places. Round appropriately for Compose:\r\n- **`dp` values** (layout, padding, margins): round to **nearest integer**\r\n  - Example: 127.86dp → 128.dp, 7.63dp → 8.dp\r\n- **`sp` values** (font sizes): round to **nearest 0.5**\r\n  - Example: 15.27sp → 15.5sp, 14.99sp → 15sp\r\n- **Exception**: If the exact value maps to a standard size, snap to it\r\n  - Example: 47.99dp → 48.dp (standard Material touch target)\r\n```\n\nFile v2.2.5:references/figma-interpretation.md\n\n# Figma Node Interpretation Rules\r\n> Referenced by SKILL.md Step 1. Read this file when analyzing Figma fetch results.\r\n\r\n## Style References and Text Sizing\r\n\r\n- Note `styleRefs` on nodes — these reference Figma shared styles (design tokens). Nodes sharing the same style ID should use the same code-level token/resource\r\n- Note `textAutoResize` on TEXT nodes — `WIDTH_AND_HEIGHT` means auto-size (wrap_content), `HEIGHT` means fixed width + auto height, absent/NONE means fixed size\r\n\r\n## Figma Node Interpretation (apply before generating any platform code)\r\n\r\n- **Skip system chrome**: StatusBar, HomeIndicator, NavigationBar are iOS design placeholders — don't generate code for them. Also skip duplicate nodes at the same position (Figma artifacts)\r\n- **Skip invisible nodes**: VECTOR/RECTANGLE with empty fills and all strokes `visible: false`, or `absoluteRenderBounds: null` — these are leftover design artifacts that render nothing\r\n- **layoutAlign=STRETCH**: child fills the cross-axis of its auto-layout parent → `match_parent` on the cross-axis / `.frame(maxWidth: .infinity)`. Only present when it differs from INHERIT\r\n- **layoutPositioning=ABSOLUTE**: child is absolutely positioned within an auto-layout parent → use explicit x/y offset constraints instead of flow layout\r\n- **Container + icon = single view**: A FRAME (with background/cornerRadius) wrapping a small VECTOR/INSTANCE is one ImageView/Image, not nested layouts\r\n- **VECTOR/ELLIPSE compositions = single asset**: Multiple small VECTOR/ELLIPSE siblings inside a FRAME are pieces of one icon — output as a single image reference, not separate views\r\n- **RECTANGLE as background**: When a GROUP's first child is a RECTANGLE matching the GROUP's dimensions, it's a background shape, not a separate view\r\n- **GROUP vs FRAME**: FRAME with `layoutMode` maps to structured layouts (LinearLayout, HStack, etc.); GROUP without `layoutMode` uses absolute positioning — map to ConstraintLayout constraints or explicit offsets\r\n- **Round Figma decimals**: Round dp to nearest integer, sp to nearest 0.5. Snap near-standard values (e.g., 47.99 → 48dp)\r\n- **Width strategy**: Don't blindly copy Figma width values — infer design intent. Elements spanning near-full screen width → `match_parent` + `marginHorizontal`. In side-by-side layouts, identify the \"flexible\" element (text/content) vs \"fixed\" element (icon/avatar) and use `0dp` + constraints for the flexible one. See xml-patterns.md \"Width Strategy\" for full rules.\r\n\r\n## Page Architecture Analysis (all platforms)\r\n\r\nThese rules apply to ALL output platforms (Android XML, Compose, SwiftUI, UIKit). The examples below use Android XML, but the principles are universal — each platform's patterns file has the equivalent implementation.\r\n\r\n- Multiple tab labels → likely `TabLayout` + `ViewPager2`, content in Fragment layouts (strong signal, not absolute — ask if unsure)\r\n- Tab color differences between items → selected/unselected state, use `tabSelectedTextColor` / `tabTextColor`, not hardcoded per-tab colors\r\n- Navigation bar with back/close icon → `ImageView` (src + background), not FrameLayout wrapper\r\n- Buttons with icon + text → **always** use `LinearLayout` + `ImageView` + `TextView`. Never use `MaterialButton` with `app:icon` (rendering issues) or `TextView` with `drawableStart`/`drawableEnd` (icon centering issues)\r\n- List item with left sidebar + right content → observe multiple items to judge if equal-height or independent\r\n- **Stacked/overlapping cards** with similar structure (same shape, offset position) → likely a card-switching interaction (swipe, stack, flip). Do NOT generate as separate static Views. Instead, ask the user: \"These cards appear stacked — is this a swipe/switch interaction? If so, what's the switching behavior (left-right swipe, tap to flip, auto-play)?\" The implementation (custom View, ViewPager2, third-party CardStackView, etc.) depends on the answer.\r\n\r\n### Component Variants → Multi-State Code\r\n\r\nWhen INSTANCE nodes have `variantProperties`, they represent different states of the same component.\r\n\r\n**How to use:**\r\n1. If the design contains multiple INSTANCE nodes with the same `componentId` but different `variantProperties` (e.g. State=Default, State=Pressed, State=Disabled), these are states of one component\r\n2. Generate **one** view/composable with state handling, not multiple separate views\r\n3. Map common variant properties to platform state mechanisms:\r\n\r\n| Variant Property | Android XML | Compose | iOS |\r\n|---|---|---|---|\r\n| State=Default/Pressed/Disabled | `selector` drawable + `state_pressed`/`state_enabled` | `Modifier.clickable` + conditional styling | `UIControl` states / `.disabled()` |\r\n| State=Selected/Unselected | `state_selected` + `duplicateParentState` | `var isSelected by remember` + conditional | `isSelected` property |\r\n| State=Active/Inactive | `state_activated` | boolean state + conditional | custom state |\r\n| Size=Small/Medium/Large | Different dimension values in same layout | Parameterized composable with size enum | Parameterized view |\r\n| Type=Primary/Secondary/Outline | Different style resources | Different color/border params | Different configuration |\r\n\r\n4. If only **one state** is present in the design (e.g. only State=Default), still note the variant property — mention to the user that other states exist and ask if they want state handling generated\r\n5. Size and Type variants → generate a parameterized component with enum/sealed class, not hardcoded values\r\n\r\n**Example — Button with State variants:**\r\nIf Figma shows a button with variantProperties `{\"State\": \"Default\"}`, and you know (from componentId) this component also has Pressed and Disabled states:\r\n- Android XML: generate the default state layout + mention selector drawable needed for other states\r\n- Compose: generate a composable with `enabled` parameter + conditional colors/alpha\r\n\r\n### Figma Style References → Consistent Token Usage\r\n\r\nWhen nodes have `styleRefs`, they reference Figma's shared styles (design tokens).\r\n\r\n**How to use:**\r\n1. Nodes sharing the **same style ID** (e.g. same `styleRefs.fill` value) should use the **same code-level resource/token**, even if the actual hex values happen to be identical\r\n2. This helps identify the designer's semantic intent — two texts with the same `styleRefs.text` ID are meant to use the same text style, even if their fontSize happens to differ slightly (designer oversight)\r\n3. When project scan is available: try to match Figma style semantic names to project resources. For example, if multiple nodes share a fill style and the color is `#0158FF` → likely the project's `@color/primary` or `colorScheme.primary`\r\n4. When generating code, **group by styleRef first, then by value**:\r\n   - Same styleRef → must use same resource reference\r\n   - Same value but different styleRef → can use same resource, but note the discrepancy\r\n   - Different value and different styleRef → different resources\r\n\r\n**Practical application:**\r\n- You don't need to resolve the Figma style ID to a name (that requires an extra API call)\r\n- Just use it as a **grouping key**: \"these 5 text nodes all share styleRef.text = S:def456, so they should all use the same text style in code\"\r\n- If you notice inconsistency (same styleRef but different rendered values), flag it to the user as a potential design inconsistency\r\n\r\n## Multi-State Batch Compare\r\n\r\nWhen the user provides multiple Figma frames representing different states of the same page:\r\n\r\n1. Use `figma_fetch.py --compare` to get all states + diff\r\n2. The diff output shows exactly what changes between states (color, text, opacity, visibility, etc.)\r\n3. Use diff results to generate appropriate state handling:\r\n   - Color changes → selector drawable / conditional color\r\n   - Opacity changes → alpha animation / enabled state\r\n   - Visibility changes → View.GONE / if-else block\r\n   - Text changes → dynamic text binding\r\n4. Output a state change summary table after the code\r\n\r\n**Usage:**\r\n```bash\r\n# Two separate Figma URLs (different frames)\r\npython figma_fetch.py \"<url1>\" \"<url2>\" --compare\r\n\r\n# Multiple node-ids from the same file\r\npython figma_fetch.py \"<base_url>\" --nodes \"100:200,100:300\" --compare\r\n```\r\n\r\nThe output JSON contains a `nodes` array (one entry per state with its full simplified tree)\r\nand a `diff` object with `changed`, `added`, and `removed` lists.\r\nBase is always the first node; each subsequent node is diffed against it.\r\n`label` is taken from the Figma node name (e.g. the frame name), or falls back to \"State 1\", \"State 2\".\n\nFile v2.2.5:references/generation-rules.md\n\n# Code Generation Rules\r\n> Referenced by SKILL.md Step 3. Read this file before generating code.\r\n\r\n## Output Rules (absolute — never break these)\r\n\r\n**Write boundary — generated code stays in conversation until confirmed:**\r\n- Output all generated code in the conversation first, as text with filename headers (e.g. `📄 activity_main.xml`).\r\n- Only write files to disk after the user sees the output, confirms it's correct, and explicitly specifies where to put them.\r\n- When writing files, always ask: \"Where should I write this?\" Wait for the user to specify a path.\r\n- Never write files into an existing project module without explicit user instruction to do so.\r\n\r\n**Project scan availability check:**\r\n- If a project scan report is available (Step 2.5 was run): match Figma values against project resources first, then hardcode unmatched values.\r\n- If NO project scan was run: hardcode ALL values directly (hex colors, literal strings, dp dimensions). Do not use `@color/placeholder` or `@string/placeholder` — write real values for instant visual preview.\r\n\r\n- **Colors**: Before hardcoding, search `res/values/colors.xml` (and `res/values/colors_*.xml` if present) for a matching hex value. If found, use the resource reference (e.g. `@color/primary`). If not found, write hex directly (`android:textColor=\"#0F0F0F\"` / `Color(0xFF0F0F0F)`).\r\n- **Strings**: Before hardcoding, search `res/values/strings.xml` for matching text content. If found, use the resource reference (e.g. `@string/notification_settings`). If not found, write text directly (`android:text=\"通知设置\"`).\r\n- **Dimensions**: write values directly (`android:textSize=\"17sp\"`). Dimension resources are rarely worth matching.\r\n- **Lists**: output main layout + separate item layout file. Do NOT generate Adapter/ViewHolder.\r\n- **Resource matching priority**: Use project-defined `@color/` and `@string/` when an exact match exists. Hardcode everything else for instant preview. Never create new resource definitions — only reference existing ones.\r\n\r\n## Drawable Resources — Generate, Don't Placeholder\r\n\r\n- **Shape drawables** (backgrounds, outlines, tracks): Generate the actual XML shape drawable code based on Figma data (color, cornerRadius, stroke, gradient). Output each as a separate file with `📄 drawable/filename.xml` header.\r\n- **Icons/vectors**: Use `figma_fetch.py --export-svg <node-ids>` to export SVG from Figma API, then convert to Android Vector Drawable XML. The simplified JSON includes an `\"id\"` field on every node — use these IDs for export. Output each as `📄 drawable/ic_name.xml`.\r\n- **Photos/bitmaps**: These cannot be generated — use `@drawable/placeholder` and note what image is needed.\r\n- **Goal**: The generated code should be copy-pasteable and immediately render a close approximation of the design, not a blank screen with placeholders.\r\n\r\n## Unmatched Resource Suggestions (when project scan is available)\r\n\r\nAfter all code output, if any colors or strings were hardcoded because they didn't match existing project resources, append a **\"Suggested Re\n\nArchive v2.2.4: 34 files, 103546 bytes\n\nFiles: LICENSE (1084b), README-zh.md (7193b), README.md (3503b), references/compose-patterns.md (23941b), references/figma-interpretation.md (8637b), references/generation-rules.md (5275b), references/scan-usage.md (4022b), references/swiftui-patterns.md (12454b), references/uikit-patterns.md (14135b), references/xml-patterns.md (24007b), scripts/feedback_analyze.py (6420b), scripts/figma_fetch.py (26437b), scripts/project_scan.py (7850b), scripts/scanners/__init__.py (1504b), scripts/scanners/android_deps.py (2485b), scripts/scanners/android_drawables.py (13181b), scripts/scanners/android_layouts.py (5030b), scripts/scanners/android_modules.py (4061b), scripts/scanners/android_resources.py (11356b), scripts/scanners/android_scanner.py (19508b), scripts/scanners/android_views.py (6321b), scripts/scanners/base.py (12022b), scripts/scanners/flutter_detector.py (452b), scripts/scanners/flutter_scanner.py (15776b), scripts/scanners/ios_assets.py (1858b), scripts/scanners/ios_detector.py (815b), scripts/scanners/ios_resources.py (5804b), scripts/scanners/ios_scanner.py (8028b), scripts/scanners/ios_swift_scan.py (4962b), scripts/scanners/ios_views.py (3148b), skill-card.md (2844b), SKILL.md (13603b), tests/test_project_scan.py (15007b), _meta.json (134b)\n\nArchive v2.2.3: 34 files, 103659 bytes\n\nFiles: LICENSE (1084b), README-zh.md (7193b), README.md (3503b), references/compose-patterns.md (23941b), references/figma-interpretation.md (8637b), references/generation-rules.md (5275b), references/scan-usage.md (4022b), references/swiftui-patterns.md (12454b), references/uikit-patterns.md (14135b), references/xml-patterns.md (24007b), scripts/feedback_analyze.py (6420b), scripts/figma_fetch.py (26437b), scripts/project_scan.py (7850b), scripts/scanners/__init__.py (1504b), scripts/scanners/android_deps.py (2485b), scripts/scanners/android_drawables.py (13181b), scripts/scanners/android_layouts.py (5030b), scripts/scanners/android_modules.py (4061b), scripts/scanners/android_resources.py (11356b), scripts/scanners/android_scanner.py (19508b), scripts/scanners/android_views.py (6321b), scripts/scanners/base.py (12022b), scripts/scanners/flutter_detector.py (452b), scripts/scanners/flutter_scanner.py (15776b), scripts/scanners/ios_assets.py (1858b), scripts/scanners/ios_detector.py (815b), scripts/scanners/ios_resources.py (5804b), scripts/scanners/ios_scanner.py (8028b), scripts/scanners/ios_swift_scan.py (4962b), scripts/scanners/ios_views.py (3148b), skill-card.md (3266b), SKILL.md (13409b), tests/test_project_scan.py (15007b), _meta.json (134b)\n\nArchive v2.2.2: 34 files, 103094 bytes\n\nFiles: LICENSE (1084b), README-zh.md (6838b), README.md (3503b), references/compose-patterns.md (23941b), references/figma-interpretation.md (8637b), references/generation-rules.md (5275b), references/scan-usage.md (4022b), references/swiftui-patterns.md (12454b), references/uikit-patterns.md (14135b), references/xml-patterns.md (24007b), scripts/feedback_analyze.py (6420b), scripts/figma_fetch.py (26437b), scripts/project_scan.py (7850b), scripts/scanners/__init__.py (1504b), scripts/scanners/android_deps.py (2485b), scripts/scanners/android_drawables.py (13181b), scripts/scanners/android_layouts.py (5030b), scripts/scanners/android_modules.py (4061b), scripts/scanners/android_resources.py (11356b), scripts/scanners/android_scanner.py (19508b), scripts/scanners/android_views.py (6321b), scripts/scanners/base.py (12022b), scripts/scanners/flutter_detector.py (452b), scripts/scanners/flutter_scanner.py (15776b), scripts/scanners/ios_assets.py (1858b), scripts/scanners/ios_detector.py (815b), scripts/scanners/ios_resources.py (5804b), scripts/scanners/ios_scanner.py (8028b), scripts/scanners/ios_swift_scan.py (4962b), scripts/scanners/ios_views.py (3148b), skill-card.md (2742b), SKILL.md (12827b), tests/test_project_scan.py (15007b), _meta.json (134b)\n\nArchive v2.2.1: 33 files, 102495 bytes\n\nFiles: README-zh.md (6838b), README.md (3503b), references/compose-patterns.md (23941b), references/figma-interpretation.md (8637b), references/generation-rules.md (5275b), references/scan-usage.md (4022b), references/swiftui-patterns.md (12454b), references/uikit-patterns.md (14135b), references/xml-patterns.md (24007b), scripts/feedback_analyze.py (6420b), scripts/figma_fetch.py (26437b), scripts/project_scan.py (7850b), scripts/scanners/__init__.py (1504b), scripts/scanners/android_deps.py (2485b), scripts/scanners/android_drawables.py (13181b), scripts/scanners/android_layouts.py (5030b), scripts/scanners/android_modules.py (4061b), scripts/scanners/android_resources.py (11356b), scripts/scanners/android_scanner.py (19508b), scripts/scanners/android_views.py (6321b), scripts/scanners/base.py (12022b), scripts/scanners/flutter_detector.py (452b), scripts/scanners/flutter_scanner.py (15776b), scripts/scanners/ios_assets.py (1858b), scripts/scanners/ios_detector.py (815b), scripts/scanners/ios_resources.py (5804b), scripts/scanners/ios_scanner.py (8028b), scripts/scanners/ios_swift_scan.py (4962b), scripts/scanners/ios_views.py (3148b), skill-card.md (3434b), SKILL.md (12389b), tests/test_project_scan.py (15007b), _meta.json (134b)\n\nArchive v2.2.0: 33 files, 102092 bytes\n\nFiles: README-zh.md (6838b), README.md (3503b), references/compose-patterns.md (23941b), references/figma-interpretation.md (8637b), references/generation-rules.md (5275b), references/scan-usage.md (4022b), references/swiftui-patterns.md (12454b), references/uikit-patterns.md (14135b), references/xml-patterns.md (24007b), scripts/feedback_analyze.py (6420b), scripts/figma_fetch.py (26437b), scripts/project_scan.py (7850b), scripts/scanners/__init__.py (1504b), scripts/scanners/android_deps.py (2485b), scripts/scanners/android_drawables.py (13181b), scripts/scanners/android_layouts.py (5030b), scripts/scanners/android_modules.py (4061b), scripts/scanners/android_resources.py (11356b), scripts/scanners/android_scanner.py (19508b), scripts/scanners/android_views.py (6321b), scripts/scanners/base.py (12022b), scripts/scanners/flutter_detector.py (452b), scripts/scanners/flutter_scanner.py (15776b), scripts/scanners/ios_assets.py (1858b), scripts/scanners/ios_detector.py (815b), scripts/scanners/ios_resources.py (5804b), scripts/scanners/ios_scanner.py (8028b), scripts/scanners/ios_swift_scan.py (4962b), scripts/scanners/ios_views.py (3148b), skill-card.md (2572b), SKILL.md (12389b), tests/test_project_scan.py (15007b), _meta.json (134b)\n\nArchive v2.1.1: 31 files, 92971 bytes\n\nFiles: README-zh.md (6668b), README.md (6295b), references/compose-patterns.md (23941b), references/figma-interpretation.md (8637b), references/generation-rules.md (5275b), references/scan-usage.md (2100b), references/swiftui-patterns.md (12454b), references/uikit-patterns.md (14135b), references/xml-patterns.md (24007b), scripts/feedback_analyze.py (6420b), scripts/figma_fetch.py (26437b), scripts/project_scan.py (7850b), scripts/scanners/__init__.py (1250b), scripts/scanners/android_deps.py (2485b), scripts/scanners/android_drawables.py (13181b), scripts/scanners/android_layouts.py (5030b), scripts/scanners/android_modules.py (4061b), scripts/scanners/android_resources.py (11356b), scripts/scanners/android_scanner.py (10320b), scripts/scanners/android_views.py (6321b), scripts/scanners/base.py (5665b), scripts/scanners/ios_assets.py (1858b), scripts/scanners/ios_detector.py (815b), scripts/scanners/ios_resources.py (5804b), scripts/scanners/ios_scanner.py (5788b), scripts/scanners/ios_swift_scan.py (4962b), scripts/scanners/ios_views.py (3148b), skill-card.md (2853b), SKILL.md (12619b), tests/test_project_scan.py (15007b), _meta.json (134b)\n\nArchive v2.1.0: 31 files, 92889 bytes\n\nFiles: README-zh.md (6668b), README.md (6164b), references/compose-patterns.md (23941b), references/figma-interpretation.md (8637b), references/generation-rules.md (5275b), references/scan-usage.md (2100b), references/swiftui-patterns.md (12454b), references/uikit-patterns.md (14135b), references/xml-patterns.md (24007b), scripts/feedback_analyze.py (6420b), scripts/figma_fetch.py (26437b), scripts/project_scan.py (7850b), scripts/scanners/__init__.py (1250b), scripts/scanners/android_deps.py (2485b), scripts/scanners/android_drawables.py (13181b), scripts/scanners/android_layouts.py (5030b), scripts/scanners/android_modules.py (4061b), scripts/scanners/android_resources.py (11356b), scripts/scanners/android_scanner.py (10320b), scripts/scanners/android_views.py (6321b), scripts/scanners/base.py (5665b), scripts/scanners/ios_assets.py (1858b), scripts/scanners/ios_detector.py (815b), scripts/scanners/ios_resources.py (5804b), scripts/scanners/ios_scanner.py (5788b), scripts/scanners/ios_swift_scan.py (4962b), scripts/scanners/ios_views.py (3148b), skill-card.md (3093b), SKILL.md (12352b), tests/test_project_scan.py (15007b), _meta.json (134b)\n\nArchive v2.0.4: 33 files, 92917 bytes\n\nFiles: _meta.json (134b), DESIGN.md (4649b), evals/evals.json (2065b), feedback-log.md (721b), README.md (3380b), references/compose-patterns.md (23941b), references/figma-interpretation.md (8637b), references/generation-rules.md (4751b), references/scan-usage.md (2100b), references/swiftui-patterns.md (12454b), references/uikit-patterns.md (14135b), references/xml-patterns.md (24007b), scripts/feedback_analyze.py (6720b), scripts/figma_fetch.py (27019b), scripts/project_scan.py (7850b), scripts/scanners/__init__.py (1250b), scripts/scanners/android_deps.py (2485b), scripts/scanners/android_drawables.py (13181b), scripts/scanners/android_layouts.py (5030b), scripts/scanners/android_modules.py (4061b), scripts/scanners/android_resources.py (11356b), scripts/scanners/android_scanner.py (10320b), scripts/scanners/android_views.py (6321b), scripts/scanners/base.py (5665b), scripts/scanners/ios_assets.py (1858b), scripts/scanners/ios_detector.py (815b), scripts/scanners/ios_resources.py (5804b), scripts/scanners/ios_scanner.py (5788b), scripts/scanners/ios_swift_scan.py (4962b), scripts/scanners/ios_views.py (3148b), skill-card.md (2822b), SKILL.md (11705b), tests/test_project_scan.py (15007b)","readmeExcerpt":"Skill: figma-to-mobile Owner: timeaground Summary: Convert Figma designs to mobile UI code (Compose/XML/SwiftUI/UIKit/Flutter) via the Figma REST API with local resource scanning, multi-frame comparison, and feedback-log corrections. Activate when a user provides a Figma link and asks for mobile layout code. Tags: latest:2.3.0 Version history: v2.3.0 | 2026-08-06T15:45:12.237Z | auto - Adds new references for error h","codeSnippets":[],"executableExamples":[{"language":"text","snippet":"File v2.3.0:references/error-handling.md\n\n# Error Handling\r\n\r\n## FIGMA_TOKEN not set (script outputs `FIGMA_TOKEN_NOT_SET`)\r\n\r\nTell the user:\r\n\r\n> I need a Figma Personal Access Token to fetch the design.\r\n> ⚠️ **Do not paste it into this chat** — chat messages may be logged.\r\n> Set it as an environment variable and restart.\r\n> Get one at: Figma → avatar → Settings → Security → Personal Access Tokens\r\n>\r\n>   Windows: `setx FIGMA_TOKEN \"figd_xxx\"`\r\n>   macOS/Linux: add `export FIGMA_TOKEN=\"figd_xxx\"` to ~/.zshrc\r\n\r\n## FIGMA_TOKEN invalid (API returns 403/401)\r\n\r\nTell the user the token may have expired or been revoked. Direct them to\r\nregenerate from Figma Settings → Security → Personal Access Tokens.\r\n\r\n## Invalid URL\r\n\r\nShow valid URL example: `https://www.figma.com/design/<fileKey>/<name>?node-id=<id>`\r\n\r\n## API error\r\n\r\nShow the error message, suggest checking network/proxy.\r\n\r\n## Node too large (>200 children)\r\n\r\nSuggest selecting a smaller frame.\r\n\r\n## Depth auto-increased\r\n\r\nThe script auto-retries with deeper depth if it detects truncated children.\r\nInform the user if this happens (\"I needed to fetch deeper to get all details\").\n\nFile v2.3.0:references/feedback-log.md\n\n# Feedback Log Format\r\n\r\n**⚠️ Every time the user corrects your output (layout issue, wrong component,\r\nspacing problem, etc.), you MUST log it to `feedback-log.md` before proceeding\r\nwith the fix (after user consent). Do not skip this step — the log is how the\r\nskill learns and improves over time.**\r\n\r\nWhenever the user corrects your generated output (with consent), log the\r\ncorrection to `feedback-log.md` in the project root (create if it doesn't\r\nexist). Each entry follows this format:"},{"language":"text","snippet":"File v2.2.5:references/figma-interpretation.md\n\n# Figma Node Interpretation Rules\r\n> Referenced by SKILL.md Step 1. Read this file when analyzing Figma fetch results.\r\n\r\n## Style References and Text Sizing\r\n\r\n- Note `styleRefs` on nodes — these reference Figma shared styles (design tokens). Nodes sharing the same style ID should use the same code-level token/resource\r\n- Note `textAutoResize` on TEXT nodes — `WIDTH_AND_HEIGHT` means auto-size (wrap_content), `HEIGHT` means fixed width + auto height, absent/NONE means fixed size\r\n\r\n## Figma Node Interpretation (apply before generating any platform code)\r\n\r\n- **Skip system chrome**: StatusBar, HomeIndicator, NavigationBar are iOS design placeholders — don't generate code for them. Also skip duplicate nodes at the same position (Figma artifacts)\r\n- **Skip invisible nodes**: VECTOR/RECTANGLE with empty fills and all strokes `visible: false`, or `absoluteRenderBounds: null` — these are leftover design artifacts that render nothing\r\n- **layoutAlign=STRETCH**: child fills the cross-axis of its auto-layout parent → `match_parent` on the cross-axis / `.frame(maxWidth: .infinity)`. Only present when it differs from INHERIT\r\n- **layoutPositioning=ABSOLUTE**: child is absolutely positioned within an auto-layout parent → use explicit x/y offset constraints instead of flow layout\r\n- **Container + icon = single view**: A FRAME (with background/cornerRadius) wrapping a small VECTOR/INSTANCE is one ImageView/Image, not nested layouts\r\n- **VECTOR/ELLIPSE compositions = single asset**: Multiple small VECTOR/ELLIPSE siblings inside a FRAME are pieces of one icon — output as a single image reference, not separate views\r\n- **RECTANGLE as background**: When a GROUP's first child is a RECTANGLE matching the GROUP's dimensions, it's a background shape, not a separate view\r\n- **GROUP vs FRAME**: FRAME with `layoutMode` maps to structured layouts (LinearLayout, HStack, etc.); GROUP without `layoutMode` uses absolute positioning — map to Constr"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\r\nname: figma-to-mobile\r\nversion: 2.3.0\r\nrequires: [Bash, Read, Write, Grep, Glob]\r\ndescription: >-\r\n  Convert Figma designs to mobile UI code (Compose/XML/SwiftUI/UIKit/Flutter)\r\n  via the Figma REST API with local resource scanning, multi-frame comparison,\r\n  and feedback-log corrections. Activate when a user provides a Figma link and\r\n  asks for mobile layout code.\r\nmetadata:\r\n  {\r\n    \"openclaw\":\r\n      {\r\n        \"requires\": { \"bins\": [\"python3\"], \"env\": [\"FIGMA_TOKEN\"] },\r\n        \"primaryEnv\": \"FIGMA_TOKEN\",\r\n        \"permissions\":\r\n          {\r\n            \"network\": [ \"api.figma.com\" ],\r\n            \"fs\":\r\n              {\r\n                \"read\": [ \"project root — for resource scanning (colors, strings, components)\" ],\r\n                \"write\": [\r\n                  \"project root — for generated UI code files\",\r\n                  \"project root — for feedback-log.md (user consent required)\"\r\n                ]\r\n              }\r\n          },\r\n        \"install\":\r\n          [\r\n            {\r\n              \"id\": \"python-requests\",\r\n              \"kind\": \"shell\",\r\n              \"command\": \"pip3 install requests\",\r\n              \"label\": \"Install Python requests package\",\r\n            },\r\n          ],\r\n      },\r\n    \"resourceManifest\":\r\n      {\r\n        \"cpu\": \"low — short-lived CLI scripts\",\r\n        \"memory\": \"low (< 512 MB)\",\r\n        \"timeout\": \"30s per API request; 3 retries; ~2s min interval between requests\",\r\n        \"network\": [ \"api.figma.com\" ],\r\n      },\r\n  }\r\n---\r\n\r\n# Figma to Mobile\r\n\r\nConvert Figma designs to mobile UI code with interactive clarification.\r\n\r\nSupported: Android Compose, Android XML, iOS SwiftUI, iOS UIKit.\r\n\r\n## Prerequisites\r\n\r\n- `FIGMA_TOKEN` environment variable set (Figma > Settings > Personal Access Tokens)\r\n  ⚠️ **Your Figma token is sensitive** — treat it like a password. Never paste it into chat\r\n    messages (they may be logged). Set it via your shell rc file or OpenClaw env config.\r\n- Python 3.8+ with `requests` package\r\n\r\n## Trigger & Input\r\n\r\nThis skill activates when a user **explicitly asks to convert a Figma design to mobile UI code** and provides a Figma link. It does NOT activate on casual mentions of Figma links, pasted URLs in logs, or design references without a conversion request.\r\n\r\nThe user may also include **inline hints** alongside the link, such as:\r\n- Target platform: \"Android XML\", \"Compose\", \"SwiftUI\", \"UIKit\"\r\n- Layout preferences: \"use ConstraintLayout\", \"prefer StackView\"\r\n- Component notes: \"the switch is our custom CompactSwitch\", \"this is a dynamic list\"\r\n- Any other context about the design\r\n\r\n**If the user provides hints, respect them and skip the corresponding questions.**\r\nFor example, if the user says \"Android XML, the 3 cards are a RecyclerView list\", do NOT ask about output format or whether the cards are dynamic/static.\r\n\r\n## Not Applicable\r\n\r\nThis skill is NOT for:\r\n\r\n- **Screenshot / image → code**: it works only with Figma design links via the Figma REST API, not with"},{"path":"README.md","content":"# figma-to-mobile\r\n\r\nConvert Figma designs to production-ready mobile UI code using AI.\r\n\r\nSupports: **Jetpack Compose** · **Android XML** · **SwiftUI** · **UIKit**\r\n\r\n## Demo\r\n\r\nHere's a side-by-side comparison using a [Material Design 3 Messaging App](https://www.figma.com/community/file/1169726503071187057/) from Figma Community:\r\n\r\n![Figma to Compose comparison](assets/demo-comparison.png)\r\n\r\n**Left:** Figma design · **Right:** Generated Jetpack Compose code running in Android Studio\r\n\r\nThe tool reads the Figma design tree (auto-layout, style refs, variants) and generates idiomatic code — not pixel-positioned boxes.\r\n\r\n## How It Works\r\n\r\n1. **Fetch** — Python script calls Figma API to extract the node tree\r\n2. **Interpret** — AI analyzes layout semantics: \"6 similar rows → `LazyColumn`\", \"horizontal stack → `Row`\"\r\n3. **Generate** — Outputs platform-idiomatic code with proper theming (Material3, SF Symbols, etc.)\r\n4. **Iterate** — Refine through natural language: \"make the header sticky\", \"switch to dark theme\"\r\n\r\n## Install\r\n\r\n### OpenClaw\r\n\r\n```bash\r\nclawhub install figma-to-mobile\r\n```\r\n\r\n### Claude Code\r\n\r\nCopy the `figma-to-mobile` folder into your project:\r\n```\r\nyour-project/.claude/skills/figma-to-mobile/\r\n```\r\n\r\n### GitHub Copilot\r\n\r\nCopy the `figma-to-mobile` folder into your project:\r\n```\r\nyour-project/.agents/skills/figma-to-mobile/\r\n```\r\n\r\n## Setup\r\n\r\n> ⚠️ **Figma Token** — Set the `FIGMA_TOKEN` environment variable.\r\n> Generate one at Figma → Avatar → Settings → Security → Personal Access Tokens.\r\n>\r\n> **Do not paste your token into chat** — chat messages may be logged.\r\n> Set it via your shell profile or system environment variables:\r\n>\r\n> ```bash\r\n> # macOS/Linux\r\n> export FIGMA_TOKEN=\"figd_your_token_here\"\r\n>\r\n> # Windows PowerShell\r\n> $env:FIGMA_TOKEN = \"figd_your_token_here\"\r\n> ```\r\n\r\n## Usage\r\n\r\nPaste a Figma design link in your AI assistant's chat:\r\n\r\n> Convert this to Jetpack Compose: https://www.figma.com/design/xxx/Project?node-id=100-200\r\n\r\nThe agent will:\r\n1. Fetch the design data from Figma API\r\n2. Ask clarifying questions (platform, list vs static, etc.)\r\n3. Generate production-ready code files\r\n4. Iterate based on your feedback — only patches the changed lines, never regenerates the full file unless you ask\r\n\r\n## What Makes It Different\r\n\r\n| Feature | Screenshot-based tools | figma-to-mobile |\r\n|---------|----------------------|-----------------|\r\n| Input | Screenshot/image | Figma API (design tree) |\r\n| Layout understanding | Pixel positions | Auto-layout semantics |\r\n| Output quality | Absolute positioning | Idiomatic code (LazyColumn, VStack, etc.) |\r\n| Iteration | Re-screenshot | Natural language refinement |\r\n| Cost | Paid subscription | Free & open source |\r\n\r\n## What's in the Box\r\n\r\n```\r\nfigma-to-mobile/\r\n├── SKILL.md              # Agent instructions (the brain)\r\n├── scripts/\r\n│   └── figma_fetch.py    # Figma API data fetcher\r\n├── references/\r\n│   ├── compose-patterns.md   # Jetpack Compose mapping rules\r\n"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn7crx9z6m9cw8gpcwbhp8na31843hnr\",\n  \"slug\": \"figma-to-mobile\",\n  \"version\": \"2.3.0\",\n  \"publishedAt\": 1786031112237\n}"},{"path":"references/compose-patterns.md","content":"# Compose Patterns — Figma to Jetpack Compose Mapping\r\n\r\n> Purpose: Map Figma properties to Jetpack Compose code.\r\n> This is a **mapping reference**, not a Compose tutorial — the agent already knows Compose conventions.\r\n\r\n## Layout Selection Guide\r\n\r\n| Figma Structure | Recommended Composable |\r\n|---|---|\r\n| Vertical stack | Column |\r\n| Horizontal stack | Row |\r\n| Overlapping / z-stacking | Box |\r\n| Repeating similar items (≥3) | LazyColumn / LazyRow |\r\n| Page structure with top/bottom bars | Scaffold |\r\n| Complex relative positioning | Box with Modifier.align / offset |\r\n\r\n## Auto-layout Mapping\r\n\r\n| Figma Property | Compose Equivalent |\r\n|---|---|\r\n| layoutMode: VERTICAL | Column |\r\n| layoutMode: HORIZONTAL | Row |\r\n| itemSpacing | Arrangement.spacedBy(X.dp) |\r\n| padding* | Modifier.padding() |\r\n| primaryAxisAlignItems: CENTER | verticalArrangement = Arrangement.Center |\r\n| counterAxisAlignItems: CENTER | horizontalAlignment = Alignment.CenterHorizontally |\r\n| layoutGrow: 1 | Modifier.weight(1f) |\r\n| primaryAxisSizingMode: FIXED | Modifier.height/width(X.dp) |\r\n| counterAxisSizingMode: AUTO | wrapContentWidth/Height |\r\n\r\n## Size Conversion\r\n\r\n- Figma px → Compose .dp (1:1)\r\n- Figma font px → Compose .sp (1:1)\r\n\r\n## Shadow Mapping\r\n\r\n```kotlin\r\n// Elevation shadow\r\nCard(elevation = CardDefaults.cardElevation(defaultElevation = 4.dp))\r\n\r\n// Custom shadow (Compose 1.6+)\r\nModifier.shadow(\r\n    elevation = 4.dp,\r\n    shape = RoundedCornerShape(12.dp),\r\n    ambientColor = Color(0x1A000000),\r\n    spotColor = Color(0x33000000)\r\n)\r\n```\r\n\r\n## Gradient Mapping\r\n\r\n```kotlin\r\n// Linear gradient\r\nModifier.background(\r\n    Brush.linearGradient(\r\n        colors = listOf(Color(0xFFFF6B6B), Color(0xFF4ECDC4)),\r\n        start = Offset(0f, 0f),\r\n        end = Offset(0f, Float.POSITIVE_INFINITY)\r\n    )\r\n)\r\n```\r\n\r\n## Per-corner Radius\r\n\r\n```kotlin\r\nRoundedCornerShape(\r\n    topStart = 12.dp,\r\n    topEnd = 12.dp,\r\n    bottomEnd = 0.dp,\r\n    bottomStart = 0.dp\r\n)\r\n```\r\n\r\n## Page Architecture Patterns\r\n\r\nThese patterns reflect how Android apps are **actually structured** in production with Compose.\r\n\r\n### Multi-Tab Pages\r\nWhen a design shows **multiple tabs** (≥2 text labels acting as navigation):\r\n- Use `TabRow` + `HorizontalPager` (accompanist or foundation) for top tabs\r\n- Use `NavigationBar` for bottom navigation\r\n- Do NOT use plain `Text` composables for tabs — they lack selection state, indicators, and swipe support\r\n- Each tab's content should be a separate `@Composable` function\r\n\r\n```kotlin\r\n@Composable\r\nfun TabScreen() {\r\n    val pagerState = rememberPagerState(pageCount = { 3 })\r\n    val scope = rememberCoroutineScope()\r\n    val tabs = listOf(\"关注\", \"推荐\", \"热榜\")\r\n\r\n    Column {\r\n        TabRow(\r\n            selectedTabIndex = pagerState.currentPage,\r\n            containerColor = Color.White,\r\n            contentColor = Color(0xFF0F0F0F),\r\n            indicator = { tabPositions ->\r\n                TabRowDefaults.SecondaryIndicator(\r\n                    modifier"},{"path":"references/error-handling.md","content":"# Error Handling\r\n\r\n## FIGMA_TOKEN not set (script outputs `FIGMA_TOKEN_NOT_SET`)\r\n\r\nTell the user:\r\n\r\n> I need a Figma Personal Access Token to fetch the design.\r\n> ⚠️ **Do not paste it into this chat** — chat messages may be logged.\r\n> Set it as an environment variable and restart.\r\n> Get one at: Figma → avatar → Settings → Security → Personal Access Tokens\r\n>\r\n>   Windows: `setx FIGMA_TOKEN \"figd_xxx\"`\r\n>   macOS/Linux: add `export FIGMA_TOKEN=\"figd_xxx\"` to ~/.zshrc\r\n\r\n## FIGMA_TOKEN invalid (API returns 403/401)\r\n\r\nTell the user the token may have expired or been revoked. Direct them to\r\nregenerate from Figma Settings → Security → Personal Access Tokens.\r\n\r\n## Invalid URL\r\n\r\nShow valid URL example: `https://www.figma.com/design/<fileKey>/<name>?node-id=<id>`\r\n\r\n## API error\r\n\r\nShow the error message, suggest checking network/proxy.\r\n\r\n## Node too large (>200 children)\r\n\r\nSuggest selecting a smaller frame.\r\n\r\n## Depth auto-increased\r\n\r\nThe script auto-retries with deeper depth if it detects truncated children.\r\nInform the user if this happens (\"I needed to fetch deeper to get all details\")."}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"Convert Figma designs to mobile UI code (Compose/XML/SwiftUI/UIKit/Flutter) via the Figma REST API with local resource scanning, multi-frame comparison, and feedback-log corrections. Activate when a user provides a Figma link and asks for mobile layout code. Skill: figma-to-mobile Owner: timeaground Summary: Convert Figma designs to mobile UI code (Compose/XML/SwiftUI/UIKit/Flutter) via the Figma REST API with local resource scanning, multi-frame comparison, and feedback-log corrections. Activate when a user provides a Figma link and asks for mobile layout code. Tags: latest:2.3.0 Version history: v2.3.0 | 2026-08-06T15:45:12.237Z | auto - Adds new references for error h","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1669,"uniquenessScore":47,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-09T16:51:27.085Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-09T16:51:27.085Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-09T19:36:29.419Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}