mockpit 0.0.0-stage → 1.1.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.
Files changed (86) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +239 -2
  3. package/bin/demoData.js +132 -0
  4. package/bin/initDesign.js +846 -0
  5. package/bin/mockpit.js +1835 -0
  6. package/bin/serveUrl.d.ts +1 -0
  7. package/bin/serveUrl.js +7 -0
  8. package/dist/mcp/server.d.ts +2 -0
  9. package/dist/mcp/server.js +252 -0
  10. package/dist/server/apiViews.d.ts +329 -0
  11. package/dist/server/apiViews.js +135 -0
  12. package/dist/server/app.d.ts +55 -0
  13. package/dist/server/app.js +3134 -0
  14. package/dist/server/base64.d.ts +1 -0
  15. package/dist/server/base64.js +16 -0
  16. package/dist/server/cdn.d.ts +6 -0
  17. package/dist/server/cdn.js +33 -0
  18. package/dist/server/codeRunner.d.ts +5 -0
  19. package/dist/server/codeRunner.js +89 -0
  20. package/dist/server/codeWorker.d.ts +1 -0
  21. package/dist/server/codeWorker.js +196 -0
  22. package/dist/server/designGuide.d.ts +16 -0
  23. package/dist/server/designGuide.js +311 -0
  24. package/dist/server/eventLog.d.ts +27 -0
  25. package/dist/server/eventLog.js +187 -0
  26. package/dist/server/events.d.ts +53 -0
  27. package/dist/server/events.js +14 -0
  28. package/dist/server/feedbackBatch.d.ts +43 -0
  29. package/dist/server/feedbackBatch.js +98 -0
  30. package/dist/server/icons.d.ts +50 -0
  31. package/dist/server/icons.js +225 -0
  32. package/dist/server/index.d.ts +1 -0
  33. package/dist/server/index.js +76 -0
  34. package/dist/server/kits.d.ts +36 -0
  35. package/dist/server/kits.js +279 -0
  36. package/dist/server/knobs.d.ts +15 -0
  37. package/dist/server/knobs.js +333 -0
  38. package/dist/server/logFile.d.ts +2 -0
  39. package/dist/server/logFile.js +27 -0
  40. package/dist/server/mcpHttp.d.ts +53 -0
  41. package/dist/server/mcpHttp.js +143 -0
  42. package/dist/server/mcpSpec.d.ts +191 -0
  43. package/dist/server/mcpSpec.js +402 -0
  44. package/dist/server/parts.d.ts +31 -0
  45. package/dist/server/parts.js +338 -0
  46. package/dist/server/postMarkdown.d.ts +10 -0
  47. package/dist/server/postMarkdown.js +271 -0
  48. package/dist/server/postSurfaces.d.ts +40 -0
  49. package/dist/server/postSurfaces.js +407 -0
  50. package/dist/server/public.d.ts +5 -0
  51. package/dist/server/public.js +5 -0
  52. package/dist/server/push.d.ts +45 -0
  53. package/dist/server/push.js +234 -0
  54. package/dist/server/richRender.d.ts +15 -0
  55. package/dist/server/richRender.js +392 -0
  56. package/dist/server/run.d.ts +82 -0
  57. package/dist/server/run.js +290 -0
  58. package/dist/server/runApi.d.ts +6 -0
  59. package/dist/server/runApi.js +82 -0
  60. package/dist/server/slots.d.ts +14 -0
  61. package/dist/server/slots.js +71 -0
  62. package/dist/server/sqlStore.d.ts +119 -0
  63. package/dist/server/sqlStore.js +954 -0
  64. package/dist/server/sqliteStorage.d.ts +2 -0
  65. package/dist/server/sqliteStorage.js +90 -0
  66. package/dist/server/surfacePage.d.ts +48 -0
  67. package/dist/server/surfacePage.js +1099 -0
  68. package/dist/server/themes.d.ts +48 -0
  69. package/dist/server/themes.js +305 -0
  70. package/dist/server/types.d.ts +599 -0
  71. package/dist/server/types.js +299 -0
  72. package/dist/workers/public.d.ts +1 -0
  73. package/dist/workers/public.js +3 -0
  74. package/extensions/mockpit.js +717 -0
  75. package/guide/AGENT_SETUP.md +23 -0
  76. package/guide/topics/asks.md +87 -0
  77. package/guide/topics/feedback.md +127 -0
  78. package/guide/topics/html.md +175 -0
  79. package/guide/topics/http.md +76 -0
  80. package/guide/topics/knobs.md +72 -0
  81. package/guide/topics/scripts.md +129 -0
  82. package/guide/topics/surfaces.md +100 -0
  83. package/package.json +165 -4
  84. package/skills/launch-video/SKILL.md +159 -0
  85. package/skills/mockpit/SKILL.md +40 -0
  86. 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
+ }