Skip to content

[RFC][WIP] Early, early draft of displaying Galaxy notebooks in Loom & auto-syncing.#351

Draft
jmchilton wants to merge 7 commits into
galaxyproject:mainfrom
jmchilton:notebook_iframe
Draft

[RFC][WIP] Early, early draft of displaying Galaxy notebooks in Loom & auto-syncing.#351
jmchilton wants to merge 7 commits into
galaxyproject:mainfrom
jmchilton:notebook_iframe

Conversation

@jmchilton

Copy link
Copy Markdown
Member

Posted by Claude (AI assistant) on behalf of @jmchilton — drafted with their direction, not authored by them personally.

⚠️ [RFC][WIP] — early, early draft. Not for merge. Sharing the Loom-side half of a cross-repo spike so the shape can be discussed. Expect rough edges and churn.

What this is

The Loom/Orbit side of rendering a Galaxy notebook as the Galaxy server renders it inside Orbit's notebook pane — full fidelity (live dataset displays, visualizations, charts) for a private, history-attached page — plus an opt-in auto-sync so the embedded view tracks local edits.

Paired with the Galaxy server changes (embed-token auth + embeddable surface): galaxyproject/galaxy#22988.

Pieces (high level)

  • Brain (Phase 2): getEmbedUrl + NotebookEmbed shell contract emitted from the notebook watcher; mintEmbedToken client + a token refresh manager. The embed token is delivered out of band to Orbit main (a swallowed EmbedToken widget key the privileged process intercepts and never forwards to the renderer) — never the API key.
  • Orbit shell (Phase 3): a Markdown ↔ Galaxy toggle in the notebook pane; the Galaxy view is a locked-down <webview partition="persist:galaxy-embed"> that injects the scoped embed token on Galaxy-origin requests, strips frame headers, blocks off-origin navigation, and is stateless (no ambient galaxysession cookie).
  • Opt-in auto-sync (this is the "& auto-syncing" half): when enabled (LOOM_AUTO_PUSH=1 / experiments.autoPush, off by default), a genuine edit to a bound + connected notebook is debounce-pushed to its Galaxy page so the embed tracks local edits without a manual /sync push. Dedups on the housekeeping-stripped body so the push's own self-write doesn't loop. Local-wins, so it clobbers server-side edits — hence opt-in; safer gates (revision/previous-hash precondition, last-writer-was-Orbit) are deliberately deferred.
  • Live-verified end-to-end against a real Galaxy + Orbit (3 E2E bugs found & fixed: ambient-cookie defeat of the token, a token re-mint loop, resume-gap emission).

Status / asks

  • Design spike, not a finished PR. Main feedback wanted: the embed-token transport (swallowed widget key) and the auto-sync safety model (ship naive local-wins now, add precondition/last-writer gates later — or gate from day one?).

🤖 Generated with Claude Code

jmchilton and others added 7 commits June 22, 2026 16:09
Shell-neutral brain side of the Orbit iframe-embed feature.

- galaxy-embed.ts: absolute embed-URL builder (API embed_url is path-
  relative) + NotebookEmbed projection + token refresh-timing math.
- shared contract: NotebookEmbed + EmbedToken widget keys, payloads,
  codecs. EmbedToken never reaches the renderer (privileged-process only).
- galaxy-pages-api: mintEmbedToken (POST /pages/{id}/embed_token).
- embed-token-manager: mint-on-bind, refresh-before-expiry, retry,
  cancel/abort/stale-drop; transport-agnostic sink.
- embed-token-bridge: drive the manager off notebook+connection state,
  emit token on the EmbedToken key (bound && connected only).
- ui-bridge: emit NotebookEmbed widget, deduped, before the hidden gate.

Tests green; brain stays shell-neutral.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hase 3)

Consumes the brain's NotebookEmbed/EmbedToken widgets.

- main intercepts the EmbedToken widget in agent.handleLine, holds it in
  EmbedTokenStore, never forwards to the renderer.
- embed-partition: pure trust boundary (sameGalaxyOrigin, nav block,
  frame-header strip) + session wiring; inject X-Galaxy-Embed-Token for
  the Galaxy origin only, strip frame dirs, block off-origin nav.
- main.ts wires the persist:galaxy-embed partition; webviewTag enabled;
  CSP frame-src adds https:.
- renderer: Markdown/Galaxy toggle, <webview> in the locked partition,
  fallback states, persisted mode, reload-on-rev. notebook-view-model is
  pure + tested.

Vehicle is <webview> not <iframe> (iframe can't take a custom partition);
Galaxy postMessage bridge no-ops in a webview, so native events stand in.
Unit + typecheck green; not yet live-verified in a running app.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Found running the server-side embed E2E (Galaxy Page in Orbit's locked-down
<webview>, authed by the scoped per-page embed token). All three live-verified.

Bug 1 (critical): the webview's /published/page load seated an anonymous
galaxysession cookie in the persist:galaxy-embed partition; Galaxy resolved that
cookie session ahead of the injected embed token, so /api/pages/{id} 403'd
despite a valid token. Strip Cookie (request) and Set-Cookie (response) on
Galaxy-origin requests so the partition is stateless and Galaxy auths by the
token alone. New pure stripHeader helper + wiring. Live: in-webview /api/pages
now 200, document.cookie empty.

Bug 2: Galaxy returns expires_at as naive UTC with no Z/offset; Date.parse read
it as local time -> expiry in the past -> refresh delay floored -> re-mint ~1/s.
New parseExpiryMs appends Z to a tz-less ISO datetime so it parses UTC.

Bug 3: NotebookEmbed/token emission was gated on onNotebookChange + a ctx only
captured in before_agent_start; a --continue resume of an already-bound notebook
never re-emitted -> Galaxy toggle "unavailable". Bridges now also capture ctx on
session_start and replay the current notebook (readCurrentNotebook), order-
independent vs init's existing notifyNotebookChange. Mint gate moved to new
isGalaxyEffectivelyConnected (env creds OR in-session flag); context.ts and the
/execute init-gate precondition refactored onto it (the raw flag is false on an
env-creds resume until a Galaxy tool runs).

995 tests green, root+app typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
getEmbedUrl appends &theme=<id> (default DEFAULT_EMBED_THEME "orbit"; null omits)
so Galaxy renders the embedded page dark to match Orbit's shell. No-op on a
Galaxy without the orbit built-in theme.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
loom-session housekeeping blocks leaked into the pushed page, where Galaxy
markdown read them as an unknown cell directive ("This cell type `loom-session`
is not available"). Add stripSessionSummaryBlocks and apply it to the Galaxy
projection only (galaxyBody); the local re-persist keeps the blocks, so durable
session history survives.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The local Markdown view rendered raw notebook.md, so the loom-session (session
history) and loom-galaxy-page (binding) blocks showed as visible metadata. The
Galaxy push already strips them; do the same for display. Add
stripHousekeepingBlocks and apply it to the Notebook widget projection in
emitNotebook -- raw content still drives the embed/binding derivation. Blocks
stay on disk; only the rendered view hides them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Debounced push of a bound+connected notebook to its Galaxy page so the
embedded Galaxy view tracks local edits without a manual /sync push. Off by
default (LOOM_AUTO_PUSH=1 / experiments.autoPush): push is local-wins and
clobbers server edits, so opting in accepts that. Dedup on the
housekeeping-stripped body breaks the push's own self-write loop. Mirrors the
embed-token bridge/manager split; debounce + in-flight coalesce + prime-on-bind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant