From 345386ff91bdc9e93363fb119692ead51183a216 Mon Sep 17 00:00:00 2001 From: Graham Barber Date: Sun, 17 May 2026 21:36:08 -0700 Subject: [PATCH] refactor(theme): pipe mermaid and QR colors through token JSON mermaid.ts embedded eleven hex literals in its RENDERER_HTML data URL, and qr.ts hardcoded one. All duplicated values from packages/theme/tokens/colors.json. Palette changes drifted silently; the audit recommended single-sourcing through the token JSON. Add a small @morkdeck/theme/values export with a typed colorHex(name) helper that imports tokens/colors.json directly. The CSS variable surface in @morkdeck/theme/css remains the preferred path for styling; colorHex is the escape hatch for literal-string consumers (canvas strokeStyle, SVG color attributes, build-time HTML templating). Rewrite mermaid.ts's RENDERER_HTML as an interpolated template literal using colorHex(base/surface/overlay/text/subtle/highlight.*) and update qr.ts's QR ink generation to use colorHex("text"). Rendered SVG byte-identical to the prior output since the token values haven't changed; visually no regression. Co-Authored-By: Claude Opus 4.7 --- packages/core/mermaid.ts | 25 +++++++++--------- packages/theme/deno.json | 3 ++- packages/theme/values.ts | 50 ++++++++++++++++++++++++++++++++++++ packages/wc/components/qr.ts | 6 ++++- 4 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 packages/theme/values.ts diff --git a/packages/core/mermaid.ts b/packages/core/mermaid.ts index 5bd8b73..2406ca5 100644 --- a/packages/core/mermaid.ts +++ b/packages/core/mermaid.ts @@ -16,6 +16,7 @@ import { type Browser, launch, type Page } from "@astral/astral"; import { encodeHex } from "@std/encoding/hex"; import { ensureDir } from "@std/fs/ensure-dir"; import { join } from "@std/path"; +import { colorHex } from "@morkdeck/theme/values"; /** Pinned mermaid version. Bump when upgrading; invalidates the cache. */ const MERMAID_VERSION = "11.9.0"; @@ -64,8 +65,8 @@ const RENDERER_HTML = ` body { margin: 0; padding: 24px; - background: #191724; - color: #e0def4; + background: ${colorHex("base")}; + color: ${colorHex("text")}; font-family: "Recursive", ui-sans-serif, system-ui, sans-serif; /* Match the size mermaid will write into the SVG via themeVariables.fontSize. Mermaid measures text against the @@ -107,16 +108,16 @@ const RENDERER_HTML = ` er: { useMaxWidth: false }, journey: { useMaxWidth: false }, themeVariables: { - primaryColor: "#26233a", - primaryTextColor: "#e0def4", - primaryBorderColor: "#524f67", - lineColor: "#908caa", - secondaryColor: "#21202e", - secondaryTextColor: "#e0def4", - secondaryBorderColor: "#403d52", - tertiaryColor: "#1f1d2e", - tertiaryTextColor: "#908caa", - tertiaryBorderColor: "#403d52", + primaryColor: "${colorHex("overlay")}", + primaryTextColor: "${colorHex("text")}", + primaryBorderColor: "${colorHex("highlight.high")}", + lineColor: "${colorHex("subtle")}", + secondaryColor: "${colorHex("highlight.low")}", + secondaryTextColor: "${colorHex("text")}", + secondaryBorderColor: "${colorHex("highlight.med")}", + tertiaryColor: "${colorHex("surface")}", + tertiaryTextColor: "${colorHex("subtle")}", + tertiaryBorderColor: "${colorHex("highlight.med")}", fontFamily: '"Recursive", ui-sans-serif, system-ui, sans-serif', fontSize: "20px" } diff --git a/packages/theme/deno.json b/packages/theme/deno.json index 17a31f6..c508656 100644 --- a/packages/theme/deno.json +++ b/packages/theme/deno.json @@ -3,7 +3,8 @@ "version": "0.0.0", "exports": { ".": "./mod.ts", - "./css": "./tokens.gen.ts" + "./css": "./tokens.gen.ts", + "./values": "./values.ts" }, "imports": { "@terrazzo/parser": "npm:@terrazzo/parser@^2.1.0", diff --git a/packages/theme/values.ts b/packages/theme/values.ts new file mode 100644 index 0000000..7e19460 --- /dev/null +++ b/packages/theme/values.ts @@ -0,0 +1,50 @@ +/** + * Raw token values for consumers that need a literal color string + * (canvas strokeStyle, SVG color attributes, build-time HTML + * templating) where CSS custom properties cannot be used directly. + * + * Sourced from `tokens/colors.json` so a palette change is single- + * source. The CSS variable surface in `@morkdeck/theme/css` is the + * preferred path for styling; reach for `colorHex` only when the + * consumer truly needs a literal hex. + */ + +import colorsToken from "./tokens/colors.json" with { type: "json" }; + +export type ColorTokenName = + | "base" + | "surface" + | "overlay" + | "muted" + | "subtle" + | "text" + | "danger" + | "warning" + | "success" + | "match" + | "bright" + | "link" + | "highlight.low" + | "highlight.med" + | "highlight.high"; + +/** + * Resolve a color token name to its `#rrggbb` hex string. Throws if + * the name does not exist in the token tree, so a typo in a call site + * surfaces at runtime instead of silently returning undefined. + */ +export function colorHex(name: ColorTokenName): string { + // deno-lint-ignore no-explicit-any + let node: any = colorsToken; + for (const part of name.split(".")) { + node = node?.[part]; + if (node === undefined) { + throw new Error(`Unknown color token: ${name}`); + } + } + const hex = node.$value?.hex; + if (typeof hex !== "string") { + throw new Error(`Color token has no hex value: ${name}`); + } + return hex; +} diff --git a/packages/wc/components/qr.ts b/packages/wc/components/qr.ts index 8ea0a6d..67e46c1 100644 --- a/packages/wc/components/qr.ts +++ b/packages/wc/components/qr.ts @@ -4,6 +4,7 @@ import { unsafeHTML } from "lit/directives/unsafe-html.js"; import { qrCode } from "@levischuck/tiny-qr"; import { toSvgString } from "@levischuck/tiny-qr-svg"; import { color, dimension, fontFamily } from "@morkdeck/theme/css"; +import { colorHex } from "@morkdeck/theme/values"; /** * QR code rendered to inline SVG at runtime. @@ -111,7 +112,10 @@ export class QR extends LitElement { margin: 0, moduleSize: 4, background: "transparent", - color: "#e0def4", + // QR ink matches the slide reading-text color so the code reads + // as part of the editorial palette, not a foreign scanner glyph. + // Sourced from the token so a palette change is single-source. + color: colorHex("text"), output: "svg", }); this.svgMarkup = result.svg; -- 2.51.2