Repository navigation
docs(design): unfold and copy a statement in the task-run log - #21428
Conversation
The log cuts every executed statement to 80 characters inside the model, so nothing downstream can show or copy the rest. #21276 removes the cut but keeps the whitespace collapse above it, which turns a wide CREATE TABLE into one run-on line inside a 200px box. The doc lands on one rule: the model returns both forms of an entry, the line the row shows and the text that line stands for, and destroys neither. Truncation becomes CSS on the line; copy and unfold act on the text. A statement row folds by default and unfolds in place with its original formatting; an error row stays open, because an error is why the log was opened. Implementation follows in a separate PR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6d854f6763
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
… contracts Three P1 findings from the Codex review, all verified against the code and the guards before changing anything. The first is a real gap in the principle, not a missed detail. A command row was assumed to have one payload, so `fullDetail` could hold the statement or the error but never both -- and a failed COMMAND_EXECUTE carries both. The converter hangs the response on the same entry that holds Range/Statement, and every driver logs a bare err.Error(), so the error text never names the SQL that produced it. Storing the error kept today's behaviour and discarded the statement; storing the statement made copy omit the error. `DisplayItem` now carries `statement` and `error` separately, the line is whichever one is the news, and unfolding and copy always mean "the SQL". A failed row opens by default, since execution stops at the first failure and that row is why the log was opened. The second and third would have failed `pnpm --dir frontend check`. D6 prescribed a native <button>, which `no-native-control` rejects in feature code; both controls are now shared Buttons, and the row stays a div so the two never nest. D11 shrank CopyButton to h-5/size-3, which `no-button-dimension-override` rejects and frontend-ux.md:75-83 forbids outright. Both controls keep `xs`, so the row is 28px rather than 24px and ITEM_HEIGHT moves 20 -> 28; the two ways to buy that density back are recorded as not taken. Reframing the fold control as "show me the SQL" also removed D5's ResizeObserver -- it existed only to hide a chevron that would have been a no-op, and D11 reserves the slot on every row regardless. Mocks A-E re-rendered: A now carries the error-only failed row so it pairs with D, every statement row shows the fold control, and the rows are 8px taller. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 354c62371c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
D3 said what copy takes and never said where it attaches, which was harmless while a row had one payload. Once the failed row carried two, the button stayed in the right-hand metrics cluster -- pinned to the error line, while copying the statement from the block underneath. The position said one thing and the behaviour did another, which is the sort of thing nobody reads a tooltip to resolve. Copy now sits beside what it copies: in the cluster while the statement is the line, and in the unfolded block's top-right corner once the statement is a block, the placement every code block already uses. The cluster button gives way when the block opens, so a statement never has two copy buttons. The error gets none. Reaching for an error message in the clipboard is rare next to reaching for the SQL, and it does not justify a second control on the densest surface in the product; it stays selectable text, exactly as it is today. That also restores one meaning for the control -- copy is the SQL -- and drops the fallback-to-error case, so a row with no recoverable statement now shows no copy button rather than one that means something else. Mocks C and D re-rendered. Removing the cluster button from the failed row also gives its error line back the width it was borrowing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two P2 findings from the Codex review, both verified in the code. D4 rested on a claim that is simply false: that execution stops at the first failure, so a run holds at most one failed row. On a lock timeout the Postgres driver retries the whole command list up to MaximumRetries (pg.go:463-485) and re-logs every command on each attempt, so the same statement can fail three times and the run still succeed. Default-opening every failed row would expand the same DDL once per attempt, on the surface with the least room for it. The rule is now to open the last failed command row in a section, and only when no command row after it succeeded -- a run that recovered ends in successes and opens nothing, a run that really failed ends at its failure and opens exactly that row. It reads the entries rather than a run status, because taskRunStatus is an optional prop the changelog and revision pages never pass. D9 overclaimed in the other direction. The block still gets no overflow of its own, so the viewer stacks no scrollbars, but the page can: DeployTaskRunHistorySheet renders the viewer inside SheetBody, which is overflow-y-auto, so that surface has always nested and D8's raised cap makes the inner region bigger. The claim is corrected rather than the layout, since the nesting predates this change and overscroll-contain keeps it from chaining, while a sheet-only layout mode would be permanent. It is recorded as a follow-up option instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5fbd76950e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The previous commit fixed the wrong scope. Sections are typed groups -- groupEntriesByType starts a new one whenever the entry type changes -- and a lock-timeout retry emits a RETRY_INFO entry between attempts, plus a rollback and a fresh begin in transaction mode. So "the last failed row in a section with nothing succeeding after it" is satisfied by every transient failure, each being the only failure in a section of its own, and all of them would still open. The rule read as though it solved the retry case while doing nothing about it. The test now runs over the entry sequence buildSectionsFromEntries is handed -- one replica, one release file -- before it is grouped. A run that recovered has successful commands after its failure and opens nothing; a run that really failed ends at the failure and opens that row. Per context rather than per run, so one replica's failure is never silenced by another's success. Tests pinned to the shape that broke it: two attempts separated by a RETRY_INFO, first failing and second succeeding, must mark no row to open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 60ad377c3e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
SectionContent renders the first 50 entries of a section until Load more is pressed. A migration whose 300th statement fails would have D4 mark a row that is not in the DOM, so the decision would silently do nothing -- and it would do nothing in exactly the sections where finding the failure by hand is worst. D13: the rendered window is the first MAX_RENDERED_ITEMS items plus the marked row when it falls outside them, with the existing Load more button between the two reporting what it still hides. Rendering the whole section would also keep the promise, but a release file can carry thousands of statements and not rendering all of them is what the cap is for. Regression case: a 60-entry section whose marked failure is last renders that row without pressing Load more, and still reports the hidden count. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3e59ef9775
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Three P2s, all consequences of the previous two commits rather than new ground. The rule was written against a finished log. A live one is a prefix: the poll runs every five seconds, so it can land after a failed attempt and before its retry, where no later command has succeeded yet and the transient failure looks terminal. The driver writes RETRY_INFO before it re-runs, so that marker is the standing signal that another attempt is coming and a failure followed by one is never terminal. The ~200ms between the failure and the marker is left to D14. D14 is the larger gap. section.items changes without datasetKey changing, so a mark applied only at mount misses the case the feature exists for -- a deploy watched from the plan page that fails while its section is already on screen. The open set is now derived rather than initialised: open when marked or when the reader opened it, folded when the reader folded it, with an explicit toggle outranking the mark for the life of the dataset. D13's sparse window also had to carry each item's own index. Numbering by position in the rendered array would label statement 300 as row 51 until Load more renumbered it, which points the reader at the wrong statement -- worse than not surfacing it at all. The regression test asserts the number, not just that the row rendered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 80f85e4121
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
D13 got the marked row into the DOM and stopped there. Fifty rows at 28px is about 1,400px of content above it, inside a box capped at max(280px, 60vh) whose scrollTop starts at zero, so the row was mounted, expanded, and off-screen -- the promise kept in the DOM and broken on the screen. The section sets its own scrollTop when a row becomes the marked one. Its own, not scrollIntoView, which would scroll the page under a reader looking at something else. Once, when the mark lands; later polls do not re-scroll, so a reader who has scrolled away stays where they are. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2069a74b8f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Codex is right that D14 does not close the ~200ms window between a failed response and the retry marker: the mark cannot move until the next poll, so a row can sit falsely expanded for up to five seconds. The previous commit implied otherwise, which is the part worth correcting. The behaviour stays. The suggested fix -- suppress auto-marking while the run is RUNNING -- removes the flicker by removing D14's entire point, since the case the mark exists for is a deploy watched from the plan page that fails while you are looking at it. The trade is a guaranteed loss of the main live behaviour against roughly a one-in-twenty-five chance, on an already uncommon lock-timeout retry, seen only by someone watching at that moment. And the transient state is not false: that command really did fail then, it is only about to be retried. Recorded in D4 as a window left open deliberately, with the rejected option and its cost, rather than fixed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 18baa68163
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…mount D14 put the overrides in SectionContent, which is mounted conditionally: collapsing an enclosing section unmounts it and reopening builds a fresh one, and a live run swaps the sole-section rendering for the multi-section tree as soon as a second entry type arrives. Either remount discarded the reader's folds and popped a marked failure back open on a dataset that never changed, which is the opposite of what D14 promises. The overrides now sit in TaskRunLogViewer, above the conditional mount, keyed by row and cleared when taskRunName changes. showAllItems keeps its remount-local behaviour -- re-hiding the tail of a long list on collapse is not a promise anyone made. Also widens D13's title to what it grew into: rendered, numbered and in view. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ac9422659b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Codex is right that both deploy surfaces key the viewer on the run's status, so a RUNNING -> terminal flip remounts it and takes the fold overrides with it, even though taskRunName never changed. Declining the fix. That key is deliberate: the comment above it says the remount exists "for a fresh disclosure state on the new phase", and it is also what invalidates an in-flight RUNNING log request before it can be cached as a complete log. Persisting overrides across it -- a module map keyed by task-run name -- would work and would quietly undo both. The behaviour it produces is also the one this doc argues for: the flip that reopens a folded failure is the flip into the phase where that failure is the outcome. D14 now says where the promise ends: folds survive polls, section collapse and the sole-to-multi swap within a phase; a new phase starts fresh. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2e231caaa9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
CockroachDB's autocommit path logs one COMMAND_EXECUTE outside its retry and a response per attempt from inside it; the converter attaches the first response and drops the rest. A statement that succeeded on retry is therefore recorded as a failure, and the entry-only predicate would unfold it on a run that finished fine. The predicate cannot fix that -- the successful response never reaches the API, so nothing reading entries can recover it. Two changes instead. D4 gains a cheap guard: a caller that passes a DONE status marks nothing, which costs D14 nothing since D14 is about a run still RUNNING, and which is correct on the two surfaces that pass the prop. And the actual defect is named as a defect. That row is already wrong before this design reaches it -- the log shows a red cross for a command that worked, and marks its whole section failed -- so it is listed as out of scope and wants a backend fix: a COMMAND_EXECUTE per attempt, matching the Postgres shape, or a converter that replaces rather than drops. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f309ac8357
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Codex is right that the guard covers only the two callers that pass taskRunStatus, leaving the changelog and revision pages exposed to the same CockroachDB shape. Declining to extend it. Closing it there means inventing a success signal for those pages -- a changelog's status is not a task run's -- and wiring it through two more call sites to half-cover one engine's converter bug. And on those pages the row already reads as a failure today, before any of this: auto-opening makes a wrong row larger, it does not make it wrong. The converter fix, already listed as out of scope for this PR, closes the hole on all four surfaces at once. D4 now says what the guard is: opportunistic, free where the prop exists, not load-bearing, and deliberately not extended. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2cfa915958
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…oken Two concrete corrections, both verified. useTaskRunLogSections owns every builder call -- flat, per-replica, release-file and orphan -- and TaskRunLogViewer invokes none of them directly. The scope table omitted the hook, so an implementer following it would have wired a DONE guard that never runs. The hook is listed now, with the requirement that it forwards taskRunStatus through all five call sites. And D10 asked for border-control-border on the unfolded block, which frontend-ux.md:143-144 reserves for controls; a framed content region takes border-block-border. Mocks C and D re-rendered with the corrected token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Peter noticed the fold controls do not line up. Measured on the mockups, they sit at 228, 235 and 242 pixels inside one section -- and so does the statement text, in the product as it stands today. The cause is the relative-time column: it has no width, and SectionContent renders it only when it has a value, so +0ms, +12ms and +506ms each push what follows by a character and the first row of a section drops the column entirely. Nobody had noticed because ragged text still reads as text. Put a column of identical chevrons beside it and the raggedness is the first thing you see. D15: the span renders always, with min-w-[7ch], right-aligned, keeping tabular-nums -- seven characters covers everything under 100 seconds and longer values push as they do now. The index moves from w-6 to min-w-6, since a fixed 24px clips at four digits, which MAX_RENDERED_ITEMS and D13 together can reach. The UX contract already asks for right-aligned numerics in tables; these are the same columns in another frame. Moving the fold control to the front of the row was considered as the alternative fix and rejected: it costs 30px of statement width on every row, puts the control four columns away from what it opens -- the mismatch we just removed from the copy button -- and lands at the same x as the section chevron, so a row would read as a peer of its own section. Mocks B-E re-rendered with the straightened column; A keeps today's ragged edge so the pair reads as before and after. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 27048ca441
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…ething Peter: mock D row 6 is a COMMENT ON TABLE that already shows whole, and it still carried a chevron. Unfolding it would redraw the identical text in a box. D5 said a row is foldable when it ran a statement, full stop, and justified that with 'unfolding a short statement is redundant rather than wrong'. It is wrong: a control that reveals nothing teaches the reader the control is decoration, and they stop reaching for it where it matters. That simplification also discarded a second test that was free. Both halves are back. The verbatim statement differs from the line it collapsed into -- a string comparison -- or the line is clamped by the width it was given, which needs scrollWidth > clientWidth and so reinstates one ResizeObserver on the scroll box. Neither covers the other: a three-line statement can collapse to a line that fits, and a one-liner can be far too wide. Mockup E is the proof, where SET statement_timeout fits at 900px and is cut at the sheet's 560px. Two consequences stated rather than left to be discovered: a narrow container turns most rows foldable, which is honest because at that width unfolding each of them does reveal more -- the ellipsis says which rows are cut, the chevron only says which can open; and only a row that ran a statement ever gets one, while the rest keep the reserved slot so the column stays straight. Mocks B-E re-rendered: B loses the chevron on rows 1, 4 and 6, D on row 6, and E keeps all four because every line clamps at 560px. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…entity Two findings from the Codex review of 27048ca, both verified. The first is a hole in the contract. D4 picks the row to open across a whole execution context, D13 renders it and D14 derives open state from it, but DisplayItem carried no field for the pick -- and SectionContent cannot work it out. It sees one section at a time, and error is no substitute, since every transient failed attempt has one and opening all of them is what D4 exists to prevent. DisplayItem gains defaultOpen, set by buildSectionsFromEntries. The second is key instability. Overrides were keyed by the row's render key, which is positional: idPrefix-groupIndex-entryIndex. Both halves move. useTaskRunLogSections switches idPrefix to the replica id once a run has a second replica, so the first replica's rows are renamed mid-poll, and groupIndex shifts for every later group when a retry marker splits a section. Either renumbering drops the reader's folds on an unchanged dataset. Overrides are now keyed by the entry's own identity -- replica, log time, type, and a command's byte offset -- which no regrouping alters. Render keys are untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 576c66b028
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…rop the arbitrary width Three findings on 576c66b, all verified. D5's measurement was under-specified in a way that breaks it. A ResizeObserver on the scroll box never fires when Load more or a poll appends rows: the box is capped, so its scroll height grows and its border box does not move. Those rows would render clamped with no fold control -- the exact failure D5 exists to prevent. The measurement now has two triggers, the observer for width and a layout effect for any change to the rendered set, and each run covers every row currently rendered rather than the first MAX_RENDERED_ITEMS. D10 put the copy button over the block's padding without saying the padding had to be wide enough to hold it. frontend-ux.md:113-114 says text must not overlap adjacent controls, and the rows D5 newly makes foldable are precisely the ones whose first wrapped line runs the block's full width, so the overlap would be the common case. The block reserves right padding for the control. D15 prescribed an arbitrary min-w-[7ch] in product UI. Accepted in substance, not in placement: SectionContent already expresses its cap as a named constant applied inline, so the column width joins ITEM_HEIGHT as a local constant. styles.stylex.ts is wrong for it -- that file holds the shared control-size scale every primitive reads, and frontend/AGENTS.md sends *repeated* measurements there, while this one has a single consumer. Mocks C and D re-rendered with the reserved clearance. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bfab4ca384
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Two findings on bfab4ca. The P1 is a trap I built. D5's two conditions both describe the statement against its own line, but on a failed row the line is the error and the statement is never on it. A terminal failure on something as short as SELECT 1 satisfies neither condition, so it would open by default under D4 and then, once folded, offer no way back -- the statement and the copy button that lives with it are both inside the block. Fold once, lose the SQL. The rule gains a third condition, listed first because it is the one an implementation would miss: a row carrying both error and statement is always foldable. The P2 is right that a failure with no recoverable statement should not enlarge its section, and I am taking that half. Not the other half: it should still be marked. The mark's first job is to say which row explains the outcome, and D13 has to render and scroll to it or the error stays hidden behind Load more -- which is exactly the case when a sheet came back partial and no row has a statement. So D8's cap now answers to a block being on screen rather than to the mark, and opening a statement-less row is a no-op, which is why D5 gives it nothing to open with. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d30d6cd283
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The tuple I specified was not an identity. task_run_log has no primary key by design -- its own comment says entries for one task run can legitimately share a created_at microsecond -- so time ties. And the byte offset cannot break the tie in the case this design exists for: LogCommandExecute writes Statement or Range and never both, and SDL sets LogCommandStatement = true, so SDL command entries carry no offset at all. Two tied rows would share one override key, and folding either would fold both. The identity is now the replica id, the log time at full precision -- the proto carries seconds and nanos while getTimestampMs discards the sub-millisecond part -- the entry type, and an ordinal among entries matching all three, taken from the API's append-only chronological order so a tie-group's members never reorder. None of the four terms names a section, a grouping or a render position, which is what keeps it stable across the regrouping it was introduced to survive. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e88743fd57
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
D14 defined an identity but no field carried it. SectionContent only ever sees DisplayItems, so with a positional key it has nothing stable to report when a reader toggles a row, and the persistence D14 promises could not be built. Fixing that by adding a second, stable key beside the positional one -- which is what the previous commit implied by saying render keys keep their shape -- would leave two keys on one row and make every consumer pick correctly, when picking wrong is the whole bug. So key becomes the identity. Nothing reads its shape: SectionContent hands it to React and that is all. React gains too, since rows currently remount for no reason when a replica appears and every key is rewritten. key is now listed in the D1 contract, because its contents change even though the field does not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 44a4f6f327
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Codex is right that min-w-6 reintroduces the bug D15 exists to fix, and the same flaw was in the column I designed rather than only the one it flagged. Each row is its own flex container, so a minimum only sets a floor. A section reaching row 1000, or a run long enough for +1234.56s, widens that row's cell and pushes its chevron and statement right while every other row stays put -- the ragged edge again, worst in D13's sparse window where a four-digit failure sits directly beneath three-digit neighbours. Both columns are now sized once per section from the widest value that section will actually render, and every row uses that width. SectionContent already knows the numbers it is about to draw, including the real index D13 gives the marked row, so it measures them and writes the two widths onto the scroll box as custom properties for the cells to read. One write per render, no per-row variation possible. The floors stay floors, so a short section is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 113477c360
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Two findings on 113477c, both about my own recent changes. The identity was defined but not placed. Computing the tie ordinal inside buildSectionsFromEntries resets it per call, and buildReleaseFileGroups calls that builder once per file with only that file's entries, so two tied entries in different release files would both be ordinal 0 and collide on a key meant to be unique across the viewer -- folding a row in one file would fold a row in another. Identities are assigned once over the run's whole entry list, before any replica or release-file filtering, and travel with the entry; the builders never mint one. And the regrouping regression still described the render key changing from section- to replicaId-, which was true of the two-key design D14 rejected one commit ago. As written the test would have asserted the bug. It now requires the key to stay byte-for-byte identical while the fold persists. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8029dd73b9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
D5 classified a successful single-line statement as foldable only by the clamp, and D10 has the block replace the line when the row opens -- so the thing the clamp measured stops existing. Opening also raises D8's cap, which resizes the scroll box, which fires the very observer that would reclassify the row. The chevron would disappear from a row while it sat expanded, and the way back with it, since the row's click target is gated on the same verdict. The rule now says an open row is foldable without measuring, and the verdict is recomputed only for closed rows. You must always be able to close what you opened. The regression drives an observer callback while a clamped one-line statement is open -- which is what raising the cap does in the product -- and asserts the control survives and still closes the row. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4fb73f0464
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…clickable Two findings on 4fb73f0. The first is a contradiction the previous commit introduced. 'An open row is foldable, full stop' swept in the marked failure that has no recoverable statement: it is nominally open yet has no block, so the unqualified rule handed it the chevron D1 and D5 both promise it will not get. The exception exists to let a reader close a block that is on screen, so it now reaches exactly as far as a block does. The second is a gap the selection guard cannot cover. It catches a drag, where the selection is already non-collapsed at mouse-up, but a double-click's first click arrives with the selection still collapsed, toggles the row, and replaces the text the second click was aiming at. Nothing observable at that moment tells the two apart, and waiting to find out would put a double-click delay on every toggle. So the statement -- clamped line or unfolded block -- is no longer a toggle target: text a reader can select must not double as a button. The index, timestamps, glyph and empty space still toggle, and the selection guard stays for a drag that begins on metadata and ends over the text. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e2ea714113
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
D14 promised a live failure opens when it arrives, without saying what happens when the section it arrives into is shut. The viewer already expands a section the moment its status turns to error, unless the reader collapsed that section themselves, which userCollapsedSections records and honours; replicas and release files get the same treatment. The promise is narrowed rather than the behaviour changed. Reopening would break the precedence D14 had just established -- an explicit toggle outranks the mark -- and would override the reader at the moment they had most clearly said what they wanted on screen. Nothing is hidden either way: a collapsed section still carries its status, so its header turns to the error glyph. D13 gains the matching clause: the scroll fires on mount when a marked row is already present, so expanding a section later lands on the failure rather than at the top. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Implements docs/design/task-run-log-statement-unfold.md (#21428), with four decisions revised during implementation review and recorded in the doc: - D4: nothing unfolds by default. The failure that explains the outcome is marked — rendered past the 50-row window and scrolled into view — but the reader opens it. - D7: every line with a fold control toggles on click, the statement and the error included; only the unfolded block does not. - D8: the section's box never changes height because a row unfolds. A block scrolls inside it, and its copy button is sticky to the box's top. - D16: unfolding reveals what it opened by scrolling the section the least that shows the row; folding puts the line back where it was clicked from. The model returns the line plus both payloads (verbatim statement and error) instead of an 80-character cut; row keys are the entry's identity, assigned once over the run so the reader's folds survive regrouping; the columns before the statement are sized per section so the chevrons line up. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Design doc for the task-run log viewer:
docs/design/task-run-log-statement-unfold.md.The log cuts every executed statement to 80 characters and appends
..., and it does it inside the model — so by the time a row renders, the statement is no longer in the component tree and nothing downstream can offer to show or copy the rest. For the statements customers actually run (SDL, or aCREATE TABLEwith a few hundred columns) the row says almost nothing. A failed command is worse: an early return hands backresponse.errorbefore the statement is ever read, so the row that a reader opened the log for shows the driver's complaint and no hint of which SQL produced it — and the error text never names it, because every driver logs a bareerr.Error(). #21276 finds the truncation and removes it, which is the right diagnosis and a partial fix: the\s+ → " "normalization above it stays, so the statement becomes one run-on line, it floods a 200px scroll box, copy is still absent, and the failed row is untouched. The doc lands on one rule: a row shows one line, and nothing the row is about is destroyed to produce that line. Frontend-only, inherited by the four surfaces that embed the viewer.What the doc decides
responseon the same entry that holdsRange/Statement.DisplayItemgainsstatementanderrorbesidedetail, both verbatim, plusdefaultOpen— the row the auto-open rule picked. The view cannot derive that one: it sees a section at a time while the pick is made across a whole execution context, anderroris no substitute, since every transient failed attempt carries one.truncate, so a wide screen shows more and a narrow sheet shows less, and an error line still wraps whole as it does today.ResizeObserveron the scroll box. A statement that is single-line and fits gets no control, because it would unfold to itself. A consequence worth stating: a narrow container turns most rows foldable, and that is honest — at the deploy sheet's width each of them really is hiding text. The ellipsis says which rows are cut; the chevron only says which can open.Buttons, never native markup —no-native-controlfailspnpm --dir frontend checkon a raw<button>in feature code. The row stays a<div>, so the fold control and copy are siblings rather than nested buttons, and each gets its own tab stop and name.frontend-ux.md:75-83forbids replacing a shared control's managed height or resizing its managed icon, andno-button-dimension-overrideenforces it; the smallest shared size isxsat 24px. So the row is 28px rather than 20px andITEM_HEIGHTmoves 20 → 28, keepingMAX_VISIBLE_ITEMShonest at ten rows and a 280px collapsed cap. Dropping to seven rows, or adding a 20px tier to the shared primitive, are both recorded as not taken.max(280px, 60vh), and the block never gets anoverflowof its own, so the section's box is the only scroller the viewer owns. The deploy sheet'sSheetBodyis an outer scroller that predates this change; handing it the scroll would cost the viewer a second layout mode, so it is recorded as an option rather than taken.DONErun marks nothing. It costs the live behavior nothing and it covers the shape the entries get wrong — CockroachDB's autocommit retry logs oneCOMMAND_EXECUTEand a response per attempt, and the converter keeps the first, so a statement that succeeded on retry is already recorded as a failure today. That row is wrong before this design reaches it, and it is listed as a backend defect rather than papered over here.+0ms,+12msand+506mseach shift what follows and the statement starts at three different x positions inside one section — 228px, 235px and 242px, measured. The log has always been ragged this way; ragged text reads as text, but a column of identical chevrons beside it does not. Reserving the column straightens the statement column too, which is the part of the change that improves the log as it stands.Mockups
Product typography, spacing and semantic colors are taken from the live component. The 80-character line in A is produced by running the current
model.tsexpression over the same statement.A · Today. The cut lands mid-identifier on rows 2, 3 and 5 and leaves a third of the row empty — the constant cannot see the width it is cutting for. Row 7 failed, and is the error and nothing else. Note the left edge: the statement starts further right on each of the first three rows, because the relative-time column has no reserved width.
B · Folded, with row 2 hovered. The line now runs to the width it is given, and copy sits in the existing right-hand cluster and shifts nothing when it appears. Against A, the left edge is straight: one x for the fold controls, one for the statements. Rows 1, 4 and 6 carry no chevron — their statements are single-line and fit, so unfolding them would change nothing. Compare E, where the same rows earn one at sheet width.
C · Row 2 unfolded. Verbatim formatting, in place, one statement at a time; the index, timestamps and glyph stay pinned to the first line, and copy has moved into the block's corner. The section is capped at
max(280px, 60vh)and still scrolls — a few hundred columns is taller than any cap worth setting.D · A failed command. The same failure as row 7 of A, under the proposal: the error keeps the line, with no copy button of its own, and the statement it came from sits in the block beneath it — open by default, with the row's only copy button in its corner.
E · The same rows in the deploy sheet. Nothing about the component changes; the clamp simply resolves against 560px — and every row earns a chevron here, including the two that had none in B, because at this width each of them really is hiding text.
Not in this PR
The implementation, which follows separately. Also out, with reasons in the doc: a smaller shared control size (the 8px the row gains is the cost of the size contract, and a 20px tier is a design-system change), syntax highlighting in the unfolded block (a Monaco instance per log row is far past what this surface can afford), and the entry types that carry status words rather than payloads.
🤖 Generated with Claude Code