feat(mobile): mobile palette adopts canvas design SSOT (purple, warm-paper) + drift gate #2740
@@ -0,0 +1,86 @@
|
||||
// Design-token SSOT drift gate (core#mobile-design-parity).
|
||||
//
|
||||
// The mobile palette's CORE tokens must stay equal to the canonical canvas
|
||||
// @theme tokens in src/app/globals.css — the single source of truth for the
|
||||
// warm-paper + near-black-dark surfaces and the PURPLE brand accent. This
|
||||
// test PARSES globals.css (not a second hardcoded copy) and asserts the
|
||||
// mobile MOL_LIGHT / MOL_DARK core values match, so the two can't silently
|
||||
// re-diverge (the mobile UI once shipped a green accent + lighter surfaces).
|
||||
//
|
||||
// Status/tier badge colors are intentionally mobile-specific and NOT gated
|
||||
// here — only the core design language (surfaces, text, borders, accent, and
|
||||
// good→green) is the cross-surface SSOT.
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { MOL_DARK, MOL_LIGHT } from "../palette";
|
||||
|
||||
const css = readFileSync(
|
||||
fileURLToPath(new URL("../../../app/globals.css", import.meta.url)),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
// Extract the `--color-<name>: <value>;` map from the FIRST block opened by
|
||||
// `selector {` that actually defines --color-surface (globals.css has
|
||||
// several blocks per selector — e.g. an @theme bg block + a
|
||||
// [data-theme="dark"] .shadow-embossed rule — that don't hold the palette).
|
||||
function tokensInBlock(selector: string): Record<string, string> {
|
||||
let from = 0;
|
||||
for (;;) {
|
||||
const start = css.indexOf(selector + " {", from);
|
||||
if (start === -1) throw new Error(`palette block not found for: ${selector}`);
|
||||
const open = css.indexOf("{", start);
|
||||
const close = css.indexOf("\n}", open);
|
||||
const body = css.slice(open, close);
|
||||
if (body.includes("--color-surface")) {
|
||||
const out: Record<string, string> = {};
|
||||
for (const m of body.matchAll(/--color-([a-z0-9-]+):\s*([^;]+);/g)) {
|
||||
out[m[1]] = m[2].trim();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
from = open + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// The warm-paper light palette lives in the first `@theme {` block that
|
||||
// defines --color-surface; dark values under `[data-theme="dark"] {`.
|
||||
const light = tokensInBlock("@theme");
|
||||
const dark = tokensInBlock('[data-theme="dark"]');
|
||||
|
||||
describe("mobile palette ↔ canvas @theme SSOT", () => {
|
||||
it("MOL_LIGHT core tokens equal the canvas light @theme", () => {
|
||||
expect(MOL_LIGHT.bg).toBe(light["surface"]);
|
||||
expect(MOL_LIGHT.surface).toBe(light["surface-elevated"]);
|
||||
expect(MOL_LIGHT.surface2).toBe(light["surface-card"]);
|
||||
expect(MOL_LIGHT.border).toBe(light["line"]);
|
||||
expect(MOL_LIGHT.divider).toBe(light["line-soft"]);
|
||||
expect(MOL_LIGHT.text).toBe(light["ink"]);
|
||||
expect(MOL_LIGHT.text2).toBe(light["ink-mid"]);
|
||||
expect(MOL_LIGHT.text3).toBe(light["ink-soft"]);
|
||||
expect(MOL_LIGHT.accent).toBe(light["accent"]);
|
||||
expect(MOL_LIGHT.green).toBe(light["good"]);
|
||||
expect(MOL_LIGHT.online).toBe(light["good"]);
|
||||
});
|
||||
|
||||
it("MOL_DARK core tokens equal the canvas dark [data-theme] block", () => {
|
||||
expect(MOL_DARK.bg).toBe(dark["surface"]);
|
||||
expect(MOL_DARK.surface).toBe(dark["surface-elevated"]);
|
||||
expect(MOL_DARK.surface2).toBe(dark["surface-card"]);
|
||||
expect(MOL_DARK.border).toBe(dark["line"]);
|
||||
expect(MOL_DARK.divider).toBe(dark["line-soft"]);
|
||||
expect(MOL_DARK.text).toBe(dark["ink"]);
|
||||
expect(MOL_DARK.text2).toBe(dark["ink-mid"]);
|
||||
expect(MOL_DARK.text3).toBe(dark["ink-soft"]);
|
||||
expect(MOL_DARK.accent).toBe(dark["accent"]);
|
||||
expect(MOL_DARK.green).toBe(dark["good"]);
|
||||
expect(MOL_DARK.online).toBe(dark["good"]);
|
||||
});
|
||||
|
||||
it("the brand accent is the canvas purple, not the legacy mobile green", () => {
|
||||
expect(MOL_LIGHT.accent.toLowerCase()).toBe("#7c3aed");
|
||||
expect(MOL_DARK.accent.toLowerCase()).toBe("#a78bfa");
|
||||
expect(MOL_LIGHT.accent).not.toBe("#2f9e6a");
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,17 @@
|
||||
// Mobile design system tokens — verbatim from the Claude Design handoff
|
||||
// (molecules-ai-mobile-app/project/shared.jsx). Kept as an inline-style
|
||||
// palette object so screens can mirror the design 1:1; theming routes
|
||||
// through `usePalette(dark)` exactly like the prototype.
|
||||
// Mobile design system tokens.
|
||||
//
|
||||
// SSOT (core#mobile-design-parity): the CORE palette — bg/surface/surface2/
|
||||
// border/divider/text/text2/text3 + the PURPLE brand accent — is kept in
|
||||
// sync with the canonical canvas @theme in
|
||||
// `molecule-core/canvas/src/app/globals.css` (the same app this mobile UI
|
||||
// ships inside). Earlier this palette shipped a divergent set built from a
|
||||
// Claude Design handoff (GREEN accent #2f9e6a + lighter warm-paper) — it now
|
||||
// adopts the canvas warm-paper + near-black-dark surfaces and the purple
|
||||
// accent so the mobile version has the SAME design as the desktop canvas.
|
||||
// `palette.ssot.test.ts` asserts these core values equal the canvas tokens;
|
||||
// `green`/`online` map to the canvas `good`, status/tier badges stay
|
||||
// mobile-specific. Don't hand-edit the core values to differ from canvas —
|
||||
// change the canvas SSOT and re-sync.
|
||||
|
||||
export interface MobilePalette {
|
||||
bg: string;
|
||||
@@ -37,16 +47,19 @@ export interface MobilePalette {
|
||||
}
|
||||
|
||||
export const MOL_LIGHT: MobilePalette = {
|
||||
bg: "#f6f4ef",
|
||||
// Core — canvas @theme light SSOT (surface / surface-elevated /
|
||||
// surface-card / line / line-soft / ink / ink-mid / ink-soft).
|
||||
bg: "#f1efe8",
|
||||
surface: "#ffffff",
|
||||
surface2: "#fbf9f4",
|
||||
border: "rgba(40,30,20,0.08)",
|
||||
divider: "rgba(40,30,20,0.06)",
|
||||
text: "#29261b",
|
||||
text2: "rgba(41,38,27,0.62)",
|
||||
text3: "rgba(41,38,27,0.42)",
|
||||
surface2: "#faf9f4",
|
||||
border: "#ddd9cf",
|
||||
divider: "#ebe8df",
|
||||
text: "#21201b",
|
||||
text2: "#5c5a52",
|
||||
text3: "#6f6c62",
|
||||
|
||||
green: "#2f9e6a",
|
||||
// green/online map to the canvas `good` (#0c8a52); soft/ink tints derived.
|
||||
green: "#0c8a52",
|
||||
greenSoft: "#d9ebe0",
|
||||
greenInk: "#1f6a47",
|
||||
|
||||
@@ -57,7 +70,7 @@ export const MOL_LIGHT: MobilePalette = {
|
||||
|
||||
t4SoftCard: "#f9ece0",
|
||||
|
||||
online: "#2f9e6a",
|
||||
online: "#0c8a52",
|
||||
starting: "#e9b53b",
|
||||
degraded: "#d28a2a",
|
||||
failed: "#c8472a",
|
||||
@@ -66,20 +79,22 @@ export const MOL_LIGHT: MobilePalette = {
|
||||
|
||||
remote: "#7a4dd1",
|
||||
remoteBg: "#ede2ff",
|
||||
accent: "#2f9e6a",
|
||||
accent: "#7c3aed", // canvas purple brand (was green #2f9e6a)
|
||||
};
|
||||
|
||||
export const MOL_DARK: MobilePalette = {
|
||||
bg: "#15140f",
|
||||
surface: "#1d1c17",
|
||||
surface2: "#22211c",
|
||||
border: "rgba(255,250,240,0.08)",
|
||||
divider: "rgba(255,250,240,0.06)",
|
||||
text: "#f1eee5",
|
||||
text2: "rgba(241,238,229,0.6)",
|
||||
text3: "rgba(241,238,229,0.38)",
|
||||
// Core — canvas @theme dark SSOT (near-black surfaces + bright ink).
|
||||
bg: "#08080a",
|
||||
surface: "#16161d",
|
||||
surface2: "#1b1b23",
|
||||
border: "#26262e",
|
||||
divider: "#1b1b22",
|
||||
text: "#ececf1",
|
||||
text2: "#9b9baa",
|
||||
text3: "#65656f",
|
||||
|
||||
green: "#3eb37c",
|
||||
// green/online map to the canvas dark `good` (#34d399).
|
||||
green: "#34d399",
|
||||
greenSoft: "#1f3a2c",
|
||||
greenInk: "#7fd3a8",
|
||||
|
||||
@@ -90,7 +105,7 @@ export const MOL_DARK: MobilePalette = {
|
||||
|
||||
t4SoftCard: "#2a1f17",
|
||||
|
||||
online: "#3eb37c",
|
||||
online: "#34d399",
|
||||
starting: "#e9b53b",
|
||||
degraded: "#d28a2a",
|
||||
failed: "#d65a3e",
|
||||
@@ -99,7 +114,7 @@ export const MOL_DARK: MobilePalette = {
|
||||
|
||||
remote: "#a38aff",
|
||||
remoteBg: "#2a1f44",
|
||||
accent: "#3eb37c",
|
||||
accent: "#a78bfa", // canvas dark purple brand (was green #3eb37c)
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user