feat: add custom theme system#103
Conversation
Outlines design for user-customizable themes built on the existing OKLCH CSS variable system. Custom themes are JSON payloads of variable values, persisted server-side, with separate theme bindings for the admin dashboard and each public status page. Includes data model, API contract, frontend editor layout, ThemeProvider refactor, test strategy, migration/rollback plan, and milestone breakdown.
Apply fixes from spec review: - Change custom_theme.created_by from INTEGER to TEXT to match the actual users.id (String primary key); drop FK to keep audit row after user deletion. - Widen OKLCH validation regex to allow optional alpha channel (number or percent), since the existing default theme already ships oklch(1 0 0 / 10%) and oklch(1 0 0 / 15%). - GET /api/settings/active-theme now returns a resolved payload including vars_light/vars_dark for custom refs, so the client applies the active theme in a single round-trip and the first-paint cache stays self-contained. - localStorage migration no longer auto-PUTs to the server. Member browsers stay read-only against the server; admins see a one-time prompt on the appearance page to optionally adopt their old per-browser color choice as the global active theme. - Delete-conflict response now uses the existing AppError envelope (plain 409 with message). Structured reference list moves to a separate GET /api/settings/themes/:id/references endpoint that the UI calls before issuing DELETE. - Replace ConfigService::get_or_default (does not exist) with an explicit get(...).await?.unwrap_or_else(...) pattern. - Fix migration filename format text from m20260YYMM to mYYYYMMDD, and correct the "three migrations" wording to two. - Pin hex<->OKLCH conversion to a maintained color library (culori) rather than rolling our own; document fallback if the dependency is rejected. - Add 8.3.1 spelling out where preset variable maps come from (handwritten preset-vars.ts plus a vitest invariant test that diffs it against the CSS files). - Extend the feature.custom_themes kill switch with read-time coercion so an existing custom-active state degrades to preset default without losing the original ref on disk; writes to custom:* refs are blocked while disabled.
- Sync the OKLCH validation regex in §7.4 with §5.4 (the residual
no-alpha pattern would have rejected the same default theme that
ships with the codebase). Add explicit numeric range checks for
L/C/H/alpha alongside the regex.
- Replace every `:id` / `:slug` Axum route placeholder with `{id}` /
`{slug}` to match Axum 0.8 syntax.
- Switch custom_theme.created_at / updated_at from raw INTEGER to
TIMESTAMP backed by sea-orm DateTimeUtc, and use ISO-8601 strings
in DTOs to match status_page / incident / maintenance conventions.
- Make the theme: ThemeResolved field a tagged union with both
custom and preset variants, used by both /api/settings/active-theme
and /api/status/{slug}; spell out scoped rendering for the preset
case (data-theme attribute + loadThemeCSS) so the implementation
does not branch.
- §7.5 explicitly assigns GET /api/settings/themes/{id}/references to
the admin (write_router) scope despite being a GET, with a full
route-to-router mapping table to prevent the obvious mistake of
routing GETs blindly into read_router.
- §3 Brand row: clarify that brand settings live as a configs table
KV ("brand" key) accessed via ConfigService, not a dedicated table.
- §8.3 culori dependency: require explicit dependency-vetting step
before adding the package.
- §7.4 OKLCH numeric range checks: pick a single rule per component instead of saying "out of range is invalid" right after "but C > 0.5 is fine, just lint." L/H/alpha out of range -> 422; C has no hard cap (only an in-editor inline hint), since high-chroma values are legal OKLCH and only get gamut-clipped at render time. - §7.6 ThemeResolved DTO consistency: add updated_at to the custom example so it matches the active-theme example, and spell out the discriminated-union field contract once for both Rust and TS so the two endpoints really do share one DTO. - §8.4 / §8.8 first-paint cache: rename localStorage key from active-theme-ref to active-theme-cache and document that it stores the full ActiveThemeResponse JSON. The previous name invited the bug of caching only the ref, which would force a flash-of-wrong-theme on every reload for custom themes.
Decompose the spec at docs/superpowers/specs/2026-04-30-custom-theme-design.md into 27 bite-sized tasks across 7 milestones (data layer, API, frontend foundation, list page, editor, status-page binding, i18n + docs + acceptance). Each task includes exact file paths, full code samples, expected test output, and a commit step. Plan is designed to be executed via subagent-driven-development or executing-plans, leaving the build green between tasks.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughImplements a complete custom themes feature for ServerBee, allowing administrators to create and manage user-defined light/dark OKLCH-based themes. Includes database schema, backend API endpoints, frontend UI components, theme resolution logic with feature flagging, and comprehensive documentation and tests. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 60 minutes.Comment |
Summary
Test Plan
Notes
src/routes/_authed/servers/index.cells.tsx, which is unrelated to this branch.Summary by CodeRabbit
Release Notes
New Features
Documentation