@marver-design/marver 0.8.0 → 0.8.1
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 +97 -5
- package/README.md +72 -16
- package/dist/{auth-B36fMCM3.mjs → auth-KQ9Aj-nB.mjs} +1 -1
- package/dist/{build-B8z902TW.mjs → build-BBVQRetk.mjs} +5 -5
- package/dist/cli.mjs +16 -7
- package/dist/{collab-pLGzSvm5.mjs → collab-s3k5byM1.mjs} +3 -3
- package/dist/{comments-DSwMhdbZ.mjs → comments-BZBKhKRO.mjs} +2 -2
- package/dist/{comments-BrpC86Kf.mjs → comments-J06jqCVV.mjs} +3 -3
- package/dist/{daemon-C-huGHAM.mjs → daemon-DkyNOwIt.mjs} +30 -51
- package/dist/{dev-BYm9ebfN.mjs → dev-BTAhTie-.mjs} +19 -5
- package/dist/{init-DrLomSWq.mjs → init-DWdhjJD5.mjs} +16 -3
- package/dist/{ledger-wFvEIEGi.mjs → ledger-CbzTJrV2.mjs} +1 -1
- package/dist/{manifest-D3eaARf4.mjs → manifest-B4zcDGBf.mjs} +3 -3
- package/dist/{plugin-BVFuRfEo.mjs → plugin-BdQEeTLg.mjs} +41 -9
- package/dist/{serve-CwAfayJk.mjs → serve-CZqPnj19.mjs} +32 -8
- package/dist/{sync-Ch4Bymb1.mjs → sync-BJKKmy1n.mjs} +2 -2
- package/dist/work-CLrmY-vQ.mjs +97 -0
- package/dist/work-lzC-lPY0.mjs +76 -0
- package/package.json +16 -1
- package/src/client/const.ts +1 -1
- package/src/client/content/diagram.tsx +1 -1
- package/src/client/content/index.tsx +2 -2
- package/src/client/content/md.ts +1 -1
- package/src/client/content/palette.ts +2 -2
- package/src/client/frame-host/bridge.js +1 -1
- package/src/client/frame-host/inspect.js +1 -1
- package/src/client/frame-host/serialize.ts +2 -2
- package/src/client/shell/App.tsx +79 -22
- package/src/client/shell/Comments.tsx +24 -18
- package/src/client/shell/Play.tsx +2 -2
- package/src/client/shell/canvas/Canvas.tsx +1 -1
- package/src/client/shell/canvas/FrameNode.tsx +16 -11
- package/src/client/shell/canvas/snapshots.ts +3 -3
- package/src/client/shell/comments-store.ts +115 -42
- package/src/client/shell/hash.ts +2 -2
- package/src/client/shell/icons.tsx +1 -1
- package/src/client/shell/keys.ts +39 -0
- package/src/client/shell/mentions.ts +1 -1
- package/src/client/shell/perf.ts +1 -1
- package/src/client/shell/store.ts +99 -39
- package/src/client/shell/styles.css +12 -12
- package/src/client/shell/tidy.ts +5 -5
- package/src/client/stage/main.tsx +2 -2
- package/src/shared/events.ts +2 -2
- package/src/shared/utm.ts +22 -0
- package/templates/AGENTS-embedded.md +48 -1
- package/templates/AGENTS-studio.md +48 -1
- package/templates/instructions/publish.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,97 @@
|
|
|
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.1 - 2026-08-19
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`marver work` - the chat agent's hand on the canvas.** The live working shimmer was
|
|
10
|
+
exclusive to Live Jam; now any coding agent can drive it: create the frame files first,
|
|
11
|
+
pin them on the board, `npx marver work start <scene/frame ...>` - the human sees the
|
|
12
|
+
request land on the canvas in seconds, before the first component exists - build
|
|
13
|
+
(independent frames in parallel, one subagent each), then `work done`. Marks are leased
|
|
14
|
+
(default 10 min, max 30) so a crashed agent can never leave a frame glowing. The dev
|
|
15
|
+
server writes `design/.local/dev.json` (port + per-boot token) as the CLI's discovery
|
|
16
|
+
and credential; presence itself never touches disk. The generated AGENTS.md teaches
|
|
17
|
+
the choreography.
|
|
18
|
+
- **`marver canvas` - a second name for `marver dev`.** Both start the same full local
|
|
19
|
+
canvas (hot reload, comments, Live Jam, working state); `dev` reads naturally to
|
|
20
|
+
developers, `canvas` to everyone else. There is no reduced mode behind either name.
|
|
21
|
+
- **Agents report marver bugs upstream.** The generated AGENTS.md now teaches the
|
|
22
|
+
coding agent to file issues on the marver repo when the TOOL itself misbehaves
|
|
23
|
+
(search first, `bug`/`enhancement` labels, version + expected-vs-actual + an
|
|
24
|
+
abstract reproduction) - with privacy as hard law: issues are public, so nothing
|
|
25
|
+
from the owner's repo (code, names, comment text, screenshots) may appear; failures
|
|
26
|
+
are described in neutral terms or handed to the owner instead. The owner is always
|
|
27
|
+
told what was filed. Every canvas becomes a field reporter, no user effort.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **The powered-by links carry automatic attribution.** Every canvas's marver.design link
|
|
32
|
+
now ships UTM-tagged with zero setup: `utm_source` says which surface class sent the
|
|
33
|
+
visitor (`published-canvas` / `dev-canvas`), `utm_medium=powered-by`, `utm_campaign` is
|
|
34
|
+
the canvas's own name slugged (`marver-tour`), and `utm_content` names the placement
|
|
35
|
+
(`gate` badge / `shell` wordmark).
|
|
36
|
+
- **`init` wires every agent in, and showing the work comes first.** The repo-root
|
|
37
|
+
CLAUDE.md @-import has a sibling: init now also creates (or appends one pointer line
|
|
38
|
+
to) a root AGENTS.md, so Codex-style agents inherit the canvas contract too. And the
|
|
39
|
+
contract's Show-the-work section is stricter - making the request visible is the
|
|
40
|
+
agent's FIRST act: skeleton frame created, pinned, and shimmering before research,
|
|
41
|
+
reading the codebase, or planning begins.
|
|
42
|
+
- **Comments are frame-scoped now, and notifications reach you anywhere.** The client holds
|
|
43
|
+
every board's comment log, not just the open board's: a thread pins to its frame wherever
|
|
44
|
+
that frame appears - the all-scenes board finally shows every conversation - and a Marver
|
|
45
|
+
reply landing on ANY board raises the notification pill no matter where you are, with View
|
|
46
|
+
navigating to the right board and opening the thread. Replies and resolves route to the
|
|
47
|
+
thread's origin log, so reading a thread from another board never forks it.
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- **The jam shell ban is enforced, not just requested.** The Claude Code adapter now passes
|
|
52
|
+
`--disallowedTools Bash` alongside its tool allowlist: `--allowedTools` only pre-approves
|
|
53
|
+
tools, so a permissive inherited `settings.json` could have re-authorized shell. The
|
|
54
|
+
documented boundary (no shell for untrusted comment text) is now explicit in the spawn.
|
|
55
|
+
- **The jam ack never narrates.** The agent's streamed first line posts to the thread
|
|
56
|
+
verbatim - and sometimes that line was plan narration ("I'll start by acknowledging,
|
|
57
|
+
then look at the board...") instead of a message to the owner. The packet now spells
|
|
58
|
+
out that the first text ships verbatim and must address the owner, and the daemon
|
|
59
|
+
backstops it: an unmistakable plan-narration first line is skipped and the next
|
|
60
|
+
streamed text becomes the ack - a later tight ack, never a leaked plan.
|
|
61
|
+
- **`data-goto` follows a frame to its board.** Clicking a link whose target frame lives on
|
|
62
|
+
another board used to SPAWN that frame onto the current board (mutating it - and in dev,
|
|
63
|
+
saving the mutation). Navigation now follows the frame home: the canvas switches to the
|
|
64
|
+
first curated board that pins the target, focuses it, and carries interact mode across the
|
|
65
|
+
switch - links are navigation, never edits. A frame no board pins still spawns in place
|
|
66
|
+
(the original single-board prototype behavior), and play mode is unchanged.
|
|
67
|
+
- **Comments no longer vanish across board switches and reloads.** Board nodes the file did not
|
|
68
|
+
key yet were minted RANDOM keys on every load - and comments anchor to node keys, so any
|
|
69
|
+
comment created on a never-saved board orphaned invisibly on the next mount (and on published
|
|
70
|
+
canvases, which can never save keys back, on every visit). Keys are now deterministic
|
|
71
|
+
(board + frame + occurrence), and a single thread-host resolver adopts threads whose stored
|
|
72
|
+
key no longer holds onto the first node still showing their frame - a comment degrades to its
|
|
73
|
+
frame, never to invisible, and never to two pins.
|
|
74
|
+
- **Old comment logs keep their origin.** Events written by 0.8.0 clients carry no `board`;
|
|
75
|
+
they now get it stamped from the log they came from, so replying to an old thread can never
|
|
76
|
+
route the reply into whatever board happens to be open.
|
|
77
|
+
- **Create-first frames land at their declared size.** A board node appended before the
|
|
78
|
+
manifest registered its frame file was sized by the 390x844 guess - and the guess persisted.
|
|
79
|
+
The size now corrects itself the moment the frame arrives, guessed sizes never reach the
|
|
80
|
+
file, and authored sizes on temporarily-missing frames are never touched.
|
|
81
|
+
- **`marver init` no longer resurrects the demo scene.** Re-running init on a workspace with
|
|
82
|
+
real scenes used to scaffold demo/ back next to real work; the demo now only lands on a
|
|
83
|
+
first canvas.
|
|
84
|
+
- **Static canvases stop knocking.** A published canvas without collaboration used to poll
|
|
85
|
+
the comments API forever (and retry an EventSource against it); the client now detects the
|
|
86
|
+
static serve once and goes quiet.
|
|
87
|
+
- **No phantom comments on static canvases.** A published serve without `MARVER_DATA_DIR` used to
|
|
88
|
+
let `/__mv/api/*` requests fall through to the static handler, which answered them with
|
|
89
|
+
`index.html` HTTP 200 - the client read that as success, so a guest's comment echoed locally
|
|
90
|
+
and silently evaporated on reload. The serve now refuses the API with a 404 JSON error the
|
|
91
|
+
client surfaces as a toast.
|
|
92
|
+
- **The published shell wordmark honors `share.name`.** Deploy hosts build from anonymous paths
|
|
93
|
+
(`/app`), so the sidebar read "App"; a declared `share.name` now names the shell like it
|
|
94
|
+
already named the gate.
|
|
95
|
+
|
|
5
96
|
## 0.8.0 - 2026-08-19
|
|
6
97
|
|
|
7
98
|
Live Jam: tag `@marver` in a canvas comment and a local coding agent picks it up, edits the real
|
|
@@ -20,8 +111,9 @@ on the comment thread experience.
|
|
|
20
111
|
Every reply carries a provenance tooltip - dev user, harness, model.
|
|
21
112
|
- **A hard trust boundary.** Only the owner's device triggers: a device-bound ledger (keyed per
|
|
22
113
|
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
|
|
24
|
-
Read/Edit/Write/Glob/Grep + WebSearch/WebFetch only
|
|
114
|
+
profile), and a recursion guard so agent replies never re-trigger. The agent runs locked down
|
|
115
|
+
(Claude Code: no shell, Read/Edit/Write/Glob/Grep + WebSearch/WebFetch only; Codex: confined
|
|
116
|
+
to its workspace-write sandbox) - and its reply is extracted structurally
|
|
25
117
|
(the `marver-reply` block), so narration can never leak into a thread. Comment text is framed
|
|
26
118
|
as untrusted data end to end.
|
|
27
119
|
- **The felt surface.** A frame being worked on wears the selection geometry in marver blue with
|
|
@@ -177,8 +269,8 @@ snapshot that IS what you see, and the real live app takes over the moment you i
|
|
|
177
269
|
|
|
178
270
|
- **Sidebar header** shows the humanized repo name (`marver-pilot` → "Marver Pilot", ellipsed if
|
|
179
271
|
long); the logo links to marver.design.
|
|
180
|
-
- **Sidebar board/scene labels** are humanized - kebab filenames render Title Case (`
|
|
181
|
-
"
|
|
272
|
+
- **Sidebar board/scene labels** are humanized - kebab filenames render Title Case (`crm-specs` →
|
|
273
|
+
"Crm Specs"), dropping the dashes, while an explicit `meta.title` is honored verbatim.
|
|
182
274
|
- **App cursor** is the marver arrowhead - tilted, rounded, small, soft-shadowed, and theme-adaptive
|
|
183
275
|
(black-on-light / white-on-dark); reverts to a normal pointer in interact/prototype and keeps the
|
|
184
276
|
pin/crosshair in comment/laser mode.
|
|
@@ -328,7 +420,7 @@ The co-thinking release: the canvas now holds the thinking, not just the screens
|
|
|
328
420
|
|
|
329
421
|
## 0.2.3 - 2026-08-12
|
|
330
422
|
|
|
331
|
-
- Hardening release:
|
|
423
|
+
- Hardening release: live-JOIN adjacency, same-directory group invariant, tsx-only inference, extractor boundaries, sceneRows dedupe, play-mode chrome fixes, extreme-zoom badge fade.
|
|
332
424
|
|
|
333
425
|
## 0.2.2 - 2026-08-12
|
|
334
426
|
|
package/README.md
CHANGED
|
@@ -1,32 +1,68 @@
|
|
|
1
1
|
# Marver
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@marver-design/marver)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](package.json)
|
|
6
|
+
|
|
7
|
+
**The agent-native design canvas.** A `design/` folder in your repo, one command, and a canvas of live frames built from your app's real components and theme. Your coding agent designs by writing files; the tool ships no AI.
|
|
8
|
+
|
|
9
|
+
[marver.design](https://marver.design) · [Deploying a canvas](docs/publish.md) · [Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md) · [Issues](https://github.com/TNEP4/marver/issues)
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
4
12
|
|
|
5
13
|
```bash
|
|
6
14
|
npm i -D @marver-design/marver
|
|
7
15
|
npx marver init # scaffolds design/ (detects shadcn, Tailwind, your router)
|
|
8
|
-
npx marver dev # canvas at localhost:5199
|
|
16
|
+
npx marver dev # canvas at localhost:5199 (npx marver canvas works too - same thing)
|
|
9
17
|
```
|
|
10
18
|
|
|
11
19
|
Then, to your agent:
|
|
12
20
|
|
|
13
21
|
> Read design/AGENTS.md. Build an onboarding scene - welcome, form, done - mobile-first, using our components.
|
|
14
22
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- **
|
|
20
|
-
-
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
Frames appear on the canvas the moment the files land. That's the loop.
|
|
24
|
+
|
|
25
|
+
## Why marver
|
|
26
|
+
|
|
27
|
+
- **Frames are real code.** Plain TSX/HTML files rendered from your repo's actual components and theme - zero imports from this package required. An approved design promotes into the app by moving a file, not by re-implementing a picture.
|
|
28
|
+
- **Everything hot-reloads.** The agent writes, you watch it land - live.
|
|
29
|
+
- **True viewports.** Each frame is a real iframe: drag its edge and your actual breakpoints fire.
|
|
30
|
+
- **No AI inside.** The designer is the coding agent you already run and pay for. `init` generates the `design/AGENTS.md` contract that teaches it the whole workflow.
|
|
31
|
+
|
|
32
|
+
## The canvas
|
|
33
|
+
|
|
34
|
+
- **Frames, scenes, boards.** Frames are screens, scenes group them (`design/scenes/<scene>/<frame>.tsx`), boards arrange them. Agents write `design/boards/<name>.json` (a frame list is enough); switch boards at the top of the sidebar. `all-scenes` is auto-managed.
|
|
35
|
+
- **Devices view.** Hotkeys `1`-`5` (or the Devices menu) size every frame to mobile / tablet / laptop / monitor / tv to sweep your breakpoints; `0` restores your own layout exactly. Widths live in `design/config.ts`.
|
|
36
|
+
- **Prototype links.** `data-goto="scene/frame"` on any element links frames into a walkable prototype - across boards, too.
|
|
37
|
+
- **Play mode.** Press `p`: the board becomes a full-screen, clickable walkthrough. `data-goto` links navigate, arrow keys step between frames, `[` / `]` cycle variants, `Escape` exits. Publish it and you have a shareable prototype.
|
|
38
|
+
- **Content frames.** Specs, Mermaid diagrams, and mood boards live on the same canvas as the screens - import `Doc`, `Md`, `Diagram`, `Img` from `@marver-design/marver/content` and think a feature through before any pixels exist. Works in a repo with no app at all: idea first, design second.
|
|
39
|
+
|
|
40
|
+
## Collaboration
|
|
41
|
+
|
|
42
|
+
- **Comments.** Google-Docs-style feedback pinned to actual elements. Press `c`, click a div inside a frame, write - the thread lives on that element and survives edits via a layered anchor (source semantics → structure → fuzzy text). Viewers on a published canvas comment with real names and avatars (invite-link accounts, no email infrastructure). `marver dev` syncs the same threads into `design/comments/*.jsonl`, where your agent works the queue: `npx marver comments list --open --json` → fork a variant → `resolve --addressed-in`. Live via SSE; one deploy, no extra services.
|
|
43
|
+
- **Laser mode.** Press `l`: every element in every frame gets depth-hued outlines plus a hover label - the fastest way to see structure. Click any element to copy its full address (frame file + CSS path) for the agent.
|
|
44
|
+
- **Publishing.** `npx marver build` exports a static canvas (default-closed: `design/publish.json` names what ships); `npx marver serve` hosts it with an optional password gate. One deploy on Railway, Docker, or any static host - the [publishing guide](docs/publish.md) has the one-pagers.
|
|
45
|
+
|
|
46
|
+
## Live Jam
|
|
47
|
+
|
|
48
|
+
Tag `@marver` in a comment and your own coding agent picks it up - reads the thread, edits the real frame source, replies with a receipt - while the frame wears a live working glow. Opt in with `jam: { agent: "claude" }` (or `"codex"`) in `design/config.ts`.
|
|
49
|
+
|
|
50
|
+
The trust boundary is hard: only comments written on the owner's machine trigger (a device-bound ledger - a drive-by comment on a published canvas cannot start work), the agent runs locked down (Claude Code with shell disabled entirely; Codex confined to its workspace-write sandbox), and every reply carries provenance: agent, model, dev user. Marver ships no AI; the agent that acts is the one you already run.
|
|
26
51
|
|
|
27
|
-
|
|
52
|
+
## Working state
|
|
28
53
|
|
|
29
|
-
|
|
54
|
+
The same glow, driven from the terminal. When your agent takes a request, it creates the frame files first, pins them on a board, and runs `npx marver work start <scene/frame ...>` - you see the work land on the canvas in seconds, watch it shimmer while subagents build in parallel, and see it settle on `work done`. Marks self-expire, so a crashed agent never leaves a frame glowing.
|
|
55
|
+
|
|
56
|
+
## Commands
|
|
57
|
+
|
|
58
|
+
| Command | What it does |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `npx marver init` | Scaffold `design/` in this repo (safe to re-run; refreshes managed files) |
|
|
61
|
+
| `npx marver dev` / `canvas` | Start the local canvas - hot reload, comments, Live Jam (`--port`, default 5199) |
|
|
62
|
+
| `npx marver build` | Static export → `design/.dist`; what ships comes from `design/publish.json` (default-closed) |
|
|
63
|
+
| `npx marver serve` | Serve the export; `MARVER_PASSWORD` gates it, `MARVER_DATA_DIR` persists comments + accounts |
|
|
64
|
+
| `npx marver comments …` | The agent's queue: `connect <url>` · `sync` · `list` · `reply` · `resolve` · `invite <email>` · `revoke <email>` |
|
|
65
|
+
| `npx marver work …` | Working glow from the terminal: `start <scene/frame …>` · `done … \| --all` · `list` |
|
|
30
66
|
|
|
31
67
|
## Shortcuts
|
|
32
68
|
|
|
@@ -39,4 +75,24 @@ Implementation contracts (SPEC.md, DECISIONS.md) live in the [GitHub repo](https
|
|
|
39
75
|
|
|
40
76
|
**Board & chrome** - `t` tidy · `d` toggle light/dark for the board · `⌘\` (ctrl+\) collapse/open sidebar.
|
|
41
77
|
|
|
42
|
-
**Selection** - click selects · shift+click (canvas or sidebar) builds a multi-selection · `⌘A` selects every frame on the board · `
|
|
78
|
+
**Selection** - click selects · shift+click (canvas or sidebar) builds a multi-selection · `⌘A` selects every frame on the board · `⇧P` copies the selected frames' file paths · double-click enters interact mode (`esc` or click outside leaves) · drag the title bar to move, edges to resize (widths snap to devices).
|
|
79
|
+
|
|
80
|
+
**Modes** - `c` comment mode · `l` laser mode · `⇧C` hide/show comment pins · `⇧L` laser comment (spotlight a thread's element) · `p` play mode · `h` hide all chrome.
|
|
81
|
+
|
|
82
|
+
## Notes
|
|
83
|
+
|
|
84
|
+
- **Next.js**: supported with one caveat - frames render in Vite, outside Next. `next/font` CSS variables are undefined inside frames (give font tokens a fallback chain), `next/image`/`next/link` should be plain `img`/`data-goto` in frames, and Server Components cannot run there. `init` writes the specifics into `design/AGENTS.md` when it detects Next.
|
|
85
|
+
- **Upgrade**: `npm i -D @marver-design/marver@latest && npx marver init`. The canvas tells you when a new version is out (one anonymous registry check per day, cached in `design/.local/`; `MARVER_NO_UPDATE_CHECK=1` disables). Re-running init refreshes the managed files (AGENTS.md, `design/instructions/`) - your edits to them are detected and preserved; when both you and a release changed a file, the fresh version is staged at `design/.local/latest/` for you (or your agent) to merge. Everything else in `design/` is yours and never touched.
|
|
86
|
+
- **Uninstall**: delete `design/`, remove the dependency. (If `init` patched your tsconfig `exclude`, revert that one line.)
|
|
87
|
+
|
|
88
|
+
## Status
|
|
89
|
+
|
|
90
|
+
Marver is a young solo side project - it works, it's dogfooded daily, and it has rough edges. The known weak spot: boards with many heavy frames (animation-rich, component-dense) can strain the canvas, especially while zooming - snapshot-based rendering helps but isn't finished. If you hit a wall, an issue with your board shape and frame count genuinely helps.
|
|
91
|
+
|
|
92
|
+
## Contributing
|
|
93
|
+
|
|
94
|
+
Bug reports, ideas, and PRs are welcome - start with [CONTRIBUTING.md](CONTRIBUTING.md). Security reports go through [private vulnerability reporting](SECURITY.md), not public issues. Agents running marver are taught to file what they hit as issues here, too - expect some robot reporters.
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
[Apache-2.0](LICENSE) · built by [Nic Touron](https://github.com/TNEP4)
|
|
@@ -3,7 +3,7 @@ import { dirname, join } from "node:path";
|
|
|
3
3
|
import { createHash, randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
|
|
4
4
|
//#region src/server/auth.ts
|
|
5
5
|
/**
|
|
6
|
-
* Accounts, invites, sessions
|
|
6
|
+
* Accounts, invites, sessions - the minimal credible implementation,
|
|
7
7
|
* extending the gate's own idiom. No framework: per-user salted scrypt verifiers,
|
|
8
8
|
* opaque session tokens stored hashed, single-use invite links as the identity
|
|
9
9
|
* bootstrap (no email infrastructure anywhere).
|
|
@@ -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-B4zcDGBf.mjs";
|
|
3
|
+
import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-BdQEeTLg.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";
|
|
@@ -8,7 +8,7 @@ import { build } from "vite";
|
|
|
8
8
|
import react from "@vitejs/plugin-react";
|
|
9
9
|
//#region src/server/build.ts
|
|
10
10
|
/**
|
|
11
|
-
* `marver build` - the static adapter
|
|
11
|
+
* `marver build` - the static adapter.
|
|
12
12
|
*
|
|
13
13
|
* Same Vite pipeline as dev (theme wrapper, tailwind, virtuals) with three JS entries
|
|
14
14
|
* (shell / frame-host / stage); the html pages are generated from the build output, so
|
|
@@ -24,7 +24,7 @@ const posix = (p) => p.split(sep).join("/");
|
|
|
24
24
|
function packageDir() {
|
|
25
25
|
return join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
26
26
|
}
|
|
27
|
-
/** Static asset references in one module's source
|
|
27
|
+
/** Static asset references in one module's source: <Img src="..."> string
|
|
28
28
|
* literals plus markdown image literals INSIDE TEMPLATE STRINGS (where Md content
|
|
29
29
|
* lives - prose in comments never counts). Comments are stripped first so a commented
|
|
30
30
|
* example cannot fail the build. A computed <Img src={...}> fails CLOSED - the build
|
|
@@ -43,7 +43,7 @@ function scanAssetRefs(src, moduleId) {
|
|
|
43
43
|
}
|
|
44
44
|
/** Same shape the client's assetUrl accepts: relative, inside design/assets/, no tricks. */
|
|
45
45
|
const isLocalAssetRef = (p) => !!p && !p.includes(":") && !p.startsWith("/") && !p.startsWith("\\") && !p.split("/").some((s) => s === ".." || s === "");
|
|
46
|
-
/** The publish policy
|
|
46
|
+
/** The publish policy: design/publish.json names what ships and with what
|
|
47
47
|
* rights. Publishing is default-CLOSED - no policy and no explicit flag = no build.
|
|
48
48
|
* Returns name -> 'read' | 'comment'. Boards absent from the result do not ship. */
|
|
49
49
|
function resolvePublish(root, allBoards, boardsFlag, allBoardsFlag) {
|
package/dist/cli.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { fileURLToPath } from "node:url";
|
|
|
9
9
|
const NAME = "marver";
|
|
10
10
|
const PKG = "@marver-design/marver";
|
|
11
11
|
const ROUTE = "/__mv";
|
|
12
|
-
/** Content-frame natural widths
|
|
12
|
+
/** Content-frame natural widths: Doc layout -> own-size width.
|
|
13
13
|
* Shared by the Doc primitive (measurement messages) and the server-side
|
|
14
14
|
* manifest scan (defaultSize for content frames) - one source, no drift. */
|
|
15
15
|
const CONTENT_WIDTH = {
|
|
@@ -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-DWdhjJD5.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
|
-
|
|
49
|
-
const { dev } = await import("./dev-
|
|
48
|
+
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) => {
|
|
49
|
+
const { dev } = await import("./dev-BTAhTie-.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-BBVQRetk.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-CZqPnj19.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-J06jqCVV.mjs");
|
|
79
79
|
try {
|
|
80
80
|
await commentsCommand(resolve(opts.root), action, value, opts);
|
|
81
81
|
} catch (err) {
|
|
@@ -83,6 +83,15 @@ cli.command("comments <action> [value]", "Comment collaboration: connect <url>
|
|
|
83
83
|
process.exit(1);
|
|
84
84
|
}
|
|
85
85
|
});
|
|
86
|
+
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) => {
|
|
87
|
+
const { workCommand } = await import("./work-lzC-lPY0.mjs");
|
|
88
|
+
try {
|
|
89
|
+
await workCommand(resolve(opts.root), action, frames ?? [], opts);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error(`[${NAME}] ${err.message}`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
86
95
|
cli.help();
|
|
87
96
|
cli.version(version());
|
|
88
97
|
cli.parse();
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { appendEvents, listBoards, readLog } from "./comments-
|
|
2
|
-
import { claimInvite, createInvite, inviteInfo, ownerName, publicUser, revokeUser, sessionUser, signIn, signOut, updateProfile } from "./auth-
|
|
1
|
+
import { appendEvents, listBoards, readLog } from "./comments-BZBKhKRO.mjs";
|
|
2
|
+
import { claimInvite, createInvite, inviteInfo, ownerName, publicUser, revokeUser, sessionUser, signIn, signOut, updateProfile } from "./auth-KQ9Aj-nB.mjs";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
6
|
//#region src/server/collab.ts
|
|
7
7
|
const MONTH = 2592e3;
|
|
8
8
|
const MAX_BODY = 262144;
|
|
9
|
-
/** The permission seam
|
|
9
|
+
/** The permission seam: v1 derives from publish policy + membership;
|
|
10
10
|
* a per-user ACL can replace the internals without touching a single route. */
|
|
11
11
|
function can(user, rights, board, action) {
|
|
12
12
|
if (action === "read") return board in rights;
|
|
@@ -3,7 +3,7 @@ import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, re
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
//#region src/server/comments.ts
|
|
5
5
|
/**
|
|
6
|
-
* The comment event store
|
|
6
|
+
* The comment event store - an append-only JSONL log, one file per board.
|
|
7
7
|
*
|
|
8
8
|
* Events are immutable and identified by client-generated UUIDs; two logs merge by SET
|
|
9
9
|
* UNION on id. That single rule is the entire sync protocol: idempotent, order-
|
|
@@ -73,7 +73,7 @@ function listBoards(dir) {
|
|
|
73
73
|
}
|
|
74
74
|
/** Where serve keeps its live store: MARVER_DATA_DIR is REQUIRED when collaboration is
|
|
75
75
|
* on - failing loudly beats silently writing into an ephemeral working directory that a
|
|
76
|
-
* redeploy wipes
|
|
76
|
+
* redeploy wipes. */
|
|
77
77
|
function dataDir() {
|
|
78
78
|
const dir = process.env.MARVER_DATA_DIR;
|
|
79
79
|
if (!dir) throw new Error("collaboration needs a durable home: set MARVER_DATA_DIR to a directory on a persistent volume (comments written anywhere else would vanish on redeploy)");
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { n as NAME } from "./cli.mjs";
|
|
2
2
|
import { n as replay } from "./events-BMtBvvgU.mjs";
|
|
3
|
-
import { appendEvents, listBoards, readLog } from "./comments-
|
|
4
|
-
import { connect, connectClaim, loadCollab, syncOnce } from "./sync-
|
|
3
|
+
import { appendEvents, listBoards, readLog } from "./comments-BZBKhKRO.mjs";
|
|
4
|
+
import { connect, connectClaim, loadCollab, syncOnce } from "./sync-BJKKmy1n.mjs";
|
|
5
5
|
import { n as localProfile } from "./profile-BkiWglVE.mjs";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { createInterface } from "node:readline";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
//#region src/cli/comments.ts
|
|
10
10
|
/**
|
|
11
|
-
* `marver comments <action>`
|
|
11
|
+
* `marver comments <action>` - the collaboration CLI.
|
|
12
12
|
*
|
|
13
13
|
* connect <url> sign in (or --invite <token> to claim) against a published canvas;
|
|
14
14
|
* stores the device credential in design/.local/collab.json
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { t as has } from "./ledger-
|
|
1
|
+
import { t as has } from "./ledger-CbzTJrV2.mjs";
|
|
2
2
|
import { n as replay } from "./events-BMtBvvgU.mjs";
|
|
3
|
-
import { appendEvents, listBoards, readLog } from "./comments-
|
|
3
|
+
import { appendEvents, listBoards, readLog } from "./comments-BZBKhKRO.mjs";
|
|
4
4
|
import { n as localProfile } from "./profile-BkiWglVE.mjs";
|
|
5
|
+
import { workActivity } from "./work-CLrmY-vQ.mjs";
|
|
5
6
|
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync, writeSync } from "node:fs";
|
|
6
7
|
import { join } from "node:path";
|
|
7
8
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
@@ -97,7 +98,10 @@ function goalText(packet) {
|
|
|
97
98
|
"```",
|
|
98
99
|
"Omit the block entirely if the element's identity is unchanged.",
|
|
99
100
|
"",
|
|
100
|
-
"YOUR FIRST MESSAGE is ONE short line to the owner, posted to the thread the moment you
|
|
101
|
+
"YOUR FIRST MESSAGE is ONE short line to the owner, posted to the thread VERBATIM the moment you",
|
|
102
|
+
"write it - so it must READ as a message TO the owner, never narration about your process. \"On it -",
|
|
103
|
+
"swapping the marks.\" posts well; \"I'll start by acknowledging, then look at the board.\" is plan",
|
|
104
|
+
"narration and must NEVER be your first text: plan silently, output only what the owner should read.",
|
|
101
105
|
"- Ask is clear: write it IMMEDIATELY, before any tool use - a tight acknowledgment matched to the",
|
|
102
106
|
" ask (\"On it - swapping the marks.\").",
|
|
103
107
|
"- Ask seems unclear: LOOK AROUND FIRST, like a human would - re-read members[].thread and",
|
|
@@ -160,7 +164,7 @@ function extractReanchors(text) {
|
|
|
160
164
|
//#endregion
|
|
161
165
|
//#region src/server/jam/adapter/claude.ts
|
|
162
166
|
/**
|
|
163
|
-
* The Claude Code adapter
|
|
167
|
+
* The Claude Code adapter. Spawns
|
|
164
168
|
* `claude -p` headless with STREAM-JSON output, so the daemon can post the agent's first
|
|
165
169
|
* message the moment it exists (the real quick ack / clarifying question - not a canned fake),
|
|
166
170
|
* and the final `result` event as the completion reply.
|
|
@@ -181,6 +185,8 @@ const claudeAdapter = {
|
|
|
181
185
|
"acceptEdits",
|
|
182
186
|
"--allowedTools",
|
|
183
187
|
"Read,Edit,Write,Glob,Grep,WebSearch,WebFetch",
|
|
188
|
+
"--disallowedTools",
|
|
189
|
+
"Bash",
|
|
184
190
|
"--output-format",
|
|
185
191
|
"stream-json",
|
|
186
192
|
"--verbose"
|
|
@@ -238,7 +244,7 @@ const claudeAdapter = {
|
|
|
238
244
|
//#endregion
|
|
239
245
|
//#region src/server/jam/adapter/codex.ts
|
|
240
246
|
/**
|
|
241
|
-
* The Codex adapter
|
|
247
|
+
* The Codex adapter. Spawns `codex exec --json`
|
|
242
248
|
* workspace-jailed. Codex emits JSONL events (thread.started, item.completed, turn.completed);
|
|
243
249
|
* the final agent_message is the reply. Codex has no in-process subagents, so a Codex job edits
|
|
244
250
|
* its frames sequentially - correct, just no parallel-frame glow within one job.
|
|
@@ -291,39 +297,9 @@ const codexAdapter = {
|
|
|
291
297
|
}
|
|
292
298
|
};
|
|
293
299
|
//#endregion
|
|
294
|
-
//#region src/server/jam/activity.ts
|
|
295
|
-
function createActivity(ttlMs = 9e4) {
|
|
296
|
-
const m = /* @__PURE__ */ new Map();
|
|
297
|
-
let cb = () => {};
|
|
298
|
-
const keys = () => [...m.keys()];
|
|
299
|
-
return {
|
|
300
|
-
mark(frame) {
|
|
301
|
-
if (!frame) return;
|
|
302
|
-
m.set(frame, Date.now() + ttlMs);
|
|
303
|
-
cb(keys());
|
|
304
|
-
},
|
|
305
|
-
clear(frame) {
|
|
306
|
-
if (m.delete(frame)) cb(keys());
|
|
307
|
-
},
|
|
308
|
-
active: keys,
|
|
309
|
-
sweep() {
|
|
310
|
-
const now = Date.now();
|
|
311
|
-
let changed = false;
|
|
312
|
-
for (const [k, until] of m) if (until <= now) {
|
|
313
|
-
m.delete(k);
|
|
314
|
-
changed = true;
|
|
315
|
-
}
|
|
316
|
-
if (changed) cb(keys());
|
|
317
|
-
},
|
|
318
|
-
onChange(fn) {
|
|
319
|
-
cb = fn;
|
|
320
|
-
}
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
//#endregion
|
|
324
300
|
//#region src/server/jam/journal.ts
|
|
325
301
|
/**
|
|
326
|
-
* The durable batch journal
|
|
302
|
+
* The durable batch journal - design/.local/jam-jobs.json.
|
|
327
303
|
*
|
|
328
304
|
* This is job-DELIVERY state (event ids + batch status), never comment content and never the
|
|
329
305
|
* agent session id (that stays in daemon memory), so it is not the session/form state the
|
|
@@ -446,7 +422,7 @@ function releaseLock(root) {
|
|
|
446
422
|
//#endregion
|
|
447
423
|
//#region src/server/jam/watch.ts
|
|
448
424
|
/**
|
|
449
|
-
* The pending-work scan
|
|
425
|
+
* The pending-work scan. Pure over the current logs + journal: returns the
|
|
450
426
|
* owner-authorized @marver mentions not yet processed. Every trigger gate is `triggers()`, in one
|
|
451
427
|
* place, so the scan AND the crash-resume path (daemon.ts) apply exactly the same rule:
|
|
452
428
|
* - agent-authored events never trigger (recursion guard, §4)
|
|
@@ -498,7 +474,7 @@ function allEventIds(commentsDir) {
|
|
|
498
474
|
//#endregion
|
|
499
475
|
//#region src/server/jam/daemon.ts
|
|
500
476
|
/**
|
|
501
|
-
* The Live Jam daemon
|
|
477
|
+
* The Live Jam daemon - a module inside the long-lived dev server.
|
|
502
478
|
*
|
|
503
479
|
* The loop: watch design/comments/ (dir-watch + ~5s rescan) → find owner-ledgered @marver
|
|
504
480
|
* mentions (watch.ts, the trust boundary) → claim each as a durable single-member batch
|
|
@@ -528,6 +504,12 @@ const fenceGroup = (pid) => {
|
|
|
528
504
|
if (pid) process.kill(-pid, "SIGKILL");
|
|
529
505
|
} catch {}
|
|
530
506
|
};
|
|
507
|
+
/** The early ack posts VERBATIM, so a first line that narrates the agent's plan instead of
|
|
508
|
+
* addressing the owner must not ship. Deliberately NARROW - "acknowledg" only ever appears
|
|
509
|
+
* in meta-talk (a real ack never names itself), and the openers are pure plan phrasing.
|
|
510
|
+
* A skipped line just promotes the NEXT streamed text to ack; worst case is a later ack,
|
|
511
|
+
* never a lost one (the final reply posts regardless). */
|
|
512
|
+
const metaNarration = (text) => /acknowledg/i.test(text) || /^(let me |i'?ll start|i will start|first,? i |my plan|i'?m going to start|i am going to start)/i.test(text.trim());
|
|
531
513
|
/** The loop, without timers/watch/lock. Baselines on creation, then each `tick()` resumes any
|
|
532
514
|
* leftover batches (re-validate + fence + re-run) and claims new owner-ledgered mentions. */
|
|
533
515
|
function createJam(root, cfg, adapter, log = () => {}, hooks = {}) {
|
|
@@ -590,7 +572,7 @@ function createJam(root, cfg, adapter, log = () => {}, hooks = {}) {
|
|
|
590
572
|
lineBuf = lines.pop() ?? "";
|
|
591
573
|
for (const line of lines) {
|
|
592
574
|
const hit = adapter.earlyText(line);
|
|
593
|
-
if (hit) {
|
|
575
|
+
if (hit && !metaNarration(hit.text)) {
|
|
594
576
|
earlyFired = true;
|
|
595
577
|
try {
|
|
596
578
|
onEarly(hit.text, hit.model);
|
|
@@ -624,7 +606,7 @@ function createJam(root, cfg, adapter, log = () => {}, hooks = {}) {
|
|
|
624
606
|
for (const { f } of files.slice(10)) rmSync(join(dir, f), { force: true });
|
|
625
607
|
} catch {}
|
|
626
608
|
};
|
|
627
|
-
/**
|
|
609
|
+
/** House style: never an em/en dash in a reply - a plain dash reads human. */
|
|
628
610
|
const plainDashes = (s) => s.replace(/\s*[—–]\s*/g, " - ");
|
|
629
611
|
const writeReply = (b, p, body, model, kind = "reply") => {
|
|
630
612
|
const me = localProfile(root);
|
|
@@ -647,7 +629,7 @@ function createJam(root, cfg, adapter, log = () => {}, hooks = {}) {
|
|
|
647
629
|
};
|
|
648
630
|
appendEvents(commentsDir, b.board, [reply]);
|
|
649
631
|
};
|
|
650
|
-
/** Emit reanchor events for threads the agent re-pinned
|
|
632
|
+
/** Emit reanchor events for threads the agent re-pinned. Owner-authored + agent:true
|
|
651
633
|
* (attributable, never re-triggers), deterministic ids so a re-run dedups. */
|
|
652
634
|
const emitReanchors = (b, reanchors) => {
|
|
653
635
|
if (!reanchors.length) return;
|
|
@@ -846,10 +828,11 @@ function createJam(root, cfg, adapter, log = () => {}, hooks = {}) {
|
|
|
846
828
|
}
|
|
847
829
|
};
|
|
848
830
|
}
|
|
849
|
-
/** Start the daemon inside the dev server.
|
|
850
|
-
*
|
|
851
|
-
*
|
|
852
|
-
|
|
831
|
+
/** Start the daemon inside the dev server. Presence goes through the process-wide shared
|
|
832
|
+
* activity set (work.ts) - the `marver work` CLI writes the same set, so the canvas shows
|
|
833
|
+
* one merged glow. Returns null when the adapter is unavailable or another dev server
|
|
834
|
+
* already holds the repo lock (that one runs the loop; this one watches without it). */
|
|
835
|
+
function startJam(root, cfg, log = () => {}, onChanged = () => {}) {
|
|
853
836
|
const adapter = cfg.agent === "claude" ? claudeAdapter : cfg.agent === "codex" ? codexAdapter : null;
|
|
854
837
|
if (!adapter) {
|
|
855
838
|
log(` jam: the "${cfg.agent}" adapter is not available yet; Live Jam is off`);
|
|
@@ -861,10 +844,9 @@ function startJam(root, cfg, log = () => {}, onActivity = () => {}, onChanged =
|
|
|
861
844
|
}
|
|
862
845
|
const commentsDir = join(root, "design", "comments");
|
|
863
846
|
mkdirSync(commentsDir, { recursive: true });
|
|
864
|
-
const activity =
|
|
865
|
-
activity.onChange(onActivity);
|
|
847
|
+
const activity = workActivity;
|
|
866
848
|
const core = createJam(root, cfg, adapter, log, {
|
|
867
|
-
work: (f, on) => on ? activity.mark(f ?? "") : activity.clear(f ?? ""),
|
|
849
|
+
work: (f, on) => on ? activity.mark(f ?? "", void 0, "jam") : activity.clear(f ?? "", "jam"),
|
|
868
850
|
changed: onChanged
|
|
869
851
|
});
|
|
870
852
|
let stopped = false;
|
|
@@ -881,15 +863,12 @@ function startJam(root, cfg, log = () => {}, onActivity = () => {}, onChanged =
|
|
|
881
863
|
} catch {}
|
|
882
864
|
const interval = setInterval(() => void core.tick(), RESCAN_MS);
|
|
883
865
|
interval.unref?.();
|
|
884
|
-
const sweep = setInterval(() => activity.sweep(), 3e4);
|
|
885
|
-
sweep.unref?.();
|
|
886
866
|
core.tick();
|
|
887
867
|
log(` jam: watching for @marver (${adapter.name})`);
|
|
888
868
|
return { stop() {
|
|
889
869
|
stopped = true;
|
|
890
870
|
if (scheduled) clearTimeout(scheduled);
|
|
891
871
|
clearInterval(interval);
|
|
892
|
-
clearInterval(sweep);
|
|
893
872
|
watcher?.close();
|
|
894
873
|
core.stop();
|
|
895
874
|
releaseLock(root);
|