mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-11 02:14:29 +00:00
docs: add plugin-author theming migration guide + scope the reconciliation rule
- docs/plugin-theming.md — the how-to for plugin authors: the golden rule (reference roles + recipe slots, never hardcode a device), what the host provides (--fbv-* role tokens incl. on-accent/focus-ring + window.feedBack.theme), and two paths — Path A (no own skin → derive surfaces from host + pick devices from capabilities) and Path B (own deliberate skins → own surfaces, adopt only the per-skin device pattern), plus accessibility + the pre-merge matrix check. Workstream item 4 of #644. - Refines the contract's reconciliation rule: "derive surfaces from host" applies to plugins WITHOUT their own identity; plugins WITH deliberate skins (note_detect) own their surfaces (deriving would erase metal's steel) and adopt only the role/recipe pattern. Records the 2026-06-29 decision to keep note_detect's skins self-owned + their current button text — so item 2 is the per-skin pattern + the verification gate, not surface-derivation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QbexxfTt8q2tAn436MqGWF
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
2a15f6e757
commit
5bfe508642
@@ -94,11 +94,20 @@ On the existing `window.feedBack` bus:
|
||||
- `theme:changed` event → `{ id, tokens, capabilities }` (emitted at theme-core's existing
|
||||
apply chokepoint; analogous to `note_detect`'s `notedetect:skin`)
|
||||
|
||||
**Reconciliation rule (ends the two-disconnected-systems problem):** a plugin skin
|
||||
**derives surface/text/border from host tokens** (`--nd-bg: var(--fb-card)`, etc.) and
|
||||
**owns only its accent + its devices**, selecting the device via the recipe. A host theme then
|
||||
pulls plugin chrome along (one truth for surfaces), while the plugin layers identity on top and
|
||||
never imposes a device the active theme neutralizes.
|
||||
**Reconciliation rule (ends the two-disconnected-systems problem) — depends on whether the
|
||||
plugin has its own identity:**
|
||||
|
||||
- **A plugin *without* its own skin** should **derive surface/text/border from host tokens**
|
||||
(`background: var(--fbv-card)`, etc.) and **own only its accent + its devices**, selecting the
|
||||
device via the recipe. A host theme then pulls its chrome along — one truth for surfaces — and
|
||||
it never imposes a device the active theme neutralizes. This is the common case.
|
||||
- **A plugin *with* deliberate skins** (e.g. `note_detect`'s neon / esports / metal, which are
|
||||
full design languages) **owns its surfaces** — deriving them from the host theme would *erase*
|
||||
the skin's identity (metal's brushed steel becomes a flat host colour). Such a plugin adopts the
|
||||
**role + recipe *pattern*** (per-skin device tokens, `on-accent`, `focus-ring`, "off" is legal)
|
||||
and verifies across **its own** skin matrix, but does not blindly inherit host surfaces.
|
||||
*(Decided 2026-06-29: keep note_detect's skins self-owned + their current button text — so the
|
||||
fix is the per-skin device pattern + the verification gate, not surface-derivation.)*
|
||||
|
||||
## 5. Consumption pattern (the rule for feature authors)
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
# Theming a plugin so it works in every theme
|
||||
|
||||
This is the how-to for plugin authors. It exists because of one recurring bug:
|
||||
a plugin's UI is built and eyeballed against the **default** look, ships, and
|
||||
then a user switches themes and the feature's *colours adapt but its visual
|
||||
devices vanish* — a glow ring, a colour gradient — because the active theme
|
||||
doesn't speak that visual language.
|
||||
|
||||
**The golden rule:** themes are different **design languages**, not palettes.
|
||||
Reference **roles** and **recipe slots**; never hardcode a *device* (a literal
|
||||
`box-shadow` glow, a literal `linear-gradient`, a hex colour). Then your feature
|
||||
renders correctly in a theme nobody has invented yet — and **verify it across
|
||||
the whole theme set before you merge.**
|
||||
|
||||
(Design + rationale: [host-theme-contract.md](host-theme-contract.md), got-feedback/feedBack#644.)
|
||||
|
||||
## What the host gives you
|
||||
|
||||
Always-present CSS role tokens on `:root` (resolve themed *or* un-themed):
|
||||
`--fbv-bg / sidebar / card / cardMuted / primary / primaryHi / accent / text /
|
||||
textDim / border / good / mid / low / gold`, plus **`--fbv-on-accent`** (a
|
||||
foreground legible *on* an accent fill) and **`--fbv-focus-ring`**. Use as
|
||||
`color: rgb(var(--fbv-text))`, `background: rgb(var(--fbv-card))`, etc.
|
||||
|
||||
A JS read surface on the event bus:
|
||||
|
||||
```js
|
||||
const t = window.feedBack?.theme;
|
||||
t?.get(); // { id, isThemed, tokens }
|
||||
t?.capabilities(); // { glow, gradients, motion } — what devices this theme permits
|
||||
t?.prefersReducedMotion(); // boolean (one central matchMedia)
|
||||
window.feedBack.on('theme:changed', (e) => { /* e.detail = {id, tokens, capabilities} */ });
|
||||
```
|
||||
|
||||
Feature-detect everything (`window.feedBack?.theme?.get`) and pass a fallback to
|
||||
every `var(--fbv-x, <fallback>)` so you degrade cleanly on an older host.
|
||||
|
||||
## Pick your path
|
||||
|
||||
### Path A — you do NOT have your own skins (most plugins)
|
||||
|
||||
Look like the host. **Derive surfaces from host tokens** and **choose devices
|
||||
from capabilities** instead of hardcoding them:
|
||||
|
||||
```js
|
||||
const caps = window.feedBack?.theme?.capabilities?.() ?? { glow: true, motion: true };
|
||||
heroEl.classList.toggle('use-glow', caps.glow && !window.feedBack.theme.prefersReducedMotion());
|
||||
```
|
||||
```css
|
||||
.hero { background: rgb(var(--fbv-primary)); color: rgb(var(--fbv-on-accent, #fff)); }
|
||||
.hero:focus-visible { outline: 2px solid rgb(var(--fbv-focus-ring)); outline-offset: 2px; }
|
||||
.hero.use-glow { box-shadow: 0 0 16px -2px rgb(var(--fbv-primary)); } /* only where the theme allows it */
|
||||
```
|
||||
Re-read on `theme:changed` if you cache anything (e.g. a canvas palette).
|
||||
|
||||
### Path B — you HAVE your own deliberate skins (like the scoring UI)
|
||||
|
||||
Your skins are an identity (neon vs steel vs clean) — **own your surfaces**;
|
||||
don't inherit host surfaces or you'll erase that identity. Adopt only the
|
||||
**pattern**: make every visual *device* a **per-skin token whose "off" value is
|
||||
legal**, so each skin authors its own version and a glow-less skin simply
|
||||
doesn't glow. Example (the "make the hero special" device):
|
||||
|
||||
```css
|
||||
/* base / neon */ :root { --hero-ring: .5; --hero-border: transparent; --acc-fill: linear-gradient(135deg, var(--accent), var(--accent2)); }
|
||||
/* glow-less skin */ [data-skin="clean"] { --hero-ring: 0; --hero-border: var(--accent); --acc-fill: var(--accent); }
|
||||
.hero { background: var(--acc-fill); border-color: var(--hero-border); }
|
||||
.hero::after { opacity: var(--hero-ring); /* the glow ring; 0 = off */ }
|
||||
```
|
||||
Neon emphasises with the ring, the clean skin with a solid border — **neither is
|
||||
empty of emphasis; each speaks its own language.** Same idea for an accent-filled
|
||||
number (`--acc-fill` = a gradient in one skin, a solid in another so it doesn't
|
||||
wash out to white). Add `on-accent` + `focus-ring` per skin too.
|
||||
|
||||
## Accessibility (part of the contract, not an afterthought)
|
||||
|
||||
- **Reduced motion:** gate decorative animation on `prefers-reduced-motion`
|
||||
(CSS `@media`, or `theme.prefersReducedMotion()`); functional transitions are fine.
|
||||
- **Focus:** always a visible `:focus-visible` outline using `focus-ring` — don't
|
||||
bind focus only to `accent` (it can ≈ the surface in some themes).
|
||||
- **On-accent contrast:** text on an accent fill uses `on-accent`; watch light
|
||||
accents (a mid-amber needs dark text, not white).
|
||||
|
||||
## Before you merge — verify across the matrix
|
||||
|
||||
Render your UI in **every** theme/skin and look at them together. If you ship
|
||||
your own skins, do this with a render-matrix in CI/local (reference: the scoring
|
||||
UI's `npm run render-skins` + `theme-matrix-checklist.md`). The checklist that
|
||||
catches this bug class:
|
||||
|
||||
- [ ] colours via tokens, no hardcoded hexes
|
||||
- [ ] rendered in **all** themes/skins (not just the default)
|
||||
- [ ] every new *device* is per-skin / capability-gated and **legible when its token is `none`**
|
||||
- [ ] reduced-motion + visible focus in every theme
|
||||
- [ ] text on accent stays legible everywhere
|
||||
Reference in New Issue
Block a user