mockpit 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +232 -2
- package/bin/demoData.js +132 -0
- package/bin/initDesign.js +846 -0
- package/bin/mockpit.js +1830 -0
- package/bin/serveUrl.d.ts +1 -0
- package/bin/serveUrl.js +7 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +230 -0
- package/dist/server/apiViews.d.ts +329 -0
- package/dist/server/apiViews.js +135 -0
- package/dist/server/app.d.ts +53 -0
- package/dist/server/app.js +3112 -0
- package/dist/server/base64.d.ts +1 -0
- package/dist/server/base64.js +16 -0
- package/dist/server/cdn.d.ts +6 -0
- package/dist/server/cdn.js +33 -0
- package/dist/server/codeRunner.d.ts +5 -0
- package/dist/server/codeRunner.js +89 -0
- package/dist/server/codeWorker.d.ts +1 -0
- package/dist/server/codeWorker.js +196 -0
- package/dist/server/designGuide.d.ts +16 -0
- package/dist/server/designGuide.js +311 -0
- package/dist/server/events.d.ts +53 -0
- package/dist/server/events.js +14 -0
- package/dist/server/feedbackBatch.d.ts +43 -0
- package/dist/server/feedbackBatch.js +98 -0
- package/dist/server/icons.d.ts +50 -0
- package/dist/server/icons.js +225 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +64 -0
- package/dist/server/kits.d.ts +36 -0
- package/dist/server/kits.js +279 -0
- package/dist/server/knobs.d.ts +15 -0
- package/dist/server/knobs.js +333 -0
- package/dist/server/mcpHttp.d.ts +53 -0
- package/dist/server/mcpHttp.js +143 -0
- package/dist/server/mcpSpec.d.ts +191 -0
- package/dist/server/mcpSpec.js +402 -0
- package/dist/server/parts.d.ts +31 -0
- package/dist/server/parts.js +338 -0
- package/dist/server/postMarkdown.d.ts +10 -0
- package/dist/server/postMarkdown.js +271 -0
- package/dist/server/postSurfaces.d.ts +40 -0
- package/dist/server/postSurfaces.js +407 -0
- package/dist/server/public.d.ts +5 -0
- package/dist/server/public.js +5 -0
- package/dist/server/push.d.ts +45 -0
- package/dist/server/push.js +234 -0
- package/dist/server/richRender.d.ts +15 -0
- package/dist/server/richRender.js +392 -0
- package/dist/server/run.d.ts +82 -0
- package/dist/server/run.js +290 -0
- package/dist/server/runApi.d.ts +6 -0
- package/dist/server/runApi.js +82 -0
- package/dist/server/slots.d.ts +14 -0
- package/dist/server/slots.js +71 -0
- package/dist/server/sqlStore.d.ts +119 -0
- package/dist/server/sqlStore.js +954 -0
- package/dist/server/sqliteStorage.d.ts +2 -0
- package/dist/server/sqliteStorage.js +90 -0
- package/dist/server/surfacePage.d.ts +48 -0
- package/dist/server/surfacePage.js +1099 -0
- package/dist/server/themes.d.ts +48 -0
- package/dist/server/themes.js +305 -0
- package/dist/server/types.d.ts +599 -0
- package/dist/server/types.js +299 -0
- package/dist/workers/public.d.ts +1 -0
- package/dist/workers/public.js +3 -0
- package/extensions/mockpit.js +703 -0
- package/guide/AGENT_SETUP.md +23 -0
- package/guide/topics/asks.md +87 -0
- package/guide/topics/feedback.md +127 -0
- package/guide/topics/html.md +175 -0
- package/guide/topics/http.md +76 -0
- package/guide/topics/knobs.md +72 -0
- package/guide/topics/scripts.md +129 -0
- package/guide/topics/surfaces.md +100 -0
- package/package.json +165 -4
- package/skills/launch-video/SKILL.md +159 -0
- package/skills/mockpit/SKILL.md +40 -0
- package/viewer/dist/index.html +1922 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface Accent {
|
|
2
|
+
bg: string;
|
|
3
|
+
text: string;
|
|
4
|
+
border: string;
|
|
5
|
+
}
|
|
6
|
+
export interface Palette {
|
|
7
|
+
bg: string;
|
|
8
|
+
panel: string;
|
|
9
|
+
surface: string;
|
|
10
|
+
text: string;
|
|
11
|
+
muted: string;
|
|
12
|
+
faint: string;
|
|
13
|
+
border: string;
|
|
14
|
+
border2: string;
|
|
15
|
+
hover: string;
|
|
16
|
+
info: Accent;
|
|
17
|
+
success: Accent;
|
|
18
|
+
warning: Accent;
|
|
19
|
+
danger: Accent;
|
|
20
|
+
}
|
|
21
|
+
export interface Theme {
|
|
22
|
+
id: string;
|
|
23
|
+
label: string;
|
|
24
|
+
shiki: {
|
|
25
|
+
light: string;
|
|
26
|
+
dark: string;
|
|
27
|
+
};
|
|
28
|
+
light: Palette;
|
|
29
|
+
dark: Palette;
|
|
30
|
+
}
|
|
31
|
+
export type Mode = "light" | "dark";
|
|
32
|
+
export declare function schemeCss(light: Record<string, string>, dark: Record<string, string>, mode?: Mode): string;
|
|
33
|
+
export declare function viewerThemeCss(t: Theme, mode?: Mode): string;
|
|
34
|
+
export declare function tokenThemeCss(t: Theme, mode?: Mode): string;
|
|
35
|
+
export declare const DIALKIT: Theme;
|
|
36
|
+
export declare const THEMES: Theme[];
|
|
37
|
+
export declare const DEFAULT_THEME_ID: string;
|
|
38
|
+
export declare const DEFAULT_MODE: Mode;
|
|
39
|
+
export declare const isMode: (v: unknown) => v is Mode;
|
|
40
|
+
export declare function themeById(_id?: string | null): Theme;
|
|
41
|
+
export declare const themeOptions: () => {
|
|
42
|
+
id: string;
|
|
43
|
+
label: string;
|
|
44
|
+
}[];
|
|
45
|
+
export declare function paletteFromCssVars(cssVars: string): {
|
|
46
|
+
light: Palette;
|
|
47
|
+
dark: Palette;
|
|
48
|
+
} | null;
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// The workspace palette — the single source of truth shared by the server
|
|
2
|
+
// (html-surface token injection in surfacePage) and the viewer (chrome palette +
|
|
3
|
+
// shiki theme for markdown/diff). Runtime-agnostic: no node imports, so it
|
|
4
|
+
// bundles into the viewer (vite) and typechecks against workers.
|
|
5
|
+
//
|
|
6
|
+
// The theme is authored as ONE palette object per color scheme; the viewer-var
|
|
7
|
+
// set (--bg, --accent, …) and the html-token set (--color-* injected into the
|
|
8
|
+
// sandboxed iframe) are both DERIVED from it, so the two palettes can never
|
|
9
|
+
// drift. Mermaid follows the viewer vars (see MermaidPart); the terminal is
|
|
10
|
+
// intentionally theme-independent (always a dark terminal window).
|
|
11
|
+
// Viewer chrome variables (styles.css names). Accent maps to the info state.
|
|
12
|
+
function viewerVars(p) {
|
|
13
|
+
return {
|
|
14
|
+
bg: p.bg,
|
|
15
|
+
panel: p.panel,
|
|
16
|
+
surface: p.surface,
|
|
17
|
+
text: p.text,
|
|
18
|
+
muted: p.muted,
|
|
19
|
+
faint: p.faint,
|
|
20
|
+
border: p.border,
|
|
21
|
+
"border-2": p.border2,
|
|
22
|
+
accent: p.info.text,
|
|
23
|
+
"accent-bg": p.info.bg,
|
|
24
|
+
hover: p.hover,
|
|
25
|
+
danger: p.danger.text,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
// Html-part design tokens (surfacePage names — the agent-facing contract).
|
|
29
|
+
// Same variable NAMES in both schemes; only the values change.
|
|
30
|
+
function tokenVars(p) {
|
|
31
|
+
return {
|
|
32
|
+
"color-background-primary": p.surface,
|
|
33
|
+
"color-background-secondary": p.panel,
|
|
34
|
+
"color-background-tertiary": p.bg,
|
|
35
|
+
"color-background-info": p.info.bg,
|
|
36
|
+
"color-background-success": p.success.bg,
|
|
37
|
+
"color-background-warning": p.warning.bg,
|
|
38
|
+
"color-background-danger": p.danger.bg,
|
|
39
|
+
"color-text-primary": p.text,
|
|
40
|
+
"color-text-secondary": p.muted,
|
|
41
|
+
"color-text-tertiary": p.faint,
|
|
42
|
+
"color-text-info": p.info.text,
|
|
43
|
+
"color-text-success": p.success.text,
|
|
44
|
+
"color-text-warning": p.warning.text,
|
|
45
|
+
"color-text-danger": p.danger.text,
|
|
46
|
+
"color-border-primary": p.border2,
|
|
47
|
+
"color-border-secondary": p.border,
|
|
48
|
+
"color-border-tertiary": p.border,
|
|
49
|
+
"color-border-info": p.info.border,
|
|
50
|
+
"color-border-success": p.success.border,
|
|
51
|
+
"color-border-warning": p.warning.border,
|
|
52
|
+
"color-border-danger": p.danger.border,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// Terminal chrome. Always sourced from the theme's DARK palette (a terminal
|
|
56
|
+
// reads as a terminal — ANSI output assumes a dark backdrop — so it stays dark
|
|
57
|
+
// in light mode too), but tinted to the theme so it doesn't look foreign.
|
|
58
|
+
function termVars(dark) {
|
|
59
|
+
return {
|
|
60
|
+
"term-bg": dark.bg,
|
|
61
|
+
"term-bar": dark.panel,
|
|
62
|
+
"term-fg": dark.text,
|
|
63
|
+
"term-title": dark.muted,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const block = (vars) => Object.entries(vars)
|
|
67
|
+
.map(([k, v]) => `--${k}: ${v};`)
|
|
68
|
+
.join("");
|
|
69
|
+
// `:root` light + a `prefers-color-scheme: dark` override — emitted as a
|
|
70
|
+
// <style> so the automatic OS light/dark flip keeps working with no JS. When
|
|
71
|
+
// `mode` is given the scheme is PINNED to it instead: a single flat `:root`
|
|
72
|
+
// block with no media query, so the document renders that mode regardless of
|
|
73
|
+
// the OS preference. The viewer uses this to force a surface iframe to the mode
|
|
74
|
+
// the chrome already resolved, since an iframe is a separate document whose
|
|
75
|
+
// `prefers-color-scheme` evaluation can diverge from its embedder's.
|
|
76
|
+
export function schemeCss(light, dark, mode) {
|
|
77
|
+
if (mode === "light")
|
|
78
|
+
return `:root{${block(light)}}`;
|
|
79
|
+
if (mode === "dark")
|
|
80
|
+
return `:root{${block(dark)}}`;
|
|
81
|
+
return `:root{${block(light)}}@media (prefers-color-scheme: dark){:root{${block(dark)}}}`;
|
|
82
|
+
}
|
|
83
|
+
// Viewer chrome palette CSS (injected into the viewer document head). The
|
|
84
|
+
// scheme-flipping chrome vars, plus the terminal vars which are scheme-
|
|
85
|
+
// independent (always the dark palette) so they sit outside the media query.
|
|
86
|
+
// `mode` pins the scheme (see schemeCss) — used for the rich-part iframes the
|
|
87
|
+
// chrome renders via renderSandboxedPart, not the chrome's own <head>.
|
|
88
|
+
export function viewerThemeCss(t, mode) {
|
|
89
|
+
return `${schemeCss(viewerVars(t.light), viewerVars(t.dark), mode)}:root{${block(termVars(t.dark))}}`;
|
|
90
|
+
}
|
|
91
|
+
// Html-part token CSS (injected into each sandboxed surface iframe). `mode`
|
|
92
|
+
// pins the scheme so the iframe matches the chrome (see schemeCss).
|
|
93
|
+
export function tokenThemeCss(t, mode) {
|
|
94
|
+
return schemeCss(tokenVars(t.light), tokenVars(t.dark), mode);
|
|
95
|
+
}
|
|
96
|
+
// The one theme: dialkit's visual language (plan D9). Dark is dialkit's own
|
|
97
|
+
// palette; light is the prototype's light chrome with the same accents
|
|
98
|
+
// (docs/tmp/mockups/concepts/7-rethink/final/final.css, home.css). The prototype
|
|
99
|
+
// has no danger colour, so red is the one value picked here rather than copied.
|
|
100
|
+
export const DIALKIT = {
|
|
101
|
+
id: "dialkit",
|
|
102
|
+
label: "dialkit",
|
|
103
|
+
shiki: { light: "github-light", dark: "github-dark" },
|
|
104
|
+
light: {
|
|
105
|
+
bg: "#fafafa",
|
|
106
|
+
panel: "#f2f2f2",
|
|
107
|
+
surface: "#ffffff",
|
|
108
|
+
text: "#1a1a1a",
|
|
109
|
+
muted: "#73726c",
|
|
110
|
+
faint: "#a6a49d",
|
|
111
|
+
border: "#e6e6e6",
|
|
112
|
+
border2: "#d6d6d6",
|
|
113
|
+
hover: "#ebebeb",
|
|
114
|
+
info: { bg: "rgba(37, 99, 235, 0.1)", text: "#2563eb", border: "#2563eb" },
|
|
115
|
+
success: { bg: "rgba(43, 147, 72, 0.12)", text: "#2b9348", border: "#2b9348" },
|
|
116
|
+
warning: { bg: "#fff8e6", text: "#e8590c", border: "#f0e0b8" },
|
|
117
|
+
danger: { bg: "rgba(220, 38, 38, 0.1)", text: "#dc2626", border: "#dc2626" },
|
|
118
|
+
},
|
|
119
|
+
dark: {
|
|
120
|
+
bg: "#0f0f0f",
|
|
121
|
+
panel: "#1f1f1f",
|
|
122
|
+
surface: "#131313",
|
|
123
|
+
text: "#f5f5f5",
|
|
124
|
+
muted: "#8f8f8f",
|
|
125
|
+
faint: "#5e5e5e",
|
|
126
|
+
border: "#242424",
|
|
127
|
+
border2: "#3a3a3a",
|
|
128
|
+
hover: "#262626",
|
|
129
|
+
info: { bg: "rgba(139, 123, 255, 0.16)", text: "#8b7bff", border: "#8b7bff" },
|
|
130
|
+
success: { bg: "rgba(74, 222, 128, 0.14)", text: "#4ade80", border: "#4ade80" },
|
|
131
|
+
warning: { bg: "#2a2312", text: "#e8590c", border: "#4a3c1c" },
|
|
132
|
+
danger: { bg: "rgba(248, 113, 113, 0.14)", text: "#f87171", border: "#f87171" },
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
export const THEMES = [DIALKIT];
|
|
136
|
+
export const DEFAULT_THEME_ID = DIALKIT.id;
|
|
137
|
+
// The workspace setting is just the mode; dark is the prototype's default.
|
|
138
|
+
export const DEFAULT_MODE = "dark";
|
|
139
|
+
export const isMode = (v) => v === "light" || v === "dark";
|
|
140
|
+
// Any id resolves to the one theme, so stale ids persisted by older builds
|
|
141
|
+
// (and `?theme=` on cached URLs) keep rendering.
|
|
142
|
+
export function themeById(_id) {
|
|
143
|
+
return DIALKIT;
|
|
144
|
+
}
|
|
145
|
+
export const themeOptions = () => THEMES.map((t) => ({ id: t.id, label: t.label }));
|
|
146
|
+
// --- Imported project palettes -------------------------------------------
|
|
147
|
+
//
|
|
148
|
+
// `mockpit init` reads a repo's own design tokens (a `:root{}` / `@theme`
|
|
149
|
+
// block from Tailwind or shadcn) and stores them per project. The raw text is
|
|
150
|
+
// injected verbatim into the html-surface sandbox, but the VIEWER chrome and
|
|
151
|
+
// the `--color-*` contract still need a Palette — so the recognised token
|
|
152
|
+
// names are mapped onto one here, runtime-agnostic like the rest of this file.
|
|
153
|
+
//
|
|
154
|
+
// Anything the repo doesn't declare falls back per field to the default theme,
|
|
155
|
+
// so a partial import can never leave a color undefined (an undefined token
|
|
156
|
+
// renders as unstyled black-on-white, which reads as "mockpit is broken"
|
|
157
|
+
// rather than "your repo declares no border color").
|
|
158
|
+
// Colour values arrive in three dialects: real CSS colors (`#fff`,
|
|
159
|
+
// `oklch(...)`), shadcn's channel-only triplets (`0 0% 100%`, meant to be
|
|
160
|
+
// wrapped by `hsl(var(--x))` at the use site), and bare rgb triplets. Only the
|
|
161
|
+
// first is usable as-is, so the other two are re-wrapped into a function.
|
|
162
|
+
function normalizeCssColor(raw) {
|
|
163
|
+
const v = raw.trim().replace(/\s*!important$/, "");
|
|
164
|
+
if (!v)
|
|
165
|
+
return null;
|
|
166
|
+
if (v.startsWith("var("))
|
|
167
|
+
return null; // an indirection we can't resolve here
|
|
168
|
+
if (/^#[0-9a-f]{3,8}$/i.test(v))
|
|
169
|
+
return v;
|
|
170
|
+
if (/^[a-z-]+\(/i.test(v))
|
|
171
|
+
return v; // oklch() / hsl() / rgb() / color-mix() / …
|
|
172
|
+
// `H S% L%` or `H S% L% / A` — shadcn's pre-v4 channel form.
|
|
173
|
+
if (/^-?[\d.]+(deg|turn|rad)?\s+[\d.]+%\s+[\d.]+%(\s*\/\s*[\d.]+%?)?$/.test(v)) {
|
|
174
|
+
return `hsl(${v})`;
|
|
175
|
+
}
|
|
176
|
+
// `R G B` or `R G B / A` — Tailwind's channel form for rgb(var(--x)).
|
|
177
|
+
if (/^\d+(\.\d+)?\s+\d+(\.\d+)?\s+\d+(\.\d+)?(\s*\/\s*[\d.]+%?)?$/.test(v)) {
|
|
178
|
+
return `rgb(${v})`;
|
|
179
|
+
}
|
|
180
|
+
if (/^[a-z]+$/i.test(v))
|
|
181
|
+
return v; // named color (white, transparent, …)
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
const DARK_SELECTOR = /(^|[\s,.[:])dark\b|prefers-color-scheme\s*:\s*dark/i;
|
|
185
|
+
// Walk `selector { … }` blocks, recursing into at-rules so a
|
|
186
|
+
// `@media (prefers-color-scheme: dark) { :root { … } }` inherits the dark flag.
|
|
187
|
+
// Deliberately tolerant: this parses files we did not write and must never
|
|
188
|
+
// throw, so anything it can't understand is simply skipped.
|
|
189
|
+
function collectBlocks(css, dark, out) {
|
|
190
|
+
let i = 0;
|
|
191
|
+
while (i < css.length) {
|
|
192
|
+
const open = css.indexOf("{", i);
|
|
193
|
+
if (open === -1)
|
|
194
|
+
return;
|
|
195
|
+
// Everything since the previous block can include statement at-rules
|
|
196
|
+
// (`@import …;`, `@custom-variant dark (…);`) whose text would otherwise be
|
|
197
|
+
// read as part of this selector — and `@custom-variant dark` would then
|
|
198
|
+
// misfile the next block as a dark override. Keep only the last statement.
|
|
199
|
+
const selector = css.slice(i, open).split(";").pop().trim();
|
|
200
|
+
let depth = 1;
|
|
201
|
+
let j = open + 1;
|
|
202
|
+
while (j < css.length && depth > 0) {
|
|
203
|
+
const ch = css[j];
|
|
204
|
+
if (ch === "{")
|
|
205
|
+
depth++;
|
|
206
|
+
else if (ch === "}")
|
|
207
|
+
depth--;
|
|
208
|
+
j++;
|
|
209
|
+
}
|
|
210
|
+
const body = css.slice(open + 1, depth === 0 ? j - 1 : css.length);
|
|
211
|
+
const isDark = dark || DARK_SELECTOR.test(selector);
|
|
212
|
+
if (body.includes("{"))
|
|
213
|
+
collectBlocks(body, isDark, out);
|
|
214
|
+
else
|
|
215
|
+
out.push({ selector, dark: isDark, declarations: body });
|
|
216
|
+
i = j;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function readCustomProps(declarations) {
|
|
220
|
+
const vars = {};
|
|
221
|
+
const re = /--([\w-]+)\s*:\s*([^;]+)/g;
|
|
222
|
+
let m;
|
|
223
|
+
while ((m = re.exec(declarations)))
|
|
224
|
+
vars[m[1]] = m[2].trim();
|
|
225
|
+
return vars;
|
|
226
|
+
}
|
|
227
|
+
// Ordered lookup: first declared name wins, so a repo that defines both
|
|
228
|
+
// `--card` and `--background` gets the more specific one for a surface.
|
|
229
|
+
function pick(vars, names) {
|
|
230
|
+
for (const name of names) {
|
|
231
|
+
const raw = vars[name];
|
|
232
|
+
if (raw == null)
|
|
233
|
+
continue;
|
|
234
|
+
const color = normalizeCssColor(raw);
|
|
235
|
+
if (color)
|
|
236
|
+
return color;
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
function accentFrom(vars, bg, text, border, fallback) {
|
|
241
|
+
return {
|
|
242
|
+
bg: pick(vars, bg) ?? fallback.bg,
|
|
243
|
+
text: pick(vars, text) ?? fallback.text,
|
|
244
|
+
border: pick(vars, border) ?? fallback.border,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function paletteFrom(vars, fallback) {
|
|
248
|
+
return {
|
|
249
|
+
bg: pick(vars, ["background", "color-background", "body-background"]) ?? fallback.bg,
|
|
250
|
+
panel: pick(vars, ["muted", "secondary", "color-muted", "popover"]) ?? fallback.panel,
|
|
251
|
+
surface: pick(vars, ["card", "popover", "background", "color-background"]) ?? fallback.surface,
|
|
252
|
+
text: pick(vars, ["foreground", "color-foreground", "card-foreground"]) ?? fallback.text,
|
|
253
|
+
muted: pick(vars, ["muted-foreground", "secondary-foreground"]) ?? fallback.muted,
|
|
254
|
+
faint: pick(vars, ["muted-foreground", "secondary-foreground"]) ?? fallback.faint,
|
|
255
|
+
border: pick(vars, ["border", "color-border", "input"]) ?? fallback.border,
|
|
256
|
+
border2: pick(vars, ["input", "ring", "border", "color-border"]) ?? fallback.border2,
|
|
257
|
+
hover: pick(vars, ["accent", "muted", "secondary"]) ?? fallback.hover,
|
|
258
|
+
// `info` doubles as the accent/link color everywhere in the viewer, so it
|
|
259
|
+
// is the one that must track the project's brand: primary for ink, accent
|
|
260
|
+
// for the wash, ring for the focus edge.
|
|
261
|
+
info: accentFrom(vars, ["accent", "primary-foreground", "secondary"], ["primary", "ring", "accent-foreground"], ["ring", "primary", "border"], fallback.info),
|
|
262
|
+
// Tailwind/shadcn ship no success or warning token, so these stay on the
|
|
263
|
+
// default theme unless a repo happens to declare them.
|
|
264
|
+
success: accentFrom(vars, ["success-background", "success-bg"], ["success", "success-foreground"], ["success-border", "success"], fallback.success),
|
|
265
|
+
warning: accentFrom(vars, ["warning-background", "warning-bg"], ["warning", "warning-foreground"], ["warning-border", "warning"], fallback.warning),
|
|
266
|
+
danger: accentFrom(vars, ["destructive-background", "destructive-bg", "danger-bg"], ["destructive", "danger", "destructive-foreground"], ["destructive-border", "destructive", "danger"], fallback.danger),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
// Map a repo's CSS custom properties onto the two Palettes a theme needs.
|
|
270
|
+
// Returns null when the text declares no recognisable color token at all —
|
|
271
|
+
// the caller (`mockpit init`) then keeps the default theme rather than
|
|
272
|
+
// storing a palette that is 100% fallback.
|
|
273
|
+
//
|
|
274
|
+
// `--radius` is intentionally NOT mapped: Palette carries colors only, and the
|
|
275
|
+
// raw block is injected into the sandbox verbatim, so the repo's radius reaches
|
|
276
|
+
// surfaces through `var(--radius)` without a second source of truth.
|
|
277
|
+
export function paletteFromCssVars(cssVars) {
|
|
278
|
+
if (!cssVars || !cssVars.trim())
|
|
279
|
+
return null;
|
|
280
|
+
const blocks = [];
|
|
281
|
+
// Strip comments first so a commented-out token can't win the ordered pick.
|
|
282
|
+
collectBlocks(cssVars.replace(/\/\*[\s\S]*?\*\//g, ""), false, blocks);
|
|
283
|
+
// A bare declaration list (no selector) is treated as the light root, so
|
|
284
|
+
// callers may pass either `:root{…}` or just its contents.
|
|
285
|
+
if (blocks.length === 0)
|
|
286
|
+
blocks.push({ selector: ":root", dark: false, declarations: cssVars });
|
|
287
|
+
const light = {};
|
|
288
|
+
const dark = {};
|
|
289
|
+
for (const b of blocks) {
|
|
290
|
+
const vars = readCustomProps(b.declarations);
|
|
291
|
+
Object.assign(b.dark ? dark : light, vars);
|
|
292
|
+
}
|
|
293
|
+
const base = themeById(DEFAULT_THEME_ID);
|
|
294
|
+
const lightPalette = paletteFrom(light, base.light);
|
|
295
|
+
// Dark inherits the light declarations first: a repo that only overrides
|
|
296
|
+
// `--background`/`--foreground` in `.dark` still gets its brand `--primary`.
|
|
297
|
+
// A repo with no dark block at all is single-palette (Loom, for one, is
|
|
298
|
+
// dark-only with everything in `:root`) — reusing the same values in both
|
|
299
|
+
// schemes keeps it looking like itself, where falling back to the default
|
|
300
|
+
// theme's dark would make the two modes look like two different products.
|
|
301
|
+
const darkPalette = paletteFrom({ ...light, ...dark }, base.dark);
|
|
302
|
+
const recognised = JSON.stringify(lightPalette) !== JSON.stringify(base.light) ||
|
|
303
|
+
JSON.stringify(darkPalette) !== JSON.stringify(base.dark);
|
|
304
|
+
return recognised ? { light: lightPalette, dark: darkPalette } : null;
|
|
305
|
+
}
|