@marver-design/marver 0.17.0 → 0.18.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 (36) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +1 -0
  3. package/dist/bake-jr38C_pX.mjs +747 -0
  4. package/dist/{build-D_g53Bp2.mjs → build-ER_7T6Cw.mjs} +179 -10
  5. package/dist/cli.mjs +19 -11
  6. package/dist/{comments-oYcZ3cE-.mjs → comments-ClVgfQib.mjs} +1 -1
  7. package/dist/{daemon-Bbh_jmui.mjs → daemon-CKhg0zuT.mjs} +1 -1
  8. package/dist/{dev-LnIISva5.mjs → dev-BvLbY98O.mjs} +204 -10
  9. package/dist/{init-B7YhcN2o.mjs → init-BWbHqVng.mjs} +2 -2
  10. package/dist/{manifest-CaslQIAO.mjs → manifest-DAnEL8_a.mjs} +1 -1
  11. package/dist/{marver-id-gate-D6By7XHj.mjs → marver-id-gate-B_idGdHm.mjs} +1 -1
  12. package/dist/{plugin-D2msH1cj.mjs → plugin-Cai5C1WV.mjs} +107 -28
  13. package/dist/{poster-BEjUcQP3.mjs → poster-CIuz_PwH.mjs} +1 -1
  14. package/dist/publish-bakes-D0LhbQQ3.mjs +216 -0
  15. package/dist/{serve-Bcwfpvhl.mjs → serve-z5qtj_wJ.mjs} +3 -3
  16. package/dist/{share-Gqo_Ygqw.mjs → share--bdSc4G5.mjs} +1 -1
  17. package/dist/{shot-BzQ0PXKH.mjs → shot-BFEuYbaz.mjs} +1 -1
  18. package/dist/{shot-DlmTO8AF.mjs → shot-iicees2e.mjs} +14 -7
  19. package/dist/{work-lzC-lPY0.mjs → work-0YopuMt9.mjs} +1 -1
  20. package/docs/publish.md +27 -5
  21. package/package.json +1 -1
  22. package/src/client/frame-host/bridge.js +4 -9
  23. package/src/client/frame-host/main.tsx +20 -6
  24. package/src/client/shell/App.tsx +7 -0
  25. package/src/client/shell/Comments.tsx +2 -2
  26. package/src/client/shell/canvas/Canvas.tsx +2 -2
  27. package/src/client/shell/canvas/FrameNode.tsx +82 -89
  28. package/src/client/shell/canvas/admission.ts +70 -0
  29. package/src/client/shell/canvas/sleep.ts +194 -0
  30. package/src/client/shell/store.ts +14 -0
  31. package/src/client/shell/styles.css +9 -25
  32. package/src/shared/sleep-rule.ts +41 -0
  33. package/templates/instructions/craft.md +9 -0
  34. package/templates/instructions/publish.md +62 -9
  35. package/src/client/frame-host/serialize.ts +0 -195
  36. package/src/client/shell/canvas/snapshots.ts +0 -233
@@ -1,13 +1,155 @@
1
- import { i as ROUTE, n as NAME } from "./cli.mjs";
1
+ import { a as ROUTE, r as NAME } from "./cli.mjs";
2
2
  import { a as listBoardFiles, d as flatten, f as isBoardName, n as checkBoardsDir, s as readRegistry, t as boardFields, u as buildTree } from "./boards-BmxcT3Lc.mjs";
3
- import { l as detectHost, n as scanFrames, s as loadConfig } from "./manifest-CaslQIAO.mjs";
4
- import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-D2msH1cj.mjs";
3
+ import { l as detectHost, n as scanFrames, s as loadConfig } from "./manifest-DAnEL8_a.mjs";
4
+ import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-Cai5C1WV.mjs";
5
5
  import { cpSync, existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
6
6
  import { basename, dirname, join, sep } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { createHash, randomBytes } from "node:crypto";
9
9
  import { build } from "vite";
10
10
  import react from "@vitejs/plugin-react";
11
+ //#region src/server/css-fix.ts
12
+ /** Every `-webkit-backdrop-filter` declaration (the property itself, never a custom property that
13
+ * happens to end in it) is mirrored by the standard one right after it, in order, importance and
14
+ * all - the standard cascade then resolves exactly as the prefixed one did - unless the declaration
15
+ * list it belongs to declares the standard property already. The list is the enclosing block's own
16
+ * declarations: nested rules, their preludes included, are blanked. Strings and comments are masked
17
+ * first, so a brace or a declaration inside one is content, not syntax; values come from the
18
+ * original text. */
19
+ function keepBackdropFilter(css) {
20
+ const masked = maskCustomProperties(maskParens(css.replace(/\/\*[\s\S]*?\*\/|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\\[\s\S]/g, (m) => " ".repeat(m.length))));
21
+ const PREFIXED = /(^|[^-\w])-webkit-backdrop-filter\s*:/gi;
22
+ const seen = /* @__PURE__ */ new Set();
23
+ const edits = [];
24
+ for (const m of masked.matchAll(PREFIXED)) {
25
+ const p = m.index + m[1].length;
26
+ let depth = 0, open = -1;
27
+ for (let i = p - 1; i >= 0; i--) {
28
+ const c = masked[i];
29
+ if (c === "}") depth++;
30
+ else if (c === "{") {
31
+ if (depth === 0) {
32
+ open = i;
33
+ break;
34
+ }
35
+ depth--;
36
+ }
37
+ }
38
+ if (open < 0 || seen.has(open)) continue;
39
+ seen.add(open);
40
+ let close = masked.length;
41
+ depth = 0;
42
+ for (let i = open + 1; i < masked.length; i++) {
43
+ const c = masked[i];
44
+ if (c === "{") depth++;
45
+ else if (c === "}") {
46
+ if (depth === 0) {
47
+ close = i;
48
+ break;
49
+ }
50
+ depth--;
51
+ }
52
+ }
53
+ const own = masked.slice(open + 1, close).split("");
54
+ depth = 0;
55
+ let seg = 0;
56
+ for (let i = 0; i < own.length; i++) {
57
+ const c = own[i];
58
+ if (depth === 0 && c === ";") {
59
+ seg = i + 1;
60
+ continue;
61
+ }
62
+ if (c === "{") {
63
+ if (depth === 0) for (let k = seg; k < i; k++) own[k] = " ";
64
+ depth++;
65
+ }
66
+ if (depth) own[i] = " ";
67
+ if (c === "}") {
68
+ depth--;
69
+ if (depth === 0) seg = i + 1;
70
+ }
71
+ }
72
+ const list = own.join("");
73
+ const decls = [];
74
+ let paren = 0, from = 0;
75
+ for (let i = 0; i <= list.length; i++) {
76
+ const c = list[i];
77
+ if (c === "(" || c === "[") paren++;
78
+ else if (c === ")" || c === "]") paren = Math.max(0, paren - 1);
79
+ if (i === list.length || c === ";" && paren === 0) {
80
+ const text = list.slice(from, i), colon = text.indexOf(":");
81
+ if (colon > 0) decls.push({
82
+ prop: text.slice(0, colon).trim().toLowerCase(),
83
+ valueAt: from + colon + 1,
84
+ end: i
85
+ });
86
+ from = i + 1;
87
+ }
88
+ }
89
+ if (decls.some((x) => x.prop === "backdrop-filter")) continue;
90
+ for (const x of decls) {
91
+ if (x.prop !== "-webkit-backdrop-filter") continue;
92
+ const value = css.slice(open + 1 + x.valueAt, open + 1 + x.end).trim();
93
+ if (value) edits.push({
94
+ at: open + 1 + x.end,
95
+ text: `;backdrop-filter:${value}`
96
+ });
97
+ }
98
+ }
99
+ let out = css;
100
+ for (const e of edits.sort((a, b) => b.at - a.at)) out = out.slice(0, e.at) + e.text + out.slice(e.at);
101
+ return out;
102
+ }
103
+ /** What sits inside parentheses is a value - `var(--f,{})`, `url(#a{b)` - never a rule: blanked,
104
+ * same length (values are copied from the original text). */
105
+ function maskParens(masked) {
106
+ const out = masked.split("");
107
+ let depth = 0;
108
+ for (let i = 0; i < out.length; i++) {
109
+ const c = out[i];
110
+ if (c === ")" && depth) {
111
+ depth--;
112
+ continue;
113
+ }
114
+ if (depth) out[i] = " ";
115
+ if (c === "(") depth++;
116
+ }
117
+ return out.join("");
118
+ }
119
+ /** A custom property's value may hold anything, braces, brackets and semicolons included (CSS
120
+ * Variables 1): blanked, same length, so no brace or property name inside one reads as syntax. */
121
+ function maskCustomProperties(masked) {
122
+ const out = masked.split("");
123
+ const re = /(^|[;{\s])--[^\s:;{}]+\s*:/g;
124
+ for (const m of masked.matchAll(re)) {
125
+ let depth = 0, i = m.index + m[0].length;
126
+ for (; i < masked.length; i++) {
127
+ const c = masked[i];
128
+ if (c === "(" || c === "[" || c === "{") depth++;
129
+ else if (c === ")" || c === "]" || c === "}") {
130
+ if (depth === 0) break;
131
+ depth--;
132
+ } else if (c === ";" && depth === 0) break;
133
+ out[i] = " ";
134
+ }
135
+ re.lastIndex = i;
136
+ }
137
+ return out.join("");
138
+ }
139
+ function cssFixPlugin() {
140
+ return {
141
+ name: "marver:css-fix",
142
+ transform(code, id) {
143
+ if (!/\.css$/.test(id.split("?")[0])) return;
144
+ const fixed = keepBackdropFilter(code);
145
+ return fixed === code ? void 0 : {
146
+ code: fixed,
147
+ map: null
148
+ };
149
+ }
150
+ };
151
+ }
152
+ //#endregion
11
153
  //#region src/server/build.ts
12
154
  /**
13
155
  * `marver build` - the static adapter.
@@ -306,7 +448,7 @@ export function layoutChain(fileKey) {
306
448
  }
307
449
  `;
308
450
  }
309
- async function buildSite(root, boardsFlag, allBoardsFlag, embedSeeds) {
451
+ async function buildSite(root, boardsFlag, allBoardsFlag, embedSeeds, textures = true) {
310
452
  const config = await loadConfig(root);
311
453
  const host = detectHost(root);
312
454
  const pkgDir = packageDir();
@@ -332,10 +474,11 @@ async function buildSite(root, boardsFlag, allBoardsFlag, embedSeeds) {
332
474
  for (const b of Object.values(boards)) for (const node of Array.isArray(b?.nodes) ? b.nodes : []) if (typeof node?.frame === "string") wanted.add(node.frame);
333
475
  frames = manifest.frames.filter((f) => wanted.has(f.id));
334
476
  }
335
- const pubManifest = publishedManifest(manifest, strip ? frames.map((f) => ({
477
+ const pubFrames = strip ? frames.map((f) => ({
336
478
  ...f,
337
479
  file: opaquePath(f.id, f.kind === "html" ? ".html" : "")
338
- })) : frames, publishedNames, strip);
480
+ })) : frames;
481
+ const pubManifest = publishedManifest(manifest, pubFrames, publishedNames, strip);
339
482
  for (const n of publishedNames) {
340
483
  const pb = policy.boards[n];
341
484
  if (pb?.type !== "slides" && pb?.open !== "slides") continue;
@@ -367,7 +510,9 @@ async function buildSite(root, boardsFlag, allBoardsFlag, embedSeeds) {
367
510
  const t = boardFields(allBoards[n], isBoardName).title;
368
511
  return t ? [[n, t]] : [];
369
512
  }));
513
+ const bakeGen = textures ? Date.now() : void 0;
370
514
  const data = {
515
+ bakes: bakeGen,
371
516
  manifest: pubManifest,
372
517
  boards,
373
518
  names: publishedNames,
@@ -413,7 +558,7 @@ async function buildSite(root, boardsFlag, allBoardsFlag, embedSeeds) {
413
558
  clientDir,
414
559
  config,
415
560
  detectedThemeCss: host.themeCss
416
- }));
561
+ }), cssFixPlugin());
417
562
  process.env.NODE_ENV = "production";
418
563
  const result = await build({
419
564
  configFile: false,
@@ -462,7 +607,7 @@ async function buildSite(root, boardsFlag, allBoardsFlag, embedSeeds) {
462
607
  const page = (srcDir, entryName, outPath) => {
463
608
  const entry = chunks.find((c) => c.isEntry && c.name === entryName);
464
609
  const links = cssFor(entryName).map((f) => `<link rel="stylesheet" href="/${f}" />`).join("\n ");
465
- const html = readFileSync(join(clientDir, srcDir, "index.html"), "utf8").replaceAll("{{ROUTE}}", ROUTE).replace("<script type=\"module\" src=\"{{ENTRY}}\"><\/script>", `${links}\n <script type="module" src="/${entry.fileName}"><\/script>`);
610
+ const html = readFileSync(join(clientDir, srcDir, "index.html"), "utf8").replaceAll("{{ROUTE}}", ROUTE).replace("<script type=\"module\" src=\"{{ENTRY}}\"><\/script>", `${links}\n <script type="module" src="/${entry.fileName}"><\/script>`).replace(/<head([^>]*)>/, (m) => entryName === "frame" && bakeGen ? `${m}\n <meta name="mv-bakes" content="${bakeGen}">` : m);
466
611
  mkdirSync(dirname(join(outDir, outPath)), { recursive: true });
467
612
  writeFileSync(join(outDir, outPath), html);
468
613
  };
@@ -470,7 +615,7 @@ async function buildSite(root, boardsFlag, allBoardsFlag, embedSeeds) {
470
615
  page("frame-host", "frame", `${ROUTE.slice(1)}/frame/index.html`);
471
616
  page("stage", "stage", `${ROUTE.slice(1)}/stage/index.html`);
472
617
  cpSync(join(clientDir, "shell", "favicon"), join(outDir, ROUTE.slice(1), "favicon"), { recursive: true });
473
- cpSync(join(clientDir, "frame-host", "bridge.js"), join(outDir, "assets", "bridge.js"));
618
+ for (const f of ["bridge.js", "inspect.js"]) cpSync(join(clientDir, "frame-host", f), join(outDir, "assets", f));
474
619
  const frameCss = cssFor("frame").map((f) => `<link rel="stylesheet" href="/${f}" />`).join("\n");
475
620
  for (const f of frames.filter((x) => x.kind === "html")) {
476
621
  const src = readFileSync(join(root, f.file), "utf8");
@@ -478,6 +623,7 @@ async function buildSite(root, boardsFlag, allBoardsFlag, embedSeeds) {
478
623
  let html = src.includes("</head>") ? src.replace("</head>", `${inject}</head>`) : inject + src;
479
624
  const shim = `<script>(function(){var a=Element.prototype.attachShadow;if(a)Element.prototype.attachShadow=function(i){if(i&&i.mode==='closed')window.__mvClosedShadow=1;return a.call(this,i)};})();<\/script>`;
480
625
  html = /<head[^>]*>/i.test(html) ? html.replace(/<head[^>]*>/i, (m) => m + shim) : shim + html;
626
+ if (bakeGen) html = /<head[^>]*>/i.test(html) ? html.replace(/<head[^>]*>/i, (m) => `${m}<meta name="mv-bakes" content="${bakeGen}">`) : `<meta name="mv-bakes" content="${bakeGen}">` + html;
481
627
  const dest = strip ? opaquePath(f.id, ".html") : f.file;
482
628
  mkdirSync(dirname(join(outDir, dest)), { recursive: true });
483
629
  writeFileSync(join(outDir, dest), html);
@@ -504,7 +650,7 @@ async function buildSite(root, boardsFlag, allBoardsFlag, embedSeeds) {
504
650
  const realAssets = existsSync(assetsDir) ? realpathSync(assetsDir) : null;
505
651
  for (const r of refs) {
506
652
  if (!isLocalAssetRef(r) || !r.endsWith(".poster.png") || existsSync(join(assetsDir, r))) continue;
507
- const { ensurePoster } = await import("./poster-BEjUcQP3.mjs");
653
+ const { ensurePoster } = await import("./poster-CIuz_PwH.mjs");
508
654
  const g = await ensurePoster(assetsDir, r.slice(0, -11));
509
655
  if (!g.ok) throw new Error(`design/assets/${r}: ${g.error}`);
510
656
  console.log(` poster: rendered design/assets/${r} (${g.width}×${g.height})`);
@@ -569,8 +715,31 @@ async function buildSite(root, boardsFlag, allBoardsFlag, embedSeeds) {
569
715
  ...config.share.frontDoor === false ? { frontDoor: false } : {},
570
716
  ...config.share.notify === false ? { notify: false } : {}
571
717
  }));
718
+ let textureLine = " textures: skipped (--no-textures)";
719
+ if (textures && bakeGen) {
720
+ const { bakePublished } = await import("./publish-bakes-D0LhbQQ3.mjs");
721
+ const pubFrameFile = new Map(pubFrames.map((f) => [f.id, f]));
722
+ const r = await bakePublished({
723
+ root,
724
+ outDir,
725
+ gen: bakeGen,
726
+ boards,
727
+ themes: config.themes,
728
+ log: (l) => console.log(l),
729
+ frames: pubFrames,
730
+ viewports: config.viewports,
731
+ allScenes: includeAll,
732
+ urlFor: (id, theme) => {
733
+ const f = pubFrameFile.get(id);
734
+ if (!f) return null;
735
+ return f.kind === "html" ? `/${f.file}?theme=${encodeURIComponent(theme)}&r=${bakeGen}` : `/__mv/frame/?id=${encodeURIComponent(id)}&theme=${encodeURIComponent(theme)}&r=${bakeGen}`;
736
+ }
737
+ });
738
+ textureLine = r === null ? ` textures: none - no Chrome on this machine; frames with glass will rest live (install Chrome, or ship as is)` : r.asked === 0 ? " textures: nothing to compile" : ` textures: ${r.asleep} frame view${r.asleep === 1 ? "" : "s"} asleep under certified glass, ${r.live} with live glass, ${r.plain} without effects (${r.asked} asked: every published node x ${config.themes.length || 1} theme${config.themes.length === 1 ? "" : "s"}; ${(r.bytes / 1024).toFixed(0)} KB, ${(r.ms / 1e3).toFixed(1)} s)`;
739
+ }
572
740
  console.log(`\n ${NAME} build → design/.dist`);
573
741
  console.log(` boards: ${publishedNames.map((n) => `${n} (${rights[n]})`).join(", ")}`);
742
+ console.log(textureLine);
574
743
  console.log(` frames: ${frames.length}${includeAll ? "" : ` of ${manifest.frames.length} (build-time filter)`}`);
575
744
  if (copiedAssets) console.log(` assets: ${copiedAssets} referenced file${copiedAssets === 1 ? "" : "s"} from design/assets/ (unreferenced assets never ship)`);
576
745
  if (!includeAll && existsSync(join(root, "public"))) console.log(` note: the host public/ directory ships in full - the --boards filter covers frames, not public assets`);
package/dist/cli.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
3
  import { cac } from "cac";
3
4
  import { readFileSync, realpathSync } from "node:fs";
4
5
  import { dirname, join, resolve } from "node:path";
@@ -31,6 +32,13 @@ function slideSize(frame) {
31
32
  return frame.slide ? SLIDE_INTRINSIC : null;
32
33
  }
33
34
  //#endregion
35
+ //#region src/cli/name.ts
36
+ var name_exports = /* @__PURE__ */ __exportAll({
37
+ NAME: () => NAME,
38
+ PKG: () => PKG,
39
+ ROUTE: () => ROUTE
40
+ });
41
+ //#endregion
34
42
  //#region src/cli/index.ts
35
43
  const [major, minor] = process.versions.node.split(".").map(Number);
36
44
  if (major < 22 || major === 22 && minor < 18) {
@@ -64,14 +72,14 @@ const resolve$1 = (dir) => {
64
72
  };
65
73
  const cli = cac(NAME);
66
74
  cli.command("init", "Scaffold design/ in this repo").option("--mode <mode>", "studio | embedded", { default: "studio" }).option("--no-demo", "Skip the demo scene (the demo ships unless this flag is passed)").option("--root <dir>", "Host repo root", { default: "." }).action(async (opts) => {
67
- const { init } = await import("./init-B7YhcN2o.mjs");
75
+ const { init } = await import("./init-BWbHqVng.mjs");
68
76
  init(resolve$1(opts.root), {
69
77
  mode: opts.mode === "embedded" ? "embedded" : "studio",
70
78
  demo: opts.demo !== false
71
79
  });
72
80
  });
73
81
  for (const [name, desc] of [["dev", "Start the local canvas (everything on: hot reload, comments, Live Jam)"], ["canvas", "Start the local canvas - same as dev"]]) cli.command(name, desc).option("--root <dir>", "Host repo root", { default: "." }).option("--port <port>", "Port (default 5199)").action(async (opts) => {
74
- const { dev } = await import("./dev-LnIISva5.mjs");
82
+ const { dev } = await import("./dev-BvLbY98O.mjs");
75
83
  let port;
76
84
  if (opts.port !== void 0) {
77
85
  const n = Number(opts.port);
@@ -80,18 +88,18 @@ for (const [name, desc] of [["dev", "Start the local canvas (everything on: hot
80
88
  }
81
89
  await dev(resolve$1(opts.root), port);
82
90
  });
83
- cli.command("build", "Static export → design/.dist (what ships comes from design/publish.json - publishing is default-closed)").option("--boards <names>", "Publish only these boards (comma-separated); overrides the publish policy").option("--all-boards", "Publish every board - the loud override for the default-closed policy").option("--embed-seeds", "Copy comment history INTO the web root (identifying - every event carries its author's email)").option("--root <dir>", "Host repo root", { default: "." }).action(async (opts) => {
84
- const { buildSite } = await import("./build-D_g53Bp2.mjs");
91
+ cli.command("build", "Static export → design/.dist (what ships comes from design/publish.json - publishing is default-closed)").option("--boards <names>", "Publish only these boards (comma-separated); overrides the publish policy").option("--all-boards", "Publish every board - the loud override for the default-closed policy").option("--embed-seeds", "Copy comment history INTO the web root (identifying - every event carries its author's email)").option("--no-textures", "Skip compiling the glass textures the published frames rest under (needs Chrome; a CI without one skips on its own)").option("--root <dir>", "Host repo root", { default: "." }).action(async (opts) => {
92
+ const { buildSite } = await import("./build-ER_7T6Cw.mjs");
85
93
  try {
86
94
  const boards = opts.boards === void 0 ? void 0 : typeof opts.boards === "string" ? opts.boards : "";
87
- await buildSite(resolve$1(opts.root), boards, opts.allBoards === true, opts.embedSeeds === true);
95
+ await buildSite(resolve$1(opts.root), boards, opts.allBoards === true, opts.embedSeeds === true, opts.textures !== false && !process.env.MARVER_NO_TEXTURES);
88
96
  } catch (err) {
89
97
  console.error(`[${NAME}] build failed: ${err.message}`);
90
98
  process.exit(1);
91
99
  }
92
100
  });
93
101
  cli.command("serve", "Serve design/.dist (set MARVER_PASSWORD to gate it)").option("--root <dir>", "Host repo root", { default: "." }).option("--port <port>", "Port (default $PORT or 4199)").action(async (opts) => {
94
- const { serve } = await import("./serve-Bcwfpvhl.mjs");
102
+ const { serve } = await import("./serve-z5qtj_wJ.mjs");
95
103
  let port;
96
104
  if (opts.port !== void 0) {
97
105
  const n = Number(opts.port);
@@ -100,7 +108,7 @@ cli.command("serve", "Serve design/.dist (set MARVER_PASSWORD to gate it)").opti
100
108
  serve(resolve$1(opts.root), port);
101
109
  });
102
110
  cli.command("comments <action> [value]", "Comment collaboration: connect <url> · sync · list · reply <thread> · resolve <thread> · invite <email> · revoke <email>").option("--root <dir>", "Host repo root", { default: "." }).option("--token <token>", "connect: the canvas's MARVER_CLI_TOKEN (default $MARVER_CLI_TOKEN) - the identity-mode path").option("--invite <token>", "connect: claim this invite instead of signing in").option("--canvas-password <password>", "connect: the canvas gate password (default $MARVER_PASSWORD or prompt)").option("--email <email>", "connect: account email (skips the prompt)").option("--password <password>", "connect: account password (skips the prompt - mind your shell history)").option("--name <name>", "connect --invite: display name for the new account").option("--open", "list: only unresolved threads").option("--json", "list: machine-readable output").option("--board <board>", "scope to one board").option("--body <text>", "reply: the reply text").option("--addressed-in <frame>", "resolve: the variant frame that answered the feedback").action(async (action, value, opts) => {
103
- const { commentsCommand } = await import("./comments-oYcZ3cE-.mjs");
111
+ const { commentsCommand } = await import("./comments-ClVgfQib.mjs");
104
112
  try {
105
113
  await commentsCommand(resolve$1(opts.root), action, value, opts);
106
114
  } catch (err) {
@@ -109,7 +117,7 @@ cli.command("comments <action> [value]", "Comment collaboration: connect <url>
109
117
  }
110
118
  });
111
119
  cli.command("share <action> [value]", "Sharing roster (owner): add <who> · remove <who> · block/unblock <email> · general <mode> · list · requests · explain <who> · who").option("--root <dir>", "Host repo root", { default: "." }).option("--role <role>", "add / requests --approve: view (default) or comment").option("--expires <iso>", "add: expiry timestamp, e.g. 2026-12-31T00:00:00Z").option("--approve <email>", "requests: approve this pending request (canvas-wide in v1)").option("--decline <email>", "requests: decline this pending request (silent to the asker)").option("--json", "list: machine-readable output").action(async (action, value, opts) => {
112
- const { shareCommand } = await import("./share-Gqo_Ygqw.mjs");
120
+ const { shareCommand } = await import("./share--bdSc4G5.mjs");
113
121
  try {
114
122
  await shareCommand(resolve$1(opts.root), action, value, opts);
115
123
  } catch (err) {
@@ -118,7 +126,7 @@ cli.command("share <action> [value]", "Sharing roster (owner): add <who> · remo
118
126
  }
119
127
  });
120
128
  cli.command("work <action> [...frames]", "Working state on the canvas: start <scene/frame ...> · done <scene/frame ...> | --all · list").option("--root <dir>", "Host repo root", { default: "." }).option("--ttl <minutes>", "start: minutes before the glow self-expires (default 10, max 30)").option("--all", "done: clear every working frame").action(async (action, frames, opts) => {
121
- const { workCommand } = await import("./work-lzC-lPY0.mjs");
129
+ const { workCommand } = await import("./work-0YopuMt9.mjs");
122
130
  try {
123
131
  await workCommand(resolve$1(opts.root), action, frames ?? [], opts);
124
132
  } catch (err) {
@@ -136,7 +144,7 @@ cli.command("boards", "The sidebar as files: every folder and board in reading o
136
144
  }
137
145
  });
138
146
  cli.command("shot [...frames]", "Render frames headless and print the PNG paths (needs `dev` running): ids, --scene <name> or --all").option("--root <dir>", "Host repo root", { default: "." }).option("--scene <name>", "Every frame of one scene").option("--all", "Every frame of the canvas").option("--theme <name>", "Theme to render (default: light)").option("--scale <n>", "Device pixels per CSS px, 1-4 (default: 2; 4 for print-quality stills)").option("--json", "Print the results as JSON ({ results: [...] }) instead of paths").action(async (frames, opts) => {
139
- const { shotCommand } = await import("./shot-BzQ0PXKH.mjs");
147
+ const { shotCommand } = await import("./shot-BFEuYbaz.mjs");
140
148
  try {
141
149
  await shotCommand(resolve$1(opts.root), frames, opts);
142
150
  } catch (err) {
@@ -148,4 +156,4 @@ cli.help();
148
156
  cli.version(version());
149
157
  cli.parse();
150
158
  //#endregion
151
- export { slideSize as a, ROUTE as i, NAME as n, PKG as r, CONTENT_WIDTH as t };
159
+ export { ROUTE as a, PKG as i, CONTENT_WIDTH as n, slideSize as o, NAME as r, name_exports as t };
@@ -1,4 +1,4 @@
1
- import { n as NAME } from "./cli.mjs";
1
+ import { r as NAME } from "./cli.mjs";
2
2
  import { n as replay } from "./events-B3LBn74P.mjs";
3
3
  import { i as readLog, r as listBoards, t as appendEvents } from "./comments-DZyobpxG.mjs";
4
4
  import { a as syncOnce, i as loadCollab, n as connectClaim, r as connectToken, t as connect } from "./sync-BZaCWqK-.mjs";
@@ -1,7 +1,7 @@
1
1
  import { n as replay } from "./events-B3LBn74P.mjs";
2
2
  import { i as readLog, r as listBoards, t as appendEvents } from "./comments-DZyobpxG.mjs";
3
3
  import { n as localProfile } from "./profile-BjAPAJSb.mjs";
4
- import { i as toFrameId } from "./manifest-CaslQIAO.mjs";
4
+ import { i as toFrameId } from "./manifest-DAnEL8_a.mjs";
5
5
  import { workActivity } from "./work-CLrmY-vQ.mjs";
6
6
  import { r as deviceId, t as has } from "./ledger-Bu0BjqIe.mjs";
7
7
  import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync, writeSync } from "node:fs";
@@ -1,12 +1,205 @@
1
- import { n as NAME, r as PKG } from "./cli.mjs";
2
- import { l as detectHost, s as loadConfig } from "./manifest-CaslQIAO.mjs";
3
- import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-D2msH1cj.mjs";
4
- import { realpathSync } from "node:fs";
1
+ import { i as PKG, r as NAME } from "./cli.mjs";
2
+ import { l as detectHost, s as loadConfig } from "./manifest-DAnEL8_a.mjs";
3
+ import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-Cai5C1WV.mjs";
4
+ import { readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
5
5
  import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
- import { createLogger, createServer, searchForWorkspaceRoot } from "vite";
7
+ import { createLogger, createServer, parseAst, searchForWorkspaceRoot } from "vite";
8
8
  import react from "@vitejs/plugin-react";
9
9
  import { createServer as createServer$1 } from "node:net";
10
+ //#region src/server/icons.ts
11
+ /**
12
+ * Icon barrels, unrolled.
13
+ *
14
+ * `import { Truck } from '@phosphor-icons/react'` makes Vite hand the frame the WHOLE prebundled
15
+ * package - 3000 icons, 6.5 MB of JavaScript - and every frame on a board is its own realm, so every
16
+ * frame parses and evaluates all of it: ~400 ms, the whole boot of a lo-fi frame (measured,
17
+ * research/hifi/frameres.ts: the scene import is 530 of a 550 ms boot; a board of 128 frames spends
18
+ * 50 s on it). The packages ship one module per icon and their barrel says which: this reads the
19
+ * INSTALLED barrel once (every alias, whatever the version), rewrites the import declarations of
20
+ * design sources to the per-icon modules, one line for one line, and names those modules to the
21
+ * dependency optimizer at server start so nothing is discovered - and reloaded for - on first use.
22
+ * A name the barrel does not map, a default or namespace import, a re-export: left as it is.
23
+ */
24
+ const LIBS = [{
25
+ barrel: "@phosphor-icons/react",
26
+ spec: (rel) => {
27
+ const m = rel.match(/^\.\/csr\/([\w-]+)\.es\.js$/);
28
+ return m ? `@phosphor-icons/react/dist/csr/${m[1]}` : null;
29
+ }
30
+ }, {
31
+ barrel: "lucide-react",
32
+ spec: (rel) => {
33
+ const m = rel.match(/^\.\/icons\/([\w-]+\.m?js)$/);
34
+ return m ? `lucide-react/dist/esm/icons/${m[1]}` : null;
35
+ }
36
+ }];
37
+ /** The map read from a barrel's source: `import { A as x } from './f'` + `export { x as Name }`
38
+ * (phosphor) and `export { default as Name } from './f'` (lucide). */
39
+ function parseBarrel(source, lib) {
40
+ const map = /* @__PURE__ */ new Map();
41
+ const locals = /* @__PURE__ */ new Map();
42
+ for (const m of source.matchAll(/import\s*\{([^}]*)\}\s*from\s*["']([^"']+)["']/g)) {
43
+ const spec = lib.spec(m[2]);
44
+ if (!spec) continue;
45
+ for (const s of m[1].split(",")) {
46
+ const [name, local = name] = s.trim().split(/\s+as\s+/);
47
+ if (name) locals.set(local, {
48
+ spec,
49
+ name
50
+ });
51
+ }
52
+ }
53
+ for (const m of source.matchAll(/export\s*\{([^}]*)\}(?:\s*from\s*["']([^"']+)["'])?/g)) {
54
+ const spec = m[2] ? lib.spec(m[2]) : null;
55
+ if (m[2] && !spec) continue;
56
+ for (const s of m[1].split(",")) {
57
+ const [name, exported = name] = s.trim().split(/\s+as\s+/);
58
+ if (!name) continue;
59
+ if (spec) map.set(exported, {
60
+ spec,
61
+ name
62
+ });
63
+ else {
64
+ const l = locals.get(name);
65
+ if (l) map.set(exported, l);
66
+ }
67
+ }
68
+ }
69
+ return map;
70
+ }
71
+ const maps = /* @__PURE__ */ new Map();
72
+ /** The maps for every icon package installed under `root` (read once per server). */
73
+ function iconMaps(root) {
74
+ let m = maps.get(root);
75
+ if (m) return m;
76
+ m = /* @__PURE__ */ new Map();
77
+ for (const lib of LIBS) {
78
+ const dir = join(root, "node_modules", lib.barrel);
79
+ try {
80
+ const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
81
+ const dot = pkg.exports?.["."];
82
+ const entry = (typeof dot === "object" ? dot.import : typeof dot === "string" ? dot : void 0) ?? pkg.module;
83
+ if (!entry) continue;
84
+ const map = parseBarrel(readFileSync(join(dir, entry), "utf8"), lib);
85
+ if (map.size) m.set(lib.barrel, map);
86
+ } catch {}
87
+ }
88
+ maps.set(root, m);
89
+ return m;
90
+ }
91
+ /** The code with every mapped icon import unrolled and the per-icon specifiers it now imports;
92
+ * null when nothing changed. Real import declarations only (a template literal is content). */
93
+ function unrollIcons(code, maps) {
94
+ if (![...maps.keys()].some((b) => code.includes(b))) return null;
95
+ let ast;
96
+ try {
97
+ ast = parseAst(code);
98
+ } catch {
99
+ return null;
100
+ }
101
+ const edits = [];
102
+ const modules = [];
103
+ for (const node of ast.body) {
104
+ if (node.type !== "ImportDeclaration" || !node.source) continue;
105
+ const map = maps.get(node.source.value);
106
+ if (!map || !node.specifiers?.length) continue;
107
+ const lines = [], keep = [];
108
+ let icons = 0;
109
+ for (const s of node.specifiers) {
110
+ const imported = s.type === "ImportSpecifier" ? s.imported?.name ?? s.imported?.value : void 0;
111
+ const hit = imported ? map.get(imported) : void 0;
112
+ if (!hit) {
113
+ if (s.type !== "ImportSpecifier") {
114
+ lines.length = 0;
115
+ icons = 0;
116
+ break;
117
+ }
118
+ keep.push(imported === s.local.name ? imported : `${imported} as ${s.local.name}`);
119
+ continue;
120
+ }
121
+ icons++;
122
+ modules.push(hit.spec);
123
+ lines.push(hit.name === "default" ? `import ${s.local.name} from '${hit.spec}';` : `import { ${hit.name === s.local.name ? hit.name : `${hit.name} as ${s.local.name}`} } from '${hit.spec}';`);
124
+ }
125
+ if (!icons) continue;
126
+ if (keep.length) lines.push(`import { ${keep.join(", ")} } from '${node.source.value}';`);
127
+ const original = code.slice(node.start, node.end);
128
+ edits.push({
129
+ start: node.start,
130
+ end: node.end,
131
+ text: lines.join(" ") + "\n".repeat((original.match(/\n/g) ?? []).length)
132
+ });
133
+ }
134
+ if (!edits.length) return null;
135
+ let out = "", at = 0;
136
+ for (const e of edits) {
137
+ out += code.slice(at, e.start) + e.text;
138
+ at = e.end;
139
+ }
140
+ return {
141
+ code: out + code.slice(at),
142
+ modules
143
+ };
144
+ }
145
+ /** Every per-icon module the design sources import (for the optimizer, at server start). Raw
146
+ * TypeScript is scanned by a plain pattern; only names the barrel maps count. */
147
+ function iconModules(root) {
148
+ const m = iconMaps(root);
149
+ if (!m.size) return [];
150
+ const found = /* @__PURE__ */ new Set();
151
+ const walk = (dir) => {
152
+ let names;
153
+ try {
154
+ names = readdirSync(dir);
155
+ } catch {
156
+ return;
157
+ }
158
+ for (const n of names) {
159
+ if (n === "node_modules" || n.startsWith(".")) continue;
160
+ const p = join(dir, n);
161
+ let st;
162
+ try {
163
+ st = statSync(p);
164
+ } catch {
165
+ continue;
166
+ }
167
+ if (st.isDirectory()) walk(p);
168
+ else if (/\.[jt]sx?$/.test(n)) {
169
+ const src = readFileSync(p, "utf8").replace(/\/\*[\s\S]*?\*\/|\/\/.*$/gm, (c) => " ".repeat(c.length));
170
+ for (const im of src.matchAll(/import\s*\{([^}]*)\}\s*from\s*['"]([^'"]+)['"]/g)) {
171
+ const map = m.get(im[2]);
172
+ if (!map) continue;
173
+ for (const s of im[1].split(",")) {
174
+ const name = s.trim().replace(/^type\s+/, "").split(/\s+as\s+/)[0];
175
+ const hit = map.get(name);
176
+ if (hit) found.add(hit.spec);
177
+ }
178
+ }
179
+ }
180
+ }
181
+ };
182
+ walk(join(root, "design"));
183
+ return [...found].sort();
184
+ }
185
+ /** The Vite plugin: design sources only, after the TS/JSX transform (a normal plugin runs after
186
+ * the core ones), before import analysis resolves the specifiers. The rewrite keeps every line
187
+ * where it was, so the file's map still lands (no map of its own). */
188
+ function iconsPlugin(root) {
189
+ const design = join(root, "design") + "/";
190
+ return {
191
+ name: "marver:icons",
192
+ transform(code, id) {
193
+ if (!id.startsWith(design) || !/\.[jt]sx?(\?|$)/.test(id)) return;
194
+ const r = unrollIcons(code, iconMaps(root));
195
+ return r ? {
196
+ code: r.code,
197
+ map: null
198
+ } : void 0;
199
+ }
200
+ };
201
+ }
202
+ //#endregion
10
203
  //#region src/server/dev.ts
11
204
  /** packageDir = the installed marver package root (dist/cli.js lives one level down). */
12
205
  function packageDir() {
@@ -174,7 +367,7 @@ async function dev(root, portFlag) {
174
367
  clientDir,
175
368
  config,
176
369
  detectedThemeCss: host.themeCss
177
- }));
370
+ }), iconsPlugin(root));
178
371
  const logger = createLogger("info");
179
372
  const noise = (msg) => /Sourcemap for .*node_modules.*points to/.test(msg);
180
373
  const warnBase = logger.warn.bind(logger);
@@ -244,7 +437,8 @@ async function dev(root, portFlag) {
244
437
  "react/jsx-dev-runtime",
245
438
  `${PKG} > marked`,
246
439
  `${PKG} > mermaid`,
247
- `${PKG} > html-to-image`
440
+ `${PKG} > html-to-image`,
441
+ ...iconModules(root)
248
442
  ],
249
443
  entries: [join(clientDir, "frame-host", "index.html"), "design/**/*.{tsx,jsx}"]
250
444
  },
@@ -257,12 +451,12 @@ async function dev(root, portFlag) {
257
451
  console.log(`\n ${NAME} · ${projectName} → http://localhost:${port}/\n`);
258
452
  setTimeout(async () => {
259
453
  try {
260
- await (await import("./shot-DlmTO8AF.mjs")).sweepGhosts((m) => console.log(` ${m}`));
454
+ await (await import("./shot-iicees2e.mjs")).sweepGhosts((m) => console.log(` ${m}`));
261
455
  } catch {}
262
456
  }, 0);
263
457
  try {
264
458
  const { staleManagedInstructions } = await import("./managed-HwHVNI3h.mjs");
265
- const { installedVersion } = await import("./plugin-D2msH1cj.mjs").then((n) => n.i);
459
+ const { installedVersion } = await import("./plugin-Cai5C1WV.mjs").then((n) => n.i);
266
460
  const stale = staleManagedInstructions(root);
267
461
  if (stale.length) {
268
462
  const shown = stale.slice(0, 4).join(", ") + (stale.length > 4 ? `, +${stale.length - 4} more` : "");
@@ -300,7 +494,7 @@ async function dev(root, portFlag) {
300
494
  });
301
495
  }
302
496
  if (config.jam) {
303
- const { startJam } = await import("./daemon-Bbh_jmui.mjs");
497
+ const { startJam } = await import("./daemon-CKhg0zuT.mjs");
304
498
  const jam = startJam(root, config.jam, (m) => console.log(m), (board) => server.ws.send("sh:jam-comment", { board }));
305
499
  if (jam) {
306
500
  const close = server.close.bind(server);
@@ -1,5 +1,5 @@
1
- import { n as NAME } from "./cli.mjs";
2
- import { a as writeManifest, c as detectAgent, l as detectHost, n as scanFrames, o as DEFAULTS, u as readJson } from "./manifest-CaslQIAO.mjs";
1
+ import { r as NAME } from "./cli.mjs";
2
+ import { a as writeManifest, c as detectAgent, l as detectHost, n as scanFrames, o as DEFAULTS, u as readJson } from "./manifest-DAnEL8_a.mjs";
3
3
  import { MANAGED_PREFIX, enumerateInstructionTemplates, hashBody, managedFile, pkgDir } from "./managed-HwHVNI3h.mjs";
4
4
  import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
5
5
  import { basename, dirname, join, relative } from "node:path";
@@ -1,4 +1,4 @@
1
- import { r as PKG, t as CONTENT_WIDTH } from "./cli.mjs";
1
+ import { i as PKG, n as CONTENT_WIDTH } from "./cli.mjs";
2
2
  import { a as listBoardFiles, d as flatten, f as isBoardName, g as hash, m as readTitle, n as checkBoardsDir, p as readDescription, r as checkRealDirs, s as readRegistry, t as boardFields, u as buildTree } from "./boards-BmxcT3Lc.mjs";
3
3
  import { accessSync, constants, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
4
  import { delimiter, isAbsolute, join, relative, sep } from "node:path";
@@ -1,6 +1,6 @@
1
1
  import { i as avatarSourceFor, n as attachAvatar, p as provisionFromMarverId } from "./auth-DdUKeVKt.mjs";
2
2
  import { TransactionStore, browserBinding, verifyAssertion } from "./marver-id-CVCoaMGI.mjs";
3
- import { t as poweredByUrl } from "./serve-Bcwfpvhl.mjs";
3
+ import { t as poweredByUrl } from "./serve-z5qtj_wJ.mjs";
4
4
  import { randomBytes } from "node:crypto";
5
5
  import { isIP } from "node:net";
6
6
  import { lookup } from "node:dns/promises";