@marver-design/marver 0.7.0 → 0.8.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 (34) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/{build-BZaPa2DS.mjs → build-B8z902TW.mjs} +2 -2
  3. package/dist/cli.mjs +5 -5
  4. package/dist/{collab-CXy8gqoz.mjs → collab-pLGzSvm5.mjs} +16 -5
  5. package/dist/{comments-odHzYdO3.mjs → comments-BrpC86Kf.mjs} +9 -15
  6. package/dist/{comments-Ba8mU600.mjs → comments-DSwMhdbZ.mjs} +2 -8
  7. package/dist/daemon-C-huGHAM.mjs +899 -0
  8. package/dist/{dev-DaPQ9xA5.mjs → dev-BYm9ebfN.mjs} +14 -3
  9. package/dist/events-BMtBvvgU.mjs +101 -0
  10. package/dist/{init-DsCUmlCW.mjs → init-DrLomSWq.mjs} +13 -1
  11. package/dist/ledger-wFvEIEGi.mjs +64 -0
  12. package/dist/{manifest-C8FODq2S.mjs → manifest-D3eaARf4.mjs} +16 -1
  13. package/dist/{plugin-wMY9lNf3.mjs → plugin-BVFuRfEo.mjs} +56 -40
  14. package/dist/profile-BkiWglVE.mjs +39 -0
  15. package/dist/{serve-D_KBK7Oy.mjs → serve-CwAfayJk.mjs} +3 -3
  16. package/dist/{sync-CkBk-tUk.mjs → sync-Ch4Bymb1.mjs} +4 -102
  17. package/package.json +1 -1
  18. package/src/client/frame-host/inspect.js +15 -2
  19. package/src/client/shell/App.tsx +91 -9
  20. package/src/client/shell/Comments.tsx +418 -68
  21. package/src/client/shell/Play.tsx +10 -6
  22. package/src/client/shell/Toolbar.tsx +14 -2
  23. package/src/client/shell/canvas/FrameNode.tsx +33 -6
  24. package/src/client/shell/comments-store.ts +47 -6
  25. package/src/client/shell/icons.tsx +4 -0
  26. package/src/client/shell/mentions.ts +18 -0
  27. package/src/client/shell/store.ts +30 -2
  28. package/src/client/shell/styles.css +294 -21
  29. package/src/shared/events.ts +22 -5
  30. package/templates/AGENTS-embedded.md +1 -0
  31. package/templates/AGENTS-studio.md +1 -0
  32. package/templates/instructions/configure.md +15 -0
  33. package/templates/instructions/jam.md +85 -0
  34. package/dist/rolldown-runtime-D7D4PA-g.mjs +0 -13
package/CHANGELOG.md CHANGED
@@ -2,6 +2,52 @@
2
2
 
3
3
  Notable changes to `@marver-design/marver`. Format follows [Keep a Changelog](https://keepachangelog.com); versions follow semver.
4
4
 
5
+ ## 0.8.0 - 2026-08-19
6
+
7
+ Live Jam: tag `@marver` in a canvas comment and a local coding agent picks it up, edits the real
8
+ frame source, and replies in the thread - live. Plus a dev identity system and a full polish pass
9
+ on the comment thread experience.
10
+
11
+ ### Added
12
+
13
+ - **Live Jam - the agent in the thread.** A daemon inside `marver dev` watches the comment logs:
14
+ an owner-authored `@marver` spawns a headless agent (`jam: { agent: "claude" }` or `"codex"`)
15
+ that reads the whole thread, edits the real frame source, and replies live - a streamed
16
+ first-line ack within seconds (model provenance included), a look-around-before-asking clarify
17
+ ladder, replies matched to your message's energy and length, follow-ups in an engaged thread
18
+ triggering WITHOUT re-tagging, parallel jobs across frames, and crash-safe recovery: a batch
19
+ killed mid-run (even by a server restart) resumes on the next boot from a durable journal.
20
+ Every reply carries a provenance tooltip - dev user, harness, model.
21
+ - **A hard trust boundary.** Only the owner's device triggers: a device-bound ledger (keyed per
22
+ board + comment id, never synced), a same-origin + cookie gate on every dev write (comments AND
23
+ profile), and a recursion guard so agent replies never re-trigger. The agent gets no shell -
24
+ Read/Edit/Write/Glob/Grep + WebSearch/WebFetch only - and its reply is extracted structurally
25
+ (the `marver-reply` block), so narration can never leak into a thread. Comment text is framed
26
+ as untrusted data end to end.
27
+ - **The felt surface.** A frame being worked on wears the selection geometry in marver blue with
28
+ a rotating shimmer, a top-to-bottom content wave, and a parallelogram shimmer matrix on its
29
+ flank - phased per frame so parallel jams don't pulse in sync, held alive by a heartbeat for
30
+ the whole job. Replies land as minimal notification pills (frame title + preview) that stack
31
+ into a card deck at 3+, expand, clear, and jump you to the thread - delivered instantly over
32
+ the dev websocket. Marver renders as its own participant with the blue mark avatar everywhere.
33
+ - **Dev identity + review controls.** Comments in dev render as "You" (green Y) until you set a
34
+ profile - hover your avatar (pen on gray), "Set up your profile": name + photo, saved to
35
+ `design/.local/` on your machine only; a connect account takes over name + email automatically,
36
+ and account-less history re-renders as the live you. New shortcuts: ⇧L toggles "laser comment"
37
+ (the element lighting comments paint in the artwork - anchoring stays precise with it off),
38
+ ⇧C hides pins, both in stateful two-row tooltips; Prototype view moved beside Hide UI.
39
+
40
+ ### Changed
41
+
42
+ - **The thread card grew up.** World-parked beside its frame with pin-style screen-constant
43
+ scaling, height rules that respect the frame, content-mask scroll fades, a custom glass
44
+ scrollbar riding the card's far edge (action buttons never collide), and a stage-docked
45
+ position in Play that re-docks on device switches. The composer centers its text exactly,
46
+ renders `@marver` bold WITHOUT caret drift (metrics-safe text-stroke), and the send button is
47
+ reliably clickable.
48
+ - Laser/comment hover lighting leaves a frame together with the pointer - no fossilized outlines
49
+ on frames the cursor crossed; the expanded notification list no longer clips pill shadows.
50
+
5
51
  ## 0.7.0 - 2026-08-17
6
52
 
7
53
  Prototype mode becomes a first-class review surface. The prototype (Play) now carries the same toolbar
@@ -1,6 +1,6 @@
1
1
  import { i as ROUTE, n as NAME } from "./cli.mjs";
2
- import { o as loadConfig, r as scanFrames, s as detectHost } from "./manifest-C8FODq2S.mjs";
3
- import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-wMY9lNf3.mjs";
2
+ import { o as loadConfig, r as scanFrames, s as detectHost } from "./manifest-D3eaARf4.mjs";
3
+ import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-BVFuRfEo.mjs";
4
4
  import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
5
5
  import { basename, dirname, join, sep } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
package/dist/cli.mjs CHANGED
@@ -39,14 +39,14 @@ function version() {
39
39
  }
40
40
  const cli = cac(NAME);
41
41
  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) => {
42
- const { init } = await import("./init-DsCUmlCW.mjs");
42
+ const { init } = await import("./init-DrLomSWq.mjs");
43
43
  init(resolve(opts.root), {
44
44
  mode: opts.mode === "embedded" ? "embedded" : "studio",
45
45
  demo: opts.demo !== false
46
46
  });
47
47
  });
48
48
  cli.command("dev", "Start the canvas").option("--root <dir>", "Host repo root", { default: "." }).option("--port <port>", "Port (default 5199)").action(async (opts) => {
49
- const { dev } = await import("./dev-DaPQ9xA5.mjs");
49
+ const { dev } = await import("./dev-BYm9ebfN.mjs");
50
50
  let port;
51
51
  if (opts.port !== void 0) {
52
52
  const n = Number(opts.port);
@@ -56,7 +56,7 @@ cli.command("dev", "Start the canvas").option("--root <dir>", "Host repo root",
56
56
  await dev(resolve(opts.root), port);
57
57
  });
58
58
  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("--root <dir>", "Host repo root", { default: "." }).action(async (opts) => {
59
- const { buildSite } = await import("./build-BZaPa2DS.mjs");
59
+ const { buildSite } = await import("./build-B8z902TW.mjs");
60
60
  try {
61
61
  const boards = opts.boards === void 0 ? void 0 : typeof opts.boards === "string" ? opts.boards : "";
62
62
  await buildSite(resolve(opts.root), boards, opts.allBoards === true);
@@ -66,7 +66,7 @@ cli.command("build", "Static export → design/.dist (what ships comes from desi
66
66
  }
67
67
  });
68
68
  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) => {
69
- const { serve } = await import("./serve-D_KBK7Oy.mjs");
69
+ const { serve } = await import("./serve-CwAfayJk.mjs");
70
70
  let port;
71
71
  if (opts.port !== void 0) {
72
72
  const n = Number(opts.port);
@@ -75,7 +75,7 @@ cli.command("serve", "Serve design/.dist (set MARVER_PASSWORD to gate it)").opti
75
75
  serve(resolve(opts.root), port);
76
76
  });
77
77
  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("--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) => {
78
- const { commentsCommand } = await import("./comments-odHzYdO3.mjs");
78
+ const { commentsCommand } = await import("./comments-BrpC86Kf.mjs");
79
79
  try {
80
80
  await commentsCommand(resolve(opts.root), action, value, opts);
81
81
  } catch (err) {
@@ -1,4 +1,4 @@
1
- import { i as readLog, r as listBoards, t as appendEvents } from "./comments-Ba8mU600.mjs";
1
+ import { appendEvents, listBoards, readLog } from "./comments-DSwMhdbZ.mjs";
2
2
  import { claimInvite, createInvite, inviteInfo, ownerName, publicUser, revokeUser, sessionUser, signIn, signOut, updateProfile } from "./auth-B36fMCM3.mjs";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
@@ -20,7 +20,8 @@ const EVENT_TYPES = /* @__PURE__ */ new Set([
20
20
  "resolve",
21
21
  "reopen",
22
22
  "react",
23
- "profile"
23
+ "profile",
24
+ "reanchor"
24
25
  ]);
25
26
  const ID_RE = /^[\w-]{8,64}$/;
26
27
  const MAX_BODY_TEXT = 1e4;
@@ -60,10 +61,11 @@ function validateEvents(incoming, log, u, board) {
60
61
  if (!ev || typeof ev !== "object") return "malformed event";
61
62
  if (typeof ev.id !== "string" || !ID_RE.test(ev.id)) return "event id must be an 8-64 char token";
62
63
  if (!EVENT_TYPES.has(ev.type)) return `unknown event type "${ev.type}"`;
64
+ if (ev.agent !== void 0 || ev.agentMeta !== void 0) return "agent provenance is daemon-only";
63
65
  if (typeof ev.ts !== "number" || ev.ts < 15778368e5 || ev.ts > now + 6e4) return "event timestamp out of bounds";
64
66
  if (ev.board !== void 0 && ev.board !== board) return "event board does not match the endpoint";
65
67
  if (ev.body !== void 0 && (typeof ev.body !== "string" || ev.body.length > MAX_BODY_TEXT)) return "body too long";
66
- if ((ev.type === "create" || ev.type === "reply" || ev.type === "react" || ev.type === "edit") && ev.author?.email?.toLowerCase() !== me) return "event author must be the signed-in account";
68
+ if ((ev.type === "create" || ev.type === "reply" || ev.type === "react" || ev.type === "edit" || ev.type === "reanchor") && ev.author?.email?.toLowerCase() !== me) return "event author must be the signed-in account";
67
69
  if (typeof ev.commentId !== "string" || !ID_RE.test(ev.commentId)) {
68
70
  if (ev.type !== "profile") return "event needs a commentId";
69
71
  }
@@ -84,7 +86,16 @@ function validateEvents(incoming, log, u, board) {
84
86
  break;
85
87
  }
86
88
  case "resolve":
87
- case "reopen": if (!creates.has(ev.commentId) && !incoming.some((x) => x.type === "create" && x.commentId === ev.commentId)) return "cannot resolve a thread that does not exist";
89
+ case "reopen":
90
+ if (!creates.has(ev.commentId) && !incoming.some((x) => x.type === "create" && x.commentId === ev.commentId)) return "cannot resolve a thread that does not exist";
91
+ break;
92
+ case "reanchor": {
93
+ if (!creates.has(ev.commentId) && !incoming.some((x) => x.type === "create" && x.commentId === ev.commentId)) return "cannot reanchor a thread that does not exist";
94
+ if (ev.anchor == null) return "reanchor needs a new anchor";
95
+ const owner = authors.get(ev.commentId);
96
+ if (owner && owner !== me) return "only the thread author can reanchor";
97
+ break;
98
+ }
88
99
  }
89
100
  }
90
101
  return null;
@@ -294,4 +305,4 @@ function collabHandler(dataDir, distDir) {
294
305
  };
295
306
  }
296
307
  //#endregion
297
- export { collabHandler };
308
+ export { collabHandler, validAvatar };
@@ -1,7 +1,8 @@
1
1
  import { n as NAME } from "./cli.mjs";
2
- import { i as syncOnce, n as connectClaim, o as replay, r as loadCollab, t as connect } from "./sync-CkBk-tUk.mjs";
3
- import { i as readLog, r as listBoards, t as appendEvents } from "./comments-Ba8mU600.mjs";
4
- import { readFileSync } from "node:fs";
2
+ import { n as replay } from "./events-BMtBvvgU.mjs";
3
+ import { appendEvents, listBoards, readLog } from "./comments-DSwMhdbZ.mjs";
4
+ import { connect, connectClaim, loadCollab, syncOnce } from "./sync-Ch4Bymb1.mjs";
5
+ import { n as localProfile } from "./profile-BkiWglVE.mjs";
5
6
  import { join } from "node:path";
6
7
  import { createInterface } from "node:readline";
7
8
  import { randomUUID } from "node:crypto";
@@ -162,18 +163,11 @@ const pushIfConnected = async (root) => {
162
163
  * authors against the session - an authorless push would be rejected), else the
163
164
  * local dev profile. */
164
165
  const localAuthor = (root) => {
165
- const collab = loadCollab(root);
166
- if (collab?.email) return {
167
- email: collab.email,
168
- name: collab.name
169
- };
170
- try {
171
- const p = JSON.parse(readFileSync(join(root, "design", ".local", "profile.json"), "utf8"));
172
- if (p?.email) return {
173
- email: p.email,
174
- name: p.name
175
- };
176
- } catch {}
166
+ const p = localProfile(root);
167
+ return p.email ? {
168
+ email: p.email,
169
+ name: p.name
170
+ } : void 0;
177
171
  };
178
172
  //#endregion
179
173
  export { commentsCommand };
@@ -1,4 +1,4 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
1
+ import "./events-BMtBvvgU.mjs";
2
2
  import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, statSync, writeSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  //#region src/server/comments.ts
@@ -11,12 +11,6 @@ import { join } from "node:path";
11
11
  * state is derived by replaying events (ordered by ts then id, last writer wins per
12
12
  * field). Files, not a database - tens to low hundreds of events per board.
13
13
  */
14
- var comments_exports = /* @__PURE__ */ __exportAll({
15
- appendEvents: () => appendEvents,
16
- dataDir: () => dataDir,
17
- listBoards: () => listBoards,
18
- readLog: () => readLog
19
- });
20
14
  const BOARD_RE = /^[a-z0-9][a-z0-9-]*$/;
21
15
  const boardFile = (dir, board) => {
22
16
  if (!BOARD_RE.test(board)) throw new Error(`bad board name: ${board}`);
@@ -87,4 +81,4 @@ function dataDir() {
87
81
  return dir;
88
82
  }
89
83
  //#endregion
90
- export { readLog as i, comments_exports as n, listBoards as r, appendEvents as t };
84
+ export { appendEvents, dataDir, listBoards, readLog };