feat(chat): open a transcript hit at its message, and give the transcript a way up and a way down - #165
Open
titan-ron wants to merge 3 commits into
Open
feat(chat): open a transcript hit at its message, and give the transcript a way up and a way down#165titan-ron wants to merge 3 commits into
titan-ron wants to merge 3 commits into
Conversation
The consensus table's fixture gave its failed seat the text "turn failed: process exited with code 1", and the view prefixes every error entry with "<Seat> turn failed:" — so the shot read "Codex turn failed: turn failed: …", a doubling main never produces (its entry text is the CLI's own line, "codex exited with code 1"). The fixture now says what main would.
…transcript a way up and a way down
Three things the transcript owed its reader.
A transcript-search hit opened its session at the bottom, leaving the person to scroll
for the message the palette had just shown them. The hit now rides along as the chat's
`anchor` (`TranscriptAnchor`), and once the log is in, `findAnchor` names the row by its
words, speaker and time — never by index, since the searcher and the chat's parser count
messages differently — brings it into the DOM window with a few rows of context, scrolls
it to the middle, rings it for a moment (`.anchored`) and says so in the status region.
Applied once per anchor, so a live session growing underneath never re-scrolls; words
the log no longer says open at the bottom as before.
The DOM window ("showing the last 400 of 1,200 messages") was a dead end: the sys-row
that said so is now the control that shows the next batch (`EarlierRow`), and the rows
land above the viewport without moving what is on screen — `useTranscriptWindow` re-adds
the height in a layout effect and `.messages` turns the browser's own scroll anchoring
off so the adjustment is made once.
And while the reader is scrolled up, the auto-scroll stays off on purpose — so rows
arriving below were invisible news. `useUnseenBelow` marks them the moment the log grows
off the bottom, and `JumpToLatest` — a sticky, zero-height last child whose "New
messages" key hangs above the bottom edge without moving a row — takes them there;
reaching the bottom by hand clears it. Hidden it is visibility: hidden, out of the tab
order and the accessibility tree.
Both controls are shared with the roundtable (`transcript-window.tsx`), where a seat
streaming into its live block counts as news the same as an entry landing. The tour
shoots the anchored chat and the key; the design pages and the guide say all three.
…s mid-turn The transcript re-pinned itself to the bottom whenever the binding object changed, and App re-makes that object mid-turn — the native id from the CLI's first event, a parent chip arriving — so a reader who had scrolled up while the agent wrote was yanked back down on the next row: the one hijack the auto-scroll rule forbids. The tour's new `chat-new-below` shot is what caught it (the "New messages" key never showed because the pin had quietly come back). The pin and the DOM window now reset per conversation — provider, directory and native id — not per object, so the same session keeps the reader's place through those updates and a different one still opens at the bottom. The shot waits for a pressable Send before its second turn (the log's own liveness holds it a beat after a turn ends) and for the key itself, since its zero-height line reads as hidden to Playwright.
Code Coverage OverviewLanguages: TypeScript TypeScript / code-coverage/unitThe overall line coverage in commit 3dfcac1 in the TypeScript / code-coverage/componentThe overall line coverage in commit 3dfcac1 in the Show a line coverage summary of the most impacted files.
Updated |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A craft pass on the transcript, the surface a person spends most of the day in, following the review that merged as #164. No reskin: everything here lives inside the settled design system.
A transcript-search hit opens at its message. Picking a hit in the ⌘K palette used to open the session at the bottom and leave you scrolling for the words it had just shown you. The hit now rides along as the chat's anchor; once the log is in, the chat finds the row by its words, speaker and time (never by index — the searcher and the parser count messages differently), brings it into the DOM window with context above, scrolls it to the middle, rings it for three seconds and says so in the status region. Applied once per anchor, so a live session growing underneath never re-scrolls; words the log no longer says open at the bottom as before.
A way up. "showing the last 400 of 1,200 messages" was a dead end. The line is now the control that shows the next 400, and the rows land above the viewport without moving what is on screen: the scroll offset is kept in a layout effect, and
.messagesturns the browser's own scroll anchoring off so the adjustment is made once.A way down. The auto-scroll deliberately never hijacks a scroll-up — which meant rows arriving below were invisible news. A "New messages" key now appears at the bottom edge the moment the log grows while you are scrolled up, and takes you there; reaching the bottom by hand clears it. It is a sticky, zero-height last child, so showing and hiding it never moves a row; hidden it is out of the tab order and the accessibility tree.
Both controls are shared with the roundtable (
transcript-window.tsx), where a seat streaming into its live block counts as news the same as an entry landing.A bug the new tour shot caught. The transcript re-pinned itself to the bottom whenever App re-made the binding object mid-turn (the native id from the CLI's first event, a parent chip arriving), so a reader who had scrolled up while the agent wrote was yanked back down on the next row — the one hijack the rule forbids. The pin and the DOM window now reset per conversation (provider, directory, native id), not per object.
Also: the tour's roundtable fixture read "Codex turn failed: turn failed: …"; it now carries the text main really writes.
Known gap, not built here
Re-opening a session whose turn Cockpit itself is running detaches the view from that turn: the chat looks idle with Send enabled while main is mid-turn, and the turn's events are dropped. It needs the busy entry to carry the turn id so the view can re-attach on open, and has a dedupe question (disk log vs stream, both at message granularity) worth its own PR.
Design-system pages (
chat.md,palette.md,roundtable.md) and the user guide (chat.md,sessions.md) updated.Verification
npm run typecheckcleannpm test: 112 files, 1697 tests passed — newtranscript-anchor.test.ts(words, speaker, time, fallbacks), and chat-view, command-palette and roundtable-view cases for the window, the anchor and the keynpm run build && npm run test:e2e: 46 passed, 6 skipped (the opt-in packaged and full-screen specs)npm run ui:tour: two new shots —chat-from-search(the ringed message) andchat-new-below(the key while a turn streams, read from the top); the latter is what found the re-pin bug, and a Playwright probe against the tour world confirmed the key holds at scrollTop 0 through a whole streamed turn