Add issue #017: customizable, switchable document theme variants

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>
This commit is contained in:
peter 2026-09-03 13:35:32 +01:00
parent 95b159c0df
commit a41e8dcfa5

View file

@ -0,0 +1,114 @@
# 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 (`theme`
isn't a `PATCH`-editable field; see `ArtifactDetailView.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)
- `theme` is a per-artifact `CharField`, set at create time:
`theme = data.get("theme") or DEFAULT_THEMES.get(kind, "base")`
(`DEFAULT_THEMES` maps 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.css` sets
`background: #faf9f6; color: #1e2b2f` directly). There is no
color-variable layer, so a new palette today means duplicating a whole
theme file rather than swapping a few values.
- `json`/`csv` viewer 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 a `PATCH`-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.** `TagColor` is already global/shared across
users by design; would palettes be per-user preferences instead? (Likely
yes — a viewing preference, not shared state.)
- **`html_full`** stays out of this entirely (verbatim, poster owns styling).
- **`deck`** is 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
1. 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.
2. Ship 2–3 curated palettes per document family.
3. Add a viewer-side palette switch + persisted per-user preference
(mirrors the dark-mode toggle plumbing).
4. 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)_