feat: add robust theme switcher #41
Merged
markhazleton merged 6 commits intomainfrom Apr 14, 2026
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Expands the app’s existing light/dark toggle into a catalog-backed runtime theme system with a dedicated /themes selector page, local Bootswatch asset support, persistence, and automated validation (unit/contract/integration).
Changes:
- Introduces a typed theme catalog + services for resolving preferences, optional Bootswatch metadata enrichment, and runtime stylesheet swapping with rollback.
- Updates the global theme context and navigation (header + new
/themesroute) to support multi-theme selection and persistence. - Adds theme assets + sync script, plus a comprehensive test suite covering persistence, fallback/rollback, responsiveness, route surfaces, and packaging.
Reviewed changes
Copilot reviewed 42 out of 70 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/theme/themePreference.test.ts | Unit coverage for localStorage preference helpers. |
| tests/unit/theme/themeCatalogService.test.ts | Unit coverage for catalog load, resolution, and metadata enrichment merge. |
| tests/integration/theme/themeShellRegression.test.tsx | Regression coverage for header/footer usability under a dark theme. |
| tests/integration/theme/themeSelectorResponsive.test.tsx | Responsive viewport assertions for selector usability. |
| tests/integration/theme/themeSelectorPage.test.tsx | Selector interaction/keyboard flow coverage. |
| tests/integration/theme/themeRouteCoverage.test.tsx | Smoke coverage for key routes/components under theme providers. |
| tests/integration/theme/themePersistenceFlow.test.tsx | Integration coverage for persistence across unmount/remount. |
| tests/integration/theme/themePerformanceValidation.test.tsx | Validates theme-switch latency budget (test-environment focused). |
| tests/integration/theme/themeFallbackRecovery.test.tsx | Validates rollback behavior and persistence after failure. |
| tests/integration/theme/themeAssetPackaging.test.ts | Ensures expected theme assets exist in public/ (and optionally docs/). |
| tests/integration/theme/renderWithThemeProviders.tsx | Shared integration test renderer wiring Theme/SEO/router providers. |
| tests/contract/theme/themeCatalog.contract.test.ts | Contract validation for catalog shape/default ordering invariants. |
| src/utils/themePreference.ts | Implements read/write/clear for persisted theme preference. |
| src/test/setup.ts | Stabilizes build-date global for tests. |
| src/services/theme/themeStylesheetService.ts | Runtime stylesheet link mounting + load/fail detection + rollback behavior. |
| src/services/theme/themeCatalogService.ts | Catalog validation, theme resolution helpers, optional Bootswatch metadata fetch/merge. |
| src/scss/styles.scss | Wires new selector SCSS into global build. |
| src/scss/components/_theme-selector.scss | Adds selector page/card styling (including dark-mode variant). |
| src/models/theme/themeCatalog.ts | Adds Zod schemas/types for catalog, options, preference, and load status. |
| src/data/theme-catalog/supportedThemes.ts | Local source-of-truth catalog (BootstrapSpark default + Bootswatch IDs). |
| src/css/styles.css | Compiled CSS output including selector styles. |
| src/contexts/ThemeContext.tsx | Replaces binary toggle with catalog-backed provider + persistence + status/error states. |
| src/components/theme/ThemeStatusNotice.tsx | User-facing status/error/fallback messaging component. |
| src/components/theme/ThemeSelectorPage.tsx | New /themes route page rendering selector UX from catalog. |
| src/components/theme/ThemeSelectorCard.tsx | Theme card UI + active/busy state + apply button. |
| src/components/Header.tsx | Replaces toggle with link to selector + active theme badge and status icon. |
| src/App.tsx | Registers /themes route via lazy-loaded selector page. |
| scripts/sync-bootswatch-themes.mjs | Copies/sanitizes Bootswatch dist CSS into public/themes. |
| public/themes/bootstrapspark.css | Adds first-party default theme stylesheet asset. |
| package.json | Adds Bootswatch dependency + sync script + postinstall hook; bumps react-router-dom/typescript-eslint. |
| package-lock.json | Locks added/updated dependencies (Bootswatch, react-router-dom, typescript-eslint) and sets package name. |
| agents-registry.json | Registers agent metadata for Copilot instructions. |
| .github/agents/copilot-instructions.md | Updates agent-facing tech/context notes for the new theme system. |
| .documentation/specs/001-robust-theme-switcher/tasks.md | Feature task plan and execution checklist. |
| .documentation/specs/001-robust-theme-switcher/spec.md | Feature specification artifact. |
| .documentation/specs/001-robust-theme-switcher/research.md | Research/tradeoffs supporting decisions. |
| .documentation/specs/001-robust-theme-switcher/quickstart.md | Verification steps, commands, and execution notes. |
| .documentation/specs/001-robust-theme-switcher/plan.md | Implementation plan artifact. |
| .documentation/specs/001-robust-theme-switcher/gates/critic.md | Critic gate result artifact. |
| .documentation/specs/001-robust-theme-switcher/gates/analyze.md | Analyze gate result artifact. |
| .documentation/specs/001-robust-theme-switcher/data-model.md | Theme domain data model artifact. |
| .documentation/specs/001-robust-theme-switcher/contracts/theme-catalog-contract.md | Contract definition for catalog/selection/enrichment. |
| .documentation/specs/001-robust-theme-switcher/checklists/requirements.md | Requirements checklist artifact. |
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.
Summary
Expand BootstrapSpark from a binary light/dark toggle into a dedicated runtime theme-selection experience with a maintained first-party default theme, full Bootswatch support, resilient fallback behavior, and persistent user preference.
Changes
/themesselector experience with card/status UI for BootstrapSpark plus the full Bootswatch catalog.public/themeswith CSP-safe sanitization.Changes Since Last Review
package.jsonand converted spec plan/task references to repo-relative paths.Task Completion
35/35 tasks complete. Checklist status: 16/16 complete.
Quality Gates
react-refresh/only-export-componentswarnings insrc/models/Project.tsxConstitution Checklist
Gate Acknowledgements
No unresolved checklist, analyze, or critic findings were present at task-generation time.
Spec Reference
.documentation/specs/001-robust-theme-switcher
Quickfix Reference
N/A
Notes
This PR now includes the original robust theme-switching implementation plus the follow-up fixes from review: startup metadata enrichment is active, theme application is protected against async ordering issues, fallback messaging is generic and correct, and the supporting spec docs are portable on GitHub.