@marver-design/marver 0.6.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.
- package/CHANGELOG.md +95 -0
- package/dist/{build-BZaPa2DS.mjs → build-B8z902TW.mjs} +2 -2
- package/dist/cli.mjs +5 -5
- package/dist/{collab-CXy8gqoz.mjs → collab-pLGzSvm5.mjs} +16 -5
- package/dist/{comments-odHzYdO3.mjs → comments-BrpC86Kf.mjs} +9 -15
- package/dist/{comments-Ba8mU600.mjs → comments-DSwMhdbZ.mjs} +2 -8
- package/dist/daemon-C-huGHAM.mjs +899 -0
- package/dist/{dev-DaPQ9xA5.mjs → dev-BYm9ebfN.mjs} +14 -3
- package/dist/events-BMtBvvgU.mjs +101 -0
- package/dist/{init-DsCUmlCW.mjs → init-DrLomSWq.mjs} +13 -1
- package/dist/ledger-wFvEIEGi.mjs +64 -0
- package/dist/{manifest-C8FODq2S.mjs → manifest-D3eaARf4.mjs} +16 -1
- package/dist/{plugin-wMY9lNf3.mjs → plugin-BVFuRfEo.mjs} +56 -40
- package/dist/profile-BkiWglVE.mjs +39 -0
- package/dist/{serve-D_KBK7Oy.mjs → serve-CwAfayJk.mjs} +3 -3
- package/dist/{sync-CkBk-tUk.mjs → sync-Ch4Bymb1.mjs} +4 -102
- package/package.json +1 -1
- package/src/client/frame-host/bridge.js +28 -242
- package/src/client/frame-host/inspect.d.ts +6 -0
- package/src/client/frame-host/inspect.js +359 -0
- package/src/client/shell/App.tsx +107 -110
- package/src/client/shell/Comments.tsx +465 -70
- package/src/client/shell/Play.tsx +218 -155
- package/src/client/shell/Toolbar.tsx +201 -0
- package/src/client/shell/canvas/FrameNode.tsx +59 -8
- package/src/client/shell/comments-store.ts +47 -6
- package/src/client/shell/icons.tsx +5 -0
- package/src/client/shell/mentions.ts +18 -0
- package/src/client/shell/store.ts +30 -2
- package/src/client/shell/styles.css +356 -30
- package/src/client/stage/main.tsx +35 -11
- package/src/shared/events.ts +22 -5
- package/templates/AGENTS-embedded.md +1 -0
- package/templates/AGENTS-studio.md +1 -0
- package/templates/instructions/configure.md +15 -0
- package/templates/instructions/jam.md +85 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +0 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,101 @@
|
|
|
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
|
+
|
|
51
|
+
## 0.7.0 - 2026-08-17
|
|
52
|
+
|
|
53
|
+
Prototype mode becomes a first-class review surface. The prototype (Play) now carries the same toolbar
|
|
54
|
+
as the canvas, laser and comments work INSIDE it, and every comment wears the colour of the element it
|
|
55
|
+
points at - the groundwork for tagging a coding agent from anywhere you review.
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- **The prototype is a review surface, not just a viewer.** Play's top-right toolbar is now the SAME
|
|
60
|
+
controls as the canvas - device switch, theme, laser, comment - built from one shared set of
|
|
61
|
+
components so the two can never drift. Laser and comment work inside the running prototype: the stage
|
|
62
|
+
runs the same element inspector the canvas frames do, so you can point at, highlight, and comment on
|
|
63
|
+
the live app while you walk a flow. The bottom-left navigator (restart / prev / i-of-N / next) stays.
|
|
64
|
+
- **Hide UI (H).** One binary toggle hides all chrome for a clean point-and-shoot frame, shared by the
|
|
65
|
+
canvas and the prototype. No auto-fade, no hover magic; a page refresh always brings the chrome back
|
|
66
|
+
(the safety net for a forgotten shortcut). It replaces the prototype's old three-state auto-hiding bar.
|
|
67
|
+
- **Comments wear their element's colour.** Comment mode reuses laser's per-element depth hue: the
|
|
68
|
+
element you hover, the composer, the pin, the thread card, and the active-element highlight all take
|
|
69
|
+
that specific element's colour - a comment on a blue heading reads blue, on a green button green. The
|
|
70
|
+
avatar keeps the commenter's own colour; filled buttons use a hue-aware shade so the glyph stays legible.
|
|
71
|
+
- **The commented element lights up.** Picking an element locks a persistent outline on it (the highlight
|
|
72
|
+
stops chasing the mouse while you compose), and opening a thread re-lights its anchored element - so
|
|
73
|
+
everyone sees exactly which element a comment is about. It clears on close.
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
|
|
77
|
+
- **Prototype chrome is discreet and reliable.** The prototype's floating toolbar wears the same quiet
|
|
78
|
+
dark skin as the bottom-left navigator, so it recedes into the stage instead of reading as a bright
|
|
79
|
+
slab; and it no longer auto-hides or sticks under the cursor - it behaves exactly like the canvas pill
|
|
80
|
+
(explicit collapse, Hide-UI, no surprises).
|
|
81
|
+
- **Prototype is an action, not a menu item.** The Prototype-view button moved into the canvas pill's
|
|
82
|
+
action group (beside comment, laser, tidy), leaving the far right purely for UI management (Hide UI,
|
|
83
|
+
collapse).
|
|
84
|
+
- **The element label reads clearer.** The laser/comment element tooltip now sits below-left of the
|
|
85
|
+
cursor with a gap - clear of the element you are pointing at - and slides in from the edge instead of
|
|
86
|
+
being clipped.
|
|
87
|
+
- **Escape exits laser mode**, matching comment mode.
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
|
|
91
|
+
- **Canvas comment highlight is now reliable.** A frame hosting an open thread or a draft shows its LIVE
|
|
92
|
+
app rather than the frozen lean snapshot, so the active-element highlight appears, updates, and clears
|
|
93
|
+
in real time - it used to be intermittently missing, or frozen after close, depending on snapshot
|
|
94
|
+
timing. A clean snapshot rebuilds once the thread closes.
|
|
95
|
+
- **Highlights only show while a thread is active.** Closing a thread or hiding pins (Shift+C) clears the
|
|
96
|
+
element highlight; a remotely-resolved thread no longer strands one.
|
|
97
|
+
- **Prototype comment overlay positions faster** after a walk and never renders behind the frame or off
|
|
98
|
+
screen. Element-hue values, message origins, and stage-swap timing are all validated.
|
|
99
|
+
|
|
5
100
|
## 0.6.0 - 2026-08-17
|
|
6
101
|
|
|
7
102
|
Image-heavy boards, done right. A board full of high-resolution screenshots now zooms fast and stays
|
|
@@ -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-
|
|
3
|
-
import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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 {
|
|
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":
|
|
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 {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
|
166
|
-
|
|
167
|
-
email:
|
|
168
|
-
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
|
|
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 {
|
|
84
|
+
export { appendEvents, dataDir, listBoards, readLog };
|