06 · Studio: framework selection and app shell #7

Closed
opened 2026-07-23 00:07:10 -04:00 by cmoriarty · 8 comments
Owner

Choose the front-end stack against these requirements, then build the shell with panes wired to Aegra streams:

  • OpenCode-style chat feed — evaluate embedding an OpenCode frame vs building native
  • Programmatic realtime LangGraph status (consume Agent Protocol / Aegra SSE natively)
  • Game playable in a subwindow
  • Minimal-IDE file browser + editor (vscode-like expectations, e.g. Monaco)
  • Asset gallery: view images, listen to audio

Deliverable: framework decision recorded (short ADR) + running app shell.

Choose the front-end stack against these requirements, then build the shell with panes wired to Aegra streams: - OpenCode-style chat feed — evaluate **embedding an OpenCode frame** vs building native - Programmatic realtime LangGraph status (consume Agent Protocol / Aegra SSE natively) - Game playable in a subwindow - Minimal-IDE file browser + editor (vscode-like expectations, e.g. Monaco) - Asset gallery: view images, listen to audio **Deliverable:** framework decision recorded (short ADR) + running app shell.
cmoriarty changed title from Studio: framework selection and app shell to 06 · Studio: framework selection and app shell 2026-07-23 00:10:07 -04:00
Author
Owner

Done — framework decided, shell live on :2027

ADR: docs/studio-framework.md (commit 7f64319). Decision: React 19 + Vite + TypeScript SPA in studio/, dockview docking panes, @langchain/langgraph-sdk's useStream consuming Aegra's Agent Protocol SSE natively, Monaco earmarked for ticket 09.

OpenCode frame: evaluated, rejected. OpenCode is its own agent runtime — an embedded frame renders OpenCode sessions, not our LangGraph runs; wiring trog through its data plane inverts "the graph is the process." Its embeddable web story is also immature (opencode web load bugs; usable web UIs are third-party rebuilds). We build the feed native and keep the look; the Agent Protocol stream makes it nearly free.

Why React: the stream layer is first-party (useStream handles chunk reassembly, reconnect, interrupts against any apiUrl — verified against our Aegra 0.9.24), Monaco + dockview have the mature wrappers, and the README's training-data argument applies to the studio itself.

The shell, running:

  • feed — wired to the hello graph end to end: browser → proxy → Agent Protocol run → brain → SSE tokens → transcript (verified live; run visible flipping busy → idle)
  • runs — polls threads/search, real status chips (idle/busy/error), seed of ticket 08's graph map
  • play — iframes the file server (:3923/games/…)
  • files / assets — placeholders owned by tickets 08/09
  • header phase strip: the production process as a pixel level map (ideation → … → ship, fun/bar gates as diamonds), lit from the newest thread's phase
  • layout drag/drop + persisted to localStorage; Sweetie-16 palette, Silkscreen pixel face (vendored, nothing phones home)

Deploy: trog-studio image (nginx serves the SPA, proxies /apiaegra:2026 — same-origin everywhere, zero CORS config). In compose on :2027, added to make build push, stack 92 redeployed with pullImage: truehttp://trogdor:2027 is serving now, /api/info answering through the proxy.

Dev loop: npm run dev in studio/ (Vite proxy → AEGRA_URL, default http://trogdor:2026).

## Done — framework decided, shell live on :2027 **ADR:** `docs/studio-framework.md` (commit 7f64319). Decision: **React 19 + Vite + TypeScript SPA** in `studio/`, **dockview** docking panes, **`@langchain/langgraph-sdk`'s `useStream`** consuming Aegra's Agent Protocol SSE natively, **Monaco** earmarked for ticket 09. **OpenCode frame: evaluated, rejected.** OpenCode is its own agent runtime — an embedded frame renders OpenCode sessions, not our LangGraph runs; wiring trog through its data plane inverts "the graph is the process." Its embeddable web story is also immature (`opencode web` load bugs; usable web UIs are third-party rebuilds). We build the feed native and keep the look; the Agent Protocol stream makes it nearly free. **Why React:** the stream layer is first-party (`useStream` handles chunk reassembly, reconnect, interrupts against any `apiUrl` — verified against our Aegra 0.9.24), Monaco + dockview have the mature wrappers, and the README's training-data argument applies to the studio itself. **The shell, running:** - **feed** — wired to the `hello` graph end to end: browser → proxy → Agent Protocol run → brain → SSE tokens → transcript (verified live; run visible flipping busy → idle) - **runs** — polls `threads/search`, real status chips (idle/busy/error), seed of ticket 08's graph map - **play** — iframes the file server (`:3923/games/…`) - **files / assets** — placeholders owned by tickets 08/09 - header **phase strip**: the production process as a pixel level map (ideation → … → ship, fun/bar gates as diamonds), lit from the newest thread's `phase` - layout drag/drop + persisted to localStorage; Sweetie-16 palette, Silkscreen pixel face (vendored, nothing phones home) **Deploy:** `trog-studio` image (nginx serves the SPA, proxies `/api` → `aegra:2026` — same-origin everywhere, zero CORS config). In compose on **:2027**, added to `make build push`, stack 92 redeployed with `pullImage: true` — **http://trogdor:2027** is serving now, `/api/info` answering through the proxy. Dev loop: `npm run dev` in `studio/` (Vite proxy → `AEGRA_URL`, default `http://trogdor:2026`).
Author
Owner

Review feedback applied (commit 66aff63, redeployed to :2027)

critique disposition
1. header phase shapes removed — carried no function yet; header center is now pane toggles
2. copyparty aesthetic default theme flipped to dark (--theme=2, trogdor-files redeployed); real fix is native panes — ticket 08 points play at the game URL directly, ticket 09 replaces asset browsing with the native gallery
3. file editor ticket 09 — scope clarified on #10: full VSCode-class editor (tree/tabs/search; leaning openvscode-server embed), not a bare Monaco widget
4. closed panes unrecoverable fixed — header toggles reopen/focus every pane (closed = struck through, active = lit)
5. LangGraph representation ticket 08 (#9) — the live graph map pane
6. no activity indicator fixed — feed shows a pulsing working line with "brain working / streaming" + elapsed seconds for the whole run (verified against a 58s Qwen think); node-by-node narration of what the graph is doing lands with ticket 07's real feed

The observability goal is understood as the studio's spine: every long operation gets visible, textual progress, and 07/08 are where the "understand what it is doing at every point" depth arrives (tool calls, todos, graph position, vitals).

## Review feedback applied (commit 66aff63, redeployed to :2027) | critique | disposition | |---|---| | 1. header phase shapes | **removed** — carried no function yet; header center is now pane toggles | | 2. copyparty aesthetic | default theme flipped to dark (`--theme=2`, trogdor-files redeployed); real fix is native panes — ticket 08 points play at the game URL directly, ticket 09 replaces asset browsing with the native gallery | | 3. file editor | ticket 09 — **scope clarified on #10: full VSCode-class editor** (tree/tabs/search; leaning openvscode-server embed), not a bare Monaco widget | | 4. closed panes unrecoverable | **fixed** — header toggles reopen/focus every pane (closed = struck through, active = lit) | | 5. LangGraph representation | ticket 08 (#9) — the live graph map pane | | 6. no activity indicator | **fixed** — feed shows a pulsing working line with "brain working / streaming" + elapsed seconds for the whole run (verified against a 58s Qwen think); node-by-node narration of what the graph is doing lands with ticket 07's real feed | The observability goal is understood as the studio's spine: every long operation gets visible, textual progress, and 07/08 are where the "understand what it is doing at every point" depth arrives (tool calls, todos, graph position, vitals).
Author
Owner

Review round 2 applied (deployed to :2027)

  • Idle threads hidden — runs pane shows busy/interrupted/error only; show idle (N) toggle brings history back
  • Graph representation — new map pane: topology pulled from Aegra's /assistants/{id}/graph, nodes in execution order, gates' backward edges called out (↩ preproduction ? in ember), and the node a busy run is about to execute lit with ◀ now (3s state poll). Ticket 08 still owns the full version — known gap: the now highlight watches the newest busy thread regardless of graph until run metadata carries graph identity
  • Graph is selectable — top-bar picker (built from Aegra's assistant list) drives both the map and the feed; switching graphs starts a fresh feed thread, so you can talk to phasewalk or hello from the same box
  • New-game wizard — filed as #25 (v2): design questionnaire (retro vs HD pairs with #24), structured brief the ideation phase must respect, ends with repo + properly-configured run kickoff

Also this round: layouts saved before a new pane shipped now auto-add it on load.

## Review round 2 applied (deployed to :2027) - **Idle threads hidden** — runs pane shows busy/interrupted/error only; `show idle (N)` toggle brings history back - **Graph representation** — new **map** pane: topology pulled from Aegra's `/assistants/{id}/graph`, nodes in execution order, gates' backward edges called out (`↩ preproduction ?` in ember), and the node a busy run is about to execute lit with `◀ now` (3s state poll). Ticket 08 still owns the full version — known gap: the `now` highlight watches the newest busy thread regardless of graph until run metadata carries graph identity - **Graph is selectable** — top-bar picker (built from Aegra's assistant list) drives both the map and the feed; switching graphs starts a fresh feed thread, so you can talk to `phasewalk` or `hello` from the same box - **New-game wizard** — filed as #25 (v2): design questionnaire (retro vs HD pairs with #24), structured brief the ideation phase must respect, ends with repo + properly-configured run kickoff Also this round: layouts saved before a new pane shipped now auto-add it on load.
Author
Owner

Top-bar status readout shipped (deployed): the aegra dot stays — it's a real /info health poll — and next to it, top right, the feed's working-pulse animation now narrates system activity: run · <node> from the busy thread's next node, plus warm orchestrator seats (image gen / audio gen / animate), dim static idle otherwise. Orchestrator reached same-origin via a new /orch nginx+Vite proxy. Verified live through a phasewalk run: run · gate_fun pulsing amber → idle at ship.

**Top-bar status readout shipped** (deployed): the aegra dot stays — it's a real `/info` health poll — and next to it, top right, the feed's working-pulse animation now narrates system activity: `run · <node>` from the busy thread's next node, plus warm orchestrator seats (`image gen` / `audio gen` / `animate`), dim static `idle` otherwise. Orchestrator reached same-origin via a new `/orch` nginx+Vite proxy. Verified live through a phasewalk run: `run · gate_fun` pulsing amber → `idle` at ship.
Author
Owner

Status sync fixed (deployed): all status widgets now render from one shared snapshot taken on a single 3s heartbeat — top bar, map, and runs pane can no longer disagree about the same fact. Animations too: every pulse and the stream cursor share a 0.9s period phase-locked to a global epoch via negative animation-delay, so everything blinks on the same beat no matter when it mounted.

**Status sync fixed** (deployed): all status widgets now render from one shared snapshot taken on a single 3s heartbeat — top bar, map, and runs pane can no longer disagree about the same fact. Animations too: every pulse and the stream cursor share a 0.9s period phase-locked to a global epoch via negative `animation-delay`, so everything blinks on the same beat no matter when it mounted.
Author
Owner

Runs pane folded into the map (deployed): the standalone table duplicated the map for the single-run case, so the run list now lives at the top of the map pane as its selector — this graph's non-idle threads (idle behind the toggle), click to pin the map to a run, unpin to auto-follow the newest busy one. Busy runs light ◀ now; errored runs light red ✕ died here with the run's error message (verified on the Jul 23 brain-loading 503); finished runs light their terminal phase green ■ ended. Threads carry graph_id metadata, so the list and highlight are graph-filtered — the cross-graph gap from the earlier round is closed.

**Runs pane folded into the map** (deployed): the standalone table duplicated the map for the single-run case, so the run list now lives at the top of the map pane as its selector — this graph's non-idle threads (idle behind the toggle), click to pin the map to a run, unpin to auto-follow the newest busy one. Busy runs light `◀ now`; errored runs light red `✕ died here` with the run's error message (verified on the Jul 23 brain-loading 503); finished runs light their terminal phase green `■ ended`. Threads carry `graph_id` metadata, so the list and highlight are graph-filtered — the cross-graph gap from the earlier round is closed.
Author
Owner

Copyparty out of the chrome (deployed): play pane no longer iframes the directory listing — empty state until a build URL (ticket 08 auto-wires the active run's build). Assets pane deleted: ticket 09's files pane absorbs the gallery — one tree, type-aware viewer (code editor / pixel-scale images / audio player). Scope recorded on #10. Toggle row is now feed · map · play · files.

**Copyparty out of the chrome** (deployed): play pane no longer iframes the directory listing — empty state until a build URL (ticket 08 auto-wires the active run's build). Assets pane deleted: ticket 09's files pane absorbs the gallery — one tree, type-aware viewer (code editor / pixel-scale images / audio player). Scope recorded on #10. Toggle row is now feed · map · play · files.
Author
Owner

Closing summary

Deliverables: framework ADR (docs/studio-framework.md, amended with all review rounds) + the running shell at http://trogdor:2027, deployed via the standard build→push→Portainer path, running image verified equal to latest.

Decided: React 19 + Vite + TS SPA · dockview panes · @langchain/langgraph-sdk useStream on Aegra's Agent Protocol · native feed (OpenCode embed rejected — wrong data plane) · same-origin proxies /api→aegra, /orch→orchestrator (zero CORS) · VSCode-class files surface at #10.

Shipped shell (after 5 review rounds): feed · map · play · files

  • feed: live run stream, working line with elapsed seconds, graph-selectable
  • map: topology from Aegra, run list as selector (busy ◀ now / error ✕ died here + message / finished ■ ended), graph-filtered
  • top bar: graph picker, pane toggles (everything reopenable), activity readout (run node + warm seats), real aegra health dot
  • one status snapshot per 3s tick + beat-locked animations — widgets can't disagree
  • phasewalk test graph (+ structural test; suite 118 passed) for exercising it all GPU-free
  • copyparty demoted to storage-only; assets pane absorbed into #10's scope

Follow-ups filed: #25 new-game wizard (v2) · #10 scope: VSCode-class tree + type-aware viewer · #8/#9 fill the feed/map/play depth (incl. thread↔run metadata for multi-run watching, auto-wiring play to the active build, brain-warmup guard noted for #11).

## Closing summary **Deliverables:** framework ADR (`docs/studio-framework.md`, amended with all review rounds) + the running shell at **http://trogdor:2027**, deployed via the standard build→push→Portainer path, running image verified equal to latest. **Decided:** React 19 + Vite + TS SPA · dockview panes · `@langchain/langgraph-sdk` `useStream` on Aegra's Agent Protocol · native feed (OpenCode embed rejected — wrong data plane) · same-origin proxies `/api`→aegra, `/orch`→orchestrator (zero CORS) · VSCode-class files surface at #10. **Shipped shell (after 5 review rounds):** feed · map · play · files - feed: live run stream, working line with elapsed seconds, graph-selectable - map: topology from Aegra, run list as selector (busy `◀ now` / error `✕ died here` + message / finished `■ ended`), graph-filtered - top bar: graph picker, pane toggles (everything reopenable), activity readout (run node + warm seats), real aegra health dot - one status snapshot per 3s tick + beat-locked animations — widgets can't disagree - `phasewalk` test graph (+ structural test; suite 118 passed) for exercising it all GPU-free - copyparty demoted to storage-only; assets pane absorbed into #10's scope **Follow-ups filed:** #25 new-game wizard (v2) · #10 scope: VSCode-class tree + type-aware viewer · #8/#9 fill the feed/map/play depth (incl. thread↔run metadata for multi-run watching, auto-wiring play to the active build, brain-warmup guard noted for #11).
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cmoriarty/trog#7
No description provided.