@marver-design/marver 0.13.0 → 0.14.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 +73 -0
- package/README.md +41 -19
- package/dist/{build-BxGrHFT2.mjs → build-ByYafIhj.mjs} +37 -5
- package/dist/cli.mjs +19 -5
- package/dist/{daemon-BChkzDqQ.mjs → daemon-Bfucyf1o.mjs} +1 -1
- package/dist/{dev-DLwt3Brb.mjs → dev-yZMyQeUj.mjs} +4 -4
- package/dist/{init-BpitOqRQ.mjs → init-Dvaso7YO.mjs} +66 -2
- package/dist/{manifest-CS6krOTe.mjs → manifest-DvOmglFp.mjs} +7 -0
- package/dist/{marver-id-gate-B2uraTHS.mjs → marver-id-gate-D6By7XHj.mjs} +1 -1
- package/dist/{plugin-DNc4Jpae.mjs → plugin-BsmG5i2X.mjs} +21 -3
- package/dist/{serve-EjEqsiYa.mjs → serve-Bcwfpvhl.mjs} +3 -2
- package/dist/{shot-Cyv3GN79.mjs → shot-kbR_xzJH.mjs} +7 -1
- package/docs/live-jam.md +173 -0
- package/docs/publish.md +270 -0
- package/docs/sharing.md +333 -0
- package/docs/slides.md +134 -0
- package/package.json +3 -1
- package/src/client/const.ts +13 -0
- package/src/client/content/chart-engine.ts +33 -0
- package/src/client/content/chart.tsx +100 -0
- package/src/client/content/index.tsx +25 -4
- package/src/client/content/slide.tsx +238 -0
- package/src/client/content/video.tsx +126 -0
- package/src/client/shell/App.tsx +30 -12
- package/src/client/shell/LockedApp.tsx +7 -2
- package/src/client/shell/Play.tsx +138 -24
- package/src/client/shell/Toolbar.tsx +12 -3
- package/src/client/shell/canvas/FrameNode.tsx +5 -3
- package/src/client/shell/hash.ts +3 -1
- package/src/client/shell/icons.tsx +2 -0
- package/src/client/shell/play-order.ts +22 -0
- package/src/client/shell/store.ts +29 -8
- package/src/client/shell/styles.css +17 -27
- package/src/client/stage/main.tsx +54 -3
- package/src/shared/utm.ts +3 -2
- package/templates/AGENTS-embedded.md +1 -0
- package/templates/AGENTS-studio.md +1 -0
- package/templates/instructions/publish.md +7 -0
- package/templates/instructions/reference/deck-layouts.md +230 -0
- package/templates/instructions/reference/deck-story.md +110 -0
- package/templates/instructions/slides.md +398 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,79 @@
|
|
|
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.14.0 - 2026-09-02
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Slides.** A deck is a scene of `slide: true` frames: 1280×720 on the
|
|
10
|
+
canvas, the slideshow badge, and a real slides mode - the standard
|
|
11
|
+
prototype toolbar and walker, arrows / Space / click to advance, and
|
|
12
|
+
**the board as the sorter** (the deck plays in the board's reading
|
|
13
|
+
order; drag to reorder). Publish with `"type": "slides", "open":
|
|
14
|
+
"slides"` plus `transition` (`fade | none`) and `chrome` (`full |
|
|
15
|
+
minimal | none`); the Slide stage scales and centers itself to ANY
|
|
16
|
+
viewport with pure CSS (fill window, phones, a resized canvas node -
|
|
17
|
+
author px and Tailwind scale together); viewers land straight in the
|
|
18
|
+
deck and the URL survives refresh. The full guide is
|
|
19
|
+
[docs/slides.md](docs/slides.md).
|
|
20
|
+
- **The `Slide` root, `Chart`, and `Video` content primitives.** One root
|
|
21
|
+
owns the stage (asymmetric margins - 88px sides, 44px top and bottom - so
|
|
22
|
+
the middle band breathes; content box 1104×632), the theme tokens
|
|
23
|
+
(`--marver-slide-ground/-ink/-muted` with dark twins, `-accent`, `-font`,
|
|
24
|
+
`-tempo` timing both entrances and morphs), fixed type roles
|
|
25
|
+
(`sl-display` 160, `sl-stat` 88, `sl-assertion` 56, `sl-support` 30,
|
|
26
|
+
`sl-body` 24, `sl-caption` 18), and a development-only overflow marker
|
|
27
|
+
that catches content escaping the stage, or a flex/grid child outgrowing
|
|
28
|
+
its parent; `Chart`
|
|
29
|
+
is Apache ECharts on a fixed supported surface (series bar, line, pie, scatter, radar, gauge, heatmap, funnel, treemap, sunburst, sankey, boxplot; SVG, lazy
|
|
30
|
+
chunk, house-themed, animation stripped at rest); `Video` is
|
|
31
|
+
poster-first with a glass control strip in play mode. The motion
|
|
32
|
+
contract: a resting slide built from the shipped primitives serializes
|
|
33
|
+
to the canvas's lean path - no canvas/video elements, no running
|
|
34
|
+
animation - so a 40-slide deck pans like 40 statics (a slide that embeds
|
|
35
|
+
its own canvas, video, or shadow DOM stays live, as any frame would).
|
|
36
|
+
- **Motion as the diff.** `view-transition-name` morphs between adjacent
|
|
37
|
+
slides are the animation system; build steps are sibling frames; three
|
|
38
|
+
one-shot entrance presets (`data-animate`) run after each transition
|
|
39
|
+
settles. Reduced motion flattens both. Slides mode offers a 1280×720
|
|
40
|
+
Slide device preset beside your devices and fill; `marver shot` renders a
|
|
41
|
+
slide at 1280×720 over any authored viewport; `/content` exports
|
|
42
|
+
`SLIDE_W` / `SLIDE_H`; `--marver-slide-pad-x/-y` set the stage margins.
|
|
43
|
+
- **The slides doctrine.** `marver init` ships
|
|
44
|
+
`design/instructions/slides.md` - assertion-first argument, the type
|
|
45
|
+
scale, 19 layout recipes with budgets and morph anchors, the choreography
|
|
46
|
+
rules, "the space IS the design" (asymmetric margins, the 85% rule, one
|
|
47
|
+
px spacing scale), seven silhouettes chosen before any recipe with a
|
|
48
|
+
storyboard step and pacing rules, and a review gate that squints the
|
|
49
|
+
contact sheet - with two depth references behind it:
|
|
50
|
+
`reference/deck-story.md` (intake, answer-first structure, the evidence
|
|
51
|
+
check, audience calibration, the writing) and `reference/deck-layouts.md`
|
|
52
|
+
(the full layout atlas grouped by job, the composition grid, content
|
|
53
|
+
budgets, rebuilding an existing deck, chart craft). Plus your
|
|
54
|
+
project-owned `design/slides.md` - now opening with a fill-in **deck
|
|
55
|
+
look** template (tokens, type, mark, colour meaning, numbers, voice) the
|
|
56
|
+
agent drafts on the first deck - which overrides the doctrine and survives
|
|
57
|
+
every upgrade.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
- **The play brand pill is a powered-by link** (marver.design, standard UTM
|
|
62
|
+
set, `utm_content=play-brand`), sized to the pixel like the toolbar pill.
|
|
63
|
+
The keyboard-hint pill is gone (two conflicting rules stretched it into a
|
|
64
|
+
full-stage circle; the walker and toolbar already carry stepping and
|
|
65
|
+
comment). Name your canvas - `share: { name }` - before containerising it:
|
|
66
|
+
the shell's fallback is the root directory, which in a container is `app`.
|
|
67
|
+
- **A `slides` board plays only its `slide: true` frames.** 0.13.0 accepted
|
|
68
|
+
`type: "slides"` / `open: "slides"` and played them as `present`. From
|
|
69
|
+
0.14.0 the deck is the board's slide frames in reading order; non-slide
|
|
70
|
+
frames on such a board warn at build, and a board with none fails the
|
|
71
|
+
build with the fix in the message (a non-slides `type` plus
|
|
72
|
+
`open: "present"` to keep the old behaviour, or `slide: true` on the
|
|
73
|
+
frames). In the app an empty deck
|
|
74
|
+
falls back to the prototype with a toast. `transition` / `chrome` are
|
|
75
|
+
refused on non-slide boards. `marver init` appends `slides-inspiration/`
|
|
76
|
+
to an existing `design/.gitignore`. `docs/` now ships in the package.
|
|
77
|
+
|
|
5
78
|
## 0.13.0 - 2026-09-01
|
|
6
79
|
|
|
7
80
|
### Added
|
package/README.md
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
|
|
7
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
8
|
|
|
9
|
-
|
|
9
|
+
Screens, prototypes, specs, and now slide decks - all real code, all on one canvas, all shareable with people who sign in as themselves.
|
|
10
|
+
|
|
11
|
+
[marver.design](https://marver.design) · [Slides](docs/slides.md) · [Live Jam](docs/live-jam.md) · [Deploying a canvas](docs/publish.md) · [Sharing](docs/sharing.md) · [Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md) · [Issues](https://github.com/TNEP4/marver/issues)
|
|
10
12
|
|
|
11
13
|
## Quickstart
|
|
12
14
|
|
|
@@ -20,42 +22,57 @@ Then, to your agent:
|
|
|
20
22
|
|
|
21
23
|
> Read design/AGENTS.md. Build an onboarding scene - welcome, form, done - mobile-first, using our components.
|
|
22
24
|
|
|
25
|
+
or
|
|
26
|
+
|
|
27
|
+
> Read design/AGENTS.md. Build me a 12-slide pitch deck from this brief, in our brand.
|
|
28
|
+
|
|
23
29
|
Frames appear on the canvas the moment the files land. That's the loop.
|
|
24
30
|
|
|
25
31
|
## Why marver
|
|
26
32
|
|
|
27
33
|
- **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.
|
|
34
|
+
- **Decks are real code too.** A slide is a frame with `slide: true`. One `Slide` primitive, your own markup inside it, a doctrine that teaches the agent to argue rather than decorate - and a stage that scales itself to any screen without the agent writing a single breakpoint. See [Slides](#slides).
|
|
28
35
|
- **Everything hot-reloads.** The agent writes, you watch it land - live.
|
|
29
36
|
- **True viewports.** Each frame is a real iframe: drag its edge and your actual breakpoints fire.
|
|
30
37
|
- **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
|
|
32
|
-
- **No AI inside.** The designer is the coding agent you already run and pay for. `init` generates
|
|
38
|
+
- **Feedback without a signup wall.** Publish the canvas and invite people by email; they sign in as themselves with one free Marver account that opens every canvas anyone ever shares with them. @-mention a reviewer, they get the mail; reply, and the thread's people hear about it. Or keep it entirely self-hosted behind a shared password. See [Collaboration](#collaboration).
|
|
39
|
+
- **No AI inside.** The designer is the coding agent you already run and pay for. `init` generates `design/AGENTS.md` and a `design/instructions/` method - configure, discover, wireframe, brand, build, review, slides, publish - that teaches it the whole workflow.
|
|
33
40
|
|
|
34
41
|
## The canvas
|
|
35
42
|
|
|
36
43
|
- **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. Right-click any board, scene, or frame in the sidebar to copy its path - the exact string to paste to your agent - and rename or drag-reorder boards from there too.
|
|
37
44
|
- **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`.
|
|
38
45
|
- **Prototype links.** `data-goto="scene/frame"` on any element links frames into a walkable prototype - across boards, too.
|
|
39
|
-
- **
|
|
40
|
-
- **Content frames.** Specs, Mermaid diagrams,
|
|
46
|
+
- **Five ways to view a board.** The canvas (frames on a plane), the board (the same, tidy), **present** (`p`: a full-screen clickable walkthrough - `data-goto` navigates, arrows step, `[` / `]` cycle variants, laser, comments, theme and device pickers in the toolbar), **focus** (one frame as a document - the reading preset for specs), and **slides** (a deck). A published board names its landing view; a frame deep link opens straight into it.
|
|
47
|
+
- **Content frames.** Specs, Mermaid diagrams, mood boards, and slides live on the same canvas as the screens - import `Doc`, `Md`, `Diagram`, `Img`, `Slide`, `Chart`, `Video` 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.
|
|
41
48
|
|
|
42
|
-
##
|
|
49
|
+
## Slides
|
|
43
50
|
|
|
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.
|
|
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.
|
|
51
|
+
A deck is a scene of `slide: true` frames. On the canvas they are 1280×720 frames like any other - comment on them, laser them, fork variants, drag them to reorder the deck (the board's reading order is the play order). Press `p` on a slides board and you get slides mode: the 16:9 stage, arrows / Space / click to advance, `d` cycles the theme, devices including fill window, morphs between slides where the agent named the same element twice.
|
|
47
52
|
|
|
48
|
-
|
|
53
|
+
What makes it light on the agent side, and good on every screen:
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
- **One primitive, no component library.** `Slide` owns the stage, the margins, six fixed type roles, your theme's tokens, and the motion contract. Everything inside is your project's own markup and components - a slide is built the way a screen is built.
|
|
56
|
+
- **The fit is pure CSS.** The agent authors at exactly 1280×720; the root scales and centers itself to a phone, a laptop, a projector, or a resized canvas node. No `vw`, no media queries, no breakpoints - the composition you approved is the composition everyone sees, and a dev-only overflow marker outlines a slide whose content escapes the stage, or whose flex/grid child outgrows its parent.
|
|
57
|
+
- **Still at rest.** Charts are final-state SVG in a lazy chunk, videos are posters, and every CSS animation under `Slide` is suspended until slides mode plays - so a 40-slide deck of the shipped primitives pans like 40 statics on the canvas. (Your own `<canvas>`, `<video>`, or JS-driven motion stays live, as in any frame.)
|
|
58
|
+
- **A doctrine, not a template.** `init` ships `design/instructions/slides.md`: assertion-first argument, the space (three bands, the 85% rule, one spacing scale), seven silhouettes chosen before any recipe so a deck never reads as one repeated shape, 19 recipes with budgets, choreography rules, and a review gate - plus two depth references and your own project-owned `design/slides.md` with a fill-in **deck look** the agent drafts from your brand.
|
|
51
59
|
|
|
52
|
-
|
|
60
|
+
Publish a deck with `{ "boards": { "pitch": { "max": "read", "type": "slides", "open": "slides", "lock": true } } }` in `design/publish.json` and the link opens straight into the deck, read-only, with no canvas behind it. The [slides guide](docs/slides.md) has the rest - morphs, build steps, `Chart` and `Video`, the theme tokens.
|
|
53
61
|
|
|
54
|
-
|
|
62
|
+
## Collaboration
|
|
55
63
|
|
|
56
|
-
|
|
64
|
+
- **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). Type `@` to mention someone who can already see the canvas; they get a mail with the comment and a link that lands on the thread, and the mention pulses its pin in their canvas with an in-app notification. A reply mails the thread's other participants, throttled per person and thread; anyone can mute a canvas's mail. `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.
|
|
65
|
+
- **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. (On a published canvas, laser needs `"reveal": { "source": true }` in `publish.json` - source paths go opaque by default.)
|
|
66
|
+
- **Publishing.** `npx marver build` exports a static canvas (default-closed: `design/publish.json` names what ships, each board's ceiling - `read` or `comment` - and, since 0.12, its **type** and **landing view**: `{ "max", "type": "doc" | "slides" | "design" | "sketch" | "refs" | "mix", "open": "canvas" | "board" | "present" | "focus" | "slides", "lock": true }`); `npx marver serve` hosts it. One deploy on Railway, Docker, or any static host - the [publishing guide](docs/publish.md) has the one-pagers.
|
|
67
|
+
- **Sharing, person by person.** On a canvas with a persistent volume, `marver share add sam@acme.com --role comment` (or the browser's share dialog) grants one person access, `@acme.com` grants a domain, `general private|password|public` sets the floor, and a refused visitor can ask to get in - approve from the terminal. One resolver answers "what can this person do here", and `marver share explain <email>` shows its reasoning. The [sharing guide](docs/sharing.md) has the whole surface.
|
|
57
68
|
|
|
58
|
-
|
|
69
|
+
- **Two ways to let people in.** They are alternatives, not layers - pick one per canvas.
|
|
70
|
+
|
|
71
|
+
**Marver Sign In.** 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. Their real name and face ride along, so a thread is from a person rather than from an address. And **[app.marver.design](https://app.marver.design)** is their front door: every canvas they can reach, in one signed-in list, each row lit by a summary the canvas itself signs - the front door holds no roster and makes no access decision.
|
|
72
|
+
|
|
73
|
+
**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. 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.
|
|
74
|
+
|
|
75
|
+
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 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 the roster decides who is on the list.
|
|
59
76
|
|
|
60
77
|
## Live Jam
|
|
61
78
|
|
|
@@ -65,7 +82,7 @@ The trust boundary is hard: only comments written on the owner's machine trigger
|
|
|
65
82
|
|
|
66
83
|
## Working state
|
|
67
84
|
|
|
68
|
-
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.
|
|
85
|
+
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. And `npx marver shot <scene/frame>` renders one frame headless to a PNG, so the agent can look at what it built before it says it is done.
|
|
69
86
|
|
|
70
87
|
## Commands
|
|
71
88
|
|
|
@@ -75,8 +92,10 @@ The same glow, driven from the terminal. When your agent takes a request, it cre
|
|
|
75
92
|
| `npx marver dev` / `canvas` | Start the local canvas - hot reload, comments, Live Jam armed (`--port`, default 5199) |
|
|
76
93
|
| `npx marver build` | Static export → `design/.dist`; what ships comes from `design/publish.json` (default-closed) |
|
|
77
94
|
| `npx marver serve` | Serve the export; `MARVER_ID_ISSUER` or `MARVER_PASSWORD` gates it, `MARVER_DATA_DIR` persists comments + accounts |
|
|
95
|
+
| `npx marver share …` | The roster (owner): `add <who> [--role]` · `remove` · `block` / `unblock` · `general <mode>` · `list` · `requests` · `explain <who>` · `who` |
|
|
78
96
|
| `npx marver comments …` | The agent's queue: `connect <url>` · `sync` · `list` · `reply` · `resolve` · `invite <email>` · `revoke <email>` |
|
|
79
97
|
| `npx marver work …` | Working glow from the terminal: `start <scene/frame …>` · `done … \| --all` · `list` |
|
|
98
|
+
| `npx marver shot <frame>` | Render one frame headless and print the PNG path (needs `dev` running) |
|
|
80
99
|
|
|
81
100
|
## Shortcuts
|
|
82
101
|
|
|
@@ -91,17 +110,20 @@ The same glow, driven from the terminal. When your agent takes a request, it cre
|
|
|
91
110
|
|
|
92
111
|
**Selection** - click selects · shift+click (canvas or sidebar) builds a multi-selection · `⌘A` selects every frame on the board · `⇧P` copies the selected frames' paths (board, frame, and file) · double-click enters interact mode (`esc` or click outside leaves) · drag the title bar to move, edges to resize (widths snap to devices).
|
|
93
112
|
|
|
94
|
-
**Modes** - `c` comment mode · `l` laser mode · `⇧C` hide/show comment pins · `⇧L` laser comment (spotlight a thread's element) · `p` play
|
|
113
|
+
**Modes** - `c` comment mode · `l` laser mode · `⇧C` hide/show comment pins · `⇧L` laser comment (spotlight a thread's element) · `p` play (present, or slides on a slides board) · `h` hide all chrome.
|
|
114
|
+
|
|
115
|
+
**In play** - `←` `→` step (Space and click advance a deck) · `[` `]` cycle variants · `d` cycles the theme · digits pick a device, the digit after your last one fills the window · `esc` exits, unless the board is locked to that view.
|
|
95
116
|
|
|
96
117
|
## Notes
|
|
97
118
|
|
|
98
119
|
- **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.
|
|
99
|
-
- **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.
|
|
120
|
+
- **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), and warns when your `design/instructions/` predate the installed package. 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.
|
|
121
|
+
- **Name your canvas**: `share: { name: "Your App" }` in `design/config.ts` titles the gate, labels the brand pill, and tags every powered-by link. Unset, the gate falls back to your `package.json` name and the canvas shell to the directory name - `app` inside most containers.
|
|
100
122
|
- **Uninstall**: delete `design/`, remove the dependency. (If `init` patched your tsconfig `exclude`, revert that one line.)
|
|
101
123
|
|
|
102
124
|
## Status
|
|
103
125
|
|
|
104
|
-
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.
|
|
126
|
+
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. Sharing controls who gets in and who may comment; per-board read privacy (a board some people can see and others cannot) is next. If you hit a wall, an issue with your board shape and frame count genuinely helps.
|
|
105
127
|
|
|
106
128
|
## Contributing
|
|
107
129
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as ROUTE, n as NAME } from "./cli.mjs";
|
|
2
|
-
import { l as detectHost, r as scanFrames, s as loadConfig } from "./manifest-
|
|
3
|
-
import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-
|
|
2
|
+
import { l as detectHost, r as scanFrames, s as loadConfig } from "./manifest-DvOmglFp.mjs";
|
|
3
|
+
import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-BsmG5i2X.mjs";
|
|
4
4
|
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { basename, dirname, join, sep } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -36,9 +36,19 @@ function scanAssetRefs(src, moduleId) {
|
|
|
36
36
|
templates.push(t);
|
|
37
37
|
return "``";
|
|
38
38
|
}).replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/.*$/gm, "$1");
|
|
39
|
-
if (
|
|
39
|
+
for (const tag of ["Img", "Video"]) if (new RegExp(`<${tag}\\b[^>]*\\b(?:src|poster)\\s*=\\s*\\{`).test(stripped)) throw new Error(`${moduleId}: <${tag}> has a computed src/poster - published builds copy only statically referenced assets. Use a string literal.`);
|
|
40
40
|
const out = [];
|
|
41
41
|
for (const m of stripped.matchAll(/<Img\b[^>]*\bsrc\s*=\s*["']([^"']+)["']/g)) out.push(m[1]);
|
|
42
|
+
for (const m of stripped.matchAll(/<Video\b[^>]*>|<Video\b[^>]*\/>/g)) {
|
|
43
|
+
const tag = m[0];
|
|
44
|
+
const srcM = /\bsrc\s*=\s*["']([^"']+)["']/.exec(tag);
|
|
45
|
+
const posterM = /\bposter\s*=\s*["']([^"']+)["']/.exec(tag);
|
|
46
|
+
if (srcM) {
|
|
47
|
+
if (!/^https:\/\//.test(srcM[1]) && !posterM) throw new Error(`${moduleId}: <Video src="${srcM[1]}"> is a local file without a poster - the poster is the slide at rest. Add poster="...".`);
|
|
48
|
+
out.push(srcM[1]);
|
|
49
|
+
}
|
|
50
|
+
if (posterM) out.push(posterM[1]);
|
|
51
|
+
}
|
|
42
52
|
for (const tpl of templates) for (const m of tpl.matchAll(/!\[[^\]]*\]\(([^)\s"']+)\)/g)) out.push(m[1]);
|
|
43
53
|
return out;
|
|
44
54
|
}
|
|
@@ -59,6 +69,12 @@ const VIEW_MODES = [
|
|
|
59
69
|
"focus",
|
|
60
70
|
"slides"
|
|
61
71
|
];
|
|
72
|
+
const DECK_TRANSITIONS = ["fade", "none"];
|
|
73
|
+
const DECK_CHROME = [
|
|
74
|
+
"full",
|
|
75
|
+
"minimal",
|
|
76
|
+
"none"
|
|
77
|
+
];
|
|
62
78
|
/** Publishing is default-CLOSED - no policy and no explicit flag = no build.
|
|
63
79
|
* Boards absent from the result do not ship. */
|
|
64
80
|
function resolvePolicy(root, allBoards, boardsFlag, allBoardsFlag) {
|
|
@@ -108,12 +124,17 @@ function resolvePolicy(root, allBoards, boardsFlag, allBoardsFlag) {
|
|
|
108
124
|
const type = p.type ?? "mix";
|
|
109
125
|
if (!ARTIFACT_TYPES.includes(type)) throw new Error(`design/publish.json: board "${n}" has type "${type}" - use ${ARTIFACT_TYPES.join(" | ")}`);
|
|
110
126
|
if (p.open !== void 0 && !VIEW_MODES.includes(p.open)) throw new Error(`design/publish.json: board "${n}" has open "${p.open}" - use ${VIEW_MODES.join(" | ")}`);
|
|
127
|
+
if (p.transition !== void 0 && !DECK_TRANSITIONS.includes(p.transition)) throw new Error(`design/publish.json: board "${n}" has transition "${p.transition}" - use ${DECK_TRANSITIONS.join(" | ")}`);
|
|
128
|
+
if (p.chrome !== void 0 && !DECK_CHROME.includes(p.chrome)) throw new Error(`design/publish.json: board "${n}" has chrome "${p.chrome}" - use ${DECK_CHROME.join(" | ")}`);
|
|
129
|
+
if ((p.transition !== void 0 || p.chrome !== void 0) && type !== "slides" && p.open !== "slides") throw new Error(`design/publish.json: board "${n}" sets transition/chrome but is not a slides board - set "type": "slides" or "open": "slides", or drop them`);
|
|
111
130
|
if (p.lock && p.open === void 0) throw new Error(`design/publish.json: board "${n}" sets "lock" without "open" - name the mode the lock freezes`);
|
|
112
131
|
out[n] = {
|
|
113
132
|
max: p.max,
|
|
114
133
|
type,
|
|
115
134
|
...p.open ? { open: p.open } : {},
|
|
116
|
-
...p.lock ? { lock: true } : {}
|
|
135
|
+
...p.lock ? { lock: true } : {},
|
|
136
|
+
...p.transition ? { transition: p.transition } : {},
|
|
137
|
+
...p.chrome ? { chrome: p.chrome } : {}
|
|
117
138
|
};
|
|
118
139
|
}
|
|
119
140
|
if (policy.reveal !== void 0) {
|
|
@@ -277,12 +298,23 @@ async function buildSite(root, boardsFlag, allBoardsFlag, embedSeeds) {
|
|
|
277
298
|
frames: pubFrames.filter((f) => f.scene === name).length
|
|
278
299
|
}))
|
|
279
300
|
};
|
|
301
|
+
for (const n of publishedNames) {
|
|
302
|
+
const pb = policy.boards[n];
|
|
303
|
+
if (pb?.type !== "slides" && pb?.open !== "slides") continue;
|
|
304
|
+
const ids = new Set((boards[n]?.nodes ?? []).map((x) => x.frame));
|
|
305
|
+
const on = pubManifest.frames.filter((f) => ids.has(f.id));
|
|
306
|
+
const off = on.filter((f) => !(f.kind === "tsx" && f.slide));
|
|
307
|
+
if (off.length === on.length) throw new Error(`design/publish.json: board "${n}" plays as slides but none of its ${on.length} frame(s) carry \`slide: true\` - add it to the frames' meta, or set "type"/"open" to "present" (0.13.0 let slides alias present; 0.14.0 plays only slide frames)`);
|
|
308
|
+
if (off.length) console.warn(` warning: board "${n}" plays as slides but ${off.length} frame(s) are not slides (${off.slice(0, 4).map((f) => f.id).join(", ")}${off.length > 4 ? ", …" : ""}) - they will not play`);
|
|
309
|
+
}
|
|
280
310
|
const boardsMeta = Object.fromEntries(publishedNames.filter((n) => policy.boards[n]).map((n) => {
|
|
281
311
|
const p = policy.boards[n];
|
|
282
312
|
return [n, {
|
|
283
313
|
type: p.type,
|
|
284
314
|
...p.open ? { open: p.open } : {},
|
|
285
|
-
...p.lock ? { lock: true } : {}
|
|
315
|
+
...p.lock ? { lock: true } : {},
|
|
316
|
+
...p.transition ? { transition: p.transition } : {},
|
|
317
|
+
...p.chrome ? { chrome: p.chrome } : {}
|
|
286
318
|
}];
|
|
287
319
|
}));
|
|
288
320
|
const lockedShell = publishedNames.length > 0 && publishedNames.every((n) => {
|
package/dist/cli.mjs
CHANGED
|
@@ -16,6 +16,20 @@ const CONTENT_WIDTH = {
|
|
|
16
16
|
document: 760,
|
|
17
17
|
wide: 1280
|
|
18
18
|
};
|
|
19
|
+
/** The slide stage (v1.5): a runtime-reserved intrinsic, deliberately NOT a
|
|
20
|
+
* config viewport - no migration for existing projects, no deck device in
|
|
21
|
+
* sweeps. Dependency-neutral so server (shot) and shell (store) share it. */
|
|
22
|
+
const SLIDE_INTRINSIC = {
|
|
23
|
+
width: 1280,
|
|
24
|
+
height: 720
|
|
25
|
+
};
|
|
26
|
+
/** The one DEFAULT sizing rule for slide frames, shared by canvas and shot:
|
|
27
|
+
* `slide: true` sets the intrinsic 1280×720 stage, over any authored viewport.
|
|
28
|
+
* Board nodes stay resizable (the Slide root scales into whatever box it is
|
|
29
|
+
* given); this governs defaults, shots, and stage coordinates. */
|
|
30
|
+
function slideSize(frame) {
|
|
31
|
+
return frame.slide ? SLIDE_INTRINSIC : null;
|
|
32
|
+
}
|
|
19
33
|
//#endregion
|
|
20
34
|
//#region src/cli/index.ts
|
|
21
35
|
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
@@ -39,14 +53,14 @@ function version() {
|
|
|
39
53
|
}
|
|
40
54
|
const cli = cac(NAME);
|
|
41
55
|
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-
|
|
56
|
+
const { init } = await import("./init-Dvaso7YO.mjs");
|
|
43
57
|
init(resolve(opts.root), {
|
|
44
58
|
mode: opts.mode === "embedded" ? "embedded" : "studio",
|
|
45
59
|
demo: opts.demo !== false
|
|
46
60
|
});
|
|
47
61
|
});
|
|
48
62
|
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-
|
|
63
|
+
const { dev } = await import("./dev-yZMyQeUj.mjs");
|
|
50
64
|
let port;
|
|
51
65
|
if (opts.port !== void 0) {
|
|
52
66
|
const n = Number(opts.port);
|
|
@@ -56,7 +70,7 @@ for (const [name, desc] of [["dev", "Start the local canvas (everything on: hot
|
|
|
56
70
|
await dev(resolve(opts.root), port);
|
|
57
71
|
});
|
|
58
72
|
cli.command("build", "Static export → design/.dist (what ships comes from design/publish.json - publishing is default-closed)").option("--boards <names>", "Publish only these boards (comma-separated); overrides the publish policy").option("--all-boards", "Publish every board - the loud override for the default-closed policy").option("--embed-seeds", "Copy comment history INTO the web root (identifying - every event carries its author's email)").option("--root <dir>", "Host repo root", { default: "." }).action(async (opts) => {
|
|
59
|
-
const { buildSite } = await import("./build-
|
|
73
|
+
const { buildSite } = await import("./build-ByYafIhj.mjs");
|
|
60
74
|
try {
|
|
61
75
|
const boards = opts.boards === void 0 ? void 0 : typeof opts.boards === "string" ? opts.boards : "";
|
|
62
76
|
await buildSite(resolve(opts.root), boards, opts.allBoards === true, opts.embedSeeds === true);
|
|
@@ -66,7 +80,7 @@ cli.command("build", "Static export → design/.dist (what ships comes from desi
|
|
|
66
80
|
}
|
|
67
81
|
});
|
|
68
82
|
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-
|
|
83
|
+
const { serve } = await import("./serve-Bcwfpvhl.mjs");
|
|
70
84
|
let port;
|
|
71
85
|
if (opts.port !== void 0) {
|
|
72
86
|
const n = Number(opts.port);
|
|
@@ -114,4 +128,4 @@ cli.help();
|
|
|
114
128
|
cli.version(version());
|
|
115
129
|
cli.parse();
|
|
116
130
|
//#endregion
|
|
117
|
-
export { ROUTE as i, NAME as n, PKG as r, CONTENT_WIDTH as t };
|
|
131
|
+
export { slideSize as a, ROUTE as i, NAME as n, PKG as r, CONTENT_WIDTH as t };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as replay } from "./events-B3LBn74P.mjs";
|
|
2
2
|
import { i as readLog, r as listBoards, t as appendEvents } from "./comments-DZyobpxG.mjs";
|
|
3
3
|
import { n as localProfile } from "./profile-BjAPAJSb.mjs";
|
|
4
|
-
import { i as toFrameId } from "./manifest-
|
|
4
|
+
import { i as toFrameId } from "./manifest-DvOmglFp.mjs";
|
|
5
5
|
import { workActivity } from "./work-CLrmY-vQ.mjs";
|
|
6
6
|
import { r as deviceId, t as has } from "./ledger-Bu0BjqIe.mjs";
|
|
7
7
|
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync, writeSync } from "node:fs";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as NAME, r as PKG } from "./cli.mjs";
|
|
2
|
-
import { l as detectHost, s as loadConfig } from "./manifest-
|
|
3
|
-
import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-
|
|
2
|
+
import { l as detectHost, s as loadConfig } from "./manifest-DvOmglFp.mjs";
|
|
3
|
+
import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-BsmG5i2X.mjs";
|
|
4
4
|
import { realpathSync } from "node:fs";
|
|
5
5
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -247,7 +247,7 @@ async function dev(root, portFlag) {
|
|
|
247
247
|
console.log(`\n ${NAME} · ${projectName} → http://localhost:${port}/\n`);
|
|
248
248
|
try {
|
|
249
249
|
const { staleManagedInstructions } = await import("./managed-HwHVNI3h.mjs");
|
|
250
|
-
const { installedVersion } = await import("./plugin-
|
|
250
|
+
const { installedVersion } = await import("./plugin-BsmG5i2X.mjs").then((n) => n.i);
|
|
251
251
|
const stale = staleManagedInstructions(root);
|
|
252
252
|
if (stale.length) {
|
|
253
253
|
const shown = stale.slice(0, 4).join(", ") + (stale.length > 4 ? `, +${stale.length - 4} more` : "");
|
|
@@ -285,7 +285,7 @@ async function dev(root, portFlag) {
|
|
|
285
285
|
});
|
|
286
286
|
}
|
|
287
287
|
if (config.jam) {
|
|
288
|
-
const { startJam } = await import("./daemon-
|
|
288
|
+
const { startJam } = await import("./daemon-Bfucyf1o.mjs");
|
|
289
289
|
const jam = startJam(root, config.jam, (m) => console.log(m), (board) => server.ws.send("sh:jam-comment", { board }));
|
|
290
290
|
if (jam) {
|
|
291
291
|
const close = server.close.bind(server);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as NAME } from "./cli.mjs";
|
|
2
|
-
import { a as writeManifest, c as detectAgent, l as detectHost, o as DEFAULTS, r as scanFrames, u as readJson } from "./manifest-
|
|
2
|
+
import { a as writeManifest, c as detectAgent, l as detectHost, o as DEFAULTS, r as scanFrames, u as readJson } from "./manifest-DvOmglFp.mjs";
|
|
3
3
|
import { MANAGED_PREFIX, enumerateInstructionTemplates, hashBody, managedFile, pkgDir } from "./managed-HwHVNI3h.mjs";
|
|
4
4
|
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { dirname, join, relative } from "node:path";
|
|
@@ -143,7 +143,71 @@ function init(root, opts) {
|
|
|
143
143
|
refresh("providers.tsx", [providersTemplate(null, null), providersTemplate(null, host.toaster, host.routerPkg)], providersTemplate(host.router, host.toaster, host.routerPkg));
|
|
144
144
|
refresh("tsconfig.json", [STANDALONE_TSCONFIG], tsconfigNow());
|
|
145
145
|
}
|
|
146
|
-
write(".gitignore", ".local/\n.dist/\n.dist-seeds/\n");
|
|
146
|
+
write(".gitignore", ".local/\n.dist/\n.dist-seeds/\nslides-inspiration/\n");
|
|
147
|
+
{
|
|
148
|
+
const gi = join(design, ".gitignore");
|
|
149
|
+
const cur = readFileSync(gi, "utf8");
|
|
150
|
+
if (!cur.split("\n").some((l) => l.trim() === "slides-inspiration/" || l.trim() === "slides-inspiration")) {
|
|
151
|
+
writeFileSync(gi, cur + (cur.endsWith("\n") || cur === "" ? "" : "\n") + "slides-inspiration/\n");
|
|
152
|
+
created.push("design/.gitignore (+ slides-inspiration/)");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
write("slides.md", [
|
|
156
|
+
"# This project's slide layouts and house rules",
|
|
157
|
+
"",
|
|
158
|
+
"The agent reads BOTH the shipped doctrine (instructions/slides.md) and this",
|
|
159
|
+
"file - and this file WINS where they disagree. Add your own layouts, house",
|
|
160
|
+
"rules, and banned moves here; it is yours and marver never touches it.",
|
|
161
|
+
"",
|
|
162
|
+
"To grow it from examples: drop decks (PPTX, PDF, screenshots) into",
|
|
163
|
+
"design/slides-inspiration/ and ask the agent to \"study my inspiration\" -",
|
|
164
|
+
"it will propose additions here for your review.",
|
|
165
|
+
"",
|
|
166
|
+
"## The deck look",
|
|
167
|
+
"",
|
|
168
|
+
"How a deck wears this brand. The agent drafts it from DESIGN.md and",
|
|
169
|
+
"theme.css on the first deck (a reviewed edit, building on provisionally",
|
|
170
|
+
"with the theme's tokens); anything it cannot settle stays `TBD` for you.",
|
|
171
|
+
"Each line shows the shape with an example - replace the example, keep the",
|
|
172
|
+
"shape.",
|
|
173
|
+
"",
|
|
174
|
+
"- **Tokens** (`--marver-slide-*` in theme.css, read by the Slide root):",
|
|
175
|
+
" ground / ink / muted, each with a `-dark` twin; accent, one value for",
|
|
176
|
+
" both themes. e.g. ground = the page background, ink = the heading",
|
|
177
|
+
" colour, accent = the primary button.",
|
|
178
|
+
"- **Type** (`--marver-slide-font`): one family, the theme's; the type",
|
|
179
|
+
" roles carry their weights. e.g. Inter.",
|
|
180
|
+
"- **The mark**: which asset, where, how big. e.g. wordmark SVG, bottom-left",
|
|
181
|
+
" of the cover and the closing only, 120px wide - never on content slides.",
|
|
182
|
+
"- **Colour meaning** (charts, cards, badges): hue = category, fixed across",
|
|
183
|
+
" the deck. e.g. accent = us, slate = competitors, muted = the baseline;",
|
|
184
|
+
" green / amber / red reserved for status.",
|
|
185
|
+
"- **Backgrounds allowed**: e.g. the flat ground; a theme gradient on covers",
|
|
186
|
+
" and sections only; no photos behind text without a scrim.",
|
|
187
|
+
"- **Imagery**: e.g. product screenshots on a device-less frame, real people,",
|
|
188
|
+
" no stock; illustrations in the product's line style only. Evidentiary",
|
|
189
|
+
" images (a report fragment, a log line, a reasoning trace, a real",
|
|
190
|
+
" screen) are evidence objects, not decoration - hero material when the",
|
|
191
|
+
" message warrants it.",
|
|
192
|
+
"- **Tempo** (`--marver-slide-tempo`): e.g. 350ms - one value, every deck.",
|
|
193
|
+
"- **Numbers**: e.g. $ and k / M (lowercase k), fiscal years as FY26,",
|
|
194
|
+
" negatives in brackets, one decimal on percentages.",
|
|
195
|
+
"- **Voice**: three words the deck sounds like, and the words it never",
|
|
196
|
+
" uses. e.g. direct, warm, specific; never \"leverage\", \"seamless\", \"journey\".",
|
|
197
|
+
"- **Terminology**: user-facing word → never-shown internal word.",
|
|
198
|
+
" e.g. \"Comments\" → \"Enrichment\".",
|
|
199
|
+
"- **End card**: yes / no. e.g. yes - the mark on the ground, no text; the",
|
|
200
|
+
" ask lives on the slide before it.",
|
|
201
|
+
"",
|
|
202
|
+
"## Layouts",
|
|
203
|
+
"",
|
|
204
|
+
"(none yet - the shipped recipe list and atlas apply)",
|
|
205
|
+
"",
|
|
206
|
+
"## House rules",
|
|
207
|
+
"",
|
|
208
|
+
"(none yet)",
|
|
209
|
+
""
|
|
210
|
+
].join("\n"));
|
|
147
211
|
write(".gitattributes", "comments/*.jsonl merge=union\n");
|
|
148
212
|
write("scenes/_layout.tsx", readFileSync(join(templates, "root-layout.tsx"), "utf8"));
|
|
149
213
|
if (!existsSync(join(design, "boards"))) {
|
|
@@ -382,6 +382,10 @@ function extractMeta(src) {
|
|
|
382
382
|
const r = new RegExp(`(?:^|[{,])\\s*${key}\\s*:\\s*(['"\`])([^'"\`]*)\\1`).exec(body);
|
|
383
383
|
return r ? r[2] : void 0;
|
|
384
384
|
};
|
|
385
|
+
const pickBool = (key) => {
|
|
386
|
+
const r = new RegExp(`(?:^|[{,])\\s*${key}\\s*:\\s*(true|false)\\b`).exec(body);
|
|
387
|
+
return r ? r[1] === "true" : void 0;
|
|
388
|
+
};
|
|
385
389
|
const out = {};
|
|
386
390
|
const title = pick("title");
|
|
387
391
|
if (title) out.title = title;
|
|
@@ -395,6 +399,8 @@ function extractMeta(src) {
|
|
|
395
399
|
if (variant) out.variant = variant;
|
|
396
400
|
const intent = pick("intent");
|
|
397
401
|
if (intent) out.intent = intent;
|
|
402
|
+
const slide = pickBool("slide");
|
|
403
|
+
if (slide !== void 0) out.slide = slide;
|
|
398
404
|
return out;
|
|
399
405
|
}
|
|
400
406
|
/** Content-frame detection: LEXICAL by convention - the frame file
|
|
@@ -484,6 +490,7 @@ function scanFrames(root) {
|
|
|
484
490
|
if (meta.theme) entry.theme = meta.theme;
|
|
485
491
|
if (meta.of) entry.variantGroup = meta.of;
|
|
486
492
|
if (meta.variant) entry.variant = meta.variant;
|
|
493
|
+
if (meta.slide) entry.slide = true;
|
|
487
494
|
const content = contentScan(src);
|
|
488
495
|
if (content || meta.intent) {
|
|
489
496
|
entry.intent = meta.intent ?? content.intent;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as avatarSourceFor, n as attachAvatar, p as provisionFromMarverId } from "./auth-DdUKeVKt.mjs";
|
|
2
2
|
import { TransactionStore, browserBinding, verifyAssertion } from "./marver-id-CVCoaMGI.mjs";
|
|
3
|
-
import { t as poweredByUrl } from "./serve-
|
|
3
|
+
import { t as poweredByUrl } from "./serve-Bcwfpvhl.mjs";
|
|
4
4
|
import { randomBytes } from "node:crypto";
|
|
5
5
|
import { isIP } from "node:net";
|
|
6
6
|
import { lookup } from "node:dns/promises";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
2
|
import { i as ROUTE, n as NAME, r as PKG } from "./cli.mjs";
|
|
3
3
|
import { n as localProfile, t as isConnected } from "./profile-BjAPAJSb.mjs";
|
|
4
|
-
import { a as writeManifest, n as hash, r as scanFrames, t as affectedFrameIds } from "./manifest-
|
|
4
|
+
import { a as writeManifest, n as hash, r as scanFrames, t as affectedFrameIds } from "./manifest-DvOmglFp.mjs";
|
|
5
5
|
import { copyFileSync, existsSync, linkSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, watch, writeFileSync } from "node:fs";
|
|
6
6
|
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
@@ -125,6 +125,24 @@ function apiMiddleware(root, opts = {}) {
|
|
|
125
125
|
const path = url.pathname.slice(`${ROUTE}/api/`.length);
|
|
126
126
|
if (req.method === "GET" && !cookie(req, "mv_c")) res.setHeader("set-cookie", `mv_c=${randomBytes(16).toString("base64url")}; Path=/; Max-Age=${CSRF_MAX_AGE}; SameSite=Lax`);
|
|
127
127
|
try {
|
|
128
|
+
if (path === "policy" && req.method === "GET") try {
|
|
129
|
+
const raw = JSON.parse(readFileSync(join(root, "design", "publish.json"), "utf8"));
|
|
130
|
+
const boards = {};
|
|
131
|
+
for (const [n, level] of Object.entries(raw?.boards ?? {})) {
|
|
132
|
+
if (typeof level !== "object" || level === null) continue;
|
|
133
|
+
const p2 = level;
|
|
134
|
+
boards[n] = {
|
|
135
|
+
...typeof p2.type === "string" ? { type: p2.type } : {},
|
|
136
|
+
...typeof p2.open === "string" ? { open: p2.open } : {},
|
|
137
|
+
...p2.lock === true ? { lock: true } : {},
|
|
138
|
+
...typeof p2.transition === "string" ? { transition: p2.transition } : {},
|
|
139
|
+
...typeof p2.chrome === "string" ? { chrome: p2.chrome } : {}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return json(res, 200, { boards });
|
|
143
|
+
} catch {
|
|
144
|
+
return json(res, 200, { boards: {} });
|
|
145
|
+
}
|
|
128
146
|
if (path === "boards" && req.method === "GET") {
|
|
129
147
|
mkdirSync(boardsDir, { recursive: true });
|
|
130
148
|
return json(res, 200, readdirSync(boardsDir).filter((f) => f.endsWith(".json") && !f.endsWith(".tmp")).map((f) => {
|
|
@@ -266,7 +284,7 @@ function apiMiddleware(root, opts = {}) {
|
|
|
266
284
|
const frameId = url.searchParams.get("frame") ?? "";
|
|
267
285
|
const theme = url.searchParams.get("theme") ?? "light";
|
|
268
286
|
const origin = opts.origin?.() ?? `http://${req.headers.host ?? "localhost"}`;
|
|
269
|
-
const { shootFrame } = await import("./shot-
|
|
287
|
+
const { shootFrame } = await import("./shot-kbR_xzJH.mjs");
|
|
270
288
|
const r = await shootFrame({
|
|
271
289
|
root,
|
|
272
290
|
viewports: opts.viewports ?? {},
|
|
@@ -799,7 +817,7 @@ function marverPlugin(ctx) {
|
|
|
799
817
|
});
|
|
800
818
|
return;
|
|
801
819
|
}
|
|
802
|
-
const { shootFrame } = await import("./shot-
|
|
820
|
+
const { shootFrame } = await import("./shot-kbR_xzJH.mjs");
|
|
803
821
|
write(await shootFrame({
|
|
804
822
|
root,
|
|
805
823
|
viewports: config.viewports,
|
|
@@ -11,7 +11,8 @@ import { createServer } from "node:http";
|
|
|
11
11
|
* utm_source = the surface class ('published-canvas' | 'dev-canvas')
|
|
12
12
|
* utm_medium = the link unit ('powered-by')
|
|
13
13
|
* utm_campaign = THIS canvas's name, slugged ("Marver tour" -> "marver-tour")
|
|
14
|
-
* utm_content = the placement ('gate' badge | 'shell' wordmark | 'sign-in' finish page
|
|
14
|
+
* utm_content = the placement ('gate' badge | 'shell' wordmark | 'sign-in' finish page |
|
|
15
|
+
* 'play-brand' the present/slides brand pill)
|
|
15
16
|
*/
|
|
16
17
|
function poweredByUrl(canvasName, source, content) {
|
|
17
18
|
const slug = (canvasName ?? "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -146,7 +147,7 @@ async function serve(root, portFlag) {
|
|
|
146
147
|
console.error(`[${NAME}] MARVER_ID_ISSUER is set but MARVER_PUBLIC_ORIGIN is not.\n Every assertion is bound to this canvas's exact origin, and it cannot be\n inferred from request headers - a proxy can make any request look local.\n Set it to the origin people actually reach this canvas on:\n MARVER_PUBLIC_ORIGIN=https://canvas.example.com\n MARVER_PUBLIC_ORIGIN=http://localhost:${portFlag ?? (Number(process.env.PORT) || 4199)} (development)`);
|
|
147
148
|
process.exit(1);
|
|
148
149
|
}
|
|
149
|
-
const { marverIdHandler } = await import("./marver-id-gate-
|
|
150
|
+
const { marverIdHandler } = await import("./marver-id-gate-D6By7XHj.mjs");
|
|
150
151
|
const { dataDir } = await import("./comments-DZyobpxG.mjs").then((n) => n.n);
|
|
151
152
|
const canvasName = humanName(meta.name);
|
|
152
153
|
const { ceilingsFromRights } = await import("./auth-DdUKeVKt.mjs").then((n) => n.R);
|