Skip to main content

max / goingson

theme: consume the shared intent layer (theme-common 0.5.0) get_theme now returns resolved SemanticTokens; themes.js applies the intent tokens directly as `--{token}` (deleted the hand-maintained COLOR_MAP). styles.css carries the goingson intent defaults for first paint plus brand aliases (--bg-*/--text-*/--accent-*/--border-color) over the intent layer, so existing selectors and the [data-color] badge ramp stay valid unchanged. Neobrute shadow + radius scales remain app-local. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-06-13 01:08 UTC
Commit: 0d1d359810987d8be9731bc31c985d47a4ca3f71
Parent: 442e655
4 files changed, +78 insertions, -62 deletions
M Cargo.lock +1 -1
@@ -6207,7 +6207,7 @@ dependencies = [
6207 6207
6208 6208 [[package]]
6209 6209 name = "theme-common"
6210 - version = "0.3.1"
6210 + version = "0.5.0"
6211 6211 dependencies = [
6212 6212 "serde",
6213 6213 "toml 0.8.2",
@@ -238,34 +238,68 @@
238 238 /* === NEOBRUTE DESIGN SYSTEM === */
239 239 /* Clean neobrutalism: bold borders, flat by default, offset shadows on actions + floats */
240 240
241 - /* Neo-brutalist color palette */
242 - /* Themeable: mapped by js/themes.js. Defaults below are the "goingson" (GoingsOn) theme. */
243 - --bg-primary: #E0E4FA; /* themeable: background.primary */
244 - --bg-secondary: #CDD3F0; /* themeable: background.secondary */
245 - --bg-tertiary: #BAC2E6; /* themeable: background.tertiary */
246 - --bg-card: #FFFFFF; /* themeable: background.surface */
247 -
248 - --text-primary: #000000; /* themeable: foreground.primary */
249 - --text-secondary: #2D2D2D; /* themeable: foreground.secondary */
250 - --text-muted: #6B6B6B; /* themeable: foreground.muted (WCAG AA 5.7:1 on white) */
251 -
252 - --accent-yellow: #F7D154; /* themeable: accent.yellow */
253 - --accent-green: #5CB85C; /* themeable: accent.green */
254 - --accent-blue: #6196FF; /* themeable: accent.blue */
255 - --accent-purple: #7B68EE; /* themeable: accent.purple */
256 - --accent-red: #DC3545; /* themeable: accent.red */
257 - --accent-cyan: #17A2B8; /* themeable: accent.cyan */
258 -
259 - --border-color: #000000; /* themeable: border.default */
241 + /* === INTENT LAYER (themeable) ===
242 + Resolved intent tokens, applied at runtime by js/themes.js (setProperty
243 + on :root). Defaults below are the "goingson" titular theme for first
244 + paint; theme-common resolves every theme — including the derived states
245 + (action-hover/active, selection, row-stripe, status surfaces) — so this
246 + is one shared mapping across audiofiles / Balanced Breakfast / MNW. */
247 + --surface-page: #E0E4FA;
248 + --surface-raised: #FFFFFF;
249 + --surface-sunken: #BAC2E6;
250 + --surface-overlay: #CDD3F0;
251 + --content: #000000;
252 + --content-secondary: #2D2D2D;
253 + --content-muted: #6B6B6B;
254 + --content-on-action: #000000;
255 + --action: #6196FF;
256 + --action-hover: #71a1ff;
257 + --action-active: #5787e6;
258 + --danger: #DC3545;
259 + --success: #5CB85C;
260 + --warning: #F7D154;
261 + --info: #17A2B8;
262 + --danger-surface: #e0cfe4;
263 + --success-surface: #d0dfe7;
264 + --warning-surface: #e3e2e6;
265 + --info-surface: #c8dcf2;
266 + --border: #000000;
267 + --border-strong: #000000;
268 + --focus-ring: #6196ff;
269 + --selection: #bacdfc;
270 + --row-stripe: #dadff7;
271 + --hover-surface: #bac2e6;
272 + --category-one: #DC3545;
273 + --category-two: #5CB85C;
274 + --category-three: #6196FF;
275 + --category-four: #F7D154;
276 + --category-five: #7B68EE;
277 + --category-six: #17A2B8;
278 +
279 + /* === BRAND ALIASES — GoingsOn vocabulary over the intent layer ===
280 + Kept so existing component selectors stay valid. Accent hues map to the
281 + categorical ramp (preserves all six distinct badge colors). */
282 + --bg-primary: var(--surface-page);
283 + --bg-secondary: var(--surface-overlay);
284 + --bg-tertiary: var(--surface-sunken);
285 + --bg-card: var(--surface-raised);
286 + --text-primary: var(--content);
287 + --text-secondary: var(--content-secondary);
288 + --text-muted: var(--content-muted);
289 + --accent-red: var(--category-one);
290 + --accent-green: var(--category-two);
291 + --accent-blue: var(--category-three);
292 + --accent-yellow: var(--category-four);
293 + --accent-purple: var(--category-five);
294 + --accent-cyan: var(--category-six);
295 + --border-color: var(--border);
260 296 --border-width: 2px; /* theme-invariant */
261 297 --border-width-sm: 2px; /* theme-invariant */
262 -
263 - /* Derived aliases — automatically follow their upstream tokens, so themes don't need to override them. */
264 - --accent-color: var(--accent-blue); /* derived from --accent-blue */
265 - --accent-primary: var(--accent-blue); /* derived from --accent-blue */
266 - --bg-hover: var(--bg-tertiary); /* derived from --bg-tertiary */
267 - --border-light: var(--bg-tertiary); /* derived from --bg-tertiary */
268 - --text-on-accent: var(--bg-card); /* derived: relies on accents being saturated mid-tones; review on theme switch */
298 + --accent-color: var(--action);
299 + --accent-primary: var(--action);
300 + --bg-hover: var(--hover-surface);
301 + --border-light: var(--surface-sunken);
302 + --text-on-accent: var(--surface-raised); /* GO's white-on-accent hack; see theming audit */
269 303
270 304 /* Shadow offset scale (used by 3D action elements only) */
271 305 --shadow-offset-xs: 1px;
@@ -8,24 +8,6 @@
8 8
9 9 const { invoke } = window.__TAURI__.core;
10 10
11 - // CSS variable mapping: TOML dot-path -> CSS custom property
12 - const COLOR_MAP = {
13 - 'background.primary': '--bg-primary',
14 - 'background.secondary': '--bg-secondary',
15 - 'background.tertiary': '--bg-tertiary',
16 - 'background.surface': '--bg-card',
17 - 'foreground.primary': '--text-primary',
18 - 'foreground.secondary': '--text-secondary',
19 - 'foreground.muted': '--text-muted',
20 - 'accent.red': '--accent-red',
21 - 'accent.green': '--accent-green',
22 - 'accent.blue': '--accent-blue',
23 - 'accent.yellow': '--accent-yellow',
24 - 'accent.purple': '--accent-purple',
25 - 'accent.cyan': '--accent-cyan',
26 - 'border.default': '--border-color',
27 - };
28 -
29 11 // Theme state lives in GoingsOn.state (centralized)
30 12 GoingsOn.state.set('currentThemeId', 'goingson');
31 13 GoingsOn.state.set('themeCache', {});
@@ -52,9 +34,9 @@
52 34 }
53 35
54 36 /**
55 - * Fetch a single theme's colors, using cache if available.
37 + * Fetch a single theme's resolved intent tokens, using cache if available.
56 38 * @param {string} themeId - Theme ID to fetch
57 - * @returns {Promise<Object|null>} Theme object with colors, or null on error
39 + * @returns {Promise<Object|null>} Theme object with `intents`, or null on error
58 40 */
59 41 async function fetchTheme(themeId) {
60 42 const cache = GoingsOn.state.themeCache;
@@ -71,21 +53,21 @@
71 53 }
72 54
73 55 /**
74 - * Apply theme colors to CSS custom properties on the document root.
75 - * @param {Object} colors - Map of TOML dot-paths to color values
56 + * Apply a theme's resolved intent tokens as CSS custom properties on the
57 + * document root. Each intent key becomes `--{key}` (e.g. `surface-page` ->
58 + * `--surface-page`); styles.css aliases the app's brand vocabulary over them.
59 + * @param {Object} intents - Map of intent token name -> color value
76 60 */
77 - function applyColors(colors) {
61 + function applyColors(intents) {
78 62 const root = document.documentElement;
79 - for (const [tomlKey, cssVar] of Object.entries(COLOR_MAP)) {
80 - if (colors[tomlKey]) {
81 - root.style.setProperty(cssVar, colors[tomlKey]);
82 - }
63 + for (const [token, value] of Object.entries(intents)) {
64 + root.style.setProperty(`--${token}`, value);
83 65 }
84 - // Phase 7 Tier 3 #13 — sync the iOS / browser chrome color with the
85 - // active theme's primary background so the status bar matches.
66 + // Sync the iOS / browser chrome color with the active theme's page
67 + // background so the status bar matches.
86 68 const themeMeta = document.getElementById('meta-theme-color');
87 - if (themeMeta && colors['background.primary']) {
88 - themeMeta.setAttribute('content', colors['background.primary']);
69 + if (themeMeta && intents['surface-page']) {
70 + themeMeta.setAttribute('content', intents['surface-page']);
89 71 }
90 72 }
91 73
@@ -103,7 +85,7 @@
103 85 return;
104 86 }
105 87
106 - applyColors(theme.colors);
88 + applyColors(theme.intents);
107 89 GoingsOn.state.set('currentThemeId', themeId);
108 90 localStorage.setItem('goingson-theme', themeId);
109 91
@@ -6,7 +6,7 @@ use tracing::instrument;
6 6
7 7 use super::ApiError;
8 8
9 - pub use theme_common::{ThemeColors, ThemeMeta};
9 + pub use theme_common::{SemanticTokens, ThemeMeta};
10 10
11 11 /// Returns theme directories in priority order (later overrides earlier by ID).
12 12 /// Each entry is `(path, is_custom)`.
@@ -46,9 +46,9 @@ pub fn list_themes(app: AppHandle) -> Result<Vec<ThemeMeta>, ApiError> {
46 46
47 47 #[tauri::command]
48 48 #[instrument(skip_all)]
49 - pub fn get_theme(app: AppHandle, id: String) -> Result<ThemeColors, ApiError> {
49 + pub fn get_theme(app: AppHandle, id: String) -> Result<SemanticTokens, ApiError> {
50 50 let dirs = theme_dirs(&app);
51 - theme_common::load_theme(&dirs, &id).map_err(ApiError::internal)
51 + theme_common::load_semantic(&dirs, &id).map_err(ApiError::internal)
52 52 }
53 53
54 54 #[tauri::command]