Repository navigation
feat(studio): open a past fire's transcript in a read-only dialog - #2371
Conversation
6f5587e to
9c699e0
Compare
kantord
left a comment
There was a problem hiding this comment.
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:
- Here the detail query is
enabled: online(schedule-transcript-dialog.tsx L49-L52). - The chat inspection dialog uses
enabled: canInspect, i.e. inspect and online (session-inspection.tsx L130-L133).
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.
transcriptAccessis exported from a component file (chat/session-inspection.tsx), soschedules/now imports fromchat/. Optional to move.- The dialog tests use
setTimeout(20)waits for absence, and nothing coverscomplete: 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.
9c699e0 to
868d6c4
Compare
868d6c4 to
f418a4e
Compare
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>
f418a4e to
970f972
Compare
kantord
left a comment
There was a problem hiding this comment.
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: falsenow says it is past the end of the session list Studio could read, instead of a bare "Transcript unavailable." - Nits.
transcriptAccessmoved out of the inspection component, and the docs say the run log opens transcripts in a dialog. There are tests for thecomplete: falseandinspect: falsecases.
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.
970f972 to
19dd29c
Compare
…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>
19dd29c to
45ab7a9
Compare
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
9c699e082is now8be9c4ba9).310ded998). The session-detail query isenabled: access.readable.access.readableistranscriptAccess()(online,inspectandviewTranscript), 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 forinspect: false(with a 403 detail stubbed), forviewTranscript: false, or for a session missing from the inventory.310ded998). When the session isn't in the list and the list'scompleteisfalse, 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.setTimeout(20)absence waits now wait for no read to be in flight (queryClient.isFetching() === 0) after the reason shows.310ded998).sm:max-w-2xlis listed inbreakpoints.test.ts, which now scans schedules onmain: it replaces DialogContent'ssm:max-w-lgat the same breakpoint, like the learned-skill dialog (970f97236).transcriptAccess()(and itsunavailable()helper) moved tochat/transcript-access.ts, soschedules/no longer imports from a component file (d686696dc, no behaviour change).Mutation proof (each applied alone, dialog suite rerun):
onlineonly (as reviewed)inspectdropped fromtranscriptAccessScreenshots
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
viewTranscriptgate, 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'sTranscriptView, 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.viewTranscriptandinspectwhile the runtime is online. That is the inspection dialog's transcript gate,transcriptAccess(), now inchat/transcript-access.tsand used by both.GET /sessions/{id}first, andGET /sessions/{id}/transcriptonly after that succeeds.viewTranscript; the capability is on the inventory row (SessionInventoryCapabilities).complete: false), says the session is past the end of the list Studio could read.user-docs/features/sessions/scheduled-tasks.mdno 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 existingsession-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'shref, 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:
inspectdropped fromtranscriptAccessScreenshots
Before =
8cc7c23(main), After =6e0cbd4(the change before restacking, now911bcf561), 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 head970f97236):task studio:checkpasses on this PR's head970f97236(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 docspasses (0 broken links or anchors).🤖 Generated with Claude Code