# 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)_