feat: runtime theme switching + second design-token colour theme (selfFridge)#88
Draft
khoinguyen-mindera wants to merge 25 commits into
Draft
feat: runtime theme switching + second design-token colour theme (selfFridge)#88khoinguyen-mindera wants to merge 25 commits into
khoinguyen-mindera wants to merge 25 commits into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds runtime theme switching to Alfie iOS by extending the design-token generator to emit multiple colour palettes (one per .primitives theme mode) and introducing a persisted theme selection that is applied during app bootstrap (and on soft-reboot), so the chosen theme survives relaunch.
Changes:
- Design-token generator now supports multi-theme colour palettes + hex colour parsing, emitting
ThemeColours,ColourPalette, andAppThemewhile makingPrimitives.Colours.*forward to the active palette. - New
ThemeServicepersists the selected theme id and triggers a soft-reboot; app bootstrap applies the persisted theme before first render / appearance setup. - Debug Menu gains a Theme picker UI and related accessibility identifiers; several UI components switch static colour captures to computed vars to avoid “frozen” colours after reboot.
Reviewed changes
Copilot reviewed 33 out of 33 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| Tools/DesignTokenGen/Tests/DesignTokenGenCoreTests/GeneratorTests.swift | Adds tests for multi-theme loading, hex parsing, and multi-theme emission/guards. |
| Tools/DesignTokenGen/Tests/DesignTokenGenCoreTests/Fixtures/mini/manifest.json | Extends mini fixture to include a second .primitives theme mode. |
| Tools/DesignTokenGen/Tests/DesignTokenGenCoreTests/Fixtures/mini/.primitives.selffridge-theme.tokens.json | Adds mini fixture token file for the second theme (hex colours). |
| Tools/DesignTokenGen/Tests/DesignTokenGenCoreTests/CoverageTests.swift | Updates expected written files and adjusts colour literal tests to target ThemeColours. |
| Tools/DesignTokenGen/Sources/DesignTokenGenCore/TokenLoader.swift | Loads per-theme colour palettes, adds .primitives theme mode handling, and parses hex colour strings. |
| Tools/DesignTokenGen/Sources/DesignTokenGenCore/Token.swift | Adds TokenValue.isColour helper for filtering colour tokens. |
| Tools/DesignTokenGen/Sources/DesignTokenGenCore/Emitter.swift | Emits new ThemeColours+Generated.swift and changes colours/semantic tokens to computed forwarders. |
| Alfie/AlfieKit/Tests/ModelTests/ThemeServiceTests.swift | Adds unit tests for theme persistence + reboot triggering. |
| Alfie/AlfieKit/Tests/DebugMenuTests/ThemePickerViewModelTests.swift | Adds unit tests for theme picker view model behavior. |
| Alfie/AlfieKit/Sources/SharedUI/Theme/Toggle/ThemedToggleView.swift | Converts captured theme colours to computed vars to reflect runtime theme switches. |
| Alfie/AlfieKit/Sources/SharedUI/Theme/Components/SizingBanner/SizingSwatchView.swift | Converts a captured colour constant to computed to avoid caching across reboot. |
| Alfie/AlfieKit/Sources/SharedUI/GeneratedTokens/ThemeColours+Generated.swift | New generated multi-theme palette holder + AppTheme enum. |
| Alfie/AlfieKit/Sources/SharedUI/GeneratedTokens/Theme+Generated.swift | Changes semantic colour tokens to computed vars so they track active theme. |
| Alfie/AlfieKit/Sources/SharedUI/GeneratedTokens/Primitives+Generated.swift | Changes primitive colours to forward to ThemeColours.current. |
| Alfie/AlfieKit/Sources/SharedUI/DesignTokens/manifest.json | Adds second .primitives theme mode for SharedUI token set. |
| Alfie/AlfieKit/Sources/SharedUI/DesignTokens/.primitives.selffridge-theme.tokens.json | Adds second theme primitive colour token set (hex colours). |
| Alfie/AlfieKit/Sources/SharedUI/Components/TabControl/TabControl.swift | Converts captured colours to computed vars for runtime theme switching. |
| Alfie/AlfieKit/Sources/Model/Services/Theme/ThemeServiceProtocol.swift | Introduces service protocol for persisting theme id and rebooting. |
| Alfie/AlfieKit/Sources/Model/Services/Theme/ThemeService.swift | Implements ThemeService backed by UserDefaults. |
| Alfie/AlfieKit/Sources/Model/Services/ServiceProviderProtocol.swift | Exposes themeService on the service provider protocol. |
| Alfie/AlfieKit/Sources/Mocks/Core/Services/MockThemeService.swift | Adds mock implementation for tests and previews. |
| Alfie/AlfieKit/Sources/Mocks/Core/Services/MockServiceProvider.swift | Wires themeService into mock provider. |
| Alfie/AlfieKit/Sources/Home/UI/HomeViewModel.swift | Passes themeService through to DebugMenu construction. |
| Alfie/AlfieKit/Sources/Home/Models/HomeDependencyContainer.swift | Adds themeService to Home dependency container. |
| Alfie/AlfieKit/Sources/DebugMenu/UI/Theme/ThemePickerViewModel.swift | Implements theme picker logic using AppTheme + ThemeServiceProtocol. |
| Alfie/AlfieKit/Sources/DebugMenu/UI/Theme/ThemePickerView.swift | Adds Theme picker UI list with selection checkmark + accessibility ids. |
| Alfie/AlfieKit/Sources/DebugMenu/UI/DebugMenuViewModel.swift | Adds themeService dependency to DebugMenu view model. |
| Alfie/AlfieKit/Sources/DebugMenu/UI/DebugMenuView.swift | Adds Theme entry to Debug Menu navigation and wires ThemePickerView. |
| Alfie/AlfieKit/Sources/AppFeature/Navigation/AppFeatureViewModel.swift | Injects themeService into Home flow dependencies. |
| Alfie/AlfieKit/Sources/AccessibilityIdentifiers/AccessibilityID.swift | Adds accessibility identifiers for Debug Menu theme picker. |
| Alfie/AlfieKit/Package.swift | Updates module dependencies for new DebugMenu/theme picker and tests. |
| Alfie/Alfie/Service/ServiceProvider.swift | Instantiates and exposes ThemeService in app service provider. |
| Alfie/Alfie/Delegate/AppDelegate.swift | Applies persisted theme during bootstrap before first UI render / appearance setup. |
Comment on lines
+52
to
54
| "selffridge-theme": [ | ||
| ".primitives.selffridge-theme.tokens.json" | ||
| ] |
Comment on lines
+7
to
+9
| private let userDefaults: UserDefaultsProtocol | ||
| private let storageKey = "SELECTED_THEME_ID" | ||
| private weak var appDelegate: AppDelegateProtocol? |
khoinguyen-mindera
marked this pull request as draft
July 7, 2026 06:28
…lour theme + runtime switch)
…Service, multi-theme/VM coverage
…ow theme switches
khoinguyen-mindera
force-pushed
the
feat/theme-switching
branch
from
July 9, 2026 13:26
cc7cd92 to
66e6e16
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ticket
n/a (free-form feature)
Summary
neutrals-0/100/200) so the switch is clearly visible for testing; dark neutrals stay dark for text contrast.Tools/DesignTokenGen): now parses hex colours, loads each.primitives.*theme mode into its own palette, and emits aColourPalettestruct +AppThemeenum + aThemeColours.currentholder.Primitives.Colours.*became computed forwarders to the active palette — so all ~429 existing call sites reskin with zero edits.ThemeService(Model, mirrorsApiEndpointService) persists the selected theme id and soft-reboots viaAppDelegate.rebootApp();AppDelegate.bootstrapapplies the palette + UIKit appearance before first render, so the selection survives relaunch.ServiceProvider→HomeDependencyContainer→DebugMenuViewModel; newAccessibilityIDentries.static letcolour captures (TabControl / ThemedToggle / SizingSwatch) to computed vars so they don't freeze on a soft-reboot.ThemeServiceunit tests added.Verification
./Alfie/scripts/verify.sh --skip-integration: build + unit PASSED. Integration was skipped (needs theAlfie-BFFrepo as a sibling checkout; unavailable in this worktree) — this change does not touch networking.Notes / follow-ups
ThemeColours.currentis a mutable non-isolated global (safe today — main-thread only); annotate@MainActorwhen SharedUI adopts strict concurrency.Screenshot