Remote code-execution provider for hub sessions (bd-sfet3264)#357
Open
cscheid wants to merge 30 commits into
Open
Remote code-execution provider for hub sessions (bd-sfet3264)#357cscheid wants to merge 30 commits into
cscheid wants to merge 30 commits into
Conversation
…-sfet3264 Phase 1) Bring server-recorded engine execution output to the shared, persistent hub-client editor. Until now only q2-preview's embedded SPA showed executed code output; hub-client rendered source-only. This phase makes hub-client a *consumer* of the existing automerge-native capture transport (capture binary docs referenced by the IndexDocument's CaptureRef sidecar), and adds the per-document "clear results" affordance. No executor yet — that's Phase 3/4. WASM (out-of-workspace crate): - render_page_in_project_with_attribution gains a 4th capture_gz_json param. The inner render helpers already accepted both captures and attribution; the attribution entry simply hardcoded captures=Vec::new(). Now it parses the gzipped EngineCapture[] (same wire format as render_page_for_preview) and threads it through, so executed output is spliced alongside attribution. render_page_in_project forwards None. preview-runtime: - renderPageInProjectWithAttribution wrapper + binding gain captureGzJson. - New clearCapture(path) re-export. quarto-sync-client: - SyncClient.clearCapture(path): a pure CRDT map-key delete of the captures sidecar entry. Needs no executor and no server round-trip; removal syncs to every peer. Distinct from re-execute (replaces) and staleness (flags). The binary-doc bytes are left for separate server GC (samod has no document-delete API). hub-client: - App.tsx holds the captures sidecar (onCapturesChange) and threads it App -> Editor -> PreviewRouter -> ReactPreview (mirroring identities). - ReactPreview fetches the active doc's capture bytes via getBinaryDocById (keyed on captureDocId, not content) and passes them to the render call; re-renders when the capture changes. - ClearCaptureControl: a two-step inline confirmation (naming the collaborator-wide effect) shown only when the active doc has a capture. Tests (TDD, RED->GREEN): - captureSplice.wasm.test.ts: a real gzipped capture spliced through the real render entry; no-capture baseline; capture+attribution coexistence. - ReactPreview.capture.integration.test.tsx: capture bytes reach the render call's 4th arg; no fetch when absent. - ClearCaptureControl.integration.test.tsx: visibility + confirm/cancel. - client.test.ts: clearCapture removes the entry, leaves siblings, no-op when absent. Design + checklist: claude-notes/plans/2026-06-29-remote-execution-provider.md The full in-browser E2E is folded into Phase 4 (faithful once the real executor writes captures, avoiding throwaway capture-injection scaffolding). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fet3264) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fet3264 Phase 2) Add the ephemeral, project-scoped channel for the remote-execution-provider feature: a capability beacon (executor announces "online with engines X") and an execute-now request, both riding Automerge ephemeral messaging on the index DocHandle (so one channel reaches every peer regardless of the active file). Per D2 this is the low-latency/non-persisted half; durable status stays in the CaptureRef sidecar. Scope (locked with user): the channel + API + capability detection only. No executor produces beacons yet (Phase 4), so this is dormant in practice today; the user-facing Run affordance and the claim/heartbeat/--force protocol (D5) also land in Phase 4. quarto-sync-client: - SyncClient.getIndexHandle(): exposes the index DocHandle for project-scoped ephemeral messaging (mirrors the existing per-file getFileHandle surface). preview-runtime: - getIndexHandle() re-export. hub-client: - services/executionChannel.ts: the cross-language wire contract (the Rust executor mirrors it in Phase 4) — kind-discriminated, exec/-namespaced beacon + request messages — plus pure helpers (builders, parse/validate, applyBeacon/pruneExecutors with the 1.5x-interval staleness from D2), and a stateful createExecutionChannel that wires broadcast/subscribe + a prune timer. BEACON_INTERVAL_MS=3000, BEACON_TIMEOUT_MS=4500. - hooks/useExecutionChannel.ts: starts/stops the channel with the connection/project, returns the live-executor set. - App holds liveExecutors and passes executorsOnline to Editor, which shows a minimal read-only "Executor online" bar (the seam for Phase 4's Run UI). Tests (TDD, RED->GREEN): - client.test.ts: getIndexHandle null before connect, handle after. - executionChannel.test.ts: wire format + pure helpers (13); stub-responder service tests against a fake DocHandle (5) — beacon appears/expires, request shape round-trips, self-beacon ignored, null when disconnected. - useExecutionChannel.integration.test.tsx: beacon -> executor; teardown. Plan + checklist: claude-notes/plans/2026-06-29-remote-execution-provider.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ange (bd-sfet3264) Verified against the actual dep (quarto-dev git fork samod@q2 v0.9.0): Repo::dial + the public Dialer trait + Transport::new let a BearerDialer inject Authorization: Bearer per (re)connect with a fresh token; only the ~25-line ws_to_bytes mapping needs replicating in-crate. D1=C proceeds unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ir materialization (bd-sfet3264) Auth-bridge hand-off uses the Node child's stdout/stdin pipes (cross-platform; not a Unix-only extra fd): Rust q2 provide-hub spawns the Node auth helper, which streams Bearer tokens on stdout. Executor materializes the project to a fresh temp dir from the VFS per run. Adds the Phase 3 checklist (BearerDialer + TokenSource, client-peer join+list with a dev token, then the subcommand + Node auth bridge). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bd-sfet3264 Phase 3A/3B) New crate quarto-hub-provider — the Rust half of the hybrid (D1=C) execution-provider: it joins a hub's automerge session as a samod client peer over an authenticated websocket. - BearerDialer (impl samod::Dialer): reimplements the small tungstenite connect path with an Authorization: Bearer header, fetching a fresh token from a TokenSource on every (re)connect (the spike-confirmed approach; no samod fork change). The ~25-line ws<->bytes mapping is replicated in-crate (inbound_to_bytes / outbound_to_ws) since samod's ws_to_bytes is private. - TokenSource trait + StaticTokenSource (dev/test token before the Phase 3C Node auth bridge). - join_and_list_files: memory-storage Repo, dial(BearerDialer), established() with timeout, IndexDocument::load -> sorted file list. The narrow Phase 3 deliverable: prove the authenticated sync path before any execution. Tests: - 6 unit tests: ws message mapping (binary<->bytes, drop control frames, text is a protocol error) + the handshake request carries the Bearer header and rejects an illegal-byte token. - integration: a bare samod acceptor behind a real tungstenite ws server + a seeded index doc; the provider connects over the real BearerDialer transport, syncs, and lists the files. Clippy -D warnings clean. The authenticated-acceptance path (hub validates the JWT) is covered by quarto-hub's auth_bearer tests and the upcoming Phase 3C real-binary run. Plan: claude-notes/plans/2026-06-29-remote-execution-provider.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…3264 Phase 3C)
Complete the hybrid (D1=C) auth path: a `q2 provide-hub` subcommand that
authenticates with the hub (via the existing OAuth machinery) and joins the
project's automerge session, then — for Phase 3 — lists the project's files.
Execution-on-request is Phase 4.
Auth bridge (cross-platform stdio, no Unix-only fd tricks):
- TS: new thin `auth-stream` entry in quarto-hub-mcp. `auth-stream/protocol.ts`
is the transport-agnostic core (`runTokenStream`) — emits the initial Bearer
on stdout, services `{"type":"refresh"}` from stdin, frames errors — and is
unit-tested with stubs (7 cases). `auth-stream.ts` wires it to the real
auth/* modules (CredentialStore + RefreshManager + AuthToolsState with a
stub connectionManager; signs in on ReauthRequired). The Bearer is the OIDC
id_token — byte-identical to what the hub validates.
- bundle: scripts/bundle.mjs factors shared esbuild options and emits a second
entry, dist-bundle/auth-stream.mjs, riding the existing include_dir embed.
Rust:
- token_bridge.rs (NodeBridge): reuses quarto-mcp-launcher (node discovery +
bundle extract + env injection; a few new pub use re-exports) to spawn
`node auth-stream.mjs` with piped stdio (stderr inherited so the user sees
the sign-in URL); a stdout-reader task parses token/error frames into a
cache; impl TokenSource feeds the BearerDialer a fresh token per reconnect.
- provide_hub.rs + main.rs: the `q2 provide-hub <share-url|id> [--server]`
subcommand → NodeBridge → join_and_list_files.
Tests:
- TS protocol (7), Rust frame parser (4), share-URL/server parsing (3).
- Integration: the NodeBridge spawns the *real* bundled helper with no creds
and the bridge surfaces its error frame (ran, not skipped). Clippy -D
warnings clean (async mutex on the helper stdin).
The interactive real-OAuth E2E (browser sign-in → list) is manual; the
automated coverage above stands in for it. Like q2 mcp, q2 provide-hub needs
the hub-mcp bundle built (cargo xtask build-hub-mcp-bundle).
Plan: claude-notes/plans/2026-06-29-remote-execution-provider.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sfet3264) Architecture for the payoff phase: provider subscribes to the exec channel, materializes the project to a temp dir from the VFS, runs record_capture_cached (the native engine path), writes the capture binary doc + CaptureRef sidecar (reusing quarto-preview/quarto-hub fns) so all peers see the output, and broadcasts the capability beacon; hub-client gains the Run button. Open decisions recorded: D4 v1 authz posture, D5 claims now/later, 4a/4b sub-split, re-execution cache. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…utor v1, always-fresh run (bd-sfet3264) D4: execution restricted to the providing user's own per-project actor id by default; --allow-all opens it to everyone with document access (noun: provider, not owner). D5: single-executor v1 — two providers double-execute (documented); true mutual exclusion needs server arbitration (Phase 6). Split 4a (execute + write capture, scripted/testable) / 4b (Run UI). Always force a fresh run (uncached record_capture) due to side effects. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…264) Add a prominent 'Known limitations (v1)' section: two providers on one project double-execute (duplicate side effects); peer-to-peer mutual exclusion is impossible without a server arbiter (Phase 6). Also notes capture-doc orphaning (no GC yet) and the manual interactive-OAuth E2E. Accepted for v1 by the user. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hase 4a) The native q2 provider now runs code on request. After joining a hub it broadcasts an `exec/beacon` on the index DocHandle's ephemeral channel every 3s, and on an `exec/request` it materializes the project's VFS to a fresh temp dir, runs the engines via the uncached `record_capture` (decision #4: always a fresh run), and writes the result back as a capture binary doc + `CaptureRef` sidecar — the transport the Phase 1 editor already consumes. Every piece reuses Phases 1–3: the capture write-back mirrors quarto-preview's re_execute.rs. New in crates/quarto-hub-provider: - exec_channel.rs: Rust mirror of the TS execution wire format. `ExecMessage` is an internally-tagged enum (camelCase renames) encoded with ciborium — the browser's DocHandle.broadcast CBOR-encodes payloads (cbor-x, useRecords:false → standard CBOR maps), so ciborium interops byte-for-byte. A unit test asserts the CBOR shape is a map with the exact keys the TS parseExecMessage checks. - materialize.rs: read-only VFS → temp-dir materializer (text via doc.text, binary via resource::read_binary_content), with a `..`/absolute path guard. - execute.rs: `Provider` (Arc-shared) with a `run` loop = concurrent beacon-broadcast + ephemeral request-listen; `execute_document` (materialize → discover → record → write capture doc → set_capture); `AuthzPolicy` (AllowAll/Deny). - join.rs refactored to expose `join()` returning the live (Repo, IndexDocument); `join_and_list_files` now builds on it. Authorization (Phase 4a: mechanism-first, fail closed, per user 2026-07-01): `q2 provide-hub` is fail-closed by default (connect + list + exit) and serves requests only with `--allow-all`. The real provider-only default (gate on the provider's own per-project actor id) needs the hub to accept a Bearer on /auth/actor and lands in Phase 5; `AuthzPolicy` is the seam. Verified end-to-end: a scripted integration test drives the real provider loop against a real samod acceptor — editor broadcasts exec/request → provider writes a capture that syncs back to the server (gunzipped to an EngineCapture with engine_name == "test-passthrough"); a Deny companion writes nothing. Full `cargo xtask verify` green (27 provider tests; workspace build/test; WASM rebuild; hub-client tests). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sfet3264 Phase 4b)
The editor can now ask a connected `q2 provide-hub` executor to run a document.
When an executor's capability beacon is live and the active document has
executable cells, the preview pane shows a Run/Re-run control that broadcasts an
`exec/request` on the index handle's ephemeral channel; the provider executes
and writes the capture back, which the Phase 1 consumption path already splices
into the preview.
- executableCells.ts: `hasExecutableCells(content)` — gates the affordance
(braced engine fences ```` ```{r} ````, not the dotted display-class form).
- useExecutionChannel now returns `{ executors, requestExecution }` (the channel
is held in a ref so the callback is stable); App threads `requestExecution`
to Editor.
- RunControl.tsx: presentational Run/Re-run affordance reflecting the durable
CaptureRef status — disabled "Executing…" while a local pending flag or
`state: running`; inline `lastError` on `state: error`; a "code changed" note
on staleness. Pending clears when a new captureDocId arrives, on error, or
after a 30s timeout (the ephemeral request may reach no executor). The UX
mirrors q2-preview-spa's StaleCaptureOverlay but triggers via automerge, not
loopback HTTP.
- Editor shows RunControl when `executorsOnline && hasExecutableCells(content)`,
keeping the plain "Executor online" bar for non-executable docs.
Tests: unit +5 (executableCells), integration +9 (useExecutionChannel run
request/offline; RunControl states). Full hub-client suite green (unit 685 /
integration 95 / wasm 124); `npm run build:all` (strict tsc -b + vite) green.
No faithful browser E2E (same rationale as Phase 1G): a real click-through needs
a browser *and* a live provider against a shared hub (interactive OAuth + a
running executor), which can't be automated here. The scripted Phase 4a test
proves the provider half E2E; these tests prove the editor wiring.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hase 4b) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d-sfet3264) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e2e harness (bd-sfet3264)
`q2 provide-hub --token <bearer>` (or the QUARTO_HUB_TOKEN env) uses a
StaticTokenSource instead of spawning the interactive Node OAuth bridge. It's a
dev/testing hatch for a local, no-auth `q2 hub` — which ignores the bearer
entirely (server.rs: no auth_config ⇒ no credential check). The OAuth path is
unchanged when --token is absent. This is what StaticTokenSource's doc comment
always anticipated ("the dev --token path").
Add claude-notes/hub-execution-e2e/: a runnable local end-to-end harness for the
Run-button feature — an example project (hello.qmd with `engine: jupyter`), a
start-local-hub.sh helper that boots the no-auth hub and prints the index-doc id
+ the exact hub-client URL and provider command, and a README walkthrough.
Verified directly while building this: the no-auth hub answers /health with the
index-doc id; `q2 provide-hub --token dev` connects and lists files; and the
Jupyter engine executes 2+3→5 on this machine (via q2 render with
`engine: jupyter`, the same registry the provider uses). Full cargo xtask verify
green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… gotcha (bd-sfet3264) The local q2-hub path only serves its own watched project, so creating a fresh project in hub-client against a project-mode hub yields '0 file(s)' in the provider. Restructure the walkthrough to lead with the simplest verified path (hub-client on its default wss://sync.automerge.org + provider on the same, no q2 hub), and document the project-mode caveat + the --no-project relay alternative for the fully-local path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diagnostic + regression harness for the bug found during Phase 4 e2e: the samod provider materializes 0 files for a document authored by a JS automerge-repo peer (hub-client), so execution fails with "project discovery failed …". - relay_sync.rs (2 self-contained tests, pass): samod peer B DOES sync a doc created by samod peer A through a bare relay AND a NeverAnnounce relay — so it's not the announce policy or the BearerDialer. - sync_probe.rs (ignored; need network/a live doc): probe_live_doc_sync watches an existing doc's files map sync; probe_create_then_find shows two SAMOD peers sync fine via sync.automerge.org. Together they isolate the gap to JS-authored docs → the Rust samod peer (likely an automerge version/protocol mismatch: JS @automerge/automerge 3.2.6 / repo 2.5.6 vs Rust automerge 0.8.0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…as Text (bd-bm0vaetl)
The provider's "0 file(s)" is NOT a sync failure (the doc syncs completely from a
JS automerge-repo peer to samod). hub-client (@automerge/automerge 3.2.6) stores
`files[path] = docId` as an automerge Text object, but quarto's Rust
IndexDocument::get_all_files reads values via ScalarValue::to_str(), which
returns None for Text — so a JS-authored files map reads empty.
- interop-repro/js-peer/{server.mjs,peer.mjs}: minimal JS automerge-repo sync
server + create/read peer, independent of quarto-hub/hub-client.
- sync_probe.rs: probe_root_keys dumps the synced doc — shows
`files=map{...=Object(Text)} value=Int(42)` (content DID sync; values are
Text). probe_files_str_or_text validates the fix: reading each value as
Str-or-Text (doc.text() for Text objects) recovers the ids.
Fix lives in crates/quarto-hub/src/index.rs (get_all_files/get_file: accept
Str or Text). Same class as the metadata-as-str lint.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e (bd-bm0vaetl)
hub-client (@automerge/automerge 3.x) stores `files[path] = docId` string values
as automerge Text objects, not scalar strings. IndexDocument::get_all_files /
get_file read values with Value::to_str(), which returns None for Text — so a
project created in hub-client read as ZERO files and `q2 provide-hub` failed to
materialize anything ("project discovery failed: canonicalize No such file").
Add a `read_str_or_text` helper (scalar Str OR Text via doc.text()) and use it
in get_all_files/get_file. Rust-authored docs (project-mode hub, scalar Str)
keep working; hub-client docs now read. Same class as the metadata-as-str lint.
The document itself synced fine JS→Rust all along (an earlier theory that samod
couldn't sync JS docs was wrong).
Tests:
- index.rs: get_all_files_reads_text_valued_ids (Text + scalar entries).
- materialize.rs: materializes_a_js_authored_index_with_text_valued_ids.
- interop-repro/: minimal JS automerge-repo peer/server + the Rust probes that
isolated the cause; e2e-run.mjs drives the whole loop (JS creates project →
provider runs knitr → capture read back = STDOUT "1 2 3").
Verified end-to-end headlessly: a JS-authored project now lists its file, the
provider materializes + runs the knitr engine, writes the capture doc + sidecar,
and a JS peer reads the executed output (cat(1,2,3) → "1 2 3").
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Plan to make hub-client's default format:html preview display server-recorded engine captures (today only format:q2-preview does). Layered fix mapped across WASM HTML branches, preview-runtime renderToHtml, and hub-client <Preview>; CaptureSpliceStage inserts into the HTML pipeline unchanged. Awaiting review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…4uygha Phase 1) Phase 1 of making hub-client's default `format: html` preview display server-recorded engine captures (today only `format: q2-preview` does). - HtmlRenderConfig gains a `captures: Vec<EngineCapture>` field + `with_captures` (default empty). - Extract a shared `insert_capture_splice_stage` helper (the splice-insert + engine-stage rebuild-with-spliced-names, formerly inline in `build_q2_preview_pipeline_stages`) so the q2-preview and HTML capture paths can't drift — both must stay cell-aligned with `build_capture_pipeline_stages`. - New `build_html_pipeline_stages_with_captures`; `render_qmd_to_html` uses it when captures are present (empty → unchanged builder, byte-identical). - `RenderToHtmlRenderer::with_captures` threads the active page's captures into the website Pass-2 HTML render. Cell alignment holds by construction: captures are recorded from this same HTML stage list truncated at engine-execution. Tests: pipeline.rs `render_qmd_to_html_splices_captures` (hand-built `.cell`-wrapped capture, fictitious non-spawning engine, mirroring captureSplice.wasm.test.ts); integration `render_to_html_captures.rs` (multi-file project, ActivePage mode). Full quarto-core suite (2406) + clippy green. WASM branches still to thread captures (Phase 2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…4uygha Phase 2) Thread the already-parsed captures (they arrive via render_page_in_project_with_attribution) into both HTML render branches, which previously dropped them: - render_single_doc_to_response `_ =>` arm builds the HtmlRenderConfig with `.with_captures(captures)`. - render_project_active_page_to_response `_ =>` arm calls `renderer.with_captures(captures)` on RenderToHtmlRenderer. No new wasm-bindgen signature — only the Rust branches change. RED→GREEN WASM vitest captureSpliceHtml.wasm.test.ts: a `format: html` doc + a gzipped capture → the marker appears in the rendered `html` (RED against the pre-rebuild WASM, GREEN after build:wasm); no-capture ⇒ source-only. Full WASM suite (126) green. The TS convenience wrappers (renderToHtml/renderPageInProject) still need to forward captureGzJson, and hub-client's <Preview> needs to fetch + pass them (Phases 3–4) before this is user-visible. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-uy4uygha Phase 3) RenderToHtmlOptions gains captureGzJson?; renderToHtmlInner forwards it into renderPageInProject, which gains a captures param forwarding to the already-capable renderPageInProjectWithAttribution. Pure pass-through so the default HTML preview (Preview.tsx → renderToHtml) can carry captures to the WASM HTML splice (Phase 2). tsc clean; preview-runtime tests (74) green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…view (bd-uy4uygha Phase 4) The default `format: html` preview (plain documents + every website page) now displays server-recorded engine captures, so a document executed by a connected `q2 provide-hub` shows its output — previously only `format: q2-preview` did, which is why the output silently failed to appear for normal docs. - New shared hook `useActiveCaptureBytes` (derive the active file's captureDocId from the sidecar, fetch the capture bytes via getBinaryDocById, keyed on the doc id). ReactPreview refactored to use it (removing its inline copy). - Preview consumes it and threads `captureGzJson` into `renderToHtml`, with the bytes in the render-callback deps so a freshly-arrived capture re-renders. - PreviewRouter now passes `captures` to <Preview> (it previously dropped it, routing captures only to ReactPreview / q2-preview). Tests: useActiveCaptureBytes.integration (3), Preview.capture.integration (2, mirroring the ReactPreview test); existing ReactPreview capture test still green after the refactor. Full hub-client test:ci green (integration + wasm 126); strict build:all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gha) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ed e2e Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…us line (bd-yai4w8ly) The preview pane showed two stacked status strips for code execution: the executor/Run bar (RunControl / .executor-online-bar — "Executor online" + Run/Re-run) and the capture bar (ClearCaptureControl — "Showing executed output" + Clear results…). When an executor was online and a capture existed they stacked as two differently-colored rows saying closely related things. Replace all three with a single PreviewStatusBar that renders at most one row: a green liveness dot (when an executor is online), one status label chosen by precedence (Executing… → error → "Showing executed output" [· code changed] → Executor online), and a right-aligned action group. Buttons are gated independently — Clear whenever a capture exists, Run/Re-run whenever an executor is online and the doc has executable cells — rendered [Clear] [Run] so Run stays pinned to the far right across state transitions. The run-pending snapshot/timeout and the two-step clear confirmation are preserved verbatim. Verified end-to-end in a real browser against the local Option-B hub harness: Run → "Showing executed output" (single row) → Clear confirm → source-only. Plan: claude-notes/plans/2026-07-01-merge-preview-status-line.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… bug (bd-gthycd33) Adds r-demo.qmd (engine: knitr) to the local e2e harness — a knitr companion to the jupyter hello.qmd. Browser testing during bd-yai4w8ly confirmed knitr output splices correctly into the hub-client preview (`[1] 2`, R version string, `[1] 55`), while jupyter does not (source-only despite a capture arriving) — filed as bd-gthycd33. Records that finding in the status-line plan's e2e notes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Integration branch for the remote code-execution provider epic (bd-sfet3264). Opened primarily to run CI on the branch and to review the latest work on top.
What this epic does
Lets a user with the
q2binary connect to a shared hub session, authenticate, and announce that this client will execute code. When a collaborator asks a document to run, the connectedq2 provide-hubprocess runs the engines (knitr/jupyter) natively and deposits the results into the automerge session — so every player sees executed output, not just source. Phases 1–4 (capture consumption + clear, request/capability channel, Rust client peer + BearerDialer, execute-on-request) are implemented andcargo xtask verify-green; Phases 5–6 (retention/dedup, real provider-only authz, hardening) remain.Full design + phase log:
claude-notes/plans/2026-06-29-remote-execution-provider.md.Latest change on top — merged preview status line (bd-yai4w8ly)
The preview pane showed two stacked status strips for code execution (executor/Run + capture/Clear). This collapses them into a single
PreviewStatusBar: one row, a liveness dot, a precedence status label, and independently-gated Clear + Run/Re-run buttons ordered so Run stays pinned far right. Presentational component with 15 tests; full hub-client suite + strict build green.0b13dbcb; changelog:c8251701; plan + knitr harness doc:354d6964.Verification
cargo xtask verify-green at the epic phase checkpoints (see plan).npm run build:all+ fullnpm run test:cigreen; browser-verified end-to-end on the local Option-B harness (claude-notes/hub-execution-e2e/).Known follow-ups
{python}cell renders as source).engine: knitrsplices correctly ([1] 2,[1] 55), so the defect is jupyter-specific. Found while browser-verifying the status-line change; a separate agent will look at it./auth/actorBearer) and Phase 6 (reconnect/refresh, multi-executor claims) per the plan.🤖 Generated with Claude Code