Skip to content

feat: add robust theme switcher #41

Merged
markhazleton merged 6 commits intomainfrom
001-robust-theme-switcher
Apr 14, 2026
Merged

feat: add robust theme switcher #41
markhazleton merged 6 commits intomainfrom
001-robust-theme-switcher

Conversation

@markhazleton
Copy link
Copy Markdown
Owner

@markhazleton markhazleton commented Apr 14, 2026

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

  • Added a dedicated /themes selector experience with card/status UI for BootstrapSpark plus the full Bootswatch catalog.
  • Introduced a typed local theme catalog with optional Bootswatch metadata enrichment and safe local fallback behavior.
  • Implemented runtime stylesheet loading with rollback to the default theme when a theme asset fails.
  • Persisted theme preferences across sessions and route transitions.
  • Synced local Bootswatch assets into public/themes with CSP-safe sanitization.
  • Added contract, unit, and integration coverage for persistence, fallback recovery, responsive behavior, route coverage, performance, and asset packaging.
  • Addressed review feedback by enabling startup metadata enrichment, guarding against out-of-order theme application, generalizing fallback copy, and making spec documentation links portable.

Changes Since Last Review

  • Enabled app initialization to opt into remote Bootswatch metadata enrichment while preserving local fallback behavior.
  • Added regression coverage proving startup metadata enrichment is attempted and that the latest theme-selection request wins over slower earlier requests.
  • Updated rollback and warning messaging to derive from the actual restored theme instead of hard-coded BootstrapSpark copy.
  • Reconciled agent instruction version notes with package.json and converted spec plan/task references to repo-relative paths.
  • Validated the follow-up with focused tests, lint, type-check, and a production build.

Task Completion

35/35 tasks complete. Checklist status: 16/16 complete.

Quality Gates

  • Analyze: pass
  • Critic: pass
  • Focused theme tests: pass
  • Lint: pass with 2 existing react-refresh/only-export-components warnings in src/models/Project.tsx
  • Type-check: pass
  • Build: pass

Constitution Checklist

  • Type safety and runtime validation remain in place for the theme/runtime changes.
  • Automated tests cover the review-driven fixes.
  • Lint, type-check, and production build were run for the latest branch head.
  • Documentation artifacts were updated alongside the code changes.
  • Review feedback was addressed without widening the implementation scope.

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.

@markhazleton markhazleton changed the title feat: add robust theme switcher feat: add robust theme switcher Apr 14, 2026
@markhazleton markhazleton requested a review from Copilot April 14, 2026 03:58
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 /themes route) 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.

Comment thread src/components/theme/ThemeStatusNotice.tsx Outdated
Comment thread .github/agents/copilot-instructions.md Outdated
Comment thread .documentation/specs/001-robust-theme-switcher/plan.md Outdated
Comment thread .documentation/specs/001-robust-theme-switcher/tasks.md Outdated
Comment thread src/contexts/ThemeContext.tsx
Comment thread src/services/theme/themeStylesheetService.ts
@markhazleton markhazleton merged commit 89e1d41 into main Apr 14, 2026
4 checks passed
@markhazleton markhazleton deleted the 001-robust-theme-switcher branch April 14, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants