mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-11 03:09:57 +00:00
feat(v3): host theme read surface — window.feedBack.theme + always-present --fbv-* tokens (#646)
First slice of the host theme contract (#644): give plugins a host-owned way to read the active theme + its device affordances, so a feature renders correctly under any theme instead of binding to whichever one the dev saw. theme-core.js previously only APPLIED themes and emitted --fbv-* vars only while a theme was equipped (nothing to read in the default state), with no read API. Now, all additive + feature-detected: - Always-present default `fb` palette as `--fbv-*` on :root (the un-themed look is unchanged — fb-* utilities still use their compiled defaults; this only hands plugins a stable host token to read + derive surfaces from). Adds two keystone ROLES the palette lacked: `on-accent` (legible fg on the accent fill) and `focus-ring`. - window.feedBack.theme.get() -> {id, isThemed, tokens}; .capabilities() -> {glow, gradients, motion} (the device-affordance signal; recolor-only themes report defaults, a theme may opt out via `capabilities` in its payload, motion is reduced-motion-gated); .prefersReducedMotion(). - Normalized `theme:changed` event from the single apply() chokepoint. The apply side stays on window.v3Theme; the read surface is attached defensively so it survives the feedBack bus being (re)built by capabilities.js regardless of load order. Verified via a headless render (apply/unequip intact, defaults present + restored, capability opt-out honored, event payload correct) + tests/js/v3_theme_read_api.test.js. See docs/host-theme-contract.md. Claude-Session: https://claude.ai/code/session_01QbexxfTt8q2tAn436MqGWF Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7ff9261000
commit
d20b33348b
@@ -0,0 +1,57 @@
|
||||
// Guard for the host theme READ surface added to the cosmetics applier
|
||||
// (static/v3/theme-core.js): always-present `--fbv-*` defaults on :root (so a
|
||||
// plugin can read host tokens un-themed), the two keystone roles the palette
|
||||
// lacked (on-accent / focus-ring), and the window.feedBack.theme read API +
|
||||
// theme:changed event. Source-level guards on the contract surface; runtime
|
||||
// behaviour (apply/unequip/defaults-restore/capabilities) is verified
|
||||
// separately by a headless render. First slice of the host theme contract
|
||||
// (got-feedback/feedBack#644).
|
||||
|
||||
'use strict';
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const TC = fs.readFileSync(
|
||||
path.join(__dirname, '..', '..', 'static', 'v3', 'theme-core.js'), 'utf8');
|
||||
|
||||
test('always-present --fbv-* defaults are injected on :root', () => {
|
||||
assert.match(TC, /const DEFAULTS = \{/);
|
||||
assert.match(TC, /function _injectDefaults\(\)/);
|
||||
assert.match(TC, /id = 'fb-theme-defaults'/);
|
||||
assert.match(TC, /':root \{/);
|
||||
// injected before the first profile read so var(--fbv-*) resolves immediately
|
||||
assert.match(TC, /_injectDefaults\(\);\s*\n\s*refresh\(\);/);
|
||||
});
|
||||
|
||||
test('two keystone roles the palette lacked are added as defaults', () => {
|
||||
assert.match(TC, /'on-accent':/);
|
||||
assert.match(TC, /'focus-ring':/);
|
||||
});
|
||||
|
||||
test('window.feedBack.theme read API is exposed (get/capabilities/prefersReducedMotion)', () => {
|
||||
assert.match(TC, /window\.feedBack = window\.feedBack \|\| \{\};/);
|
||||
assert.match(TC, /window\.feedBack\.theme = \{ get, capabilities, prefersReducedMotion \};/);
|
||||
assert.match(TC, /function get\(\)/);
|
||||
assert.match(TC, /function capabilities\(\)/);
|
||||
assert.match(TC, /function prefersReducedMotion\(\)/);
|
||||
});
|
||||
|
||||
test('capabilities reports device affordances and gates motion on reduced-motion', () => {
|
||||
assert.match(TC, /glow:/);
|
||||
assert.match(TC, /gradients:/);
|
||||
assert.match(TC, /motion: allowMotion && !prefersReducedMotion\(\)/);
|
||||
});
|
||||
|
||||
test('apply() tracks theme id + declared capabilities and emits theme:changed', () => {
|
||||
assert.match(TC, /function apply\(payload, meta\)/);
|
||||
assert.match(TC, /_themeId =/);
|
||||
assert.match(TC, /_themeCaps =/);
|
||||
assert.match(TC, /_emitThemeChanged\(\)/);
|
||||
assert.match(TC, /emit\('theme:changed'/);
|
||||
});
|
||||
|
||||
test('the apply side stays on window.v3Theme (read surface is additive)', () => {
|
||||
assert.match(TC, /window\.v3Theme = \{/);
|
||||
});
|
||||
Reference in New Issue
Block a user