Skip to content

Commit c7829ea

Browse files
committed
feat(batch): add nodes and components pre-fetch for granular cache
1. New `PreFetchedNodes` and `PreFetchedComponents` structs with TaskLocal storage for batch-mode sharing 2. Extended `FileVersionPreFetcher` with methods for pre-fetching nodes and components 3. Updated `Batch.swift` to coordinate the three-phase pre-fetch (metadata → components → nodes) 4. Added new warning types for pre-fetch partial failures 5. Updated loaders to use pre-fetched components 6. Updated `GranularCacheManager` to use pre-fetched nodes * feat(icons): support multiple icons configs with per-frame exports * feat(config): support multiple colors and images configs 1. New `ColorsConfiguration` and `ImagesConfiguration` enums in `Params.swift` for iOS, Android, and Flutter 2. New `ColorsEntry` and `ImagesEntry` structs with per-frame settings 3. `ImagesLoaderConfig` for passing frame-specific settings to the loader 4. Updated `ExportColors` and `ExportImages` commands to handle multiple entries 5. Comprehensive tests for the new configuration formats 6. Documentation updates in CLAUDE.md and CONFIG.md * feat(images): add granular cache support for raster images Add PNG/WebP granular cache tracking to ImagesLoader using the same pattern as vector images. This enables per-node change detection for raster exports, skipping unchanged assets even when Figma file version changes. Changes: - Add loadPNGImagesWithGranularCache method for raster images - Support granular cache in both single-file and multi-file modes - Update Configuration.md with images array format documentation - Add comprehensive tests for ImagesLoaderConfig * feat(flutter): use dedicated Platform.flutter for Flutter exports Previously Flutter used .android platform internally, which could cause incorrect behavior in platform-specific code paths. This change adds a proper .flutter case to the Platform enum and updates all Flutter export commands and loaders to use it. Also adds a Flutter config template for the init command. * chore(cli): add Claude Code slash commands Add project-specific slash commands for development workflow: - /local-review: comprehensive code review vs origin/develop - /plan: task breakdown with dependencies for parallel execution - /pr-summary: generate GitHub PR summary and copy to clipboard
1 parent 338d3b7 commit c7829ea

33 files changed

Lines changed: 5518 additions & 336 deletions

.claude/EXFIG.toon

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,25 @@ keyDirectories:
128128
docc: Sources/ExFig/ExFig.docc/
129129
tests: Tests/
130130

131+
configTypes:
132+
IconsConfiguration:
133+
purpose: Enum for backward-compatible icons config parsing
134+
cases: [single(Icons), multiple([IconsEntry])]
135+
platforms: [iOS, Android, Flutter]
136+
properties:
137+
entries: "[IconsEntry] - unified access to all entries"
138+
isMultiple: "Bool - true if array format"
139+
decoding: "Try array first, fallback to single object"
140+
IconsEntry:
141+
purpose: Per-frame icons configuration
142+
requiredFields: [format/output (platform-specific)]
143+
optionalFields: [figmaFrameName]
144+
fallback: "figmaFrameName defaults to common.icons.figmaFrameName or 'Icons'"
145+
IconsLoaderConfig:
146+
purpose: Sendable struct for IconsLoader frame settings
147+
file: Sources/ExFig/Loaders/IconsLoader.swift
148+
factoryMethods: [forIOS, forAndroid, forFlutter, defaultConfig]
149+
131150
keyFiles:
132151
cli: Sources/ExFig/ExFigCommand.swift
133152
config: Sources/ExFig/Input/Params.swift

.claude/commands/plan.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
---
2+
description: Break down a task into subtasks with dependencies for parallel execution
3+
allowed-tools: Read, Glob, Grep, TodoWrite, Task(Explore), Task(Plan)
4+
argument-hint: <task description>
5+
version: 1.0
6+
---
7+
8+
# Task Planning with Dependencies
9+
10+
**Purpose**: Analyze a task and break it down into subtasks with explicit dependencies, enabling parallel execution
11+
where possible.
12+
13+
## Output Format (TOON-like)
14+
15+
Generate plan in this structured format:
16+
17+
```yaml
18+
command:
19+
name: plan-<task-slug>
20+
purpose: <task description>
21+
version: 1.0
22+
23+
tasks[N]{id,title,description,depends_on,parallel_group,type}:
24+
<id>,<title>,<description>,[deps],[group],[type]
25+
...
26+
27+
execution_order:
28+
phase_1:
29+
parallel: [task_ids that can run in parallel]
30+
phase_2:
31+
sequential: [task_id] # build/test tasks
32+
phase_3:
33+
parallel: [task_ids for fixes if needed]
34+
...
35+
36+
dependency_graph:
37+
<task_id>: [list of task_ids this depends on]
38+
...
39+
```
40+
41+
## Task Types
42+
43+
- `analysis` - Code analysis, research, reading
44+
- `implementation` - Writing new code
45+
- `modification` - Changing existing code
46+
- `build` - Building project/module
47+
- `test` - Running tests
48+
- `fix` - Fixing issues found by build/tests
49+
- `review` - Code review, validation
50+
51+
## Dependency Rules
52+
53+
### Critical Rules for Parallel Execution
54+
55+
1. **Independent tasks** (no shared files/modules) can run in parallel
56+
2. **Build tasks** MUST wait for ALL parallel implementation tasks to complete
57+
3. **Test tasks** MUST wait for build to succeed
58+
4. **Fix tasks** MUST wait for test results
59+
5. **Tasks modifying same file** MUST be sequential
60+
61+
### Dependency Detection
62+
63+
- Same file modification → sequential
64+
- Same module modification → sequential (unless different files)
65+
- Different modules → parallel possible
66+
- Build depends on → all implementation tasks
67+
- Test depends on → successful build
68+
- Fix depends on → test results
69+
70+
## Instructions
71+
72+
1. **Analyze the task**:
73+
74+
- Read relevant code files mentioned in task
75+
- Identify affected modules/files
76+
- Detect potential conflicts
77+
78+
2. **Break down into subtasks**:
79+
80+
- Create atomic, independent subtasks where possible
81+
- Identify dependencies between subtasks
82+
- Group parallelizable tasks
83+
84+
3. **Generate execution plan**:
85+
86+
- Phase 1: Parallel analysis/implementation tasks
87+
- Phase 2: Build (waits for Phase 1)
88+
- Phase 3: Tests (waits for Phase 2)
89+
- Phase 4: Fixes if needed (based on Phase 3 results)
90+
- Phase 5: Final build/test validation
91+
92+
4. **Output the plan** in TOON format above
93+
94+
## Example Output
95+
96+
```yaml
97+
command:
98+
name: plan-add-analytics-tracking
99+
purpose: Add analytics tracking to user profile module
100+
version: 1.0
101+
102+
tasks[6]{id,title,description,depends_on,parallel_group,type}:
103+
T1,Create analytics service,Implement AnalyticsService protocol,[],G1,implementation
104+
T2,Add tracking to ProfileView,Integrate analytics calls,[],G1,implementation
105+
T3,Add tracking to SettingsView,Integrate analytics calls,[],G1,implementation
106+
T4,Build module,Build UserProfile module,[T1,T2,T3],G2,build
107+
T5,Run tests,Execute unit tests,[T4],G3,test
108+
T6,Fix issues,Address any test failures,[T5],G4,fix
109+
110+
execution_order:
111+
phase_1:
112+
parallel: [T1, T2, T3] # Can run simultaneously - different files
113+
phase_2:
114+
sequential: [T4] # Build waits for all implementations
115+
phase_3:
116+
sequential: [T5] # Tests wait for build
117+
phase_4:
118+
conditional: [T6] # Only if tests fail
119+
120+
dependency_graph:
121+
T1: []
122+
T2: []
123+
T3: []
124+
T4: [T1, T2, T3]
125+
T5: [T4]
126+
T6: [T5]
127+
128+
notes:
129+
- T1, T2, T3 modify different files, safe to parallelize
130+
- T4 must wait for ALL implementations before building
131+
- T6 is conditional - only execute if T5 finds failures
132+
```
133+
134+
## Validation Checklist
135+
136+
Before finalizing plan, verify:
137+
138+
- [ ] No circular dependencies
139+
- [ ] Build tasks depend on ALL related implementations
140+
- [ ] Test tasks depend on successful build
141+
- [ ] Fix tasks depend on test results
142+
- [ ] Parallel tasks don't modify same files
143+
- [ ] All task IDs are unique
144+
- [ ] dependency_graph matches depends_on fields

.swiftlint.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ excluded:
77
- Packages
88
- Sources/ExFig/Resources/iOSConfig.swift
99
- Sources/ExFig/Resources/androidConfig.swift
10+
- Sources/ExFig/Resources/flutterConfig.swift
1011
- Tests/XcodeExportTests/XcodeIconsExporterTests.swift
1112

1213
disabled_rules:

CLAUDE.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,94 @@ Tests/ # Test targets mirror source structure
110110

111111
Templates are in `Sources/*/Resources/`. Use Stencil syntax. Update tests after changes.
112112

113+
### Multiple Icons Configuration
114+
115+
Icons can be configured as a single object (legacy) or array (new format) in `Params.swift`:
116+
117+
```swift
118+
// IconsConfiguration enum handles both formats via custom Decodable
119+
enum IconsConfiguration: Decodable {
120+
case single(Icons) // Legacy: icons: { format: svg, ... }
121+
case multiple([IconsEntry]) // New: icons: [{ figmaFrameName: "Actions", ... }]
122+
123+
var entries: [IconsEntry] // Unified access to all entries
124+
var isMultiple: Bool // Check format type
125+
}
126+
127+
// IconsLoaderConfig passes frame-specific settings to loader
128+
let config = IconsLoaderConfig.forIOS(entry: entry, params: params)
129+
let loader = IconsLoader(client: client, params: params, platform: .ios, logger: logger, config: config)
130+
```
131+
132+
**Key types:**
133+
134+
| Type | Purpose |
135+
| -------------------- | -------------------------------------------------------- |
136+
| `IconsConfiguration` | Enum with `.single`/`.multiple` for backward compat |
137+
| `IconsEntry` | Per-frame config (figmaFrameName, format, assetsFolder) |
138+
| `IconsLoaderConfig` | Sendable struct passed to IconsLoader for frame settings |
139+
140+
**Frame name resolution:** `entry.figmaFrameName``params.common?.icons?.figmaFrameName``"Icons"`
141+
142+
### Multiple Colors Configuration
143+
144+
Colors can be configured as a single object (legacy) or array (new format) in `Params.swift`:
145+
146+
```swift
147+
// ColorsConfiguration enum handles both formats via custom Decodable
148+
enum ColorsConfiguration: Decodable {
149+
case single(Colors) // Legacy: colors: { useColorAssets: true, ... }
150+
case multiple([ColorsEntry]) // New: colors: [{ tokensFileId: "...", ... }]
151+
152+
var entries: [ColorsEntry] // Unified access to all entries
153+
var isMultiple: Bool // Check format type
154+
}
155+
156+
// Each platform has its own ColorsEntry with platform-specific output fields
157+
// iOS: useColorAssets, assetsFolder, colorSwift, swiftuiColorSwift
158+
// Android: xmlOutputFileName, composePackageName
159+
// Flutter: output, className
160+
```
161+
162+
**Key types:**
163+
164+
| Type | Purpose |
165+
| --------------------- | ---------------------------------------------------------- |
166+
| `ColorsConfiguration` | Enum with `.single`/`.multiple` for backward compat |
167+
| `ColorsEntry` | Per-collection config (tokensFileId, tokensCollectionName) |
168+
169+
**Note:** Colors array format is self-contained—each entry specifies its own Figma Variables source (`tokensFileId`,
170+
`tokensCollectionName`, mode names) and output paths. Legacy format uses `common.variablesColors` for source.
171+
172+
### Multiple Images Configuration
173+
174+
Images can be configured as a single object (legacy) or array (new format) in `Params.swift`:
175+
176+
```swift
177+
// ImagesConfiguration enum handles both formats via custom Decodable
178+
enum ImagesConfiguration: Decodable {
179+
case single(Images) // Legacy: images: { assetsFolder: "Illustrations", ... }
180+
case multiple([ImagesEntry]) // New: images: [{ figmaFrameName: "Promo", ... }]
181+
182+
var entries: [ImagesEntry] // Unified access to all entries
183+
var isMultiple: Bool // Check format type
184+
}
185+
186+
// ImagesLoaderConfig passes frame-specific settings to loader
187+
let config = ImagesLoaderConfig.forIOS(entry: entry, params: params)
188+
let loader = ImagesLoader(client: client, params: params, platform: .ios, logger: logger, config: config)
189+
```
190+
191+
**Key types:**
192+
193+
| Type | Purpose |
194+
| --------------------- | --------------------------------------------------------- |
195+
| `ImagesConfiguration` | Enum with `.single`/`.multiple` for backward compat |
196+
| `ImagesEntry` | Per-frame config (figmaFrameName, scales, output paths) |
197+
| `ImagesLoaderConfig` | Sendable struct passed to ImagesLoader for frame settings |
198+
199+
**Frame name resolution:** `entry.figmaFrameName``params.common?.images?.figmaFrameName``"Illustrations"`
200+
113201
### TerminalUI Usage
114202

115203
```swift

0 commit comments

Comments
 (0)