feat(icons): Add dark mode generation via Figma Variable Modes#78
feat(icons): Add dark mode generation via Figma Variable Modes#78alexey1312 merged 17 commits intomainfrom
Conversation
Enable dark SVG variant generation by resolving Figma Variable bindings instead of requiring separate dark files or suffix-based splitting. - Add `boundVariables` to Paint in swift-figma-api (0.3.0) - Add 4 PKL fields to FrameSource: variablesCollectionName, variablesLightModeName, variablesDarkModeName, variablesPrimitivesModeName - Propagate fields through IconsSourceInput and all platform entry bridges - Create SVGColorReplacer for hex color replacement in SVG content - Create VariableModeDarkGenerator for resolving variable alias chains and generating dark SVGs via color substitution - Integrate as third dark mode approach in FigmaComponentsSource.loadIcons
- CLAUDE.md: add variable-mode pattern, data flow, and three dark mode approaches - CONFIG.md: add 4 new FrameSource fields to configuration reference - ExFigCLI/CLAUDE.md: add VariableModeDarkGenerator and SVGColorReplacer to key files - ExFigCore/CLAUDE.md: document new IconsSourceInput variable-mode fields - ExFig-iOS/CLAUDE.md: add variable modes as third dark mode approach - PKL examples: add DoubleColor icons entry with variable-mode config
BREAKING CHANGE: Replace flat `useSingleFile`/`darkModeSuffix` fields with nested `suffixDarkMode: SuffixDarkMode?` on Common.Icons/Images/Colors. Replace 4 flat variable-mode fields on FrameSource with nested `variablesDarkMode: VariablesDarkMode?`. - Add `Common.VariablesDarkMode` class (collectionName, lightModeName, darkModeName, primitivesModeName) - Add `Common.SuffixDarkMode` class (suffix with default "_dark") - Update all loaders to read from nested objects - Update all 4 platform entry bridges - Update init templates, PKL examples, DocC articles - Update CONFIG.md, CLAUDE.md, and llms-full.txt
…nner - Add Common.VariablesDarkMode and Common.SuffixDarkMode to registerPklTypes() in PKLEvaluator.swift — missing registration caused pkl-swift to silently return nil for variablesDarkMode field, preventing dark icon generation - Fix Spinner non-animated mode (verbose/quiet): remove start() output to avoid duplicated messages; only stop() prints the final ✓/✗ result - Apply VariableModeDarkGenerator in granular cache path via applyVariableModeDark() - Document all three root causes in CLAUDE.md and module CLAUDE.md files
…ion test pkl-swift TypeRegistry is only for PklAny polymorphic decoding and performance — concrete Decodable structs decode via synthesized init(from:) regardless of registerPklTypes. Corrected misleading "silent nil" claims. - Add PKLEvaluatorTests.evaluatesVariablesDarkMode proving deserialization works - Add diagnostic log in FigmaComponentsSource to trace variablesDarkMode values - Add PKL deserialization debugging guide to ExFigConfig CLAUDE.md
Figma variable IDs are file-scoped — alias targets from the icons file don't exist in library files by ID. When variables reference an external library (common in design systems with semantic + primitive layers), dark mode generation failed silently because alias chains couldn't be resolved. Added `variablesFileId` to `VariablesDarkMode` PKL config. When set, variables are loaded from BOTH files: icons file (semantic variables matching node boundVariables) + library file (primitives). Cross-file matching uses variable NAME and mode NAME (not file-scoped IDs). - Add `variablesFileId: String?` to Common.VariablesDarkMode PKL schema - Add `variablesFileId` to IconsSourceInput and all platform entry bridges - Implement `resolveViaLibrary()` name-based fallback in VariableModeDarkGenerator - Add debug-level logs for verbose mode tracing - Update PKL test fixture and PKLEvaluatorTests
Add darkCount to IconsExportResult so the success message reflects generated dark variants: "Exported 47 icons (47 dark variants)".
Add VariablesCache (Lock + Task dedup) to avoid redundant Figma Variables API calls when multiple icon entries share the same fileId. With 15 DC entries, this reduces 30 API calls to exactly 2. - New VariablesCache class using NSLock + Task for concurrent dedup - Injected per platform section: PluginIconsExport → SourceFactory → FigmaComponentsSource → VariableModeDarkGenerator - Also threaded through IconsExportContextImpl for granular cache path
- CONFIG.md: add variablesFileId to VariablesDarkMode field list - iOSIcons.md: add "Variable Mode" section with PKL example - AndroidIcons.md: add "Variable Mode" section with PKL example - exfig-ios.pkl example: add variablesFileId usage
SVG hex replacement now handles opacity changes via ColorReplacement(hex, alpha). When the dark variable value has alpha < 1.0 (e.g., transparent background), the replacer adds fill-opacity/stroke-opacity attributes to SVG elements. This fixes double-color icons where the dark mode background should be fully transparent (alpha: 0) but was rendered as opaque. - Add ColorReplacement struct with hex + alpha fields - SVGColorReplacer adds -opacity attributes when alpha < 1.0 - Update VariableModeDarkGenerator to propagate alpha from PaintColor - Add 9 new test cases for alpha/opacity handling
|
@gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a new 'Variable-mode' dark icon generation feature, which resolves Figma Variable bindings to automatically generate dark SVG variants. Key changes include the addition of VariableModeDarkGenerator and VariablesCache to handle variable resolution and caching, updates to the IconsExportContext to support this generation flow, and modifications to the PKL configuration schemas to support variablesDarkMode and suffixDarkMode configurations. I have reviewed the code and suggest conforming ColorReplacement to Equatable to improve comparison robustness.
|
Warning Gemini is experiencing higher than usual traffic and was unable to create the review. Please try again in a few hours by commenting |
…dark mode - Add warning/debug logs to all guard/continue paths in processLightPacks - Add debug logs in resolveDarkColor for deleted, not-found, and non-color variables - Add precondition for empty fileId in Config.init - Extract maxAliasDepth constant, use UUID in temp directory name - Add failed-task eviction in VariablesCache for transient error retry - Add stderr fallback in SVGColorReplacer for release-build regex errors - Add 18 unit tests for VariableModeDarkGenerator (alias chains, cross-file, depth limit) - Add 3 tests for VariablesCache (dedup, separate fileIds, eviction) - Add 2 tests for SVGColorReplacer (flood-color, lighting-color)
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces a new variable-mode dark icon generation feature, allowing dark SVG variants to be created by resolving Figma Variable bindings and performing hex color replacement. It includes the new VariableModeDarkGenerator and SVGColorReplacer utilities, updates to the PKL configuration schemas to support variablesDarkMode, and integration across all platform exporters. My feedback focuses on optimizing the VariablesCache lifecycle to enable cross-platform sharing, hardening the SVG color replacement regex to prevent partial hex matches, and reducing code duplication in the source loading logic.
…ry variable names - Add lookahead (?=[;"'\s]|$) to CSS regex patterns to prevent matching #aabbcc inside #aabbccdd (8-digit hex) - Warn when library file has duplicate variable names across collections - Extract buildLibNameIndex helper to keep function body under 60 lines
ComponentPreFetcher only works in batch mode (BatchSharedState is nil in standalone). Add ComponentsCache (same Lock+Task dedup pattern as VariablesCache) to deduplicate Components API calls when multiple icon entries share the same fileId — reduces 15 identical API calls to 1.
Update mise tools to latest versions (dprint 0.53.1, hk 1.39.0, git-cliff 2.12.0, actionlint 1.7.11, pkl 0.31.1, usage 3.2.0, xcsift 1.2.0) and regenerate mise bootstrap (2026.3.17). Add plugin sync checklist, MCP guide sync docs, and design requirements for variable-mode dark icons. Improve TerminalUI with formatLink helper and update MCP tool handlers with variablesDarkMode support.
Strip trailing blank lines in generate-llms.sh output and add depends = "llms-check" to hk newlines hook to prevent race condition where newlines check runs before llms regeneration completes.
Description
variablesDarkModeonFrameSource)VariablesDarkModeandSuffixDarkModePKL objectsvariablesFileId)VariablesCache(Lock + Task dedup)fill-opacity/stroke-opacityHow it works
boundVariableson fills/strokesAdditional notes
resolveViaLibrary)registerPklTypesin pkl-swift is a performance optimization, not a correctness requirement for concreteDecodabletypes — corrected misleading docs from initial debuggingPKLEvaluatorTestsupdated to covervariablesDarkModedeserializationSVGColorReplacerTestsexpanded from 11 to 20 test cases (alpha/opacity coverage)