@divlet/core 0.0.1 → 0.0.3
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/AGENTS.md +3 -2
- package/BACKLOG.md +4 -3
- package/CHANGELOG.md +17 -2
- package/README.md +16 -6
- package/dist/core/lib/acceptance.d.ts +18 -0
- package/dist/core/lib/acceptance.js +29 -0
- package/dist/core/lib/assets.js +1 -1
- package/dist/core/lib/browser-board.d.ts +41 -0
- package/dist/core/lib/browser-board.js +203 -0
- package/dist/core/lib/browser-mcp.d.ts +131 -0
- package/dist/core/lib/browser-mcp.js +448 -0
- package/dist/core/lib/browser-media-type.d.ts +4 -0
- package/dist/core/lib/browser-media-type.js +7 -0
- package/dist/core/lib/browser-runtime.d.ts +598 -0
- package/dist/core/lib/browser-runtime.js +674 -0
- package/dist/core/lib/codec.js +2 -1
- package/dist/core/lib/contract.d.ts +1 -152
- package/dist/core/lib/contract.js +3 -359
- package/dist/core/lib/diff.js +1 -1
- package/dist/core/lib/prepare.d.ts +27 -0
- package/dist/core/lib/prepare.js +41 -0
- package/dist/core/lib/project.js +3 -3
- package/dist/core/lib/render.d.ts +2 -9
- package/dist/core/lib/render.js +10 -35
- package/dist/core/lib/runtime.d.ts +1 -1
- package/dist/core/lib/runtime.js +1 -21
- package/dist/core/lib/tools.d.ts +154 -0
- package/dist/core/lib/tools.js +364 -0
- package/dist/core/lib/validation.d.ts +5 -0
- package/dist/core/lib/validation.js +21 -0
- package/dist/skills/divlet/SKILL.md +7 -1
- package/docs/README.md +2 -1
- package/docs/agents.md +11 -1
- package/docs/architecture.md +15 -7
- package/docs/board.md +1 -1
- package/docs/browser-mcp.md +108 -0
- package/docs/browser-runtime.md +6 -4
- package/docs/publishing.md +54 -5
- package/docs/testing.md +16 -3
- package/examples/README.md +2 -2
- package/examples/landing/landing.divlet +1 -1
- package/package.json +24 -5
- package/skills/divlet/SKILL.md +7 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Browser-hosted MCP
|
|
2
|
+
|
|
3
|
+
**Status: implemented for tools; deployed on GitHub Pages; `--connect` is in the 0.0.3 release candidate.** A static website can be an executable MCP provider: its tools run in the visitor's (or the agent's) browser, and a local agent connects by URL. The divlet GitHub Pages build is the first such provider — the real board, opening the canonical landing project, with the same contract tools Pi and the local MCP adapter offer. No MCP server is deployed for it. From a checkout: `node scripts/source.mjs mcp/index.js --connect <url>`.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# Register in any MCP host (Claude Code, an editor, a script). Tools run in a managed browser page, not on this machine.
|
|
7
|
+
npx -y @divlet/mcp --connect https://llblab.github.io/divlet/
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
A completely unmodified MCP client cannot speak to a static site: something on the agent side must supply the browser runtime and this transport. Here that is `@divlet/mcp --connect` (for any stdio MCP host) or `@divlet/core/browser-mcp` (for a harness that integrates it directly). What disappears is the website author's server, not the agent-side adapter.
|
|
11
|
+
|
|
12
|
+
## Layers
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
agent host (MCP client) ── stdio ──▶ divlet-mcp --connect @divlet/mcp mcp/lib/connect.js
|
|
16
|
+
│ connectBrowserMcp(url) @divlet/core core/lib/browser-mcp.ts
|
|
17
|
+
▼ page.evaluate(JSON text) (Playwright, isolated context, no bindings)
|
|
18
|
+
static website ──▶ globalThis[Symbol.for('browser-mcp/1')]
|
|
19
|
+
│ createBrowserMcpProvider @divlet/web/mcp web/lib/mcp-provider.ts
|
|
20
|
+
│ defineTools (one registry) ──────────────── web/lib/mcp-tools.ts
|
|
21
|
+
│ registerWebMcpTools (optional) ───────────── web/lib/webmcp.ts
|
|
22
|
+
▼
|
|
23
|
+
application core: the tool's own operations (divlet: BrowserRuntime + board ports)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- **Tool registry** (`defineTools`): one canonical list of `{ name, title, description, inputSchema, annotations, execute(args, { signal }) }`. Input is validated by a bounded JSON Schema 2020-12 subset (`type`, `properties`, `required`, `additionalProperties`, `items`, `enum`, `const`, `anyOf`, `oneOf`, numeric/length/item bounds); any other keyword is refused when the tool is defined, so a schema never claims validation it does not get. Execution has a deadline, an abort signal and a byte budget for the JSON result. The MCP provider and the WebMCP adapter both call the same `run`.
|
|
27
|
+
- **Browser MCP provider** (`createBrowserMcpProvider`, `exposeBrowserMcp`): a JSON-RPC 2.0 handler for MCP **2026-07-28** and nothing older. Implemented: `server/discover`, `tools/list`, `tools/call`, `notifications/cancelled`. It needs no Node, backend, extension or experimental API, and calls nothing outside the page.
|
|
28
|
+
- **Browser MCP transport** (`connectBrowserMcp`, `browserMcpTransport`): opens the URL in a managed Chromium, finds the endpoint, and exchanges messages by evaluating JSON text in the page. `browserMcpTransport(connection)` adapts a connection to the official TypeScript SDK's `Transport` interface.
|
|
29
|
+
- **WebMCP adapter** (`registerWebMcpTools`): registers the same registry with `document.modelContext.registerTool(tool, { signal })` when the browser provides it (WebMCP Community Group draft, 12 November 2026). Absent API → nothing is installed or emulated, and the MCP provider keeps working. WebMCP defines tools only; MCP resources/prompts are not mapped.
|
|
30
|
+
|
|
31
|
+
## Protocol revision
|
|
32
|
+
|
|
33
|
+
Revision 2026-07-28 is stateless: there is no `initialize`. Every request carries `_meta["io.modelcontextprotocol/protocolVersion"]` and `_meta["io.modelcontextprotocol/clientCapabilities"]` (missing → `-32602`); another version → `-32022` with `{ supported, requested }`. Results carry `resultType: "complete"` and `_meta["io.modelcontextprotocol/serverInfo"]`; discovery and list results carry `ttlMs: 0` and `cacheScope: "private"` (the tool set belongs to one page instance). Unknown tools are `-32602`; argument and domain refusals are tool results with `isError: true` and `structuredContent.error = { code, message, details? }`. A legacy `initialize` is refused with the supported versions named. Too many concurrent calls → `-31001` (application-defined, outside the reserved range). Cancelled calls get no response. The official SDK client (`@modelcontextprotocol/client` 2.4, `versionNegotiation: { mode: { pin: '2026-07-28' } }`) interoperates through both an in-memory and the browser transport.
|
|
34
|
+
|
|
35
|
+
`divlet-mcp --connect` answers both eras on its stdio side (legacy `initialize` clients and 2026-07-28 clients) and always uses 2026-07-28 toward the page.
|
|
36
|
+
|
|
37
|
+
## Discovery and connection
|
|
38
|
+
|
|
39
|
+
The discovery contract is project-specific and versioned (`browser-mcp/1`), not an MCP standard:
|
|
40
|
+
|
|
41
|
+
- The page publishes `globalThis[Symbol.for('browser-mcp/1')] = { protocol: 'browser-mcp/1', instance, handle(text) }` — non-enumerable, non-writable, a fresh random `instance` per provider. `handle` takes one JSON-RPC message as text and resolves to the response text (or `null`).
|
|
42
|
+
- An optional `<meta name="browser-mcp" content="browser-mcp/1">` says a provider is coming. With it, the transport waits (bounded, observable polling) for asynchronous initialization; without it, a page that has no endpoint after `load` fails fast with `NO_PROVIDER`. The meta tag carries no tool definitions — the only tool list is the one the live provider returns.
|
|
43
|
+
|
|
44
|
+
`connectBrowserMcp(url)` navigates, requires the final URL to stay on the requested origin (plus explicit `allowedOrigins`), waits for the endpoint, calls `server/discover` (refusing a provider without 2026-07-28 or without tools) and `tools/list`, then admits well-formed tools (name syntax, object input schema, bounded text) through the caller's `allowTools` policy. Repository subpaths work: every URL the application uses is relative.
|
|
45
|
+
|
|
46
|
+
## Lifecycle
|
|
47
|
+
|
|
48
|
+
| State | Kept separately |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| Agent state | The MCP host's conversation; never in the page. |
|
|
51
|
+
| Transport state | One connection = one origin + one provider `instance`; pending requests with ids, deadlines and abort signals. |
|
|
52
|
+
| Browser context | An isolated Playwright context per connection by default (no ordinary profile, cookies, permissions or downloads); `userDataDir` opts into a persistent profile. |
|
|
53
|
+
| Application state | Whatever the page keeps (divlet: one in-memory accepted project state, its history and selection). |
|
|
54
|
+
| Persistent storage | The page's own (divlet: IndexedDB in that browser profile). |
|
|
55
|
+
|
|
56
|
+
Any main-frame navigation or reload, or a replaced provider instance, makes the connection **stale**: every pending call is rejected with `PROVIDER_RELOADED` (it may or may not have completed; it is never retried), new calls fail with `STALE`, and `reconnect()` waits for the new provider and lists its tools again. Navigation to an untrusted origin, page close, crash or context loss **closes** the connection (`CLOSED`); authority is never transferred. `--connect` reconnects a stale page on the next call and emits `notifications/tools/list_changed` when the tool set changed. Pending requests are bounded (`maxPending`), each has a timeout and cancellation sends `notifications/cancelled` to the page.
|
|
57
|
+
|
|
58
|
+
## Security boundaries
|
|
59
|
+
|
|
60
|
+
- **No host privilege from the page.** The transport exposes no binding, function or route to the page; it only evaluates a fixed exchange function and reads its JSON text. Website code cannot call anything on the agent's machine, and the hostile-page test checks that no host functions appear.
|
|
61
|
+
- **Website text is data.** Tool descriptions, discovery instructions, results and errors are untrusted. `--connect` prefixes each description with its origin and "supplied by that website", drops behaviour annotations (claims, not guarantees), returns only text and JSON content (no website-chosen images, links or embedded resources) and caps text size.
|
|
62
|
+
- **Explicit trust.** The URL the user configures is the trusted origin; redirects elsewhere fail with `ORIGIN_CHANGED`; `--allow-tools` narrows what is offered. Per-call consent is the agent host's own tool-approval prompt, which applies to these tools like any other MCP tools.
|
|
63
|
+
- **Bounded traffic.** Request and response text are size-limited in both directions, replies must match the request id and JSON-RPC shape (`PROTOCOL_VIOLATION` otherwise), and `resultType`s other than `complete` are refused.
|
|
64
|
+
- **Isolation.** A fresh browser context per connection by default; nothing from the user's ordinary browser profile is used.
|
|
65
|
+
|
|
66
|
+
The page's own power is the browser's: it can do in its origin what any website can, and its storage is that browser profile's. A `--connect` session therefore shares state with a human only when both use the same page (the window `--connect` shows by default) — not automatically with the user's everyday browser.
|
|
67
|
+
|
|
68
|
+
## divlet on GitHub Pages
|
|
69
|
+
|
|
70
|
+
`scripts/pages-build.mjs` builds the public set: the real board client (`web/`), a bundled browser host (`core/lib/browser-board.ts`: `BrowserRuntime`, the board's writer/preview/export ports, the MCP provider and WebMCP registration), `project.divlet` (the canonical landing), `styles.json` (CSS compiled at build time by the local engine), a static `preview-bridge.js`, the prepared landing page under `page/`, and `.nojekyll`. `index.html` carries a hash-based meta CSP (Pages cannot send headers) and the `browser-mcp` hint.
|
|
71
|
+
|
|
72
|
+
- **One writer, shared vectors.** `BrowserRuntime` runs the same `read_divlet`, `patch_divlet`, `edit_divlet`, `frame_divlet`, `undo_divlet` and `mark_divlet` from `core/lib/tools.ts` over the shared validation, operations, component expansion, preparation, diff and acceptance modules. `core/tests/browser-runtime.test.js` drives the same calls through it and the Node runtime and requires identical models, pages, refusals, history and serialized projects. Its orchestration still mirrors `runtime.js` rather than sharing one writer class (backlog).
|
|
73
|
+
- **Human and agent edit one state.** Board edits go through the same writer; selection and measured geometry are shared, so an agent's `view:selection` and `view:preview` see what the visitor selected and sees. Agent calls show in the activity plaque and History as `agent` changes.
|
|
74
|
+
- **First visit.** A visitor with no saved view of the project reads the landing frame expanded at the window's width (a phone gets the phone layout); "Edit on the board" goes back to the canvas, whose view is then remembered. The expanded page keeps receiving live renders, so agent edits appear while it is read.
|
|
75
|
+
- **Storage.** The visitor's copy lives in IndexedDB (key: page path + project), tied to the fingerprint of the published source; a new publication starts a fresh copy. "Download project file" saves it as a `.divlet` for the local host. Undo history belongs to the page instance.
|
|
76
|
+
- **Absent here, by design.** Opening other files, save-as, build output, image/font upload and embedded asset bytes (`ASSETS_UNAVAILABLE`), Markdown sites, utility compilation (new utility classes are reported as unstyled; use frame CSS or inline style) and the board conversation (`agent.conversation: false`; the Agent tab offers the `--connect` command instead).
|
|
77
|
+
|
|
78
|
+
Deploy: push to `main`; `.github/workflows/landing.yml` checks the landing source/cache, runs the unit checks and `web/tests/pages.mjs` against the built set on a static server, then copies the set to the `gh-pages` root only when its fingerprint (`.pages-hash`) changed. Configure Pages to serve the `gh-pages` branch root. A different static host works the same way: serve the directory from `node scripts/pages-build.mjs --copy <dir>` under any path.
|
|
79
|
+
|
|
80
|
+
## Reuse
|
|
81
|
+
|
|
82
|
+
**Another SPA.** Keep your operations; describe them once:
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
import { defineTools, createBrowserMcpProvider, exposeBrowserMcp, registerWebMcpTools } from '@divlet/web/mcp';
|
|
86
|
+
const tools = defineTools([{ name: 'todos.add', description: 'Add a todo.', inputSchema: { type: 'object', properties: { text: { type: 'string', minLength: 1 } }, required: ['text'], additionalProperties: false }, execute: ({ text }) => app.addTodo(text) }]);
|
|
87
|
+
exposeBrowserMcp(createBrowserMcpProvider({ name: 'todos', version: '1.0.0', tools }));
|
|
88
|
+
await registerWebMcpTools(tools); // optional; a no-op without document.modelContext
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Add `<meta name="browser-mcp" content="browser-mcp/1">` if the provider appears after asynchronous start-up. `web/tests/fixtures/browser-mcp-site/` is a complete example (IndexedDB todo list whose UI and tools share one domain module).
|
|
92
|
+
|
|
93
|
+
**Another agent harness.** Use the connection directly, or hand the SDK-shaped transport to an MCP client:
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
import { connectBrowserMcp, browserMcpTransport } from '@divlet/core/browser-mcp';
|
|
97
|
+
const site = await connectBrowserMcp('https://example.github.io/app/', { headless: false, allowTools: ['todos.add'] });
|
|
98
|
+
await site.callTool('todos.add', { text: 'From an agent' });
|
|
99
|
+
// or: await new Client(info, { versionNegotiation: { mode: { pin: '2026-07-28' } } }).connect(browserMcpTransport(site));
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`playwright-core` is an optional peer of `@divlet/core` (a dependency of `@divlet/mcp`); the system Chrome is used first, then Playwright's Chromium, then Edge.
|
|
103
|
+
|
|
104
|
+
## Not supported
|
|
105
|
+
|
|
106
|
+
Resources, prompts, subscriptions (`listChanged` from the page), multi-round-trip input, tasks, progress and logging; pagination (one page of tools); non-Chromium browsers for the transport; attaching to an already running user browser; real WebMCP interoperability beyond a draft-shaped stand-in (no shipping browser was available to test); offline reload of the static board (no Service Worker); multi-tab ownership of one browser copy; and HTTP-reachable MCP for clients that cannot run an adapter.
|
|
107
|
+
|
|
108
|
+
Tests: `web/tests/mcp-provider.test.js` (protocol, SDK interop, bounds, WebMCP), `web/tests/browser-mcp.mjs` (real Chromium against static fixtures: correlation, timeouts, cancellation, reload/close, origin, hostile pages), `core/tests/browser-runtime.test.js` (writer parity), `web/tests/pages.mjs` (the built site under a prefix, agent by URL, persistence) and `mcp/tests/connect.test.js` / `connect.e2e.mjs` (the stdio adapter, both eras, through the SDK's stdio client).
|
package/docs/browser-runtime.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# Browser runtime, offline demo and agent connectivity
|
|
2
2
|
|
|
3
|
-
**Status: incremental implementation, not a completed
|
|
3
|
+
**Status: incremental implementation, not a completed offline editor or a frozen API.** Shared standalone-frame validation, ordinary patches and inspection execute in native browser ESM as well as Node. A browser-local whole-project writer (`core/lib/browser-runtime.ts`: frames, components, structural edits, frame lifecycle, guarded Undo/Redo, work marks) now runs the shared contract tools with Node parity vectors, and the real board runs on it as a static site — the GitHub Pages board, which is also its own MCP provider ([browser-hosted MCP](browser-mcp.md)). Embedded asset bytes, fonts, sites, utility compilation, offline reload and multi-tab ownership still require the Node host or remain open. This note separates the portable semantic kernel direction, static-hosted demo and browser-agent transports. Implementation proceeds through bounded validated slices, not a wholesale runtime rewrite or a new release prerequisite. [BACKLOG.md](../BACKLOG.md) owns remaining work. The [web-native contract](web-native.md) remains the semantic authority.
|
|
4
4
|
|
|
5
5
|
## Current evidence
|
|
6
6
|
|
|
7
7
|
- Generated project pages already work on ordinary static hosting. That does not mean the editor can run without its host.
|
|
8
|
-
- Board bootstrap, state/change subscriptions and cached frame reads
|
|
8
|
+
- Board bootstrap, state/change subscriptions and cached frame reads use `BoardReadSession`; writes, previews, exports and component lists use `BoardWriter`, `BoardPreviews`, `BoardExports` and `BoardComponents`. `web/lib/host.js` composes the native HTTP/SSE adapters, or a complete `BoardHostComposition` published before startup (the static browser host), with no fallback to native routes. Native-only affordances (open/save-as, build output, image/font upload, the board conversation) are absent in the browser host by `native`/capability checks, not disabled.
|
|
9
9
|
- `core/lib/validation.ts` owns shared domain errors, JSON/object/key/text checks and bounded UTF-8 budget admission without runtime imports or Node globals. Existing model-facing error/function identities remain shared.
|
|
10
10
|
- `core/lib/asset-reference.ts` shares portable declared-name/path checks with the Node byte decoder and implements the explicit `asset:` URI codec and bounded relative dependency resolution. Native-browser ESM runs the same reference witnesses without bundling or import rewriting. The frame model also uses that codec for resource-only attributes, including head icons and SVG `use`, with shared navigation/form/context refusals and tag-change safety. Controlled native-frame preparation followed by declared closure proves real image/SVG-use loading under a prefix. The Node writer now admits and projects the project asset tree, but that decoder still needs its Node host; this is not browser-local project storage or font upload UI.
|
|
11
11
|
- `model.ts` binds style lowering through `model-styles.ts`, which retains the existing eager Node engine. The package's standard browser file mapping swaps only that port for `browser-styles.ts`, an explicit `NO_STYLE_ENGINE` refusal. Browser validation/native `style` maps do not imply Tailwind compilation or declarative `styles` lowering. No new public subpath or serialized capability field is introduced.
|
|
12
12
|
- CSS admission uses the same PostCSS grammar parser with source-map interpretation disabled. Browser bundling respects PostCSS's own optional-feature browser mappings; it adds no Node-global polyfill or alternate validator. `scripts/browser-model.mjs` owns the source/installed verification build profile and guards against concrete engine/writer/Tailwind inputs and external imports. It returns bytes in memory, not a shipped standalone browser SDK artifact.
|
|
13
|
-
-
|
|
13
|
+
- `project.ts`, `codec.ts`, `diff.ts`, `ops.ts`, `prepare.ts`, `acceptance.ts` and `tools.ts` no longer reach Node built-ins. Concrete provider bootstrap remains Node-only, and canonical MIME parsing maps to an explicit `ASSETS_UNAVAILABLE` refusal in browser builds (no approximation of Node's WHATWG MIME canonicalization). Browser compatibility cannot be inferred from TypeScript compilation; `scripts/pages-build.mjs` refuses a host bundle that reaches Node/provider modules.
|
|
14
14
|
- The current style/provider, component and format-transition gaps remain relevant. An offline fork must not silently establish different cascade, override, resource or renderer rules.
|
|
15
15
|
|
|
16
16
|
## Recommended boundary: portable semantics, explicit hosts
|
|
@@ -47,7 +47,7 @@ Preview measurements first update the client's observation cache and local event
|
|
|
47
47
|
|
|
48
48
|
`web/lib/board-host.ts`, public as `@divlet/web/host`, separately types optional History and source-update services. They can carry native authority (`navigate`, reimport/open) and are never added to the read session. History reads are omitted when its service is absent; restore controls additionally require both the host's restore method and the state's navigation advertisement. A reader-only service may supply audit data without restore controls. Source-change scanning and its entire notification action workflow require the source-update service, so absence produces neither a scan nor an unsupported import prompt. Native adapters keep the same routes, captured History targets, generation guards, scan-failure policy and deduplicated notifications.
|
|
49
49
|
|
|
50
|
-
These gates are proven with actual activity/project/store modules under fixed test compositions
|
|
50
|
+
These gates are proven with actual activity/project/store modules under fixed test compositions. The complete board also runs on the static browser composition (`core/lib/browser-board.ts`), proven in Chromium against the built Pages set under a repository prefix (`web/tests/pages.mjs`). That is a host-replaceable board, not an offline one: there is no Service Worker, so a first visit and every reload need the static files.
|
|
51
51
|
|
|
52
52
|
## Public prepared-page inspection data
|
|
53
53
|
|
|
@@ -82,6 +82,8 @@ The core must not import a marketing project. A demo is composition-owned and op
|
|
|
82
82
|
|
|
83
83
|
## Service Worker is not a public MCP endpoint
|
|
84
84
|
|
|
85
|
+
Implemented route (see [browser-hosted MCP](browser-mcp.md)): the agent side supplies the browser. `divlet-mcp --connect <url>` or `@divlet/core/browser-mcp` opens the static site in a managed Chromium and exchanges MCP 2026-07-28 messages with the page's provider by direct evaluation; nothing listens, relays or is deployed. That is an integrated agent-side adapter, not a generally reachable MCP server, and it does not change the points below.
|
|
86
|
+
|
|
85
87
|
A Service Worker intercepts requests for its controlled browser clients within its origin/scope. It does not accept arbitrary inbound TCP connections or cause a remote MCP client's requests to GitHub Pages to execute in the visitor's browser. It can emulate a local route for a controlled page; that is not a generally reachable MCP HTTP server. Background lifetime also cannot be assumed.
|
|
86
88
|
|
|
87
89
|
There are two useful, distinct agent routes:
|
package/docs/publishing.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# npm publication
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
All five **0.0.2** packages are published on npm through GitHub Actions/OIDC, with frozen-archive integrity, provenance metadata and clean registry CLI/MCP/Pi installations verified. **0.0.3** is the next candidate; do not treat it as published until all five registry checks and the installed-host CI job pass. This guide owns publication safeguards and the first-publication procedure below; it is not permission to publish again. A prepared tree or versioned changelog is not a completed registry publication. Publishing packages, pushing release refs and deploying the landing page require explicit maintainer authorization. Packaging does not freeze or cut over the native schema.
|
|
4
4
|
|
|
5
5
|
npm refused the original unscoped `divlet` name as too similar to `figlet`. The maintainer approved `@divlet/core` and a separately owned `@divlet/web`; the old four-package archives are historical candidates, not scoped substitutes. Never edit archive manifests in place or retry the rejected publisher.
|
|
6
6
|
|
|
@@ -22,21 +22,68 @@ npm refused the original unscoped `divlet` name as too similar to `figlet`. The
|
|
|
22
22
|
|
|
23
23
|
Core's HTTP composition imports web's public asset locator; generic model/format modules do not import web. Web's portable contracts own opaque host payloads, not project validation/writes, and have no type/JSDoc back-edge to core. Browser modules use host HTTP/SSE and preview ports, not imports of the Node runtime. Backend plugins consume core contracts; client plugins use the served web module graph. See [architecture](architecture.md).
|
|
24
24
|
|
|
25
|
-
The root owns `@divlet/core`, `web/` owns `@divlet/web`, and `cli/`, `mcp/` and `pi/` own the adapters, without wrappers. All five versions match.
|
|
25
|
+
The root owns `@divlet/core`, `web/` owns `@divlet/web`, and `cli/`, `mcp/` and `pi/` own the adapters, without wrappers. All five versions match. The 0.0.3 candidate adapters pin `@divlet/core@0.0.3`; core pins `@divlet/web@0.0.3`. Core has no bins, Pi manifest/peer requirements or `./pi` export. Its tarball excludes the web and adapter implementations. Web owns its only client copy and portable declarations. Built-in plugins remain bundled in core, not separately published.
|
|
26
26
|
|
|
27
|
-
Node.js 22.19 or later is required. Pi is not required for CLI/MCP. Shared configurations should pin adapter versions, including `npm:@divlet/pi@0.0.
|
|
27
|
+
Node.js 22.19 or later is required. Pi is not required for CLI/MCP. Shared configurations should pin adapter versions, including `npm:@divlet/pi@0.0.3` after the candidate is published. For MCP:
|
|
28
28
|
|
|
29
29
|
```json
|
|
30
30
|
{
|
|
31
31
|
"mcpServers": {
|
|
32
32
|
"divlet": {
|
|
33
33
|
"command": "npx",
|
|
34
|
-
"args": ["-y", "@divlet/mcp@0.0.
|
|
34
|
+
"args": ["-y", "@divlet/mcp@0.0.3", "--cwd", "/absolute/path/to/workspace"]
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
## Subsequent releases
|
|
41
|
+
|
|
42
|
+
Keep all five versions aligned and update every exact internal dependency pin plus the lockfile. Validate and freeze a new candidate, then publish the new version in web → core → adapters order under the same integrity/install/auth safeguards. Published `0.0.1` bytes are immutable; fixes require a new version, never an overwrite or unpublish/retry. Git release refs and Pages remain separately authorized delivery surfaces. The first-publication sequence below is retained for its guards and recovery rules, not as a command to rerun the initial release.
|
|
43
|
+
|
|
44
|
+
## GitHub Actions publishing
|
|
45
|
+
|
|
46
|
+
The [Release workflow](https://github.com/llblab/divlet/blob/main/.github/workflows/release.yml) uses **npm Trusted Publishing (OIDC)**, not a stored `NPM_TOKEN`. It is manually dispatched from `main` with an existing stable `release_tag`, for example `v0.0.2`. Ordinary pushes, tag pushes and GitHub Release creation do not publish npm packages. Prereleases are not supported by this workflow.
|
|
47
|
+
|
|
48
|
+
### One-time maintainer setup
|
|
49
|
+
|
|
50
|
+
1. Push the reviewed workflow and its scripts to `main` only when Git/Pages delivery is authorized. The workflow must exist on the default branch before dispatch. No setup command in this guide creates tags, pushes or publishes automatically.
|
|
51
|
+
2. In GitHub Settings → Environments, create **`npm`** and restrict deployment branches to `main`. Add required reviewers if an approval gate is wanted; a solo maintainer must not enable a self-review prohibition that prevents any approval.
|
|
52
|
+
3. For **each** of `@divlet/web`, `@divlet/core`, `@divlet/cli`, `@divlet/mcp` and `@divlet/pi`, open npm package Settings → Trusted Publisher and add GitHub Actions with these exact, case-sensitive values:
|
|
53
|
+
| Field | Value |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| Organization or user | `llblab` |
|
|
56
|
+
| Repository | `divlet` |
|
|
57
|
+
| Workflow filename | `release.yml` (filename only) |
|
|
58
|
+
| Environment name | `npm` |
|
|
59
|
+
| Allowed action | Direct **`npm publish`** |
|
|
60
|
+
Stage-only permission is not sufficient for this workflow; dist-tag management permission is not needed. Do not add an npm write token to Actions secrets. According to the [npm trusted-publisher guide](https://docs.npmjs.com/trusted-publishers/), a new connection must complete its first successful publish within **two days** or expire: create these bindings near the next intended release, not by republishing `0.0.1` to test them. npm does not validate the connection when it is saved.
|
|
61
|
+
4. After the first successful OIDC release, optionally restrict npm Publishing access to require 2FA and disallow traditional tokens. Make that account change deliberately; this repository does not change account settings or revoke tokens for you.
|
|
62
|
+
|
|
63
|
+
### Run a release
|
|
64
|
+
|
|
65
|
+
Align the five manifests, exact pins, lockfile and candidate notes, then create the new stable tag on the intended `main` commit under separate Git authorization. The tag must resolve to **the same SHA as the dispatched `main` revision**. This prevents automatic npm provenance from naming a different source commit than the one used to build the archives. If `main` has advanced past an old tag, this workflow refuses it rather than falsifying provenance or moving a tag.
|
|
66
|
+
|
|
67
|
+
Select Actions → **Release** → Run workflow, branch `main`, and supply the tag. The equivalent explicit publication action is:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
gh workflow run release.yml --ref main -f release_tag=v0.0.3
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The workflow has three boundaries:
|
|
74
|
+
|
|
75
|
+
- **Validate and freeze:** Node 22.19.0, clean locked dependencies without install hooks or restored Actions caches, source units/types, the full browser battery, then one build and five hook-free packs. Test the exact frozen archives through the installed CLI/MCP and required isolated Pi 1.5.2 host before uploading the candidate artifact. No OIDC permission is available here.
|
|
76
|
+
- **Publish npm:** A separate hosted runner, the `npm` environment, read-only GitHub history access and `id-token: write`. npm 11.19.0 performs OIDC/provenance with upload transport retries explicitly disabled (`--fetch-retries=0`); a retryable network response cannot silently replay the package PUT. No checkout dependencies, product builds or package lifecycle scripts run in this job. The script rechecks tag/commit, package order and archive SHA-512, checks all five registry names, then publishes web → core → adapters and waits for each exact version/integrity. The whole workflow is serialized without cancelling a running publication.
|
|
77
|
+
- **Verify registry installation:** A fresh non-OIDC job installs by public registry names, checks archive and installed-lock integrity against the frozen manifest, serves the actual board/client/plugin routes, exercises MCP's seven tools and runs the real isolated Pi/font/history cases. It does not reload any active user session or install Pi globally.
|
|
78
|
+
|
|
79
|
+
### Failure and recovery
|
|
80
|
+
|
|
81
|
+
Keep the `npm-candidate-<run-id>` archive set and `npm-outcome-<run-id>-<attempt>` receipt. They have 90-day artifact retention; download the evidence if longer retention is required. The publisher records `attempt_started` before invoking npm, `npm_accepted` after success and `registry_verified` only after exact-byte visibility. Registry absence after an attempt means wait/read or stop, never another upload.
|
|
82
|
+
|
|
83
|
+
Publication jobs **cannot be rerun automatically**: attempts after the first are refused, and a fresh dispatch checks prior runs of this workflow for the same tag, including all job attempts. A previously started publisher requires explicit operator recovery even if no registry version is visible. Do not delete run history or edit the run title to bypass that guard. Runs whose publisher was skipped because validation failed do not count as upload attempts; use a fresh dispatch after fixing validation while preserving the tag/source constraint. A failed registry verification after all uploads is not permission to publish again; rerun only the read-only verification job.
|
|
84
|
+
|
|
85
|
+
For partial or uncertain outcomes, inspect the original logs, frozen bytes, receipt and fresh registry metadata before choosing an explicit manual recovery or a new aligned version. Do not rebuild a failed release's archives, overwrite accepted versions, infer token rights from `npm whoami`, or run manual and CI publishing concurrently. A local/mock pass proves guard behavior, not a successful live OIDC exchange. The first real CI release was verified for 0.0.2; changes to workflow identity, environment bindings or account policy still require their own live authorization check.
|
|
86
|
+
|
|
40
87
|
## Readiness gates
|
|
41
88
|
|
|
42
89
|
- Freeze one coherent candidate after intended changes settle. Preserve unrelated work; root `dist/`, `web/dist/` and adapter `dist/` are generated publication output, not source development prerequisites.
|
|
@@ -45,7 +92,7 @@ Node.js 22.19 or later is required. Pi is not required for CLI/MCP. Shared confi
|
|
|
45
92
|
- Inspect five tarballs: root, `web`, and each adapter. Check public exports/declarations, native client closure, sole file ownership, bins, manifests, licenses, excluded tests/caches and packaged documentation links. Root documentation links to separately packaged READMEs use absolute GitHub URLs. Repeat builds must not nest outputs or alter bytes.
|
|
46
93
|
- Local release checks install both local web/core tarballs, then adapters, into an empty consumer. They must not fetch unpublished product packages, link checkout code or rescue missing client files from a sibling directory. These checks are not registry-install evidence.
|
|
47
94
|
- Recheck each exact name/version immediately before publication. `E404` is neither reservation nor entitlement; name-similarity policy may appear only during PUT. An unexpected existing version requires an ownership/integrity decision, not an overwrite or automatic retry.
|
|
48
|
-
-
|
|
95
|
+
- For local publishing, confirm maintainer identity, `@divlet` organization rights, permission to publish all five packages publicly and current npm 2FA requirements. CI instead requires the exact trusted-publisher/environment bindings above; `npm whoami` is not an OIDC permission check. Never store credentials in the repository or request OTPs/tokens in chat. This guide itself authorizes no publication.
|
|
49
96
|
- GitHub origin/homepage configuration proves neither push permission nor an authorized Pages deployment.
|
|
50
97
|
|
|
51
98
|
## First-publication sequence
|
|
@@ -60,6 +107,8 @@ Only after explicit authorization, using the built, frozen and validated candida
|
|
|
60
107
|
6. Verify clean registry installations outside the checkout: CLI/MCP help plus an actual board and MCP tool call, and a real isolated Pi profile with `/divlet`, seven tools and the shared skill. No duplicate local adapter, global extension changes or active-session reload is permitted as a shortcut.
|
|
61
108
|
7. Only after registry checks pass, reconcile [BACKLOG.md](../BACKLOG.md), [CHANGELOG.md](../CHANGELOG.md) and release usage with the actual outcome. Create `v0.0.1`/push only if separately authorized. Never describe an attempted or failed publication as successful.
|
|
62
109
|
|
|
110
|
+
After an upload is attempted, a public `404` is not proof that it is safe to upload again. Persist an attempt checkpoint before invoking npm and record acceptance before waiting for registry visibility. Verify the exact version and frozen integrity, not merely the latest tag; first-publication visibility can take minutes. Retry only reads within a bounded wait. On timeout or an uncertain command outcome, retain the checkpoint and stop; never turn a negative visibility check into another upload. The operator handoff may resume a verified version without republishing it. A failed npm browser-auth callback is distinct from a package lookup failure: inspect its stage and fresh registry state before admitting one explicit package-targeted authentication retry. Preserve the failed attempt in history; a retry flag must not bypass an unreviewed upload outcome or change a published archive.
|
|
111
|
+
|
|
63
112
|
npm publication is **not atomic across five names**. If a later package fails, report the partial state and resume only missing publications after refreshing registry evidence. Never republish/unpublish successful packages to simulate atomicity; defective immutable bytes require a new aligned version and dependency pins.
|
|
64
113
|
|
|
65
114
|
The static landing is a separate delivery surface. Its workflow verifies the prepared native example cache and publishes only when its public-file fingerprint changes. npm publication neither enables Pages nor authorizes deployment.
|
package/docs/testing.md
CHANGED
|
@@ -5,12 +5,15 @@ Development runs source files, not `dist/`. Root `npm test`, start/demo and Pi/M
|
|
|
5
5
|
| Check | Command | Covers |
|
|
6
6
|
| --- | --- | --- |
|
|
7
7
|
| Unit | `node scripts/source.mjs --test` | Model, project, codec, styles, runtime, server, launcher, import, history, safety, output ownership, static/site builds, the Pi adapter (`pi/tests`, with CLI/MCP tests in their own adapter packages), plugins (`plugins/*/tests`) and package contents. |
|
|
8
|
-
| Browser (all) | `npm run test:browser:all` | The
|
|
8
|
+
| Browser (all) | `npm run test:browser:all` | The 36 real Chromium suites in `web/tests/`: `validation`, `run`, `responsive`, `layout-wrap`, `site`, `fullscreen`, `settings`, `interact`, `selection-completion`, `size-editor`, `controls`, `history`, `features`, `agent-status`, `chat`, `spacing`, `toolbars`, `appearance`, `font-containers`, `reimport`, `chrome`, `fluid`, `snapshots`, `launcher`, `launcher-errors`, `native-collections`, `projects`, `security`, `static`, `native-default`, `landing`, `preview-links`, `text-edit-render`, `toast-priority`, `browser-mcp`, `pages`. Most have a `test:<name>` script (`test:spacing`, `test:chrome`, …); `appearance`, `reimport` and a few others run only through this command or through a combined script (`test:controls`, `test:launcher`, `test:source`). |
|
|
9
9
|
| History | `npm run test:history` | Core point/replay and board HTTP regressions, followed by the dedicated real Chromium whole-project acceptance suite (`history.mjs`); basic row/keyboard/phone/race checks also live in `controls.mjs`. |
|
|
10
10
|
| PEN fidelity | `npm run test:pen-fidelity -- /absolute/path/to/file.pen` | Renders every root frame of a caller-supplied `.pen` (no authored PEN example is bundled) from the native projection and from the converted Divlet frame in Chromium and requires identical pixels; a flat, single-colour screenshot counts as a failure too, because an identical blank canvas would prove nothing. `PI_DIVLET_FIDELITY_LIMIT=N` samples the first N frames. The 68-frame, 3 195-instance result quoted in the changelog came from a design that is not part of this repository and cannot be reproduced from it. |
|
|
11
11
|
| Release | `npm run test:release` | Packs five tarballs (`@divlet/web`, `@divlet/core`, `@divlet/cli`, `@divlet/mcp`, `@divlet/pi`) and installs them into an empty directory. Checks the public library, CLI, board/client/plugin serving and installed MCP initialization, tools and board opening **without Pi**, including the installed CLI/MCP font scenario in Chromium before adding Pi peers. Checks the Pi adapter package entry and manifest against the installed product; this is not a real Pi session. Core excludes adapter and web implementation trees; web owns its client/portable ports with no core dependency. Both exclude tests and caches. Exercises the installed opt-in versioned compiler. Needs Chromium and the npm registry (or its cache); publishes nothing. |
|
|
12
12
|
| Release + real Pi | `npm run test:release:pi` | Adds required real Pi RPC and installed-font Chromium acceptance to `test:release`, using compiled consumer exports and an isolated test profile/mock provider. A missing or broken `pi` CLI fails rather than skips. Requires Chromium and a working local Pi; validated with Pi 1.5.2 on Node 22.19. Installs no global extension and touches no active session or account. |
|
|
13
13
|
| MCP | `npm run test:mcp` | Spawns the MCP adapter source entry through the source-condition runner and speaks its stdio transport for real: `initialize`, `tools/list`, tool calls with a domain refusal, two clients sharing one board (attachment, not a second writer), concurrent writes refused by revision, shutdown on EOF and board de-registration. |
|
|
14
|
+
| Browser MCP | `npm run test:browser-mcp` | `@divlet/core/browser-mcp` against bundled static fixture pages under a repository prefix in real Chromium: connect by URL, discovery, calls observed in the page UI, the official SDK client over the browser transport, concurrent correlation, timeouts and cancellation reaching the page, reload invalidation and reconnect with IndexedDB persistence, page close, fast failure without a provider and waiting for an asynchronous one, cross-origin redirect/navigation refusal, a hostile page (no host bindings, oversized and mismatched replies, instruction-like descriptions kept as data) and WebMCP through a draft-shaped stand-in. Chromium ships no native WebMCP, so real browser-agent interoperability is not proven. |
|
|
15
|
+
| Pages | `npm run test:pages` | The `scripts/pages-build.mjs` set served by a file-only server under `/divlet/`: the real board opens the landing with only static GETs and no errors, native-only affordances are absent, export runs in the page, an agent connects by URL, edits what the visitor sees, refusals stay tool errors, visitor and agent edits share one writer and History, and the browser copy survives a reload. |
|
|
16
|
+
| Connect | `npm run test:connect` | `divlet-mcp --connect` started by the official SDK stdio client against the built Pages set, in both protocol eras. `mcp/tests/connect.test.js` (in `test:mcp`) covers the stdio side without a browser. |
|
|
14
17
|
| Contract | `npm run test:contract` | One tool list; the JSON Schema and TypeBox renderings agreeing on required parameters; the agent vocabulary; a board running only the tools it publishes; the board registry (one live owner, stale entries removed). |
|
|
15
18
|
| Pi RPC | `npm run test:pi` | A real Pi in RPC mode with a scripted model: a board request reaches a divlet tool, changes the project and returns as a reply. Skipped when the `pi` CLI is absent. |
|
|
16
19
|
|
|
@@ -24,6 +27,10 @@ The installed font scenario resolves only compiled `@divlet/core` public exports
|
|
|
24
27
|
|
|
25
28
|
`npm run test:toast-priority` uses the browser clock with a connected but idle test host: the delayed agent-start warning must not replace a currently actionable Undo toast, and appears after that toast expires. This regression is included in the full source browser battery.
|
|
26
29
|
|
|
30
|
+
## Browser writer parity
|
|
31
|
+
|
|
32
|
+
`node scripts/source.mjs --test core/tests/browser-runtime.test.js` opens the landing in the Node runtime and in `BrowserRuntime` (with the same style compiler) and drives the same contract tool calls through both: patches, a no-op, stale refusals, inserts including sanitized HTML, move/wrap, invalid operations, components and dependent re-renders, frame create/duplicate/delete refusal, project tokens/CSS, Undo/Redo (global and frame-scoped), marks and reads. Models, rendered pages, component uses, results, refusal codes, history summaries (except the Node-only trash note) and the serialized project must be identical. It also checks what the browser writer refuses (assets, sites, `open_divlet`) and that a failing store keeps the accepted change with a diagnostic. It does not prove asset, font or site parity, which the browser writer does not implement.
|
|
33
|
+
|
|
27
34
|
## Portable kernel slice
|
|
28
35
|
|
|
29
36
|
`node scripts/source.mjs --test core/tests/validation.test.js core/tests/model-browser.test.js` checks shared admission/error identity, UTF-16/UTF-8 budgets (with an independent Node `Buffer.byteLength` oracle), full standalone-frame candidate/inspection witnesses, strict HTML round trips and cold default-provider behavior. `npm run test:browser:kernel` runs 38 primitive, 48 UTF-8, 53 frame, 111 declared-asset-reference and 3 immutable snapshot-session witnesses plus 12 actual-store local/shared-selection and 12 optional-service consumer checks as native browser ESM under a repository prefix. Primitive source is type-erased only; the model uses the shared esbuild profile in `scripts/browser-model.mjs` with the package's explicit browser style-port mapping and PostCSS's own optional-feature browser mappings. The actual browser has no Node globals/polyfills, model backend API, SSE route or dependency rescue. It verifies both the shared successful/refused operations and explicit absent declarative lowering. The browser entry is included once in the full battery. The release smoke separately bundles the installed compiled package and evaluates its frame/lowering behavior in Node; that is not installed-bundle browser execution. These checks do not establish full-project/component expansion, shipped native-browser SDK delivery or an offline editor.
|
|
@@ -81,6 +88,12 @@ The renderer requirement seam has a separate focused gate: `node scripts/source.
|
|
|
81
88
|
|
|
82
89
|
`node scripts/source.mjs --test core/tests/html-import.test.js core/tests/import.test.js` checks core-owned HTML admission without an HTML plugin registration, with no style engine, and through explicit `.html`/`.htm` opening, folder-page ingestion and re-import. It covers reported sanitization losses, preserved sources, existing destinations, listing policy, link refusal and unsupported input. It does not establish managed-cache reverse editing; generated-page hand edits remain diagnosed rather than imported.
|
|
83
90
|
|
|
91
|
+
## CI release checks
|
|
92
|
+
|
|
93
|
+
`node scripts/source.mjs --test core/tests/npm-release.test.js` tests the Release workflow's OIDC/ref boundary, aligned manifests/lock pins, immutable archive identities, prior-attempt guard and serial publication checkpoints with injected effects. Its fixture packs real local archives without running lifecycle hooks; it never publishes or changes credentials. The mocked accepted-upload/404, mismatched-byte and command/read-failure cases prove refusal behavior, not live npm authorization. A loopback-only mock registry also drives the real npm CLI with the publisher's retry flags: an HTTP 502 must produce exactly one package PUT. The fixture uses an owned dummy token and disables provenance only for that local transport test; no public registry publication occurs.
|
|
94
|
+
|
|
95
|
+
The existing `core/tests/release/install-smoke.mjs` also accepts `DIVLET_RELEASE_MANIFEST=/absolute/path/manifest.json` to consume and check exact frozen archives instead of rebuilding them. Add `--registry --pi` to fetch packs and install by published names while comparing both pack and installed-lock integrity to that manifest. Without these options the ordinary local-tarball checks remain unchanged. Registry mode requires a manifest and an available real Pi CLI when `--pi` is requested. See [publication](publishing.md#github-actions-publishing) for the three CI jobs and activation gates.
|
|
96
|
+
|
|
84
97
|
## Conventions
|
|
85
98
|
|
|
86
99
|
- Assert actual state (project file, model, accepted version), not only that a request returned.
|
|
@@ -98,8 +111,8 @@ The renderer requirement seam has a separate focused gate: `node scripts/source.
|
|
|
98
111
|
- Tooltip copy: `node web/tests/toolbars.mjs` checks full text, wrapping and no horizontal/vertical clipping for long prose and unbroken references at the viewport edges and centre, at desktop/phone/200%. Keyboard-triggered diagnostic headings also wrap rather than ellipsize. These checks do not remove the existing height budget for diagnostic code excerpts.
|
|
99
112
|
- Narrow sidebar chrome: `npm run test:chrome` drags the real edge to 240px, checks complete tab labels and separate icons/counts (including four-digit diagnostics), and compares open/folded label positions. Board/frame/node context headers must contain their text and stay clear of the prompt. `npm run test:fluid` repeats tab containment, context separation and logical-pixel density classification at phone/200%; `toolbars.mjs` retains keyboard, contrast and touch checks.
|
|
100
113
|
- Agent conversation: `npm run test:chat` checks a bottom-aligned short stack without overflow, chronological/reachable history after overflow, following new messages versus preserving an older reading position, returning to the bottom or sending a new request, fixed composer geometry, composer/activity resizing, folded-pane arrivals and explicit message focus at desktop, phone and phone/200%. It rejects interpreting a queued scroll from an old layout as a reader leaving the bottom. Footer geometry also keeps Send/Queue bottom-right without overlapping controls in selector, key-hint, enabled, disabled and disconnected states, including actual 240px edge resizing.
|
|
101
|
-
- Agent activity: `node scripts/source.mjs web/tests/agent-status.mjs` independently checks the folded spinner/timer/Stop footer alignment, full-width description, scrolling and original-node restoration at 390/820/1500px and 100/150/200%, with a working keyboard Stop. `node web/tests/features.mjs` checks the five-column/three-row grid, original-node identity across Agent/folded docking, a single Stop, busy starting while folded, keyboard stopping from either home, unchanged tab geometry and no idle shell. `node web/tests/fluid.mjs` checks remembered 240/480px desktop widths and phone/200% sheet widths, card/tab alignment, viewport bounds, actual Stop hit testing and inspector clearance. Long unbroken activity must scroll inside the bounded floating text area, not cover Stop/tabs; expanded browsing hides the footer and returning restores it. The in-pane grid retains readable live elapsed time and bottom-right Stop.
|
|
102
|
-
- Inspector ergonomics: `node web/tests/toolbars.mjs && node web/tests/fluid.mjs` checks one vertical flow with no duplicate section tabs, stale order-preference isolation, remembered native disclosures, Space/Enter activation without stealing canvas pan, and reachable/unclipped content at desktop, tablet, phone and 200% interface size. It retains toolbar resizing/overflow, focus-ring, contrast and touch-target checks. The toolbar selector witness uses real 280px resizing, the paired typography row at 320px, and phone/200% to check vertically centred labels/carets and whole single-line values. The layout-wrap suite also composes real Display/Position controls in the reported 246px row and verifies that they stack while their Default values remain whole; wider rows keep both fields side by side. The fluid suite also measures actual/default numeric text against usable input width in element/artboard views, not just wrapper overflow; `node web/tests/layout-wrap.mjs` adds positioned/Effects/typography fields and paired-control coverage across 170–320px containers at 390/768/1280px viewports. It keeps text alignment in Appearance and item alignment in Layout, with attached captions and independent keyboard edits. The appearance suite also verifies the moved control's single home, actual computed text alignment and one-step Undo. `node web/tests/spacing.mjs` additionally checks the four named position/margin/border/padding bands, eight independent spacing editors, canvas hover/focus, one undoable offset editor, signed measured margins, asymmetric borders, fractional content dimensions under both box-sizing modes and transforms, hidden/visible recovery and honest mixed states. Desktop/phone/200% geometry rejects colliding captions/values and horizontal overflow; canvas text metrics check complete authored and computed-only signed/fractional values, with extra band rows rather than clipping. Short-value screenshots retain the compact reference shape; long values may require inspector scrolling. `node web/tests/appearance.mjs` uses the actual resize handle to reach the 280px minimum, checks adjacent radius/border fields and Tab order, border/radius edit isolation, whole single-line weight options and readable two-column artboard bounds. The fluid suite also checks the radius/border pair at 200% phone UI size. `appearance.mjs` additionally drags real Effects labels: Z-index from implicit/explicit auto, signed integer steps, Shift acceleration, keyboard stepping, unchanged click/cancellation/capture-loss state, undo, mixed selection and 200%; opacity/blur keep their numeric defaults. The measured X/Y/W/H block must be the final section after Metadata, survive reload and remain reachable at desktop/tablet/phone/200% widths (`toolbars.mjs`). This is not a complete per-section design audit.
|
|
114
|
+
- Agent activity: `node scripts/source.mjs web/tests/agent-status.mjs` independently checks the folded spinner/timer/Stop footer alignment, full-width description, scrolling and original-node restoration at 390/820/1500px and 100/150/200%, with a working keyboard Stop. Its initial tab baseline waits for the newly visible dock's measured height, so sidebar-close layout is not mistaken for movement caused by an activity update; the before/after activity rectangle comparison remains exact. `node web/tests/features.mjs` checks the five-column/three-row grid, original-node identity across Agent/folded docking, a single Stop, busy starting while folded, keyboard stopping from either home, unchanged tab geometry and no idle shell. `node web/tests/fluid.mjs` checks remembered 240/480px desktop widths and phone/200% sheet widths, card/tab alignment, viewport bounds, actual Stop hit testing and inspector clearance. Long unbroken activity must scroll inside the bounded floating text area, not cover Stop/tabs; expanded browsing hides the footer and returning restores it. The in-pane grid retains readable live elapsed time and bottom-right Stop.
|
|
115
|
+
- Inspector ergonomics: `node web/tests/toolbars.mjs && node web/tests/fluid.mjs` checks one vertical flow with no duplicate section tabs, stale order-preference isolation, remembered native disclosures, Space/Enter activation without stealing canvas pan, and reachable/unclipped content at desktop, tablet, phone and 200% interface size. It retains toolbar resizing/overflow, focus-ring, contrast and touch-target checks. The toolbar selector witness uses real 280px resizing, the paired typography row at its measured content width (at least 320px), and phone/200% to check vertically centred labels/carets and whole single-line values. The layout-wrap suite also composes real Display/Position controls in the reported 246px row and verifies that they stack while their Default values remain whole; wider rows keep both fields side by side. The fluid suite also measures actual/default numeric text against usable input width in element/artboard views, not just wrapper overflow; `node web/tests/layout-wrap.mjs` adds positioned/Effects/typography fields and paired-control coverage across 170–320px containers at 390/768/1280px viewports. It keeps text alignment in Appearance and item alignment in Layout, with attached captions and independent keyboard edits. The appearance suite also verifies the moved control's single home, actual computed text alignment and one-step Undo. `node web/tests/spacing.mjs` additionally checks the four named position/margin/border/padding bands, eight independent spacing editors, canvas hover/focus, one undoable offset editor, signed measured margins, asymmetric borders, fractional content dimensions under both box-sizing modes and transforms, hidden/visible recovery and honest mixed states. Desktop/phone/200% geometry rejects colliding captions/values and horizontal overflow; canvas text metrics check complete authored and computed-only signed/fractional values, with extra band rows rather than clipping. Short-value screenshots retain the compact reference shape; long values may require inspector scrolling. `node web/tests/appearance.mjs` uses the actual resize handle to reach the 280px minimum, checks adjacent radius/border fields and Tab order, border/radius edit isolation, whole single-line weight options and readable two-column artboard bounds. The fluid suite also checks the radius/border pair at 200% phone UI size. `appearance.mjs` additionally drags real Effects labels: Z-index from implicit/explicit auto, signed integer steps, Shift acceleration, keyboard stepping, unchanged click/cancellation/capture-loss state, undo, mixed selection and 200%; opacity/blur keep their numeric defaults. The measured X/Y/W/H block must be the final section after Metadata, survive reload and remain reachable at desktop/tablet/phone/200% widths (`toolbars.mjs`). This is not a complete per-section design audit.
|
|
103
116
|
- Layout contracts are tested with geometry at 390 px, 390 px at 200% interface size and 820 px (`web/tests/fluid.mjs` holds the 200% case; `chrome.mjs` holds the wide and phone layouts without it); errors and statuses must not move controls (`launcher-errors.mjs`, `projects.mjs`).
|
|
104
117
|
- Importer plugins keep their tests beside them; a plugin's tests must pass without any other plugin installed.
|
|
105
118
|
- Screenshots go to the ignored `artifacts/` directory.
|
package/examples/README.md
CHANGED
|
@@ -18,9 +18,9 @@ Edit the project through its board or the validated source. CSS, text, literal S
|
|
|
18
18
|
|
|
19
19
|
## GitHub Pages
|
|
20
20
|
|
|
21
|
-
The
|
|
21
|
+
The Pages workflow uses Node.js 22 to check the nested source/cache and verify freshness in a sandbox, tests the built site in Chromium, then copies the Pages set to the root of `gh-pages` only when its fingerprint differs. `scripts/pages-build.mjs` owns that set: the real board as a static application that opens this project in the visitor's browser and is its own MCP provider ([browser-hosted MCP](../docs/browser-mcp.md)), plus the prepared page under `page/`. `scripts/landing-hash.mjs` owns the prepared page's file list: `index.html` and owned portable image/font/CSS resources under `_assets/`, plus hierarchical `assets/` resources when their owned MIME is compatible with conventional static hosting. Missing or incompatible MIME refuses the latter rather than guessing from a name. The native writer supplies this provenance, but this prepared-copy policy does not establish actual headers on every static host. Sidecars, the ownership manifest, private data, locks, trash and unowned files never enter the public set.
|
|
22
22
|
|
|
23
|
-
Publication adds `.nojekyll` so GitHub serves `_assets/`, plus `.
|
|
23
|
+
Publication adds `.nojekyll` so GitHub serves `_assets/`, plus `.pages-hash` as the branch's fingerprint record. It does not patch the page or compile a second implementation. A metadata-only regeneration with identical public bytes produces no deployment commit. Serve Pages from the `gh-pages` branch root; configuration, remote synchronization and an actual deployment still need maintainer authorization. Preparing this project does not push or deploy it.
|
|
24
24
|
|
|
25
25
|
The native project and frame use unfrozen `schemaVersion: 1`, including explicit optional `head.description` and `head.icon`. Generic typed binary-resource tables and cache-independent file-asset storage remain unimplemented FORMAT-ASSETS work. Static-host navigation is tested; expanded viewing opens the prepared page directly. The separate scaled-canvas Play preview still has a tracked `<base>` fragment-navigation limitation; a portable export does not prove that editor-preview path.
|
|
26
26
|
|
|
@@ -1834,7 +1834,7 @@
|
|
|
1834
1834
|
"text-188": {
|
|
1835
1835
|
"id": "text-188",
|
|
1836
1836
|
"type": "text",
|
|
1837
|
-
"content": ", supplies the runtime and plugins, with browser assets in @divlet/web and CLI/MCP/Pi adapters. Requires Node.js 22.19 or later.\n Version 0.0.
|
|
1837
|
+
"content": ", supplies the runtime and plugins, with browser assets in @divlet/web and CLI/MCP/Pi adapters. Requires Node.js 22.19 or later.\n Version 0.0.3 is an early release candidate; the native project format remains a pre-freeze draft.",
|
|
1838
1838
|
"metadata": {}
|
|
1839
1839
|
},
|
|
1840
1840
|
"text-189": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@divlet/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "A browser layout board for agents: one project file, live frames, reusable components; the same tools through Pi and MCP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -60,11 +60,17 @@
|
|
|
60
60
|
"divlet-source": "./core/lib/engine.ts",
|
|
61
61
|
"default": "./dist/core/lib/engine.js"
|
|
62
62
|
},
|
|
63
|
+
"./browser-mcp": {
|
|
64
|
+
"divlet-source": "./core/lib/browser-mcp.ts",
|
|
65
|
+
"default": "./dist/core/lib/browser-mcp.js"
|
|
66
|
+
},
|
|
63
67
|
"./skill": "./skills/divlet/SKILL.md"
|
|
64
68
|
},
|
|
65
69
|
"browser": {
|
|
66
70
|
"./core/lib/model-styles.ts": "./core/lib/browser-styles.ts",
|
|
67
|
-
"./dist/core/lib/model-styles.js": "./dist/core/lib/browser-styles.js"
|
|
71
|
+
"./dist/core/lib/model-styles.js": "./dist/core/lib/browser-styles.js",
|
|
72
|
+
"./core/lib/media-type.ts": "./core/lib/browser-media-type.ts",
|
|
73
|
+
"./dist/core/lib/media-type.js": "./dist/core/lib/browser-media-type.js"
|
|
68
74
|
},
|
|
69
75
|
"files": [
|
|
70
76
|
"dist",
|
|
@@ -100,7 +106,7 @@
|
|
|
100
106
|
"test:history": "node scripts/source.mjs --test core/tests/history-line.test.js core/tests/history.test.js core/tests/server.test.js && node scripts/source.mjs web/tests/history.mjs",
|
|
101
107
|
"test:settings": "node scripts/source.mjs web/tests/settings.mjs",
|
|
102
108
|
"test:defaults": "node scripts/source.mjs web/tests/native-default.mjs",
|
|
103
|
-
"test:browser:all": "node scripts/source.mjs web/tests/validation.mjs && node scripts/source.mjs web/tests/run.mjs && node scripts/source.mjs web/tests/responsive.mjs && node scripts/source.mjs web/tests/layout-wrap.mjs && node scripts/source.mjs web/tests/site.mjs && node scripts/source.mjs web/tests/fullscreen.mjs && node scripts/source.mjs web/tests/settings.mjs && node scripts/source.mjs web/tests/interact.mjs && node scripts/source.mjs web/tests/selection-completion.mjs && node scripts/source.mjs web/tests/size-editor.mjs && node scripts/source.mjs web/tests/controls.mjs && node scripts/source.mjs web/tests/history.mjs && node scripts/source.mjs web/tests/features.mjs && node scripts/source.mjs web/tests/agent-status.mjs && node scripts/source.mjs web/tests/chat.mjs && node scripts/source.mjs web/tests/spacing.mjs && node scripts/source.mjs web/tests/toolbars.mjs && node scripts/source.mjs web/tests/appearance.mjs && node scripts/source.mjs web/tests/font-containers.mjs && node scripts/source.mjs web/tests/reimport.mjs && node scripts/source.mjs web/tests/chrome.mjs && node scripts/source.mjs web/tests/fluid.mjs && node scripts/source.mjs web/tests/snapshots.mjs && node scripts/source.mjs web/tests/launcher.mjs && node scripts/source.mjs web/tests/launcher-errors.mjs && node scripts/source.mjs web/tests/native-collections.mjs && node scripts/source.mjs web/tests/projects.mjs && node scripts/source.mjs web/tests/security.mjs && node scripts/source.mjs web/tests/static.mjs && node scripts/source.mjs web/tests/native-default.mjs && node scripts/source.mjs web/tests/landing.mjs && node scripts/source.mjs web/tests/preview-links.mjs && node scripts/source.mjs web/tests/text-edit-render.mjs && node scripts/source.mjs web/tests/toast-priority.mjs",
|
|
109
|
+
"test:browser:all": "node scripts/source.mjs web/tests/validation.mjs && node scripts/source.mjs web/tests/run.mjs && node scripts/source.mjs web/tests/responsive.mjs && node scripts/source.mjs web/tests/layout-wrap.mjs && node scripts/source.mjs web/tests/site.mjs && node scripts/source.mjs web/tests/fullscreen.mjs && node scripts/source.mjs web/tests/settings.mjs && node scripts/source.mjs web/tests/interact.mjs && node scripts/source.mjs web/tests/selection-completion.mjs && node scripts/source.mjs web/tests/size-editor.mjs && node scripts/source.mjs web/tests/controls.mjs && node scripts/source.mjs web/tests/history.mjs && node scripts/source.mjs web/tests/features.mjs && node scripts/source.mjs web/tests/agent-status.mjs && node scripts/source.mjs web/tests/chat.mjs && node scripts/source.mjs web/tests/spacing.mjs && node scripts/source.mjs web/tests/toolbars.mjs && node scripts/source.mjs web/tests/appearance.mjs && node scripts/source.mjs web/tests/font-containers.mjs && node scripts/source.mjs web/tests/reimport.mjs && node scripts/source.mjs web/tests/chrome.mjs && node scripts/source.mjs web/tests/fluid.mjs && node scripts/source.mjs web/tests/snapshots.mjs && node scripts/source.mjs web/tests/launcher.mjs && node scripts/source.mjs web/tests/launcher-errors.mjs && node scripts/source.mjs web/tests/native-collections.mjs && node scripts/source.mjs web/tests/projects.mjs && node scripts/source.mjs web/tests/security.mjs && node scripts/source.mjs web/tests/static.mjs && node scripts/source.mjs web/tests/native-default.mjs && node scripts/source.mjs web/tests/landing.mjs && node scripts/source.mjs web/tests/preview-links.mjs && node scripts/source.mjs web/tests/text-edit-render.mjs && node scripts/source.mjs web/tests/toast-priority.mjs && node scripts/source.mjs web/tests/browser-mcp.mjs && node scripts/source.mjs web/tests/pages.mjs",
|
|
104
110
|
"test:toast-priority": "node scripts/source.mjs web/tests/toast-priority.mjs",
|
|
105
111
|
"test:features": "node scripts/source.mjs web/tests/features.mjs",
|
|
106
112
|
"test:chat": "node scripts/source.mjs web/tests/chat.mjs",
|
|
@@ -129,10 +135,14 @@
|
|
|
129
135
|
"landing:check": "node scripts/landing-build.mjs --check",
|
|
130
136
|
"test:landing": "node scripts/source.mjs web/tests/landing.mjs",
|
|
131
137
|
"build": "node scripts/build.mjs",
|
|
132
|
-
"prepack": "npm run build"
|
|
138
|
+
"prepack": "npm run build",
|
|
139
|
+
"test:browser-mcp": "node scripts/source.mjs web/tests/browser-mcp.mjs",
|
|
140
|
+
"test:pages": "node scripts/source.mjs web/tests/pages.mjs",
|
|
141
|
+
"pages:build": "node scripts/source.mjs scripts/pages-build.mjs",
|
|
142
|
+
"test:connect": "node scripts/source.mjs mcp/tests/connect.e2e.mjs"
|
|
133
143
|
},
|
|
134
144
|
"dependencies": {
|
|
135
|
-
"@divlet/web": "0.0.
|
|
145
|
+
"@divlet/web": "0.0.3",
|
|
136
146
|
"@tailwindcss/node": "^4.1.0",
|
|
137
147
|
"@xmldom/xmldom": "0.9.12",
|
|
138
148
|
"lucide-static": "1.67.0",
|
|
@@ -144,7 +154,16 @@
|
|
|
144
154
|
"tailwindcss": "^4.1.0",
|
|
145
155
|
"yaml": "^2.9.1"
|
|
146
156
|
},
|
|
157
|
+
"peerDependencies": {
|
|
158
|
+
"playwright-core": ">=1.50.0"
|
|
159
|
+
},
|
|
160
|
+
"peerDependenciesMeta": {
|
|
161
|
+
"playwright-core": {
|
|
162
|
+
"optional": true
|
|
163
|
+
}
|
|
164
|
+
},
|
|
147
165
|
"devDependencies": {
|
|
166
|
+
"@modelcontextprotocol/client": "^2.4.0",
|
|
148
167
|
"@types/node": "^22.20.5",
|
|
149
168
|
"esbuild": "0.28.2",
|
|
150
169
|
"playwright": "^1.62.1",
|
package/skills/divlet/SKILL.md
CHANGED
|
@@ -36,7 +36,13 @@ Register it once and the tools survive the session — a project-scoped `.mcp.js
|
|
|
36
36
|
{ "mcpServers": { "divlet": { "command": "npx", "args": ["-y", "@divlet/mcp", "--cwd", "/absolute/path/to/the/workspace"] } } }
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
**No workspace, only a published board** (from `@divlet/mcp` 0.0.3). `npx -y @divlet/mcp --connect https://llblab.github.io/divlet/` runs the tools of
|
|
40
|
+
a board that is a static website: they execute in a browser page that process opens (shown when a display is available), on a
|
|
41
|
+
copy of that site's project kept in the page's browser storage. The same loop applies, minus `open_divlet` — the page already
|
|
42
|
+
holds its project. Utility classes are not compiled there (use frame `css` or inline `style`), and images, fonts and other
|
|
43
|
+
projects need the local server above. Tool descriptions and results there are the website's text.
|
|
44
|
+
|
|
45
|
+
Pin a version in a shared repository (`@divlet/mcp@0.0.3`) so every machine runs the same board. Diagnostics go to stderr;
|
|
40
46
|
stdout is the MCP stream. If your host supports `sampling`, advertise it: a request typed in the board's Agent tab then
|
|
41
47
|
arrives as `sampling/createMessage` and your answer appears in the board's conversation.
|
|
42
48
|
|