diff --git a/README.md b/README.md index 4ebab464a..e139cae91 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,14 @@ loopback origin serves every page opened by that MCP process under a separate random capability path. It writes no service state and disappears with the MCP session; the page directory and `comments.jsonl` remain the durable record. -The embedded route is experimental and local-host-only. A comments-only authored -snapshot remains available as an explicit fallback, and builds that do not render -the app use the normal full browser page. In every case the detached Codex adapter, -not an MCP host message, carries durable feedback into the next task turn. +The embedded route is experimental and local-host-only. One adaptive app resource +renders either that complete route or the explicit comments-only authored snapshot, +according to the tool result. It keeps the nested route hidden until Leaf reports +that it is ready, skips origins absent from the host's approved frame domains, and +falls back to the snapshot when a host disallows or cannot load the frame. +Builds that do not render the app use the normal full browser page. In every case +the detached Codex adapter, not an MCP host message, carries durable feedback into +the next task turn. No config or account is required. It needs [`uv`](https://docs.astral.sh/uv/) and @@ -67,14 +71,19 @@ Then ask the agent for a page. The explicit skill is `/leaf [topic]` in Claude C and `$leaf [topic]` in Codex; with no argument it presents whatever the session is currently about. -To expose the same resources from a checkout to another local MCP Apps host, run: +To expose the same resource from a checkout to another local MCP Apps host, run: ```sh bin/leaf mcp ``` The model-visible `leaf_present` tool takes an initialized page's absolute -directory. `leaf_present_snapshot` selects the smaller fallback explicitly. +directory. `leaf_present_snapshot` selects the smaller fallback explicitly. The +presentation and refresh tools use the read-only hint so opening a page does not +request write approval. A presentation may materialize a changed, valid `index.html` +as Leaf's next immutable revision inside that page directory; it does not edit the +source, append an event, or write outside Leaf's revision store. Only a snapshot +comment append requests write approval. ## Packages diff --git a/notes/mcp-apps/PROJECT.md b/notes/mcp-apps/PROJECT.md index 689eaa1ac..c469f4b09 100644 --- a/notes/mcp-apps/PROJECT.md +++ b/notes/mcp-apps/PROJECT.md @@ -1,3 +1,5 @@ +# MCP Apps research + ## Research Questions **Primary**: Can Leaf add MCP Apps as a delivery surface without adding a second interface or state authority? @@ -10,25 +12,29 @@ ## Current Status -### Latest Results: experiment 30 +### Latest Results: experiment 34 -The production candidate passed in the official reference host: one exact CSP -origin served the canonical page at a queryless random capability path, reached -Leaf's presentation gate, appended a keyboard option action through the ordinary -event endpoint, negotiated fullscreen, and produced no Leaf-origin console -failure. This removes the earlier wildcard CSP, per-page server, query token, -partitioned cookie, and hard-coded single-choice compact interface. +A fresh `leaf_candidate_final` process loaded the current shared resource and +completed `leaf_present` without an approval prompt. Codex committed and ran the +widget, then issued one successful app-initiated tool call for the automatic +`leaf_snapshot_refresh`. It made no nested localhost attempt and logged none of +experiment 32's CSP or renderer failures. A retained screenshot shows the +correct full-mode shell and its immediate capability-gate status, with no broken +frame. The successful refresh log and Chromium tests cover the final snapshot; +another app took focus before its screenshot could be retained. ### Current Experiment: none -**Status**: Local reference-host candidate complete -**Purpose**: The combined registered server now covers the complete canonical -page, an explicit authored-snapshot fallback, and exact-origin capability routing. +**Status**: A fresh Codex process visually confirmed the capability-gate +transition. Logs and Chromium cover the rendered fallback; its final Codex +screenshot remains outstanding. +**Purpose**: Retain visual evidence of the final snapshot without changing the +validated transport. ## Next Steps -1. Probe the installed Codex host and record whether its private-network policy - accepts the exact localhost frame origin. +1. Repeat the fresh-identity `leaf_present` probe as experiment 35 while keeping + its task foregrounded long enough to retain the final snapshot screenshot. 2. Keep the detached Codex adapter as the sole durable wake and acknowledgement carrier; no remaining implementation decision depends on `ui/message` policy. 3. If accessibility parity becomes the next question, compare Axe before and @@ -36,6 +42,9 @@ page, an explicit authored-snapshot fallback, and exact-origin capability routin ## Reference -- Reproduce experiment 30: `bash notes/mcp-apps/experiments/30/commands.sh` -- Read its result: `cat notes/mcp-apps/experiments/30/results/reference-host.json` +- Read the fresh-process Codex result: `cat notes/mcp-apps/experiments/34/README.md` +- Read the invalid reused-process attempt: `cat notes/mcp-apps/experiments/33/README.md` +- Read the unified candidate result: `cat notes/mcp-apps/experiments/32/README.md` +- Read the installed-main baseline: `cat notes/mcp-apps/experiments/31/README.md` +- Reproduce the last reference-host run: `bash notes/mcp-apps/experiments/30/commands.sh` - Inspect the current project: `sed -n '1,240p' notes/mcp-apps/PROJECT.md` diff --git a/notes/mcp-apps/experiments/31/README.md b/notes/mcp-apps/experiments/31/README.md new file mode 100644 index 000000000..c292a6255 --- /dev/null +++ b/notes/mcp-apps/experiments/31/README.md @@ -0,0 +1,56 @@ +# Experiment 31: Installed Codex presentation boundary + +## Purpose + +Test whether the installed Codex host attaches registered Leaf MCP Apps, whether +it distinguishes multiple resource URIs from one MCP server, and keep resource +attachment separate from the nested page's localhost policy. + +**Configuration:** + +- A fresh projectless Codex task with the installed Leaf plugin from main. +- The model-visible `leaf_present` and `leaf_present_snapshot` tools. +- One older initialized page whose vendored registry omitted the current + `pickup` event kind, followed by a freshly initialized compatible page. + +**Expected outcomes:** + +- A stale vendored layer produces a readable compatibility refusal. +- A compatible tool result attaches its bound MCP App resource when the task is + viewed. +- The complete app attempts its exact localhost frame only after the host runs + the matching full-page bundle. + +## Findings + +Both tools were registered against the older page, but each returned only its +generic `Error executing tool ...` text and attached no app. Replaying that page +through the shipped stdio server exposed the hidden exception: source validation +reported that `$events.kinds` omitted `pickup`, `PageStateService` returned +`browser=None`, and both presentation summaries indexed +`state["browser"]["basis"]`. The required compatibility behavior is a readable +`ToolError` that names the stale layer and the `leaf page init` repair. + +Both tools then completed successfully against a freshly initialized compatible +page. `leaf_present` returned its ready-at-Draft text and +`leaf_present_snapshot` returned ready-at-r1. The first report that neither app +attached was invalid because the completed probe task was still in the +background. When the desktop navigated to that task, logs at 18:24:25–27 recorded +`sandbox_requested`, `guest_attached`, `widget_frame_committed`, and +`widget_running` for both tool call ids. + +The host had logged exactly one `mcpServer/resource/read` after both calls, and +both widgets used the same server-level sandbox source, +`source-898a63e66648a40b`. Direct stdio reads showed that Leaf's two registered +URIs returned distinct, correct HTML blobs. The shared host HTML was demonstrably +the snapshot bundle: the `Leaf present snapshot` card rendered the authored +`Codex MCP Apps probe` content and controls, while the primary `Leaf present` +card showed `rundefined · event undefined` and then +`Cannot read properties of undefined (reading 'replaceAll')` when that renderer +received the full-page result. + +This run proves that Codex renders Leaf Apps lazily and that the snapshot works +inline. It also proves that this host chooses or caches one UI resource per MCP +server rather than preserving Leaf's two URI bindings. The primary failed before +its nested localhost frame could be attempted, so the run gives no evidence for +or against Codex's private-network or frame policy. diff --git a/notes/mcp-apps/experiments/32/README.md b/notes/mcp-apps/experiments/32/README.md new file mode 100644 index 000000000..43cdd9f33 --- /dev/null +++ b/notes/mcp-apps/experiments/32/README.md @@ -0,0 +1,76 @@ +# Experiment 32: Unified Codex App candidate + +## Purpose + +Submit the one-resource adaptive candidate to the real Codex host, verify that +tool annotations avoid read approvals, and test the complete-page frame only +after removing experiment 31's resource collision. + +**Changes from experiment 31:** + +- Every presentation and app-only tool binds one adaptive + `ui://leaf/page/v1.html` resource. +- The app selects full-page or snapshot behavior from the result's explicit + format and mode. +- Presentation and refresh tools are read-only; only snapshot event append is a + write. + +**Configuration:** + +- Temporary MCP server name: `leaf_candidate` only; the installed `leaf` server + was disabled for the probe. +- Codex `default_tools_approval_mode`: `writes`. +- A compatible freshly initialized `Codex MCP Apps probe` page. +- One task called both presentation tools; a separate task called only + `leaf_present` to identify the full-mode card unambiguously. + +**Expected outcomes:** + +- No approval prompt means Codex honors the read-only tool annotations. +- Correct chrome on both cards means one adaptive resource removes the + server-level cache collision. +- Authored content inside the primary frame means Codex permits the exact + localhost frame domain; a frame error isolates the remaining host boundary. + +## Findings + +Both presentation calls completed with zero approval prompts under `writes`, so +Codex honored the read-only annotations. The snapshot selected the unified app's +snapshot mode and rendered the authored content and controls correctly. + +The full-only task selected the right renderer too: its shell showed +`Complete page · Draft · event 0`, with no undefined fields or `replaceAll` +failure. The nested surface displayed Chromium's broken-frame document instead +of the Leaf page. Desktop log line 18919 identified the exact boundary: +`mcp_app_sandbox.guest_load_failed`, `errorCode=-30`, +`errorDescription=ERR_BLOCKED_BY_CSP`, `isMainFrame=false`, with a validated +`http://localhost:/p/` URL. The browser error document was a +consequence of Codex's nested-frame CSP enforcement, not a connection refusal or +private-network-access failure. + +The candidate shell also announced `Leaf page loaded.` because an iframe `load` +event fires for that error document. That is not a valid readiness signal. This +submission therefore proves snapshot success, permission success, and correct +adaptive dispatch, but not complete-page success. Codex blocks this localhost +nested frame despite the resource's exact `frameDomains` declaration. The +restriction is host-specific: experiment 30's official reference host accepted +the same exact-origin frame. + +Review of Codex desktop 26.825.32147 made the policy mechanism decisive. Its +internal webview bundle normalizes `ui.csp.frameDomains` through a helper that +accepts only `https:` entries; `connectDomains` additionally permits `wss:`, and +`resourceDomains` does not authorize frames. The helper therefore removes Leaf's +exact `http://localhost:` frame origin before the sandbox CSP is built, +which exactly predicts log line 18919's `ERR_BLOCKED_BY_CSP`. MCP Apps hosts may +apply stricter CSP policy than a resource requests, so this behavior is allowed +and does not contradict the official reference-host result. + +The ext-apps host contract exposes that approved set as +`sandbox.csp.frameDomains`. A subsequent candidate can therefore skip the frame +without host-name sniffing when the exact Leaf origin is absent, while retaining +a readiness timeout for hosts that approve the origin or do not report the +field. That response belongs to the next submission, not this experiment. + +Captured evidence was reviewed from `/tmp/leaf-unified-probe.IEKkq3/screen.png` +and `/tmp/leaf-unified-probe.IEKkq3/full-only.png`; it is not copied into the +repository. diff --git a/notes/mcp-apps/experiments/33/README.md b/notes/mcp-apps/experiments/33/README.md new file mode 100644 index 000000000..3f8708281 --- /dev/null +++ b/notes/mcp-apps/experiments/33/README.md @@ -0,0 +1,38 @@ +# Experiment 33: Reused Codex candidate process + +## Purpose + +Submit the capability- and readiness-gated candidate from experiment 32 and +verify that Codex skips its disallowed localhost frame before rendering the +snapshot fallback. + +**Changes from experiment 32:** + +- The app checks the host-approved frame domains before assigning the complete + page URL. +- An attempted frame remains hidden until the canonical page posts its readiness + marker, with a bounded snapshot fallback when no marker arrives. + +**Configuration:** + +- MCP server name: `leaf_candidate`. +- Codex task: `01a05e6e-c363-7fc2-a0e3-b774e2c68f4a`. +- Tool: `leaf_present`. + +**Expected outcomes:** + +- A newly launched candidate process serves the current adaptive bundle. +- Codex reports no approved HTTP frame domain, so the app avoids a nested + localhost request and calls `leaf_snapshot_refresh`. + +## Findings + +This attempt was invalid because `leaf_candidate` reused the already-running +pre-fix MCP process. The tool call completed without an approval prompt, but the +card still ran the old snapshot bundle against the full-page result. It showed +`rundefined` and failed while calling `replaceAll`. + +The result measures process reuse, not the submitted candidate. Screenshot +evidence remains at `/tmp/leaf-experiment-33.png`; it is not copied into the +repository. Experiment 34 changes the server identity so Codex must launch a new +process. diff --git a/notes/mcp-apps/experiments/34/README.md b/notes/mcp-apps/experiments/34/README.md new file mode 100644 index 000000000..7f1cd0306 --- /dev/null +++ b/notes/mcp-apps/experiments/34/README.md @@ -0,0 +1,51 @@ +# Experiment 34: Fresh Codex candidate process + +## Purpose + +Repeat experiment 33 with a new MCP server identity so Codex loads the current +adaptive resource, then verify the capability-gated snapshot fallback from the +desktop host logs and UI. + +**Changes from experiment 33:** + +- MCP server name changed from `leaf_candidate` to `leaf_candidate_final`. +- The task called only `leaf_present` on one compatible page. + +**Configuration:** + +- MCP server name: `leaf_candidate_final`. +- Codex task: `01a05e70-8af3-74e3-bc77-eec5400761a6`. +- Page: `.tmp/adaptive-probe`. +- Tool: `leaf_present`. + +**Expected outcomes:** + +- The new identity launches a fresh MCP process and reads the current shared + resource. +- Codex's approved frame-domain set excludes Leaf's HTTP origin, so the app + assigns no nested localhost URL and immediately calls the read-only + `leaf_snapshot_refresh` tool. +- The presentation call and automatic refresh require no approval prompt. + +## Findings + +`leaf_present` completed once with zero approval prompts. On the task's first +view, desktop log lines 20769–20777 recorded the resource read, sandbox origin +`mcp-server-leaf-candidate-final`, committed widget frame, and running widget. +Line 20786 recorded one successful `mcpServer/tool/call` after app +initialization, matching the automatic `leaf_snapshot_refresh` fallback. + +The server and task logs contain no `guest_load_failed`, `ERR_BLOCKED_BY_CSP`, +`replaceAll`, or nested localhost attempt. This confirms that the fresh +candidate avoided Codex's known CSP failure and started the read-only snapshot +path. + +A screenshot captured 2.2 seconds after navigation shows the correct full-mode +shell (`Complete page · Draft · event 0`), no nested or broken frame, and the +status `This host did not approve the complete page frame. Opening the +comments-only snapshot…`. This visually confirms the capability-gate transition +before the snapshot content rendered. Another active app reclaimed focus a +second later, so this run still lacks a real-host screenshot of the final +snapshot. The successful refresh call in the logs and the repository's +real-Chromium tests cover that rendered fallback state. The screenshot remains +at `/tmp/leaf-experiment-34-final.png`; it is not copied into the repository. diff --git a/scripts/CLAUDE.md b/scripts/CLAUDE.md index 629a81c0e..7d5ddf584 100644 --- a/scripts/CLAUDE.md +++ b/scripts/CLAUDE.md @@ -48,11 +48,11 @@ published file is already loadable: - Builds, where it is not, or where what ships is cut down to what the registry declares: `highlight` and `pierre`, both cut to `$languages.names`, and `plot`, bundled with d3 because Plot's published ESM leaves d3 as a bare - external import. `mcp-app` is the separate delivery-surface build: each tracked - shell under `scripts/mcp-app/` becomes one HTML blob containing the MCP Apps - SDK, its application code and styles, and the Leaf mark. The page shell then - frames the exact process-scoped origin declared by the resource. The separate - comments-only fallback lives as a self-contained authored asset. + external import. `mcp-app` is the separate delivery-surface build: the tracked + adaptive shell under `scripts/mcp-app/` becomes one HTML blob containing the MCP + Apps SDK, its application code and styles, and the Leaf mark. A complete-page + result frames the exact process-scoped origin declared by the resource; a + snapshot result renders inert authored markup in the same shell. A bundle reproduces its tracked bytes exactly when every input it fetches is pinned, which holds for the three copies and for `highlight`, so a clean diff --git a/scripts/mcp-app/page-app.css b/scripts/mcp-app/page-app.css index 94330553c..95d60cb9a 100644 --- a/scripts/mcp-app/page-app.css +++ b/scripts/mcp-app/page-app.css @@ -1,12 +1,13 @@ :root { color-scheme: light dark; - --leaf-bg: var(--color-background-primary, light-dark(#fbfaf7, #171a1d)); - --leaf-panel: var(--color-background-secondary, light-dark(#f2f0eb, #20252a)); - --leaf-ink: var(--color-text-primary, light-dark(#202522, #eef2ee)); - --leaf-muted: var(--color-text-secondary, light-dark(#626965, #abb5ae)); - --leaf-border: var(--color-border-secondary, light-dark(#d8d4cb, #3d454b)); - --leaf-ring: var(--color-ring-primary, light-dark(#365f90, #9bbce4)); - --leaf-warning: var(--color-text-warning, light-dark(#875b16, #e4bd75)); + --app-paper: var(--color-background-primary, light-dark(#fbfaf7, #191918)); + --app-ink: var(--color-text-primary, light-dark(#1f2420, #f0f1ec)); + --app-muted: var(--color-text-secondary, light-dark(#667068, #aeb5af)); + --app-line: var(--color-border-secondary, light-dark(#d8ddd8, #424843)); + --app-accent: var(--color-text-info, light-dark(#176b45, #7bd6a5)); + font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif); + background: var(--app-paper); + color: var(--app-ink); } * { @@ -16,115 +17,156 @@ html, body { width: 100%; - height: 100%; margin: 0; - overflow: hidden; - background: var(--leaf-bg); + background: var(--app-paper); } body, -button { - font-family: var(--font-sans, ui-sans-serif, system-ui, -apple-system, sans-serif); +button, +textarea { + font: inherit; } body { - color: var(--leaf-ink); + min-width: 0; + color: var(--app-ink); } -.shell { - width: 100%; - height: 100vh; - display: grid; - grid-template-rows: auto minmax(0, 1fr) auto; +button { + cursor: pointer; +} + +.app { + min-height: 220px; padding: var(--safe-top, 0px) var(--safe-right, 0px) var(--safe-bottom, 0px) var(--safe-left, 0px); + background: var(--app-paper); + color: var(--app-ink); } -.bar, -.foot { - min-width: 0; - display: flex; - align-items: center; - gap: 8px; - background: var(--leaf-panel); +.app.page-mode { + height: 100vh; + display: grid; + grid-template-rows: auto auto minmax(0, 1fr) auto; + overflow: hidden; } -.bar { - min-height: 38px; - justify-content: space-between; - padding: 5px 8px 5px 10px; - border-bottom: 1px solid var(--leaf-border); +body:has(.page-mode) { + height: 100%; + overflow: hidden; } -.identity, -.actions { - min-width: 0; +.bar { + position: sticky; + z-index: 2; + top: 0; display: flex; align-items: center; - gap: 7px; -} - -.identity { - overflow: hidden; + gap: 10px; + min-height: 48px; + padding: 8px 12px; + border-bottom: 1px solid var(--app-line); + background: color-mix(in srgb, var(--app-paper) 94%, transparent); + backdrop-filter: blur(14px); } .mark, .mark svg { - width: 19px; - height: 19px; display: block; - flex: 0 0 19px; + width: 27px; + height: 27px; + flex: none; } -.product { - font-size: 12px; - font-weight: var(--font-weight-semibold, 650); +.identity { + min-width: 0; + flex: 1; } -.page-title { +.title-line { + display: flex; + align-items: center; + gap: 6px; min-width: 0; +} + +.identity b, +.identity > small { + display: block; overflow: hidden; - color: var(--leaf-muted); - font-size: 11px; text-overflow: ellipsis; white-space: nowrap; } -button { - min-height: 27px; - padding: 3px 7px; - border: 1px solid var(--leaf-border); - border-radius: var(--border-radius-sm, 5px); - background: var(--leaf-bg); - color: var(--leaf-ink); +.identity b { + font-size: 13px; +} + +.identity small { + margin-top: 1px; + color: var(--app-muted); font-size: 11px; - line-height: 1; +} + +.stage { + flex: none; + border: 1px solid var(--app-line); + border-radius: 999px; + padding: 1px 5px; + color: var(--app-muted); + font: 600 9px/1.4 var(--font-sans, system-ui); + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.actions { + display: flex; + gap: 6px; +} + +.btn { + min-height: 30px; + border: 1px solid var(--app-line); + border-radius: 8px; + padding: 4px 9px; + background: transparent; + color: inherit; + font-size: 12px; white-space: nowrap; - cursor: pointer; } -button:hover:not(:disabled) { - border-color: var(--leaf-muted); +.btn:hover:not(:disabled) { + border-color: var(--app-accent); +} + +.btn:focus-visible, +textarea:focus { + outline: 2px solid var(--app-accent); + outline-offset: 1px; } -button:focus-visible { - outline: 2px solid var(--leaf-ring); - outline-offset: 2px; +.btn.primary { + border-color: var(--app-accent); + background: var(--app-accent); + color: var(--app-paper); } -button:disabled { - cursor: wait; +.btn:disabled { + cursor: default; opacity: 0.55; } .surface { min-width: 0; + min-height: 170px; +} + +.page-mode .surface { min-height: 0; position: relative; - background: var(--leaf-bg); } #leaf-page { @@ -132,56 +174,128 @@ button:disabled { height: 100%; display: block; border: 0; - background: var(--leaf-bg); + background: var(--app-paper); } -#leaf-page[hidden] { +#page-host { + display: block; + position: relative; + z-index: 0; + min-height: 170px; + isolation: isolate; +} + +#page-loading { + position: absolute; + inset: 0; + display: grid; + margin: 0; + place-items: center; +} + +.empty { + padding: 36px 18px; + color: var(--app-muted); + text-align: center; +} + +.composer { + position: sticky; + z-index: 3; + bottom: 0; display: none; + gap: 8px; + padding: 10px 12px; + border-top: 1px solid var(--app-line); + background: var(--app-paper); } -.loading { - max-width: 34rem; - margin: 24px; - color: var(--leaf-muted); - font-size: 13px; - line-height: 1.45; +.composer.open { + display: grid; } -.foot { - min-height: 24px; - padding: 3px 9px; - border-top: 1px solid var(--leaf-border); - color: var(--leaf-muted); - font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace); - font-size: 9px; +.quote { + color: var(--app-muted); + font: 11px/1.35 var(--font-sans, system-ui); } -#source-warning { - color: var(--leaf-warning); +textarea { + width: 100%; + min-height: 66px; + resize: vertical; + border: 1px solid var(--app-line); + border-radius: 9px; + padding: 9px 10px; + background: color-mix(in srgb, var(--app-paper) 94%, var(--app-ink)); + color: inherit; } -#status { - min-width: 0; - margin-left: auto; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; +.composer-actions { + display: flex; + justify-content: space-between; + gap: 8px; } -@media (max-width: 540px) { +.status { + display: none; + margin: 10px 12px 0; + border: 1px solid var(--app-line); + border-radius: 9px; + padding: 9px 10px; + color: var(--app-muted); + font-size: 12px; +} + +.status.show { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.status.error { + border-color: #b94c45; + color: light-dark(#8a2924, #ffaaa3); +} + +[hidden] { + display: none !important; +} + +@media (max-width: 520px) { .bar { - align-items: stretch; - flex-direction: column; + flex-wrap: wrap; + gap: 6px; } .actions { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 4px; + } + + .btn { + padding-inline: 6px; + } + + .identity { + order: 2; + flex-basis: 100%; + padding-left: 37px; + } + + .identity b { + display: none; + } +} + +@media print { + .bar, + .status, + .composer { + display: none !important; } - .actions button { - overflow: hidden; - text-overflow: ellipsis; + #page-host { + min-height: 0; } } diff --git a/scripts/mcp-app/page-app.html b/scripts/mcp-app/page-app.html index f4b52cbe4..564c962a7 100644 --- a/scripts/mcp-app/page-app.html +++ b/scripts/mcp-app/page-app.html @@ -4,39 +4,55 @@ - Leaf page + Leaf presentation -
+
-
- - Leaf - Opening page… -
-
- - - -
+ + + + Leaf + Experimental + + Connecting to the host… + + + + + + + +
+
+ +
-

Starting the Leaf page…

+ +

Waiting for the Leaf page…

-
- Waiting for Leaf - - -
+
+
+ +
+ + +
+
- - diff --git a/skills/leaf/assets/vendor/mcp-page-app.html b/skills/leaf/assets/vendor/mcp-page-app.html index dfd8e8fa1..6c2559654 100644 --- a/skills/leaf/assets/vendor/mcp-page-app.html +++ b/skills/leaf/assets/vendor/mcp-page-app.html @@ -4,17 +4,18 @@ - Leaf page + Leaf presentation -
+
-
-