diff --git a/issues/017-customizable-document-theme-variants.md b/issues/017-customizable-document-theme-variants.md new file mode 100644 index 0000000..18bec32 --- /dev/null +++ b/issues/017-customizable-document-theme-variants.md @@ -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)_