docs(design): unfold and copy a statement in the task-run log by zchpeter · Pull Request #21428 · bytebase/bytebase · GitHub
Skip to content

docs(design): unfold and copy a statement in the task-run log - #21428

Merged
zchpeter merged 26 commits into
mainfrom
worktree-byt-10168-statement-unfold
Sep 18, 2026
Merged

zchpeter merged 26 commits into
mainfrom
worktree-byt-10168-statement-unfold

Conversation

@zchpeter

@zchpeter zchpeter commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator

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 a CREATE TABLE with a few hundred columns) the row says almost nothing. A failed command is worse: an early return hands back response.error before 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 bare err.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

  • A command row has up to two payloads, and the entry already carries both — the converter hangs response on the same entry that holds Range/Statement. DisplayItem gains statement and error beside detail, both verbatim, plus defaultOpen — 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, and error is no substitute, since every transient failed attempt carries one.
  • The line is the news: the error when the command failed, the statement when it did not. No length constant survives in the model — the line is clamped by truncate, so a wide screen shows more and a narrow sheet shows less, and an error line still wraps whole as it does today.
  • Unfolding always reveals the statement, and so does copy — and copy sits beside it, in the row's cluster while the statement is the line and in the unfolded block's corner once it is a block, so position and behavior agree. The error carries no copy button: reaching for an error message in the clipboard is rare next to reaching for the SQL, and it stays selectable text. A row with no recoverable statement gets no copy button rather than one that means something else.
  • What unfolds by default says what the row is for. A successful statement starts folded — the reader is scanning. A failed one starts open, with the error as its line and the statement beneath it — but only the failure that explains the outcome: a lock timeout makes the Postgres driver retry the whole command list and re-log every command, so the viewer opens the last failed row and only when nothing after it succeeded — judged across one replica or release file's whole entry sequence rather than within a section, since the retry marker starts a new section and would make every transient failure look terminal, and read from the entries rather than a task-run status two of the four surfaces never pass.
  • A row is foldable when unfolding would show something new — the verbatim statement differs from the line it collapsed into, or that line is clamped by the width it was given. Neither test covers the other: a three-line statement can collapse to a line that fits, and a one-liner can be far too wide, which is why the second half is measured with one ResizeObserver on 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.
  • Both controls are shared Buttons, never native markup — no-native-control fails pnpm --dir frontend check on 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.
  • Neither control overrides its size. frontend-ux.md:75-83 forbids replacing a shared control's managed height or resizing its managed icon, and no-button-dimension-override enforces it; the smallest shared size is xs at 24px. So the row is 28px rather than 20px and ITEM_HEIGHT moves 20 → 28, keeping MAX_VISIBLE_ITEMS honest 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.
  • While a row is open the section is capped at max(280px, 60vh), and the block never gets an overflow of its own, so the section's box is the only scroller the viewer owns. The deploy sheet's SheetBody is 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.
  • The row that opens by default is rendered even when it falls past the 50-entry window a section shows before Load more, since otherwise the promise is void in exactly the long migrations where finding the failure by hand is worst — it keeps its real row number rather than its position in the window, and the section scrolls to it, since a row mounted 1,400px below the fold keeps the promise in the DOM and breaks it on the screen.
  • The rule holds on a live log, not just a finished one. The viewer polls every five seconds, so a failure followed by the driver's retry marker is never treated as terminal, and open state is derived rather than set at mount — open when marked or when the reader opened it, folded when the reader folded it — so a deploy watched from the plan page opens its failure when it arrives, and a failure the reader folded stays folded — through polls, through collapsing the section around it, and up to the phase boundary, where the status-keyed remount deliberately starts the new phase fresh. Those folds are keyed by the entry's own identity rather than by the row's positional render key, which is renamed under them when a second replica appears or a retry marker splits a section.
  • One guard reads the run's status where a caller passes it: a DONE run marks nothing. It costs the live behavior nothing and it covers the shape the entries get wrong — CockroachDB's autocommit retry logs one COMMAND_EXECUTE and 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.
  • Every column before the statement reserves its width. The relative-time column has none today and is dropped entirely when empty, so +0ms, +12ms and +506ms each 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.
  • Tests are listed for both layers; none exist today, which is how fix: remove 80-char truncation of executed statement in changelog viewer #21276 passed a clean suite while changing this function's behavior.

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.ts expression 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.

A · Today: the 80-character cut, and a failed row that is error-only

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.

B · Folded, with row 2 hovered

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.

C · Row 2 unfolded, with copy in the block's corner

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.

D · A failed command, with the statement it came from beneath the error

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.

E · The same rows in the deploy sheet

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

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>
@zchpeter
zchpeter requested a review from a team as a code owner September 11, 2026 11:08

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md Outdated
Comment thread docs/design/task-run-log-statement-unfold.md Outdated
Comment thread docs/design/task-run-log-statement-unfold.md Outdated
… 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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md Outdated
Comment thread docs/design/task-run-log-statement-unfold.md Outdated
zchpeter and others added 2 commits September 16, 2026 06:25
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md Outdated
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md
Comment thread docs/design/task-run-log-statement-unfold.md Outdated
Comment thread docs/design/task-run-log-statement-unfold.md
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md Outdated
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md Outdated
…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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md Outdated
Comment thread docs/design/task-run-log-statement-unfold.md Outdated
…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>
@ecmadao
ecmadao requested a review from boojack September 18, 2026 07:00
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md
Comment thread docs/design/task-run-log-statement-unfold.md
zchpeter and others added 2 commits September 18, 2026 03:22
…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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md Outdated
Comment thread docs/design/task-run-log-statement-unfold.md Outdated
Comment thread docs/design/task-run-log-statement-unfold.md Outdated
…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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md
Comment thread docs/design/task-run-log-statement-unfold.md
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md Outdated
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md Outdated
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md
Comment thread docs/design/task-run-log-statement-unfold.md Outdated
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md Outdated
Comment thread docs/design/task-run-log-statement-unfold.md Outdated
…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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread docs/design/task-run-log-statement-unfold.md
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>
@zchpeter
zchpeter merged commit 77d4aac into main Sep 18, 2026
9 checks passed
@zchpeter
zchpeter deleted the worktree-byt-10168-statement-unfold branch September 18, 2026 10:12
zchpeter added a commit that referenced this pull request Sep 28, 2026
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>
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.

2 participants