Skip to content

feat(stella-observatory): inspect a turn on its own page, not in a drawer - #2055

Merged
macanderson merged 3 commits into
mainfrom
worktree-observatory-transcript-page
Aug 7, 2026
Merged

feat(stella-observatory): inspect a turn on its own page, not in a drawer#2055
macanderson merged 3 commits into
mainfrom
worktree-observatory-transcript-page

Conversation

@macanderson

@macanderson macanderson commented Aug 7, 2026

Copy link
Copy Markdown
Owner

What & why

Inspecting a turn in the Observatory happened in a right-hand drawer. That was the
wrong container for what it held. A reconstructed prompt and a prompt diff are the
widest text this product produces, and the drawer gave them min(680px, 94vw) at
12px, reached by clicking an unmarked table row, with the diff two disclosures deep
and no URL to reload or share. The path to it was invisible too: the sessions and
turn tables carried no cursor, no hover, and a sel class no stylesheet defined,
while the panel a click revealed opened below the fold — so a click read as a click
that did nothing.

Inspecting a turn is now a route: #transcript/<execution>.

  • Full page, one step up the type scale. Diff and transcript bodies move from
    12px to 13px with reading line-height, at page width instead of a third of it.
  • The prompt diff is the first thing you see. The newest call's diff opens on
    arrival — the newest, because a role's first call has no previous call to diff
    against and renders the entire prompt as one wall of additions. The diff is drawn
    the way a reviewer reads one: both line-number gutters, and tinted rows rather than
    colored glyphs alone (hue is never the only signal — BRAND.md).
  • The call picker is a control that looks like one. A real turn in this
    workspace records 202 receipts, so it scrolls in a fixed box with the picked row
    lit and pulled into view, above the panel it drives.
  • A sticky rail across the six sections, with counts.
  • ← / → step through the owning session's turns without going back to the list.
  • The descent is visible again: tr.click on both tables, a gold turns ↓ /
    open ↗ cell that names what a row does, a real selected-row style, and a scroll
    to the panel a click just revealed.

Every payload is unchanged — the same four endpoints, the same lazy per-call
reconstruction, the same after_seq live append. One field is added to
/api/execution: session_id. Without it a transcript reached by URL alone can name
neither its session nor the turns either side of it.

Exemplar for the route grammar: none needed — it is #<tab> / #<tab>/<arg>, the
same shape the page already used for tabs, extended by one argument. The diff view
follows git's own hunk presentation, which is what the served endpoint already emits.

The witness

  • This PR includes a witness test (fails on main, passes here)

Two, one per half:

  • execution_detail_carries_its_session_id — asserts /api/execution serves
    session_id, and that the key survives as null (not as a missing key) for a run
    recorded before schema v8 stamped it, because the page distinguishes "no session"
    from "field not served". On main the execution head selects nine columns and
    session_id is not among them, so the first assertion fails.
  • inspecting_a_turn_is_a_page_not_a_drawer — asserts the transcript panel and its
    route exist, that both drill paths navigate to it, and that the drawer is gone
    rather than merely bypassed (a second, stale way in is how two renderings of the
    same data drift apart). On main, id="drawer" is present and
    data-tab="transcript" is not.

The second is a structural witness and does not claim the page renders right;
that is not decidable from Rust. It was verified by driving the served dashboard in
headless Chrome over CDP against this repository's own
.stella/private/store.db — real sessions, real receipts, real 202-call turns:

checked result
#transcript/83 cold, no session list in memory route resolves, rail builds 5 sections, 202 call rows, newest diff open at 51 lines / +48 −0
#transcript/73 cold (turn 3 of 4) breadcrumb resolves the session from session_id alone; ← and → both enabled; all 6 sections including Reflection
#transcript/76 (session_id NULL, no receipts) "session unknown" / "Execution 76", prev-next disabled, the server's own no-receipts note preserved
#transcript/999999 the honest "could not be loaded" panel
#transcript/notanumber route rejected, address restored to the route actually on screen
Sessions → turn row → back button → Escape #transcript/83#sessions, tab re-hidden, panel cleared
Overview execution row navigates to the same page
full navigation cycle zero JS errors, zero unhandled rejections

Screenshots of the transcript page and the Sessions tab were reviewed.

The gate

  • cargo fmt --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace
  • Docs updated where behavior changed
  • CLA signed

make gate CARGO_SCOPE="-p stella-observatory" exits 0.

Docs: website/content/docs/telemetry/dashboard.mdx loses its drawer paragraph and
gains sessions and transcript cards; the crate README gains the route grammar.
Three stale claims the change touched are repaired in the same PR — the doc's tab
count (said nine, was eleven before this PR), the README's seeded_workspace
location (src/lib.rssrc/tests.rs), and its list of store columns the fixture
diverges on, which named session_id and no longer can.

Nothing left behind

Ground-rule check

  • No I/O added to stella-core; no new deps
  • No new outbound network calls — the page stays fully self-contained
    (dashboard_html_has_no_external_references still passes)

Anything reviewers should know?

  • The drawer is deleted, not deprecated. Both entry points (Overview executions,
    Sessions turns) now navigate to the page, and the witness asserts no drawer
    survives. If you would rather keep an overlay for the Overview table, say so — but
    two renderings of the same four endpoints is exactly the drift this repo files
    issues about.
  • The transcript tab is hidden until a turn is open and disappears when you
    leave. It is a child route of Sessions, not a section you visit; an always-present
    tab with nothing in it is a dead end. The consequence is that the tab bar's
    roving-tabindex arrow keys now walk visibleTabs(), since focus must never park on
    a hidden button.
  • Section order puts context above the transcript. That is deliberate: what a
    call was given is the question the drawer buried deepest, and the rail makes both
    one click away regardless.
  • markCtxRow scrolls the picker box by setting scrollTop rather than calling
    scrollIntoView, which walks every scrollable ancestor — on arrival that would
    scroll the page to reach the list, which is the below-the-fold jump this page
    exists to remove.

Summary by Sourcery

Replace the execution detail drawer with a dedicated transcript page route and update backend, UI, tests, and docs to support deep-linked turn inspection.

New Features:

  • Add a transcript tab and full-page transcript view addressed as the route #transcript/<execution> with sections for context, transcript, steps, tools, files, and reflection.
  • Enable keyboard and hash-based navigation for transcripts, including previous/next turn stepping within a session and direct linking/reloadable URLs.

Bug Fixes:

  • Make session and turn tables visibly interactive with proper cursor/hover states, selected-row styling, and explicit affordances for opening turns.

Enhancements:

  • Redesign transcript and diff presentation for better readability, including wider layout, larger type, sticky section rail, and an improved unified diff view.
  • Refine context receipt UI with a scrollable call list, clear selection state, and inline controls to toggle between context and diff views.
  • Integrate transcript live-refresh into the main polling/live stream loop, continuing to append journal entries while a run is active.
  • Adjust tab bar behavior so arrow-key navigation respects only visible tabs and correctly handles the conditional transcript tab.

Documentation:

  • Update telemetry dashboard docs and README to describe the new transcript page, routing grammar, and corrected tab/schema details.

Tests:

  • Add tests ensuring /api/execution includes session_id and that the transcript page and its routes exist while the old drawer implementation is fully removed.

Chores:

  • Extend the test SQLite schema and seed data to include executions.session_id now that the observatory reads it from the store.

…awer

The execution drawer was the wrong container for what it held. A
reconstructed prompt and a prompt diff are the widest text this product
produces, and it gave them min(680px, 94vw) at 12px, reached by clicking
an unmarked table row, with the diff two disclosures deep and no URL to
reload or share.

Inspecting a turn is now a route: `#transcript/<execution>`, a full-width
page with a sticky section rail, the type one step up the scale, prev/next
through the owning session's turns, and the newest call's prompt diff open
on arrival. The diff itself is drawn the way a reviewer reads one — both
line-number gutters, tinted rows rather than colored glyphs alone.

The descent that led there is made visible too: the sessions and turn
tables carried no cursor, no hover, and a `sel` class no stylesheet
defined, while the panel a click revealed opened below the fold.

Every payload is unchanged — the same four endpoints, the same lazy
per-call reconstruction. `/api/execution` gains one field, `session_id`,
without which a transcript reached by URL alone cannot name its session
or step to the turns either side of it.
…date the docs

Verified against this workspace's own store: a real turn records 202
receipts, so the picker scrolls in a fixed box with the lit row pulled into
view, and the diff opens directly beneath it.

Also drops a duplicated running badge, restores the address bar when a route
is rejected, and repairs three stale claims in the docs the change touched:
the tab count, the drawer, and the fixture's session_id divergence.
…ot just the tab

A bare '#transcript' is not a route either, so restoring to state.tab alone
left the address in a state that would not survive a reload.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @macanderson, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
stella-cli-docs Ignored Ignored Aug 7, 2026 5:49am

@sourcery-ai

sourcery-ai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Replaces the execution-detail drawer with a full-page transcript route (#transcript/) including a sticky section rail, context/diff tooling, keyboard navigation, and URL-based routing, updates styles and accessibility for sessions/turns tables, wires both Overview and Sessions to the new route, extends the /api/execution DB query and test schema to expose session_id for deep-linked transcripts, and updates docs/README plus adds tests to witness the new behavior and schema.

Sequence diagram for navigating to the new transcript page

sequenceDiagram
    actor User
    participant Dashboard as BrowserDashboard
    participant API

    User->>Dashboard: click executions row
    Dashboard->>Dashboard: goTranscript(id)
    Dashboard->>Dashboard: location.hash = "transcript/" + id
    Dashboard-->>Dashboard: hashchange
    Dashboard->>Dashboard: routeTo(location.hash)
    Dashboard->>Dashboard: switchTab("transcript", id)
    Dashboard->>Dashboard: openTranscript(id)

    Note over Dashboard,API: Initial execution + folds
    Dashboard->>API: api(/api/execution?id=id)
    API-->>Dashboard: execution { id, session_id, ... }
    Dashboard->>API: api(/api/execution-journal?id=id[&full])
    Dashboard->>API: api(/api/execution-context?id=id)
    Dashboard->>API: api(/api/execution-tendencies?id=id)
    API-->>Dashboard: journal entries / context / tendencies

    Note over Dashboard,API: Load sibling turns for prev/next
    Dashboard->>Dashboard: txLoadSiblings(execution.session_id)
    Dashboard->>API: api(/api/session?id=session_id)
    API-->>Dashboard: session.turns[]

    Dashboard->>Dashboard: render transcript head, rail, sections
    Dashboard->>Dashboard: loadContextDiff(id, turn, step, call_seq)

    User->>Dashboard: press ← / →
    Dashboard->>Dashboard: goTranscript(siblingId)
    Dashboard-->>Dashboard: hashchange (repeat routeTo/openTranscript)
Loading

Entity-relationship diagram for executions.session_id addition

erDiagram
    SESSIONS {
      string id
    }

    EXECUTIONS {
      int id
      string session_id  "nullable"
    }

    SESSIONS ||--o{ EXECUTIONS : "has turns"
    EXECUTIONS }o--|| SESSIONS : "execution.session_id"
Loading

File-Level Changes

Change Details Files
Replace the execution drawer with a hash-routed transcript page that reuses existing data endpoints and supports deep links and keyboard navigation.
  • Remove drawer markup, scrim, styling, focus trapping, and associated open/close/refresh logic.
  • Introduce a transcript tab/panel (#transcript/) with dedicated layout, typography, sticky rail, sections (context, transcript, steps, tools, files, reflection), and responsive behavior.
  • Add client-side routing helpers to parse hash routes (#[/]), validate transcript IDs, and keep URL and visible tab state in sync, including rejection of invalid transcript routes.
  • Implement openTranscript/closeTranscript/leaveTranscript/goTranscript helpers that fetch execution/journal/context/tendencies, build sections, wire context call/diff interactions, preserve live transcript refresh (stella-observatory: live transcript refresh while a run is in flight (after_seq incremental fetch) #1476), and enable ←/→ stepping through turns via a session-level sibling list.
  • Switch Overview execution clicks and Sessions turn-row clicks from openDrawer to goTranscript, and ensure transcript navigation resets scroll to top and hides the transcript tab when leaving.
crates/stella-observatory/src/assets/index.html
Improve context and diff UX in the transcript view, including a scrollable call picker, clear selection state, and a unified-diff renderer aligned with git’s hunk format.
  • Refactor sentContextSectionHtml to render a fixed-height, scrollable call list (.ctx-list and .ctx-row) with two buttons per call (context sent / what changed) and a shared display panel (#ctx-body).
  • Add markCtxRow to visually mark the active call and active button, and keep the selected row scrolled into view by manipulating scrollTop instead of scrollIntoView to avoid page-level jumps.
  • Simplify sentContextVerdictHtml and sentContextMessageHtml, treating digest mismatches as integrity failures and rendering them as error-styled journal entries.
  • Rewrite loadSentContext and loadContextDiff to integrate with the new layout, add contextual headings, and maintain the ability to toggle between full vs clipped bodies and all-roles vs system-only diffs.
  • Introduce diffHunksHtml and a .dx* CSS block to render hunks with separate old/new line-number gutters, a sigil column, tinted add/remove rows, and a tally header summarizing +/− changes.
crates/stella-observatory/src/assets/index.html
Make the Sessions and turns tables clearly interactive, connect them to transcript navigation, and smooth the scroll behavior when drilling into a session.
  • Add CSS for tbody tr.sel and td.go to visually indicate selected rows and the action cell; style tr.click/tr.drill for pointer cursor and hover/focus wash.
  • Update the Sessions table to add a trailing "turns ↓" action cell, mark rows with class click, and clarify the copy around selecting sessions and turns.
  • Update the turns table to add a trailing "open ↗" action cell, mark rows with class click, and ensure turn selection scrolls the detail card into view only on explicit user clicks (not on auto-refresh).
  • Propagate the session’s turn IDs into txSiblings on openSession so the transcript page can offer prev/next turn navigation without extra API calls.
  • Ensure sessions and turns click handlers call goTranscript instead of openDrawer, while preserving the existing inline diff expander behavior for the ± diff affordance.
crates/stella-observatory/src/assets/index.html
Extend the /api/execution detail response and test fixture schema to include session_id so deep-linked transcripts can resolve their owning session and neighboring turns.
  • Update the execution detail SQL in db.rs to select executions.session_id and expose it as a nullable JSON field in the response object.
  • Adjust the seeded test schema DDL in tests.rs to add a session_id TEXT column to executions and populate it for one seeded execution while leaving it NULL for another.
  • Add the execution_detail_carries_its_session_id test to assert that /api/execution includes session_id, that it is non-null for a stamped execution and explicitly null (not missing) for an older run.
  • Clarify README and test comments to note that session_id is now part of the observatory’s test schema subset because it is actually read by the crate, and that the subset intentionally omits other unused columns like usage_complete/usage_status.
  • Ensure docs that previously mentioned session_id as an unused column are corrected to reflect that it is now served by the execution detail route.
crates/stella-observatory/src/db.rs
crates/stella-observatory/src/tests.rs
crates/stella-observatory/README.md
Document the new transcript route and behavior and add a structural witness test ensuring the drawer is fully removed and both drill paths land on the new page.
  • Update the telemetry dashboard docs to describe the new transcript tab/page, its route grammar (#transcript/), its sections, and its relationship to Overview and Sessions, including prev/next turn navigation.
  • Update the observatory README to document the fragment routing grammar (#[/]) and the transcript tab’s child-route behavior and visibility rules.
  • Add the inspecting_a_turn_is_a_page_not_a_drawer test to assert that the built index.html contains the transcript tab/panel/route markers and does not contain the old drawer/scrim/aria-modal/openDrawer artifacts.
  • Adjust existing docs describing the tab count and seeded_workspace location to match current behavior (11 base tabs plus conditional transcript, tests.rs as the DDL location).
website/content/docs/telemetry/dashboard.mdx
crates/stella-observatory/README.md
crates/stella-observatory/src/tests.rs
crates/stella-observatory/src/assets/index.html

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@macanderson
macanderson merged commit d38354e into main Aug 7, 2026
17 checks passed
@macanderson
macanderson deleted the worktree-observatory-transcript-page branch August 7, 2026 05:52
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