Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
42fa84d
feat(hub-client): consume engine captures + per-doc clear results (bd…
cscheid Jun 30, 2026
03cd983
docs(changelog): hub-client capture consumption + clear results (bd-s…
cscheid Jun 30, 2026
7b82387
feat(hub-client): execution request + capability beacon channel (bd-s…
cscheid Jun 30, 2026
0e68f61
docs(changelog): execution capability beacon indicator (bd-sfet3264)
cscheid Jun 30, 2026
dfdf308
docs(plan): BearerDialer feasibility spike — viable, no samod fork ch…
cscheid Jun 30, 2026
1e9611a
docs(plan): lock Phase 3 decisions — stdio-pipe token bridge + temp-d…
cscheid Jun 30, 2026
9f47be5
feat(provider): BearerDialer + join-and-list a hub as a client peer (…
cscheid Jun 30, 2026
5b31827
feat(provider): q2 provide-hub subcommand + Node auth bridge (bd-sfet…
cscheid Jun 30, 2026
a2b9af3
docs(plan): Phase 4 (execute-on-request) design + open decisions (bd-…
cscheid Jun 30, 2026
4150eb2
docs(plan): lock Phase 4 decisions — provider-only authz, single-exec…
cscheid Jun 30, 2026
3f23865
docs(plan): make the single-executor v1 limitation explicit (bd-sfet3…
cscheid Jul 1, 2026
f19e00b
feat(provider): execute-on-request + capability beacon (bd-sfet3264 P…
cscheid Jul 1, 2026
76a0116
feat(hub-client): Run button — request execution from the editor (bd-…
cscheid Jul 1, 2026
6e279c8
docs(hub-client): changelog for the preview Run button (bd-sfet3264 P…
cscheid Jul 1, 2026
c4a83d5
docs(plan): Phase 4 complete (4a provider execute + 4b Run button) (b…
cscheid Jul 1, 2026
a3e6446
feat(provide-hub): --token/QUARTO_HUB_TOKEN dev escape hatch + local …
cscheid Jul 1, 2026
a9aa582
docs(e2e): lead with public sync server; fix project-mode foreign-doc…
cscheid Jul 1, 2026
aa00887
test(provider): reproduce the JS-authored-doc sync failure (bd-bm0vaetl)
cscheid Jul 1, 2026
12691a3
test(interop): pin the real root cause — JS stores map string-values …
cscheid Jul 1, 2026
ac1457d
fix(hub): read Text-valued file ids so hub-client projects materializ…
cscheid Jul 1, 2026
b4b35fe
docs(plan): html-format capture display fix (bd-uy4uygha)
cscheid Jul 1, 2026
449f93b
feat(quarto-core): splice engine captures into the HTML render (bd-uy…
cscheid Jul 1, 2026
9e8a321
feat(wasm): splice captures in the format:html render branches (bd-uy…
cscheid Jul 1, 2026
6d4d0ba
feat(preview-runtime): forward captureGzJson through renderToHtml (bd…
cscheid Jul 1, 2026
465f41d
feat(hub-client): show executed output in the default format:html pre…
cscheid Jul 1, 2026
0633786
docs(hub-client): changelog for format:html capture display (bd-uy4uy…
cscheid Jul 1, 2026
deee0ed
docs(plan): bd-uy4uygha complete — format:html capture display verifi…
cscheid Jul 1, 2026
0b13dbc
feat(hub-client): merge preview executor + capture bars into one stat…
cscheid Jul 2, 2026
c825170
docs(hub-client): changelog for merged preview status line (bd-yai4w8ly)
cscheid Jul 2, 2026
354d696
docs(hub-execution-e2e): add knitr harness doc; record jupyter splice…
cscheid Jul 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

163 changes: 163 additions & 0 deletions claude-notes/hub-execution-e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# End-to-end test: run code from hub-client via a `q2` executor

A hands-on test of the remote code-execution feature (bd-sfet3264, Phases
4a + 4b): you click **Run** in the hub-client editor and a connected `q2`
process executes the document's code and streams the output back into the
preview — the path a real collaborator would use.

You run hub-client locally (`npm run dev`) plus a `q2 provide-hub` executor.
Both talk to an automerge **sync server**, which stores and relays the project
docs (index, files, captures) and the ephemeral run requests/beacons. There are
two ways to provide that sync server — pick one:

```
┌───────────────┐ automerge sync (index+file+capture docs, ephemeral msgs)
│ hub-client │◄─────────────────────┐
│ (npm run dev) │ │
│ Run button ──┼──── exec/request ─────┤ ┌──────────────────────┐
│ shows output │◄─── capture doc ──────┤◄─►│ sync server │
└───────────────┘ │ │ (A: sync.automerge.org
┌──────────────────────┐│ │ B: local q2 hub) │
│ q2 provide-hub ├┘ └──────────────────────┘
│ --allow-all │ runs engines, writes captures
└──────────────────────┘
```

## Option A — public sync server (simplest; verified)

Uses `wss://sync.automerge.org` (hub-client's built-in default) as the sync
server. **No `q2 hub` needed** — the public server stores and relays every doc
any peer creates, so a project you make in hub-client is visible to the
provider. Requires internet, and note that **your document text + code are
pushed to a public server** (fine for throwaway test code; use Option B if you
care).

### Prerequisites

- A built `q2` (the commands use `cargo run`, which builds on demand).
- `npm install` from the **repo root**.
- **A real engine matching the document:** `engine: jupyter` needs
`python3` + `jupyter` with a `python3` kernel; `engine: knitr` needs `R`.
- **Build the hub-client WASM once** (the dev server doesn't):
`cd hub-client && npm run build:wasm`. Without it the preview won't render.

### Steps

1. **hub-client** (terminal 1), on its default sync server:
```bash
cd hub-client
npm run dev # do NOT set VITE_DEFAULT_SYNC_SERVER
```
Open http://localhost:5173, **create a new project**, and add a document
with an executable cell — front matter **must** declare an engine:
```
---
title: demo
engine: knitr # or: jupyter
---

```{r}
cat(1, 2, 3)
```
```
> **The `engine:` key is required.** Without it the cell renders as source,
> no Run button appears, and nothing executes.

2. Get the project's **index-document id**: click **Share** in hub-client; the
id is the part after `#/share/` in the URL it shows. (Shortcut: you can pass
the *whole* Share URL to the provider — it extracts the id.)

3. **provider** (terminal 2):
```bash
# from the repo root
cargo run --bin q2 -- provide-hub --server wss://sync.automerge.org --allow-all --token dev <indexDocId>
```
- `--token dev` skips the interactive OAuth bridge (the public server ignores
the bearer). **Local testing only.**
- `--allow-all` opts this machine in to running code for anyone in the
session. Without it the provider is *fail-closed* (connect + list + exit).

You should see `Connected. N file(s)…` (N > 0) and `Execution ENABLED…`.

4. In the browser: open the document, switch to the **preview**, and click
**Run**. The button shows **Executing…**; within a moment the preview shows
the executed output in place of the source. Edit the cell and **Re-run** to
see it update; a staleness note appears when the code changed since the last
run.

## Option B — fully local (offline / private): `q2 hub`

Runs a local sync server so nothing leaves your machine. The example project
(`project/hello.qmd`, `engine: jupyter`) and the `start-local-hub.sh` helper
are for this path.

**Important:** `q2 hub --project <dir>` (project mode) only serves *its own*
watched project. So you must **open that project via the Share URL** — do **not**
create a new project in hub-client (a fresh hub-client project isn't stored by a
project-mode hub, and the provider will see `0 file(s)`).

```bash
cd claude-notes/hub-execution-e2e
./start-local-hub.sh # builds q2, starts the no-auth hub, prints the URLs
```

It prints the project's index id (from `GET /health`) and the exact commands
for the other two terminals:

- **hub-client**, pointed at the local hub:
```bash
cd hub-client
VITE_DEFAULT_SYNC_SERVER=ws://127.0.0.1:3031 npm run dev
```
Then open the printed **Share URL** (`…/#/share/<ID>?server=ws://127.0.0.1:3031&file=hello.qmd&name=Local%20demo`) — this opens the hub-owned project, not a new one.
- **provider**:
```bash
cargo run --bin q2 -- provide-hub --server ws://127.0.0.1:3031 --allow-all --token dev <ID>
```

`q2 hub` runs with **no auth** (no `--oidc-client-id`), and hub-client runs with
auth off because `VITE_GOOGLE_CLIENT_ID` is unset — so there's no login screen.

(If you'd rather create projects freely in hub-client while staying local, run a
general relay instead of project mode: `q2 hub --no-project --port 3031`. It
stores/relays arbitrary docs the way `sync.automerge.org` does.)

## Troubleshooting

- **Provider prints `0 file(s)` / `project discovery failed: … No such file`:**
the provider can't see the project's files. In Option B this means you created
a *new* project instead of opening the hub-owned one via the Share URL (a
project-mode hub only serves its own project). Use the Share URL, or switch to
Option A / `q2 hub --no-project`.
- **No Run bar, only "Executor online" (or nothing):** the document has no
executable cell — check the `engine:` front-matter key and that the fence is
```` ```{r} ```` / ```` ```{python} ````, not ```` ```r ```` or ```` ```{.r} ````.
- **No "Executor online" at all:** the provider isn't connected to the *same*
sync server as hub-client, or against a different index id. Confirm the
provider printed `Execution ENABLED` and `N file(s)` with N > 0.
- **Run does nothing / error in the bar:** the engine isn't installed or failed;
the provider terminal logs `exec request failed …`. Sanity-check the engine
standalone: `cargo run --bin q2 -- render <doc>.qmd --to html` should contain
the computed output.
- **Preview blank / won't render:** build the WASM — `cd hub-client && npm run build:wasm`.

## Notes & caveats

- **Single executor.** Two `provide-hub` processes on one project both execute
every request (v1 limitation; see the plan's "Known limitations").
- **`--allow-all` is the only mode wired today.** The safer provider-only
default (only your own requests run) needs Phase 5; here any peer can trigger
execution — fine for a solo test.
- **Every run creates a fresh capture doc** (always-fresh execution); old
capture docs accumulate until server-side GC exists (Phase 5/6).
- Design + phase details: `claude-notes/plans/2026-06-29-remote-execution-provider.md`.

### Verified directly while writing this

- Provider dials `wss://sync.automerge.org` (TLS) and syncs; an absent doc
returns a fast "not found" (no hang).
- Provider against a hub-owned local project lists its files; the Jupyter/knitr
engines execute on this machine (`q2 render`, same registry the provider uses).
- The provider execute loop (receive `exec/request` → run engine → write capture
back) is covered by `crates/quarto-hub-provider/tests/integration/execute.rs`.
- The browser Run-click is the manual last mile this harness drives.
4 changes: 4 additions & 0 deletions claude-notes/hub-execution-e2e/project/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Hub project-mode data (index doc + synced file docs) is generated at runtime.
.quarto/
*.html
*_files/
24 changes: 24 additions & 0 deletions claude-notes/hub-execution-e2e/project/hello.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
title: Local execution demo
engine: jupyter
---

This document has an executable Python cell. When a `q2 provide-hub` executor
is connected, the preview shows a **Run** button; clicking it runs the code on
the executor's machine and splices the output back into every collaborator's
preview.

The `engine: jupyter` line in the front matter above is what makes the cell
executable — without an `engine:` key the cell renders as source only. (For R,
use `engine: knitr` and an `{r}` cell instead.)

```{python}
2 + 3
```

You can add more cells and re-run:

```{python}
import sys
f"python {sys.version_info.major}.{sys.version_info.minor} on the executor"
```
17 changes: 17 additions & 0 deletions claude-notes/hub-execution-e2e/project/r-demo.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
title: R execution demo
engine: knitr
---

An R cell executed via the connected `q2` provider (engine: knitr).

```{r}
1 + 1
```

A second cell to confirm output splicing:

```{r}
cat("hello from", R.version.string, "\n")
sum(1:10)
```
60 changes: 60 additions & 0 deletions claude-notes/hub-execution-e2e/start-local-hub.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
#
# Start a local, no-auth Quarto hub watching the example project, then print
# the project's index-document id and the exact URLs/commands for the other
# two processes (hub-client dev server + q2 provide-hub executor).
#
# See README.md in this directory for the full walkthrough.
#
# Usage:
# ./start-local-hub.sh # hub on port 3031
# PORT=4000 ./start-local-hub.sh

set -euo pipefail

PORT="${PORT:-3031}"
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="$(cd "$HERE/../.." && pwd)"
PROJECT="$HERE/project"
Q2="$REPO/target/debug/q2"

echo "Building q2 (if needed)…"
( cd "$REPO" && cargo build --bin q2 )

echo "Starting hub on 127.0.0.1:$PORT (no auth), watching $PROJECT …"
"$Q2" hub --project "$PROJECT" --port "$PORT" &
HUB_PID=$!
trap 'kill "$HUB_PID" 2>/dev/null || true' EXIT

# Wait for the hub to answer /health, then read the project's index doc id.
until curl -sf "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; do
sleep 0.3
done
ID="$(curl -s "http://127.0.0.1:$PORT/health" \
| sed 's/.*"index_document_id":"\([^"]*\)".*/\1/')"

cat <<EOF

────────────────────────────────────────────────────────────────────────
Hub is ready on ws://127.0.0.1:$PORT (no auth — this is local only)
Project index-document id:
$ID

Terminal 2 — hub-client dev server (point it at the local hub):
cd "$REPO/hub-client"
VITE_DEFAULT_SYNC_SERVER=ws://127.0.0.1:$PORT npm run dev

Then open this URL in the browser (opens the example project):
http://localhost:5173/#/share/$ID?server=ws://127.0.0.1:$PORT&file=hello.qmd&name=Local%20demo

Terminal 3 — the execution provider (offers THIS machine to run the code):
cd "$REPO"
cargo run --bin q2 -- provide-hub --server ws://127.0.0.1:$PORT --allow-all --token dev $ID

Then in the browser: open hello.qmd, switch to the preview, and click Run.
────────────────────────────────────────────────────────────────────────

Hub running (pid $HUB_PID). Press Ctrl-C to stop.
EOF

wait "$HUB_PID"
Loading
Loading