Design issue capturing the request to let users customize document-artifact colors and switch between theme variants, instead of everyone getting one forced theme. Notes the two separable pieces (structure/skin split via CSS variables as a prerequisite — the themes currently hardcode colors — and a switching mechanism), the preset-vs-raw-CSS security tradeoff, dark-mode interaction, and deck as the concrete trigger. References the existing planning docs on theme variants/palettes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.5 KiB
017 — Customizable, switchable theme variants for document artifacts
Status: Open Type: Feature / design (not a bug)
Motivation
Document artifacts (markdown, html_fragment, deck, and the json/csv
viewers) are effectively locked into a single look. The poster's agent can
pass a theme (report / tasks / base), but:
- There's no way for a viewer to switch an artifact's appearance — the
theme is fixed at post time and can't even be changed afterward (
themeisn't aPATCH-editable field; seeArtifactDetailView.patch). - There are only three document themes, differing in more than just color.
- Recent consistency work (unifying artifact backgrounds to one off-white, issue-driven) further homogenized the look — good for consistency, but it underlines that everyone gets one palette whether they want it or not.
The ask: let people customize the colors for the different document types, keeping the same structural CSS (same elements/selectors, same layout), and provide a way to switch between variants — so the app isn't forcing a single theme on everyone.
Current state (for grounding)
themeis a per-artifactCharField, set at create time:theme = data.get("theme") or DEFAULT_THEMES.get(kind, "base")(DEFAULT_THEMESmaps markdown/html_fragment→report, deck→deck).- Rendered pages link
/static/themes/{theme}.css. Theme files:base.css,report.css,tasks.css,deck.css(+pygments.css). - The theme CSS hardcodes colors (e.g.
report.csssetsbackground: #faf9f6; color: #1e2b2fdirectly). There is no color-variable layer, so a new palette today means duplicating a whole theme file rather than swapping a few values. json/csvviewer styling is worse for this: it's inlined into each artifact's stored HTML at create time (wrap_json_viewer/wrap_csv_viewer), so it can't be restyled after the fact at all without re-rendering.
Two separable pieces
1. Structure/skin separation (prerequisite). Refactor the document
themes so structural rules (layout, spacing, element selectors, code
highlighting) are defined once, and color lives in CSS custom properties.
A "palette" then becomes a small set of variable values, not a duplicated
stylesheet. This is the same pattern the app chrome already uses
(daybook.css :root + [data-theme="dark"]); the artifact themes just
never adopted it. Without this, "customizable colors" means maintaining N
near-identical full CSS files.
Note: for json/csv to be re-skinnable, their viewer CSS needs to move
from inline-baked to a linked stylesheet (or the palette needs to be linked
separately), otherwise only newly-created ones can change.
2. Switching mechanism. Once palettes are swappable, decide who picks and when:
- Viewer-side switch (like the dark-mode toggle): the person browsing chooses a palette, applied at render/serve time; a personal preference persisted per user. Doesn't change the stored artifact.
- Per-artifact, editable: make
theme/palette aPATCH-editable field so an artifact's look can be changed after posting. - Per-kind or global default: a Settings option for "default palette for my markdown artifacts," etc.
These aren't mutually exclusive; a viewer-side personal default is probably the most flexible and matches the "don't force one theme on everyone" framing.
Open questions
- Preset palettes vs. user-authored CSS. "Customize the CSS" could mean
(a) pick from a set of provided palettes, (b) pick colors via a UI that
generates a palette, or (c) let users paste raw CSS. (c) is the most
flexible but has a real downside: user CSS is an injection surface (less
severe than JS — see issue #014 — but still, e.g. CSS can load external
resources / exfiltrate via background-image URLs). A curated-palette or
color-picker approach (a/b) avoids that; raw-CSS would need the same
"trusted content only" caveat as
html_full. Recommend starting with presets + maybe a color-picker, not raw CSS. - Interaction with app light/dark mode. Artifacts currently always render light regardless of the app's dark mode. Should palettes include dark artifact variants, and should they follow the app theme or be independent?
- Scope: global vs per-user.
TagColoris already global/shared across users by design; would palettes be per-user preferences instead? (Likely yes — a viewing preference, not shared state.) html_fullstays out of this entirely (verbatim, poster owns styling).deckis the concrete trigger: it's intentionally dark, and the request to make backgrounds consistent stalled on it precisely because it can't just take the off-white without reworking its text colors — a palette system with proper light/dark deck variants is the real fix.
Suggested phasing
- Refactor
base/report/tasks/deck(and the json/csv viewers) to drive all colors from CSS variables — no behavior change, just makes everything else possible. - Ship 2–3 curated palettes per document family.
- Add a viewer-side palette switch + persisted per-user preference (mirrors the dark-mode toggle plumbing).
- Later, optionally: per-artifact editable palette, or a color-picker for custom palettes.
References
planning/2026-09-02-theme_variants_and_user_palettes.md— prior design conversation on this exact topic.planning/framework_artifact_rendering.md— rendering/theming framework notes (incl. the "vendor locally, CSS-only" stance and future renderers).
Resolution
(pending)