feat: user theme preferences (#99)
* docs: add user theme preferences design plan
Completed brainstorming session. Design includes:
- Cookie-based preference storage (atbb-light-theme, atbb-dark-theme)
- PRG form with HTMX live color swatch preview
- User preference as first step in theme resolution waterfall
- 5 implementation phases with full acceptance criteria
* feat: add resolveUserThemePreference to theme resolution waterfall
* test: resolveUserThemePreference unit and resolveTheme integration tests
* fix: use Array<T> syntax instead of T[] for complex object types
Per TypeScript house style, complex object array types should use
Array<{ uri: string }> syntax instead of { uri: string }[].
File: apps/web/src/lib/theme-resolution.ts, line 78
Function: resolveUserThemePreference()
* feat: add settings page with light/dark theme preference form
* test: settings route GET and POST integration tests
* fix: address code review feedback on Phase 2 settings routes
- [Critical] Wrap decodeURIComponent(errorParam) in try/catch to prevent URIError on malformed URLs
- [Minor] Change array type syntax from T[] to Array<T> for consistency
- [Minor] Remove duplicate POST happy-path test
All settings tests pass; build and lint succeed.
* fix: use Array<string> instead of string[] in settings test helper
* feat: add HTMX theme preview endpoint and wire up select elements
* test: settings preview endpoint and HTMX attribute tests
* feat: add Settings nav link for authenticated users
* test: Settings nav link auth visibility tests
* docs: add Bruno collection entries for settings endpoints
* docs: update project context for user-theme-preferences branch
Document the theme resolution waterfall (now 5 steps with user
preference cookies) and the cookie protocol contract for
atbb-light-theme / atbb-dark-theme in CLAUDE.md.
* docs: clarify preview endpoint is unauthenticated in CLAUDE.md
* docs: add test plan for user theme preferences
* chore: gitignore .claude/ and add user theme preferences implementation plan
Adds .claude/ to .gitignore (Claude Code local state, machine-specific).
Commits the 5-phase implementation plan and test requirements used to
implement user theme preferences.
* fix: address PR review feedback on user theme preferences
Security:
- Add CSS injection guard to ThemeSwatchPreview (rejects values containing
; < }) matching the pattern already used in admin-themes.tsx
Error handling:
- Split network/JSON try blocks in preview, GET themes list, POST policy
fetch — SyntaxError from res.json() is a data error, not a code bug,
and must not be re-thrown via isProgrammingError
- Promote logger.warn → logger.error for themes list fetch failure
- Add logger.warn to preview endpoint catch block (was silently swallowing
AppView failures)
User-facing:
- Map ?error= codes to friendly messages; drop unknown codes (phishing
vector for crafted URLs showing raw internal codes like "invalid-theme")
Tests:
- Add getSetCookie absence assertions to allowUserChoice:false and
invalid-theme POST rejection tests
- Update ?error=invalid-theme GET test to verify friendly message in
settings-banner--error element
- Add tests for themes list non-ok response and network throw paths
- Add test for unknown ?error= code producing no banner
Docs:
- Align theme-resolution.ts internal section comments to use descriptive
headings instead of "Step N" (conflicted with JSDoc 5-step waterfall)
- CLAUDE.md: clarify settings routes bypass ThemeCache intentionally