Skip to content

feat(studio): open a past fire's transcript in a read-only dialog - #2371

Merged
peppescg merged 6 commits into
mainfrom
feat/studio-schedules-2299-transcript
Oct 19, 2026
Merged

peppescg merged 6 commits into
mainfrom
feat/studio-schedules-2299-transcript

Conversation

@peppescg

@peppescg peppescg commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Stack (3 of 4) for #2299 and #2341: the inventory and detail → the dialog → this PR → the title= audit. Review bottom to top; this PR's own diff is its six commits.

Review changes

Fixes for the review of 2026-10-16, as new commits on top of the reviewed head (restacked onto #2369's and #2370's updates; the reviewed 9c699e082 is now 8be9c4ba9).

  • Detail read behind the inspection gate (310ded998). The session-detail query is enabled: access.readable. access.readable is transcriptAccess() (online, inspect and viewTranscript), so a refused session, or one missing from the inventory, is never read. The refusal is checked before the detail error, so a session the daemon refuses shows its reason, not "Saved transcript could not be read." with Retry. The SPEC comment now describes the gate the detail read uses. Tests assert that no /sessions/s1* request goes out for inspect: false (with a 403 detail stubbed), for viewTranscript: false, or for a session missing from the inventory.
  • Cut-short session list (310ded998). When the session isn't in the list and the list's complete is false, the dialog reads "Transcript unavailable: this run's session is past the end of the session list Studio could read." A cut-short list that does include the session still replays it. Both cases are tested.
  • Nits.
    • The setTimeout(20) absence waits now wait for no read to be in flight (queryClient.isFetching() === 0) after the reason shows.
    • New tests cover an empty transcript, and a failed list or runtime read with Retry recovering (310ded998).
    • The dialog's sm:max-w-2xl is listed in breakpoints.test.ts, which now scans schedules on main: it replaces DialogContent's sm:max-w-lg at the same breakpoint, like the learned-skill dialog (970f97236).
    • transcriptAccess() (and its unavailable() helper) moved to chat/transcript-access.ts, so schedules/ no longer imports from a component file (d686696dc, no behaviour change).
    • Not changed: the run log keeps a button rather than a deep link, as Studio schedules: align the inventory, detail and dialog with the prototype #2299 asks. Whether reopening a still-running fire serves a cached transcript was not checked in this round.

Mutation proof (each applied alone, dialog suite rerun):

Mutation Failing tests
Detail read gated on online only (as reviewed) 5
Detail error checked before the refusal reason 1
Cut-short list ignored 1
Cut-short message for a complete list 1
Retry doesn't refetch the list / the runtime 1 each
Detail failure without Retry 1
inspect dropped from transcriptAccess 1
Empty-transcript note removed 1

Screenshots

Before = 9c699e0 (reviewed head), After = 310ded9. The prototype has neither state, so there is no Prototype column. Red marks the dialog body. The detail facts behind the dialog changed in #2369's review fix, not here.

Refused session, desktop light. Before: the detail read was refused, and the dialog showed a read error with Retry. After: the daemon's reason.

Session past a cut-short list, phone dark.

All other frames






Summary

The run log's View transcript now opens the fire's saved transcript in a read-only dialog over the schedule page, as in the prototype and as #1852 asked, instead of linking to chat. It reads the session detail first and keeps the inspection dialog's viewTranscript gate, which the prototype's dialog skips.

This finishes #2299's items. The title= audit for #2341 follows in the next PR.

Closes #2299
Part of #2210

What changed

  • ScheduleTranscriptDialog (schedules/schedule-transcript-dialog.tsx) renders chat's TranscriptView, so there is no second transcript renderer. Its title is " — ", in mono as in the prototype. Its description is "Saved transcript", plus the session id when the session allows copying it.
  • Read order and gate.
    • It reads nothing about the session unless the session's inventory row grants viewTranscript and inspect while the runtime is online. That is the inspection dialog's transcript gate, transcriptAccess(), now in chat/transcript-access.ts and used by both.
    • Once the gate allows it, it reads GET /sessions/{id} first, and GET /sessions/{id}/transcript only after that succeeds.
    • The session detail itself carries no viewTranscript; the capability is on the inventory row (SessionInventoryCapabilities).
  • States.
    • A refused session shows the daemon's reason.
    • A session missing from the caller's inventory reads "Transcript unavailable.", or, when the BFF's session list was cut short (complete: false), says the session is past the end of the list Studio could read.
    • Offline, nothing about the session is read.
    • A failed list, runtime or detail read offers Retry, and the transcript is not read.
  • Run log. The desktop table and the phone cards open the dialog from a button. Escape closes it and returns focus to that button.
  • Docs. user-docs/features/sessions/scheduled-tasks.md no longer says each run links to Chats.

Differences from the prototype

No regression

  • Pins first (74b54adc3, schedule-transcript.pinning.test.tsx, 2 tests), on the chat link: each fire with a session offers one keyboard-reachable View transcript, on the table and the phone cards, and a fire without one offers none. 2/2 pass before and after.

  • Change (911bcf561): schedule-transcript-dialog.test.tsx (8 tests; 13 after the review fixes). The existing session-inspection.test.tsx (15) passes unchanged on the extracted gate.

  • Modified test. In e2e/schedules.spec.ts, "schedule detail links each fire to its owning session" checked the chat link's href, which this PR replaces on purpose. It is now "schedule detail opens each fire's saved transcript in a read-only dialog". It checks that only the fire with a session offers the button, that the dialog shows the transcript, that the URL stays on the schedule page, and that Escape returns focus.

  • Mutation proof:

    Mutation Failing tests
    Transcript read without the gate 3
    Transcript read without waiting for the detail 1
    inspect dropped from transcriptAccess 1
    Detail read while offline 1
    Focus not returned on close 1

Screenshots

Before = 8cc7c23 (main), After = 6e0cbd4 (the change before restacking, now 911bcf561), Prototype = stack-08. Before's View transcript navigates to chat and opens the session inspection dialog, which is what the Before column shows.

Transcript, desktop light. Matches the prototype's dialog. The "activity · 1 tool call" line is Studio's TranscriptView.

Transcript, phone dark. Opened from a run-log card.

All other frames


The detail page with the button (the run log's look is unchanged from #1):




Gates

Through the shared lock, at the top of the restacked stack (97f3f8c7f, which includes this PR's head 970f97236):

  • task studio:check passes on this PR's head 970f97236 (server 252/252, web 1742/1742) and at the top: lint, typecheck and the generated-artifact check pass; server tests 252/252; web tests 1744/1744.
  • task studio:browser: 153 passed, 41 skipped, including the schedules journeys on desktop and mobile.
  • task docs passes (0 broken links or anchors).
  • No BFF contract changes: the dialog uses existing routes and generated clients.

🤖 Generated with Claude Code

@kantord kantord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for this, it matches what #2299 asks for (detail first, the viewTranscript gate kept), the transcriptAccess extraction is behaviour-identical for the chat inspection dialog, and the dialog states, Retry and focus return look right. I'm requesting changes for two things.

1. The detail read doesn't use the inspection gate

The PR says the dialog keeps the inspection dialog's gate, which holds for the transcript read but not for the session-detail read:

So the detail request goes out even when the session's inventory row has inspect: false or the session isn't in the inventory at all, and detail.data is never used beyond ordering. There is also a likely wrong message: the error branch (L84) is checked before !access.readable (L94). If the daemon refuses the detail for such a session, the user would see the generic error with Retry instead of the daemon's reason. I haven't seen the daemon's response, so that symptom is my inference. As far as I can tell the BFF passes the call through without a capability check, so enforcement is the daemon's and this is not a leak, but the comment saying "the same gate" overstates it.

The tests wouldn't catch this: the refusal tests only assert the transcript isn't read (L205, L219, L248), never that the detail wasn't read.

Suggested change: enabled: online && access.readable (or check !access.readable before detail.isError), plus assertions that the detail path isn't hit for inspect: false and for a session missing from the inventory.

2. A missing session reads "Transcript unavailable." even when the list was truncated

The dialog finds the session in listSessionsOptions() and ignores complete (L53-L59). The server reads at most 20 pages of 100 (chat.ts L35-L36) and returns complete: false beyond that (L436-L446). Scheduled fires accumulate sessions, so an old fire's session could plausibly fall outside the first 2000, and the run log would offer "View transcript" for a dialog that says only "Transcript unavailable." I didn't check the daemon's list ordering, so how likely this is in practice is an open question.

Please show a distinct message when the session isn't found and complete === false, or note explicitly that this is acceptable.

Nits and questions, not blocking

  • "View transcript" is now a button, so there is no deep link and no open-in-new-tab (schedule-detail.tsx L99 rewrites the earlier DECISION). #2299 asks for the dialog, so this is spec-compliant; I'm only noting the trade-off.
  • transcriptAccess is exported from a component file (chat/session-inspection.tsx), so schedules/ now imports from chat/. Optional to move.
  • The dialog tests use setTimeout(20) waits for absence, and nothing covers complete: false, an empty transcript, or a failed list or runtime read.
  • I didn't check whether reopening the same fire serves a cached transcript for a still-running session.

I read the code and CI only and didn't run the app or the tests.

Fully or partially written by an AI agent.

@peppescg
peppescg force-pushed the feat/studio-schedules-2299-transcript branch from 9c699e0 to 868d6c4 Compare October 16, 2026 19:51
peppescg added a commit that referenced this pull request Oct 16, 2026
…ript gate)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@peppescg
peppescg force-pushed the feat/studio-schedules-2299-transcript branch from 868d6c4 to f418a4e Compare October 16, 2026 19:56
peppescg added a commit that referenced this pull request Oct 16, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
peppescg added a commit that referenced this pull request Oct 16, 2026
… inspection gate

Review of #2371:

- The session detail is read only when `transcriptAccess` allows the
  transcript (inspect, viewTranscript, online), as the inspection
  dialog gates its own reads. A refused session, or one missing from
  the inventory, is never read, and the dialog shows the daemon's
  reason instead of a read error and Retry.
- When the session is missing from a session list the BFF cut short
  (`complete: false`), the dialog says the session is past the end of
  the list Studio could read, not a bare "Transcript unavailable."
- Tests assert no detail read for a refused or missing session, cover
  the cut-short list, an empty transcript and failed list and runtime
  reads, and wait for in-flight reads to settle instead of sleeping.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@peppescg
peppescg force-pushed the feat/studio-schedules-2299-transcript branch from f418a4e to 970f972 Compare October 16, 2026 20:22

@kantord kantord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the follow-up. I read the current head against my requests and they are resolved:

  • Gate. The detail read is now enabled: access.readable, so it no longer goes out for a session the caller can't inspect or one missing from the inventory.
  • Truncated list. A session not found when complete: false now says it is past the end of the session list Studio could read, instead of a bare "Transcript unavailable."
  • Nits. transcriptAccess moved out of the inspection component, and the docs say the run log opens transcripts in a dialog. There are tests for the complete: false and inspect: false cases.

Approving. The missing deep link and open-in-new-tab remain a trade-off, which #2299 accepts. I read the code and CI only and did not run the app or the tests. Merge order is still after #2369 and #2370.

Fully or partially written by an AI agent.

@peppescg
peppescg force-pushed the feat/studio-schedules-2299-transcript branch from 970f972 to 19dd29c Compare October 19, 2026 08:53
Base automatically changed from feat/studio-schedules-2299-dialog to main October 19, 2026 08:53
peppescg and others added 6 commits October 19, 2026 10:53
…dialog

Pins that each fire with a session offers one keyboard-reachable View
transcript control, on the desktop table and the phone cards, and a
fire without a session offers none. Passes on the current chat link.

Refs #2299

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The run log's View transcript now opens the saved transcript in a
dialog over the schedule detail page, as in the prototype and as #1852
asked, instead of linking to chat. The dialog reuses chat's
TranscriptView.

The dialog reads the session detail first, and the transcript only
after it, and only when the session's inventory row grants
viewTranscript and inspect while the runtime is online. That is the
inspection dialog's gate, now shared as transcriptAccess() from
session-inspection.tsx; the prototype's dialog skips it. A refused or
unlisted session shows the reason instead, offline reads nothing, and a
failed detail read offers Retry. Escape closes the dialog and returns
focus to the button that opened it.

Refs #2299

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Refs #2299

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The transcript gate is a plain function shared by the chat inspection
dialog and the schedule run log's transcript dialog, so it moves from
`chat/session-inspection.tsx` to `chat/transcript-access.ts` and
schedules no longer import a component file for it. No behaviour change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… inspection gate

Review of #2371:

- The session detail is read only when `transcriptAccess` allows the
  transcript (inspect, viewTranscript, online), as the inspection
  dialog gates its own reads. A refused session, or one missing from
  the inventory, is never read, and the dialog shows the daemon's
  reason instead of a read error and Retry.
- When the session is missing from a session list the BFF cut short
  (`complete: false`), the dialog says the session is past the end of
  the list Studio could read, not a bare "Transcript unavailable."
- Tests assert no detail read for a refused or missing session, cover
  the cut-short list, an empty transcript and failed list and runtime
  reads, and wait for in-flight reads to settle instead of sleeping.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he breakpoint guard

The transcript dialog widens DialogContent, whose own width is sm:max-w-lg.
It has to use sm: to replace that class at the same breakpoint, as the
learned-skill dialog does, so the guard lists it with that reason.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@peppescg
peppescg force-pushed the feat/studio-schedules-2299-transcript branch from 19dd29c to 45ab7a9 Compare October 19, 2026 08:53
@peppescg
peppescg merged commit 5c672e7 into main Oct 19, 2026
31 checks passed
@peppescg
peppescg deleted the feat/studio-schedules-2299-transcript branch October 19, 2026 09:15
@toolhive-release-app toolhive-release-app Bot mentioned this pull request Oct 20, 2026
2 tasks
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.

Studio schedules: align the inventory, detail and dialog with the prototype

2 participants