From 05c333d5a185aeb3dc15a43278b586dbe25170e4 Mon Sep 17 00:00:00 2001 From: callumalpass Date: Fri, 2 Oct 2026 00:06:46 +1000 Subject: [PATCH] Inline shared UI tokens into the synced theme stylesheet mdbase-connect #508 moved the theme tokens out of packages/ui/styles.css into the @mdbase-dev/ui package, leaving bare @import specifiers at the top of the file. sync-sources copied those imports verbatim into public/mdbase-theme.css, where browsers cannot resolve them, so every colour, border and control token on the deployed site went undefined. Resolve the imports while syncing: inline tokens.css, drop brand.css and controls.css (app component styles the site does not use, and brand.css would restyle the site's .wordmark), and fail the sync on any other or leftover @import. Refresh the committed theme files from Connect beta120. --- public/mdbase-theme.css | 211 ++++++++++++++++++++++++++------------ public/theme-bootstrap.js | 15 +-- scripts/sync-sources.mjs | 25 ++++- 3 files changed, 173 insertions(+), 78 deletions(-) diff --git a/public/mdbase-theme.css b/public/mdbase-theme.css index cd6b4e7..0bed40c 100644 --- a/public/mdbase-theme.css +++ b/public/mdbase-theme.css @@ -1,25 +1,151 @@ +/* + * mdbase semantic roles. Components consume roles rather than palette values so + * both themes keep the same hierarchy. Short aliases (`--ink`, `--muted`, ...) are + * app-local and must map onto these roles. + */ :root { color-scheme: light; - color: var(--color-text); - background: var(--color-canvas); - font-family: "Atkinson Hyperlegible", "Segoe UI", sans-serif; + --color-canvas: oklch(99.5% 0.002 255); + --color-surface: oklch(99.5% 0.002 255); + --color-surface-subtle: oklch(97.8% 0.003 255); + --color-text: oklch(21% 0.018 255); + --color-text-soft: oklch(50% 0.014 255); + --color-text-muted: oklch(54% 0.01 255); + --color-text-faint: oklch(70% 0.009 255); + --color-border: oklch(92% 0.006 255); + --color-border-strong: oklch(82% 0.008 255); + --color-selected: oklch(96.8% 0.004 255); + --color-accent: oklch(47% 0.1 238); + --color-accent-strong: oklch(41% 0.1 238); + --color-accent-soft: oklch(93.5% 0.035 238); + --color-on-accent: oklch(98% 0.004 245); + --color-success: oklch(44% 0.085 153); + --color-danger: oklch(50% 0.11 28); + --color-danger-soft: oklch(97% 0.018 28); + --color-warning: oklch(60% 0.09 75); + --color-warning-border: oklch(83% 0.055 82); + --color-warning-surface: oklch(98% 0.018 86); + --color-warning-text: oklch(43% 0.055 69); + --color-warning-strong: oklch(36% 0.052 67); + --color-editor-text: oklch(28% 0.014 255); + --color-placeholder: oklch(78% 0.007 255); + --color-skeleton: var(--color-border); + --color-shadow: oklch(20% 0.02 255 / 0.06); + --color-scrim: oklch(21% 0.018 255 / 0.14); + --scrollbar-thumb-rest: oklch(51% 0.014 255 / 0.07); + --scrollbar-thumb: oklch(51% 0.014 255 / 0.22); + --scrollbar-thumb-hover: oklch(42% 0.016 255 / 0.4); + --syntax-comment: oklch(58% 0.012 255); + --syntax-keyword: oklch(42% 0.09 294); + --syntax-string: oklch(39% 0.075 153); + --syntax-number: oklch(43% 0.1 45); + --syntax-link: oklch(43% 0.1 238); + --syntax-heading: var(--color-text); + --app-editor: #005c88; + --app-reader: #a4467f; + --app-writer: #d55e00; + --sans: "Atkinson Hyperlegible", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --mono: "Azeret Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + /* Motion: popovers and small entrances are quick and ease out; a settled + confirmation holds, then fades away (see controls.css). */ + --ease-out: cubic-bezier(0.22, 1, 0.36, 1); + --motion-quick: 150ms; + --motion-base: 180ms; + --motion-settle: 2.4s; +} + +:root[data-theme="dark"] { + color-scheme: dark; + --color-canvas: oklch(17.5% 0.012 255); + --color-surface: oklch(19.5% 0.012 255); + --color-surface-subtle: oklch(22.5% 0.012 255); + --color-text: oklch(92% 0.008 255); + --color-text-soft: oklch(70% 0.012 255); + --color-text-muted: oklch(62% 0.011 255); + --color-text-faint: oklch(56% 0.011 255); + --color-border: oklch(29% 0.012 255); + --color-border-strong: oklch(39% 0.012 255); + --color-selected: oklch(23.5% 0.012 255); + --color-accent: oklch(73% 0.105 238); + --color-accent-strong: oklch(65% 0.105 238); + --color-accent-soft: oklch(31% 0.045 238); + --color-on-accent: oklch(17% 0.012 255); + --color-success: oklch(72% 0.09 153); + --color-danger: oklch(72% 0.115 28); + --color-danger-soft: oklch(25% 0.03 28); + --color-warning: oklch(78% 0.09 75); + --color-warning-border: oklch(48% 0.06 82); + --color-warning-surface: oklch(25% 0.026 82); + --color-warning-text: oklch(78% 0.07 82); + --color-warning-strong: oklch(86% 0.065 82); + --color-editor-text: oklch(88% 0.009 255); + --color-placeholder: oklch(56% 0.01 255); + --color-skeleton: oklch(25% 0.012 255); + --color-shadow: oklch(5% 0.01 255 / 0.22); + --color-scrim: oklch(5% 0.01 255 / 0.46); + --scrollbar-thumb-rest: oklch(72% 0.012 255 / 0.06); + --scrollbar-thumb: oklch(72% 0.012 255 / 0.2); + --scrollbar-thumb-hover: oklch(78% 0.012 255 / 0.38); + --syntax-comment: oklch(62% 0.012 255); + --syntax-keyword: oklch(76% 0.09 294); + --syntax-string: oklch(75% 0.08 153); + --syntax-number: oklch(78% 0.1 65); + --syntax-link: oklch(77% 0.1 238); + --app-editor: #63b1e2; + --app-reader: #e08cc0; + --app-writer: #f58d4a; +} + +@media (prefers-color-scheme: dark) { + :root:not([data-theme]) { + color-scheme: dark; + --color-canvas: oklch(17.5% 0.012 255); + --color-surface: oklch(19.5% 0.012 255); + --color-surface-subtle: oklch(22.5% 0.012 255); + --color-text: oklch(92% 0.008 255); + --color-text-soft: oklch(70% 0.012 255); + --color-text-muted: oklch(62% 0.011 255); + --color-text-faint: oklch(56% 0.011 255); + --color-border: oklch(29% 0.012 255); + --color-border-strong: oklch(39% 0.012 255); + --color-selected: oklch(23.5% 0.012 255); + --color-accent: oklch(73% 0.105 238); + --color-accent-strong: oklch(65% 0.105 238); + --color-accent-soft: oklch(31% 0.045 238); + --color-on-accent: oklch(17% 0.012 255); + --color-success: oklch(72% 0.09 153); + --color-danger: oklch(72% 0.115 28); + --color-danger-soft: oklch(25% 0.03 28); + --color-warning: oklch(78% 0.09 75); + --color-warning-border: oklch(48% 0.06 82); + --color-warning-surface: oklch(25% 0.026 82); + --color-warning-text: oklch(78% 0.07 82); + --color-warning-strong: oklch(86% 0.065 82); + --color-editor-text: oklch(88% 0.009 255); + --color-placeholder: oklch(56% 0.01 255); + --color-skeleton: oklch(25% 0.012 255); + --color-shadow: oklch(5% 0.01 255 / 0.22); + --color-scrim: oklch(5% 0.01 255 / 0.46); + --scrollbar-thumb-rest: oklch(72% 0.012 255 / 0.06); + --scrollbar-thumb: oklch(72% 0.012 255 / 0.2); + --scrollbar-thumb-hover: oklch(78% 0.012 255 / 0.38); + --syntax-comment: oklch(62% 0.012 255); + --syntax-keyword: oklch(76% 0.09 294); + --syntax-string: oklch(75% 0.08 153); + --syntax-number: oklch(78% 0.1 65); + --syntax-link: oklch(77% 0.1 238); + --app-editor: #63b1e2; + --app-reader: #e08cc0; + --app-writer: #f58d4a; + } +} + + +:root { + color: var(--ink); + background: var(--paper); + font-family: var(--sans); font-synthesis: none; - --color-canvas: #ffffff; - --color-surface: #ffffff; - --color-surface-subtle: #ffffff; - --color-selected: #ffffff; - --color-text: #171717; - --color-text-soft: rgb(23 23 23 / 74%); - --color-text-muted: rgb(23 23 23 / 54%); - --color-text-faint: rgb(23 23 23 / 30%); - --color-border: rgb(23 23 23 / 18%); - --color-border-strong: rgb(23 23 23 / 42%); - --color-accent: #171717; - --color-accent-strong: #171717; - --color-success: #171717; - --color-warning: #171717; - --color-danger: #171717; - --color-scrim: rgb(23 23 23 / 22%); --paper: var(--color-surface); --hover: var(--color-surface-subtle); --selected: var(--color-selected); @@ -35,52 +161,5 @@ --connected: var(--color-success); --warning: var(--color-warning); --danger: var(--color-danger); - --mono: "Azeret Mono", "SFMono-Regular", "Cascadia Code", monospace; --ease: cubic-bezier(0.16, 1, 0.3, 1); } - -html { - font-size: 17px; -} - -:root[data-theme="dark"] { - color-scheme: dark; - --color-canvas: #121212; - --color-surface: #121212; - --color-surface-subtle: #181818; - --color-selected: #1b1b1b; - --color-text: #f0f0ed; - --color-text-soft: rgb(240 240 237 / 78%); - --color-text-muted: rgb(240 240 237 / 56%); - --color-text-faint: rgb(240 240 237 / 30%); - --color-border: rgb(240 240 237 / 22%); - --color-border-strong: rgb(240 240 237 / 48%); - --color-accent: #f0f0ed; - --color-accent-strong: #f0f0ed; - --color-success: #f0f0ed; - --color-warning: #f0f0ed; - --color-danger: #f0f0ed; - --color-scrim: rgb(0 0 0 / 62%); -} - -@media (prefers-color-scheme: dark) { - :root:not([data-theme]) { - color-scheme: dark; - --color-canvas: #121212; - --color-surface: #121212; - --color-surface-subtle: #181818; - --color-selected: #1b1b1b; - --color-text: #f0f0ed; - --color-text-soft: rgb(240 240 237 / 78%); - --color-text-muted: rgb(240 240 237 / 56%); - --color-text-faint: rgb(240 240 237 / 30%); - --color-border: rgb(240 240 237 / 22%); - --color-border-strong: rgb(240 240 237 / 48%); - --color-accent: #f0f0ed; - --color-accent-strong: #f0f0ed; - --color-success: #f0f0ed; - --color-warning: #f0f0ed; - --color-danger: #f0f0ed; - --color-scrim: rgb(0 0 0 / 62%); - } -} diff --git a/public/theme-bootstrap.js b/public/theme-bootstrap.js index 0b3b958..3e60606 100644 --- a/public/theme-bootstrap.js +++ b/public/theme-bootstrap.js @@ -1,15 +1,8 @@ try { - const theme = /^\/authorize\/[0-9a-f-]+$/i.test(location.pathname) - ? null - : localStorage.getItem("mdbase:theme"); - if (theme === "light" || theme === "dark") { - document.documentElement.dataset.theme = theme; - } - const dark = theme === "dark" - || (theme !== "light" && matchMedia("(prefers-color-scheme: dark)").matches); - document - .querySelector('meta[name="theme-color"]') - ?.setAttribute("content", dark ? "#121212" : "#ffffff"); + const theme = localStorage.getItem("mdbase:theme"); + if (theme === "light" || theme === "dark") document.documentElement.dataset.theme = theme; + const dark = theme === "dark" || (theme !== "light" && matchMedia("(prefers-color-scheme: dark)").matches); + document.querySelector('meta[name="theme-color"]')?.setAttribute("content", dark ? "#1c1e24" : "#fcfcfd"); } catch { // The system preference remains available when local storage is unavailable. } diff --git a/scripts/sync-sources.mjs b/scripts/sync-sources.mjs index de59bd5..4fbc176 100644 --- a/scripts/sync-sources.mjs +++ b/scripts/sync-sources.mjs @@ -74,7 +74,7 @@ if (connectThemeBoundary === -1) { } writeFileSync( join(root, "public", "mdbase-theme.css"), - `${connectTheme.slice(0, connectThemeBoundary).trim()}\n` + `${resolveConnectThemeImports(connectTheme.slice(0, connectThemeBoundary)).trim()}\n` ); cpSync( join(connectDir, "apps", "portal", "public", "theme-bootstrap.js"), @@ -422,6 +422,29 @@ function replaceCanvasFunction(source, pattern, replacement) { return source.replace(pattern, replacement); } +// The Connect theme imports its tokens from the shared @mdbase-dev/ui package. +// Browsers cannot resolve bare package specifiers, so inline the tokens and +// drop the app component styles, which this site does not use. +function resolveConnectThemeImports(source) { + const inlined = { "tokens.css": true, "brand.css": false, "controls.css": false }; + const resolved = source.replace( + /^@import\s+"@mdbase-dev\/ui\/([^"]+)";[^\S\n]*\n?/gm, + (statement, name) => { + if (!(name in inlined)) { + throw new Error(`Unexpected Connect theme import: ${statement.trim()}`); + } + if (!inlined[name]) return ""; + const path = join(connectDir, "packages", "app-ui", "css", name); + required(path, `@mdbase-dev/ui ${name}`); + return `${readFileSync(path, "utf8").trim()}\n\n`; + } + ); + if (/@import/.test(resolved)) { + throw new Error("Connect theme still contains an @import after inlining"); + } + return resolved; +} + function copyAlias(source, destination) { cpSync( join(schemaSource, source),