@marver-design/marver 0.10.2 → 0.11.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 +161 -0
- package/README.md +16 -3
- package/dist/auth-B5yuwnOq.mjs +494 -0
- package/dist/{build-zUf_kwKp.mjs → build-DI31MFTB.mjs} +3 -2
- package/dist/cli.mjs +6 -6
- package/dist/{collab-s3k5byM1.mjs → collab-CWaG3Q4w.mjs} +43 -11
- package/dist/{comments-J06jqCVV.mjs → comments-CRqaO2MH.mjs} +26 -11
- package/dist/{comments-BZBKhKRO.mjs → comments-DZyobpxG.mjs} +8 -2
- package/dist/{manifest-DIsp3ldB.mjs → config-t9coJ-Pq.mjs} +3 -233
- package/dist/{daemon-BpKBJZee.mjs → daemon-DSmaS453.mjs} +23 -5
- package/dist/{dev-D2PG6igI.mjs → dev-BWIL2ewj.mjs} +117 -13
- package/dist/{init-DwV0_d9G.mjs → init-BSclDg4I.mjs} +2 -1
- package/dist/manifest-C2tzkNaC.mjs +233 -0
- package/dist/marver-id-B8-3WiHk.mjs +411 -0
- package/dist/marver-id-gate-BDzW6ahN.mjs +728 -0
- package/dist/{plugin-C1sDewZ3.mjs → plugin-DjBFxEQ7.mjs} +12 -84
- package/dist/{profile-BkiWglVE.mjs → profile-DcsJyppw.mjs} +9 -6
- package/dist/secure-cookie-_K1Hsx8H.mjs +26 -0
- package/dist/{serve-CZqPnj19.mjs → serve-3APqaNSB.mjs} +196 -43
- package/dist/sync-DELGomPk.mjs +397 -0
- package/dist/update-DuWDj5nR.mjs +77 -0
- package/dist/utm-CxC3QN5X.mjs +20 -0
- package/package.json +1 -1
- package/src/shared/utm.ts +2 -2
- package/templates/instructions/configure.md +4 -0
- package/templates/instructions/publish.md +99 -16
- package/dist/auth-KQ9Aj-nB.mjs +0 -245
- package/dist/events-BMtBvvgU.mjs +0 -101
- package/dist/sync-BJKKmy1n.mjs +0 -150
- package/dist/{shot-D7I0MCLu.mjs → shot-Cyv3GN79.mjs} +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,167 @@
|
|
|
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.11.1 - 2026-08-27
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **The password gate's cookie now gets `Secure` from the pinned origin too.** 0.11.0 fixed
|
|
10
|
+
this for the collaboration session cookie and missed the gate's own, which is set in a
|
|
11
|
+
different file and is the one most likely to be sitting behind a plain `proxy_pass` - it is
|
|
12
|
+
the door on every password-mode canvas. Same rule, same reason: `MARVER_PUBLIC_ORIGIN`
|
|
13
|
+
decides when it is set, and `X-Forwarded-Proto` is the fallback only when there is nothing
|
|
14
|
+
better. Set `MARVER_PUBLIC_ORIGIN=https://your-canvas.example.com` and the thirty-day gate
|
|
15
|
+
cookie is https-only regardless of what your proxy tells the process.
|
|
16
|
+
|
|
17
|
+
Canvases behind a proxy that does send `X-Forwarded-Proto` - Railway, Fly, Vercel, Caddy,
|
|
18
|
+
and nginx configured with `proxy_set_header` - were never affected.
|
|
19
|
+
|
|
20
|
+
## 0.11.0 - 2026-08-27
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **Marver Sign In: a canvas can ask people who they are, instead of asking for a shared
|
|
25
|
+
password.** Set `MARVER_ID_ISSUER=https://id.marver.design` (plus `MARVER_PUBLIC_ORIGIN`
|
|
26
|
+
and `MARVER_DATA_DIR`) and the gate becomes an identity gate. People sign in once - with
|
|
27
|
+
Google, or a code emailed to them - and every canvas gated this way opens without another
|
|
28
|
+
password. Nobody types a canvas password, so there is none to leak, rotate, or forget, and
|
|
29
|
+
revoking one person revokes exactly them. This is the better default for a team, and the
|
|
30
|
+
one we run ourselves.
|
|
31
|
+
|
|
32
|
+
**Your guest list stays yours.** The identity service proves *who somebody is* and has no
|
|
33
|
+
say in *where they may go*: an address gets in if it already has an account on your canvas,
|
|
34
|
+
holds an unexpired invite, or is `MARVER_OWNER_EMAIL` on a canvas with no accounts yet.
|
|
35
|
+
That decision is made on your disk, and the identity service is never told the answer.
|
|
36
|
+
Somebody with a perfectly valid Marver account who is not on your list gets nothing.
|
|
37
|
+
|
|
38
|
+
**The sovereign path is unchanged and is not going anywhere.** `MARVER_PASSWORD` still
|
|
39
|
+
gates a canvas with accounts that live entirely in your `MARVER_DATA_DIR`, and a served
|
|
40
|
+
canvas with no `MARVER_ID_ISSUER` set makes no outbound request at all - nothing to depend
|
|
41
|
+
on, nothing to phone home to. (Stated precisely, because it is the kind of promise that
|
|
42
|
+
should be: `marver serve` is silent. `marver dev` still makes the one anonymous registry
|
|
43
|
+
request it always has, once a day, to notice a new version - `MARVER_NO_UPDATE_CHECK=1`
|
|
44
|
+
turns it off - and a workspace you have connected to a published canvas still syncs its
|
|
45
|
+
comments with that canvas.) The two are alternatives rather than layers: running both
|
|
46
|
+
would weaken your invite list to "an account OR whoever has the password", so the identity
|
|
47
|
+
gate replaces the password gate rather than sitting beside it.
|
|
48
|
+
|
|
49
|
+
How it works, for anyone who wants to check rather than trust: the canvas mints a
|
|
50
|
+
single-use nonce bound to the browser that started the sign-in, the tab visits the identity
|
|
51
|
+
service and comes back with a short-lived ES256 assertion in the URL fragment - the one
|
|
52
|
+
part of a link no server ever receives - and the canvas verifies it against published JWKS
|
|
53
|
+
before issuing its own ordinary session. The assertion's audience is the canvas's exact
|
|
54
|
+
origin, port included, so one minted for one canvas is inert at another. The verifying half
|
|
55
|
+
is dependency-free and lives in this repo (`src/server/marver-id.ts`), which is why
|
|
56
|
+
pointing a canvas at a different issuer is a configuration change rather than a fork.
|
|
57
|
+
|
|
58
|
+
`MARVER_PUBLIC_ORIGIN` is required and the canvas will not start without it, in
|
|
59
|
+
development too. The audience cannot be inferred safely: nginx's documented
|
|
60
|
+
`proxy_pass http://localhost:PORT` rewrites `Host` and adds no `X-Forwarded-*`, so a
|
|
61
|
+
request from the internet is indistinguishable from a local one - and a canvas that
|
|
62
|
+
guessed would hand its caller a `http://localhost` audience and a cookie with no Secure
|
|
63
|
+
flag.
|
|
64
|
+
|
|
65
|
+
Sign-in fails closed: if the identity service is unreachable, existing sessions keep
|
|
66
|
+
working and new ones are refused. Nothing falls back to open.
|
|
67
|
+
|
|
68
|
+
**Managing people from the repo needs `MARVER_CLI_TOKEN`.** `marver comments invite`,
|
|
69
|
+
`revoke` and `sync` authenticate the CLI with a password, and an identity account has none -
|
|
70
|
+
so set `MARVER_CLI_TOKEN` on the canvas to a *generated* secret of 32 characters or more
|
|
71
|
+
(`openssl rand -hex 24`) and pass the same value back:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
MARVER_CLI_TOKEN='<that same value>' marver comments connect https://canvas.example.com
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
(`--token` works too, but a secret on the command line is visible to anything that can
|
|
78
|
+
list processes.)
|
|
79
|
+
|
|
80
|
+
It acts as whoever owns the canvas, so it does nothing until somebody has signed in and
|
|
81
|
+
claimed it. Generate it rather than choosing one: nothing rate-limits this credential and
|
|
82
|
+
nothing slows a guess down, so its entropy is the whole defence, and a length floor is the
|
|
83
|
+
only part of entropy a program can check. The canvas refuses to start on a value that is too
|
|
84
|
+
short, and on one containing characters an `Authorization` header cannot carry - hex rather
|
|
85
|
+
than base64, so that a canvas never boots holding a secret it would go on to reject.
|
|
86
|
+
|
|
87
|
+
`connect` trades that secret for an ordinary session and stores THAT in
|
|
88
|
+
`~/.marver/canvases/`, so neither the secret nor the session lands in your repo. **Rotate
|
|
89
|
+
`MARVER_CLI_TOKEN` to revoke it**: every session it minted stops working the moment the
|
|
90
|
+
variable changes, and sessions people hold in their browsers are untouched. That is the
|
|
91
|
+
lever rather than `comments revoke`, because the session acts as the owner and a canvas
|
|
92
|
+
refuses to remove its last owner.
|
|
93
|
+
|
|
94
|
+
It is an environment variable, and not a page that hands out tokens to whoever is signed in,
|
|
95
|
+
for a reason worth stating plainly: authored frames run same-origin in a canvas. That is
|
|
96
|
+
deliberate and documented, and it means frame JavaScript can read `mv_c` and ride the
|
|
97
|
+
viewer's session - so any browser-reachable way to mint a durable credential is a way for a
|
|
98
|
+
frame to mint one silently and carry it off, an owner's if an owner is the one looking. A
|
|
99
|
+
browser-approved device flow was built for exactly this job and pulled before release for
|
|
100
|
+
exactly that reason; requiring a real top-level navigation narrowed it and did not close it,
|
|
101
|
+
because `Sec-Fetch-Site: same-origin` proves where a request came from and never that a
|
|
102
|
+
person meant it. An environment variable is on the other side of that line: it is never
|
|
103
|
+
sent to a page, and reaching it already means reaching the deployment.
|
|
104
|
+
|
|
105
|
+
The cost is honest. It is a static secret that rotates by redeploying, and it acts as the
|
|
106
|
+
owner rather than as a person, so it is an operator's credential and belongs with your other
|
|
107
|
+
deployment secrets. Per-member CLI credentials still want the frame isolation this release
|
|
108
|
+
does not have.
|
|
109
|
+
|
|
110
|
+
### Changed
|
|
111
|
+
|
|
112
|
+
- **The gate refuses to be framed** - every gate response now carries
|
|
113
|
+
`Content-Security-Policy: frame-ancestors 'none'` and `X-Frame-Options: DENY`. A
|
|
114
|
+
credential form inside somebody else's page, under a transparent button, is the classic
|
|
115
|
+
clickjack, and `SameSite=Lax` does not help because a framed page on the same site still
|
|
116
|
+
carries its cookies. Called out because it is a behaviour change rather than an addition:
|
|
117
|
+
if you were embedding a password-gated canvas in an iframe, that stops working. Embedding
|
|
118
|
+
a canvas people have already signed into is unaffected - it is the gate itself that
|
|
119
|
+
refuses.
|
|
120
|
+
|
|
121
|
+
### Fixed
|
|
122
|
+
|
|
123
|
+
- **`Secure` on the collaboration session cookie no longer depends on a header your proxy may
|
|
124
|
+
not send.** A canvas decided whether it was on https by reading `X-Forwarded-Proto`, and
|
|
125
|
+
nginx's own documented `proxy_pass http://localhost:PORT` sets no `X-Forwarded-*` at all. A
|
|
126
|
+
canvas served over https behind that configuration saw no header, concluded "not secure",
|
|
127
|
+
and issued a thirty-day session cookie the browser would happily send over plain http. Where
|
|
128
|
+
`MARVER_PUBLIC_ORIGIN` is set it now decides - a deliberate statement by whoever deployed
|
|
129
|
+
the canvas, rather than a guess about a proxy that may not be speaking. The header remains
|
|
130
|
+
the fallback only where there is nothing better, which is development on loopback.
|
|
131
|
+
|
|
132
|
+
This release fixed the collaboration session cookie only. The password gate's own cookie
|
|
133
|
+
still guessed from the header - see 0.11.1.
|
|
134
|
+
|
|
135
|
+
- **The collaboration credential has moved out of your repository, because `marver dev` was
|
|
136
|
+
serving it.** It lived at `design/.local/collab.json`, and the dev server puts the repository
|
|
137
|
+
on the web so frames can import from it. Authored frames run same-origin, so any frame could
|
|
138
|
+
`fetch('/design/.local/collab.json')` and carry off a live session for your published canvas.
|
|
139
|
+
|
|
140
|
+
It now lives in `~/.marver/canvases/`, keyed by the project's path - one file per canvas per
|
|
141
|
+
machine, outside anything that is served. `connect` writes there, `dev` moves an older
|
|
142
|
+
credential out of the repo the first time it runs, and nothing needs doing by hand.
|
|
143
|
+
|
|
144
|
+
Guarding the old location was tried first and is the reason for the move: a deny rule matched
|
|
145
|
+
the path asked for rather than the file served, so a repository containing
|
|
146
|
+
`leak.json -> design/.local/collab.json` walked straight past it - and after that was fixed,
|
|
147
|
+
`public/` and Vite's derived `index.html` candidates each did the same. Enumerating a
|
|
148
|
+
bundler's resolution rules is not a thing anyone finishes. Those guards stayed as depth,
|
|
149
|
+
`design/.local` is still never served whatever route a request takes, and Vite's own default
|
|
150
|
+
deny list is no longer replaced by ours.
|
|
151
|
+
|
|
152
|
+
If you have ever run `marver dev` on a repository you did not write, treat that credential as
|
|
153
|
+
exposed: change `MARVER_CLI_TOKEN` on the canvas, which ends every session it minted, or on a
|
|
154
|
+
password-account canvas `marver comments revoke` and reconnect.
|
|
155
|
+
|
|
156
|
+
- **A gated canvas is no longer publicly cacheable in identity mode.** The `Cache-Control`
|
|
157
|
+
decision keyed off the password verifier alone, so an identity-gated canvas marked its
|
|
158
|
+
assets `public, immutable` - an invitation for a shared CDN to serve somebody's private
|
|
159
|
+
frames to anybody who asked. Both modes are now `private, no-store`.
|
|
160
|
+
|
|
161
|
+
- **A missing cosmetic file no longer falls through to the bundle.** Favicon and logo paths
|
|
162
|
+
are allowed past the gate on the promise that they are favicons and logos; when no such
|
|
163
|
+
file existed, the hash-routing fallback handed an unauthenticated caller `index.html`
|
|
164
|
+
instead. For those paths a miss is now a miss.
|
|
165
|
+
|
|
5
166
|
## 0.10.2 - 2026-08-24
|
|
6
167
|
|
|
7
168
|
### Fixed
|
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@ Frames appear on the canvas the moment the files land. That's the loop.
|
|
|
28
28
|
- **Everything hot-reloads.** The agent writes, you watch it land - live.
|
|
29
29
|
- **True viewports.** Each frame is a real iframe: drag its edge and your actual breakpoints fire.
|
|
30
30
|
- **Your agent answers on the canvas.** Tag `@marver` in a comment and it picks up the job, edits the real source, and replies in the thread - no wiring, on by default. See [Live Jam](#live-jam).
|
|
31
|
+
- **Feedback without a signup wall.** Publish the canvas, invite people by email, and they sign in as themselves - one free Marver account, Google or an emailed code, and it opens every canvas you ever share with them. Or keep it entirely self-hosted behind a shared password. See [Collaboration](#collaboration).
|
|
31
32
|
- **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.
|
|
32
33
|
|
|
33
34
|
## The canvas
|
|
@@ -40,9 +41,21 @@ Frames appear on the canvas the moment the files land. That's the loop.
|
|
|
40
41
|
|
|
41
42
|
## Collaboration
|
|
42
43
|
|
|
43
|
-
- **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
|
|
44
|
+
- **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 - either a Marver account they already have, or an invite-link account on that canvas alone. `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.
|
|
44
45
|
- **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.
|
|
45
|
-
- **Publishing.** `npx marver build` exports a static canvas (default-closed: `design/publish.json` names what ships); `npx marver serve` hosts it
|
|
46
|
+
- **Publishing.** `npx marver build` exports a static canvas (default-closed: `design/publish.json` names what ships, and whether each board is `read` or `comment`); `npx marver serve` hosts it. One deploy on Railway, Docker, or any static host - the [publishing guide](docs/publish.md) has the one-pagers.
|
|
47
|
+
|
|
48
|
+
- **Two ways to let people in.** They are alternatives, not layers - pick one per canvas.
|
|
49
|
+
|
|
50
|
+
**Marver Sign In** (new in 0.11). Set `MARVER_ID_ISSUER=https://id.marver.design` and reviewers sign in as themselves, with Google or a six-digit code emailed to them. One free Marver account opens *every* canvas gated this way, so the second board you share costs them nothing: no new signup, no new password, no link to keep. You invite an email address and they are in.
|
|
51
|
+
|
|
52
|
+
That is the whole difference, and it is the difference between "I'll look later" and a comment actually landing on the board. It also means their real name and face ride along, so a thread is from a person rather than from an address.
|
|
53
|
+
|
|
54
|
+
**A shared password** (`MARVER_PASSWORD`). Fully self-hosted, no account anywhere but your own volume, nothing about your reviewers leaves your infrastructure - and still fully supported, not a legacy path. The trade is that every canvas is an island: reviewers claim an invite link and pick a name and password *on that canvas*, and do it again for the next one. Fine for one board, a toll on the fifth.
|
|
55
|
+
|
|
56
|
+
Either way the canvas runs on your infrastructure and stores its own comments and members. With Marver Sign In the identity service only ever tells your canvas that a verified address matched an entry on your invite list - it never sees your frames, your files, or your comments. Rights stay yours: `design/publish.json` decides which boards are readable and which are commentable, and `marver comments invite`/`revoke` decides who is on the list.
|
|
57
|
+
|
|
58
|
+
*Coming next:* one home for your account - every canvas you have been invited to, every canvas you have shared, and the access each one carries, in a single list. Today the account page at `id.marver.design` shows the canvases you have approved and lets you revoke them.
|
|
46
59
|
|
|
47
60
|
## Live Jam
|
|
48
61
|
|
|
@@ -61,7 +74,7 @@ The same glow, driven from the terminal. When your agent takes a request, it cre
|
|
|
61
74
|
| `npx marver init` | Scaffold `design/` in this repo (safe to re-run; refreshes managed files) |
|
|
62
75
|
| `npx marver dev` / `canvas` | Start the local canvas - hot reload, comments, Live Jam armed (`--port`, default 5199) |
|
|
63
76
|
| `npx marver build` | Static export → `design/.dist`; what ships comes from `design/publish.json` (default-closed) |
|
|
64
|
-
| `npx marver serve` | Serve the export; `MARVER_PASSWORD` gates it, `MARVER_DATA_DIR` persists comments + accounts |
|
|
77
|
+
| `npx marver serve` | Serve the export; `MARVER_ID_ISSUER` or `MARVER_PASSWORD` gates it, `MARVER_DATA_DIR` persists comments + accounts |
|
|
65
78
|
| `npx marver comments …` | The agent's queue: `connect <url>` · `sync` · `list` · `reply` · `resolve` · `invite <email>` · `revoke <email>` |
|
|
66
79
|
| `npx marver work …` | Working glow from the terminal: `start <scene/frame …>` · `done … \| --all` · `list` |
|
|
67
80
|
|