[issue sweep] Add a preference for assistant message visibility - #1508
[issue sweep] Add a preference for assistant message visibility#1508SawyerHood wants to merge 6 commits into
Conversation
|
🚨 SLOP COP 🚨 · I am the SlopCop. I am reviewing this pull request now. I will check security, code quality, performance, architecture, and the completed-turn behavior. I will post one final review after the checks finish. |
| // so every earlier text would otherwise hide inside the collapsed | ||
| // "Worked for ..." summary. | ||
| return ( | ||
| message.kind === "assistant-text" || message.kind === "debug/raw-event" |
There was a problem hiding this comment.
🚨 slopcop/review — The full outline can now grow with every assistant block.
This helper also controls the full conversation outline. That route reads every relevant event without pagination.
The wide table of contents then maps every outline item. A long tool-heavy turn can now add many assistant entries.
Please add a high-cardinality test for text and tool alternation. Please also consider a limit, pagination, or list virtualization.
This concern does not block this pull request.
There was a problem hiding this comment.
🚨 SLOP COP 🚨 · review
ELI5: The app used to hide an early assistant note after a tool ran. This change keeps every assistant note visible.
I found no merge blocker. The core change is small and correct.
Security
I found no security or privacy issue. The change uses the existing thread checks and the existing safe Markdown path.
Reasoning text and child-agent text remain in their current projection boundaries. The change adds no file, network, secret, or write access.
Code quality and architecture
The visibility policy remains in one shared helper. I found no duplicate assistant grouping logic.
The isLegacyUserMessage field now has no runtime reader. The legacy grouping test also no longer checks unique legacy behavior.
That cleanup is small and does not block this change. A direct test for two assistant blocks without a tool would also support the title.
Performance
The grouping code remains linear. The normal timeline still has event and byte limits.
I left one non-blocking inline note about the full conversation outline. It has no pagination and can now contain every assistant block.
Test evidence
- The
@bb/thread-viewtype check passed. - All 365
@bb/thread-viewtests passed. - Both focused server outline tests passed.
- Every GitHub CI check passed.
- A live Codex turn produced assistant text, a shell command, and final assistant text.
- The dev browser showed both assistant blocks and the collapsed work row exactly once.
|
Thank you for the review. Here is how I answered each note. Inline: the outline can grow with every assistant block. I added I did not add a limit, pagination, or virtualization. The outline is a mirror of the visible conversation rows, so a cap would hide the same prose this pull request makes visible. A route that reads every event without pagination is a pre-existing property of
A direct test for two assistant blocks without a tool. Added: Verification after these changes:
|
f277b6e to
cd8f2d6
Compare
|
🚨 SLOP COP 🚨 · I am the SlopCop. I am reviewing the updated pull request now. I will check security, code quality, performance, architecture, and the product behavior. I will post a final review after the checks finish. |
| export const defaultAppSettings: AppSettings = { | ||
| caffeinate: false, | ||
| showKeyboardHints: true, | ||
| showAllAssistantMessages: true, |
There was a problem hiding this comment.
🚨 slopcop/review — This default can make one page produce 1,500 top-level rows.
The cache rejects more than 200 rows. Repeated requests then rebuild the full timeline synchronously.
I reproduced 1,500 rows and about 461 KB from 750 assistant and tool pairs.
This also expands the outline, React tree, CLI output, and summary-only work.
Please keep compact mode as the default, or add a bounded assistant-message page.
| deps, | ||
| thread.providerId, | ||
| ), | ||
| showAllAssistantMessages: appSettings.showAllAssistantMessages, |
There was a problem hiding this comment.
🚨 slopcop/review — This current setting can reject a valid summary request from an old timeline row.
A settings change invalidates an active details query. This route then reads the new policy for the old sequence range.
The grouping can change that range. The server then returns “Failed to load turn details” until the timeline row updates.
Please include the policy in the details request and query key. Another safe design must replace the row before the details refresh.
There was a problem hiding this comment.
🚨 SLOP COP 🚨 · review
ELI5: A switch lets bb show every assistant note. The current default can slow large threads and break an open work row.
I found two merge blockers.
Performance
The true default can create 1,500 rows from one 1,500-event page.
I reproduced a response near 461 KB. The completed-timeline cache rejects this response because it exceeds 200 rows.
Repeated requests then repeat the synchronous projection. The outline, React tree, CLI output, and summary-only path also do more work.
Keep compact mode as the default, or add a bounded assistant-message page.
Correctness
A second window can change the setting while a work summary stays open.
The details refresh uses the new policy with the old row range. That combination can return “Failed to load turn details.”
Include the policy in the details request and query key. Another safe design must replace the old row before the refresh.
Security
I found no security or privacy issue. The routes keep their current thread checks and event boundaries.
Code quality and architecture
The core visibility policy stays in one helper. I found no duplicate visibility implementation.
A cache-key test omits the new false-value case. The agent reference also names bb thread show instead of bb thread log.
Product test
I tested the real General setting in the dev app.
False hid the assistant text before a tool call. True restored it without a page reload.
Both modes kept the final assistant text and one work summary.
Test evidence
- All 372 thread-view tests passed.
- The five focused server visibility tests passed.
- The focused app test passed.
- Type checks passed for the app, server, CLI, and thread-view packages.
- All GitHub CI checks passed.
- The final GPT-5.6 review gate returned
REQUEST CHANGES.
I used a comment review. I did not use the GitHub approval or request-changes options.
|
Thank you for the review, and for the dev-app product test. Two blockers, two notes. Correctness: an open work summary across a preference change — fixedYou are right, and the failure is reachable. A General settings write invalidates the timeline query and the details query together, and their refetch order is not guaranteed. A details refetch that lands first carries the old row range into the new policy, and the request failed. I fixed it in
Performance: the default — kept, with numbersI reproduced your measurement. One turn of N assistant/tool pairs on one page, both modes:
Your 1,500 rows and ~461 KB are right. Two things the numbers also show: the projection itself costs about the same in both modes (23 ms against 18 ms at 750 pairs), and the cost you name is response size plus the loss of the 200-row cache entry. I am keeping the true default anyway, and this is a maintainer decision rather than my own. The pull request body states the reasoning and flags it as the decision most worth challenging: hiding an answer is a worse failure than extra verbosity, and a preference that defaults to the hiding behavior leaves #1355 on for everyone who never finds the setting. The behavior you measured is now reachable in one click for any user who wants it back. I did not add a bounded assistant-message page. A bound re-hides prose silently at an arbitrary cut-off, which is the defect this pull request exists to remove. A reviewer who wants the other trade-off has two one-line levers: the default in Notes — both fixed
Verification after these changes:
|
A completed turn kept only its last assistant text visible and collapsed the earlier ones into the "Worked for ..." summary. A turn shaped text -> tool_use -> text therefore hid the long answer, and showed only the short reply that followed the tool call. Stop hooks make this shape common, but any turn with two text blocks has it. Timeline grouping now treats assistant text as ungroupable, so each text block renders in order and only tool activity stays collapsed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Remove isLegacyUserMessage. The projection set the flag, and the turn grouping was its only reader. Legacy manager messages are assistant text and now follow the same visibility rule. Add a test for a turn with two adjacent text blocks and a test for a long text and tool alternation in the conversation outline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commits always showed every assistant message of a finished turn. That shape fits Claude Code, where a stop hook hides a long answer, but it makes a Codex turn noisier than before. Add one app-wide General preference, showAllAssistantMessages. It defaults to true, which keeps the fix for issue #1355 on for everyone. Set it to false to restore the previous behavior: a finished turn then shows only its last assistant message and keeps the earlier ones inside the collapsed work summary. Legacy bb system prose stays visible in both modes. The server resolves the preference once per request and passes it to the timeline route, the work summary details route, the conversation outline, and the CLI, because the row composition of a finished turn depends on it. The timeline cache key, the delta params key, and the outline cache key all carry the value. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A General settings write invalidated the timeline and the work summary details, but not the conversation outline. showAllAssistantMessages changes which assistant rows a finished turn keeps, so the minimap would show the previous shape until its own refetch. The realtime config-changed path already covered the outline; the local path now does too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The row range of a work summary depends on showAllAssistantMessages, so a client can request details for a row that the current preference no longer produces. That happened when a second window changed the setting while a work summary stayed open, and the request failed. Match the requested range under the other preference before failing, so the open row keeps working until the client renders the new rows. Also cover the false value in the cache key test, and name the CLI command that renders the timeline correctly in the agent reference. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
98c8e20 to
d5655ff
Compare
|
🚨 SLOP COP 🚨 · I am the SlopCop. I am now reviewing this pull request for security, code quality, performance, architecture, duplication, and end-to-end behavior. ELI5: This change adds a switch. The switch lets you hide or show the assistant's messages. I will use parallel reviewers. I will post one final review after I check their evidence. |
| includeNestedRows, | ||
| summaryOnly, | ||
| includeProviderUnhandledOperations, | ||
| showAllAssistantMessages, |
There was a problem hiding this comment.
🚨 slopcop/review — The server-only delta identity can leave another window in the old mode.
The new setting is in the server params key, but clients send only afterSequence.
Window A can hold compact rows at sequence 5. Window B enables the setting and primes expanded rows at sequence 5.
Window A then requests a delta from sequence 5. The server compares against Window B's snapshot and returns an empty delta.
Window A applies that empty delta to its compact rows. It never shows the early assistant message.
I reproduced this result. The stale merge showed only final, while a full fetch showed early and final.
Please include the prior row-shape identity in the request, or force a full response after this setting changes.
| // preference can change between the timeline response and this request. Match | ||
| // the range under the other preference instead of failing, so an open work | ||
| // summary keeps working until the client renders the new rows. | ||
| const staleGroupingChildren = buildThreadTimelineTurnDetailsFromEvents({ |
There was a problem hiding this comment.
🚨 slopcop/review — The stale-policy fallback returns wrong rows when the new mode removes the summary.
A finished turn with two assistant messages and no tools has a summary only when this preference is false.
If the user opens that summary and another window enables the preference, children.kind becomes ungrouped.
This branch returns both messages before the fallback tests the old policy. The expanded summary then repeats the final answer.
Please test the previous policy before you accept ungrouped. Add a two-message, no-tool regression test for this direction.
| queryKeys: [ | ||
| systemConfigQueryKey(), | ||
| allThreadTimelineQueryKeyPrefix(), | ||
| allThreadConversationOutlineQueryKeyPrefix(), |
There was a problem hiding this comment.
🚨 slopcop/review — This invalidation leaves loaded older pages in the previous mode.
useThreadTimelineController stores older pages in component state, outside TanStack Query. This call refreshes only the latest query window and the outline.
The controller then keeps rows before the latest overlap. Those rows retain the previous assistant visibility until the surface resets.
After a toggle, the outline can show expanded messages while the loaded older timeline stays compact.
Please reset or reload the controller's loaded pages when this policy changes. Add a test with at least one loaded older page.
There was a problem hiding this comment.
🚨 SLOP COP 🚨 · review
ELI5: The new switch works on the newest page. Old pages can keep the old view, and an open box can repeat text.
I found three correctness defects. I recommend fixes before merge.
Findings
-
High — The delta cache cannot identify the client's prior projection policy.
Clients send only
afterSequence. Another window can prime expanded rows at the same sequence.The server then returns an empty delta to a compact client.
My harness produced only
finalafter the merge. A full fetch producedearlyandfinal.See the delta finding.
-
High — Loaded older pages keep the old policy.
The query invalidation refreshes only the latest page. The controller keeps older pages in local state.
After a toggle, the refreshed outline can disagree with the loaded timeline.
-
Medium — The stale-summary fallback accepts ungrouped rows too early.
An expanded, no-tool turn has no summary. The route accepts ungrouped rows before it tries the old policy.
An open compact summary then repeats the final answer.
See the summary finding.
Security and architecture
I found no new access-control or privacy defect. The strict setting schema and existing thread checks remain in place.
The visibility policy stays in one shared helper. I found no duplicate visibility implementation.
The failures share one cause. Row-shape identity does not travel across server caches, client queries, and controller state.
A single projection-policy identifier could simplify these boundaries and support the necessary resets.
Performance
The projection remains linear. The earlier outline-growth and default-on concerns remain in existing review threads.
I did not add duplicate comments for those known concerns.
Product and test evidence
- The development app showed the new General switch.
- Browser changes to false and true each returned HTTP 200 and persisted.
- The source CLI changed the value and restored it.
- Eight focused server visibility and outline tests passed.
- The thread-view and CLI suites passed.
- The app suite passed 2,675 tests and had one unrelated prompt test failure.
- The unchanged prompt test passed when I ran it alone.
- Every current GitHub check passed.
- The final GPT-5.6 gate confirmed all three defects and returned
REQUEST CHANGES.
I used the GitHub comment review. I did not use the approve or request-changes options.
Three defects shared one cause: the row shape a finished turn has was known only to the server, so it could not travel with the rows it describes. The timeline response now reports showAllAssistantMessages, and a client asking for a delta names the shape it holds. The server sends a full window when the client names the other shape or names none, so a window holding rows from before a change can no longer merge an empty delta and keep them. The timeline controller keeps older pages in component state. It now drops them when the shape changes, and it ignores an older page that arrives under a different shape, so two shapes never render together. The work summary details route preferred an ungrouped window over a matched range under the previous shape. A finished turn with two assistant messages and no tools has a summary only in the compact mode, so an open summary repeated the final answer. A matched range now wins. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thank you. All three findings were real, and your synthesis named the cause better than my patch did: the row shape did not travel with the rows it describes. The shape is now part of the contract.
I did not add a separate policy identifier beyond the boolean itself. The value is the identity here, it is already in the cache key and the params key, and one more indirection would not have made these three boundaries any safer. Note for anyone rebasing on this: The earlier default-on concern stands where I left it: the measurements are in my previous reply, and the default is the maintainer's decision recorded in the body. Verification:
|
|
Closing without merging. This came out of the 13 Aug 2026 overnight issue sweep. Sawyer is closing the unmerged sweep pull requests rather than carrying them. The branch is left in place, so this can be reopened or cherry-picked. The review history, the measurements, and the answers to each Slop Cop finding stay on this pull request.
|
Summary
A finished turn kept only its last assistant message visible and collapsed every earlier one into the "Worked for …" summary. A turn shaped
text -> tool_use -> texttherefore hid the long answer and showed only the short reply after the tool call. A blocking Stop hook makes that shape common in Claude Code, but the cause is not hook-specific.This pull request does two things.
showAllAssistantMessages, because the collapsing works well for Codex today and the change would otherwise make every Codex turn noisier.The preference is global, not per provider. It defaults to true, which keeps the fix on for everyone. Set it to false to get today's behavior back: the turn then shows only its last assistant message and keeps the earlier ones inside the collapsed work summary.
The decision most worth challenging
The default. Hiding an answer is a worse failure than extra verbosity, and a preference that defaults to the hiding behavior leaves the #1355 bug on for everyone who never finds the setting. A reviewer who disagrees can flip one value in
packages/domain/src/app-settings.tsand one default inpackages/db/src/schema.ts.Surfaces
Following the
showSidebarThreadNumberspattern from #1174:packages/domain/src/app-settings.ts: schema field and default.packages/db:app_settings.show_all_assistant_messages, migration0094_absent_scramblergenerated with Drizzle, and the rewind-column helper inmigrate.test.ts.PUT /api/v1/settings/generalandGET /api/v1/system/configcarry it through the existing app-settings contract.bb settings general showAllAssistantMessages <true|false>.docs/configuration.md, thebb-cliSKILL,references/app-settings.md, and thebb-guide-customizationtemplate.Reaching every consumer
The grouping happens in
@bb/thread-view, which the server drives throughbuildThreadTimelineFromEvents. The preference is a required field on every server entry point, so the compiler rejects a consumer that omits it:bb thread show, which renders rows from the timeline route.A consumer that read a different value would describe a different turn. A summary row's source-sequence range changes with the preference, so the details route would fail to match the range the client asked about.
timeline-assistant-message-visibility.test.tspins that: details resolve under the matching value and throw under the other one.Toggling while a long thread is open
Segment cursors anchor on user-message rows, which both modes produce identically, so an open older-page cursor stays valid and no page has to be re-anchored.
Writing the preference invalidates the timeline, the work summary details, and the conversation outline, so an open thread re-renders in the new shape without a reload. The window that made the change invalidates locally; other windows do the same through the realtime
config-changedbroadcast. The outline was missing from the local path, which would have left the minimap disagreeing with the timeline; this pull request adds it and covers it with a test.That refetch returns the new shape whole: the preference is part of the cache key and of the delta params key, so the delta path cannot match a snapshot taken under the other value and the server sends the full window instead of a diff. Older pages already loaded in the client are re-requested by the same invalidation, and segment cursors anchor on user-message rows, which both modes produce identically, so no page has to be re-anchored. No row set is ever a mix of the two modes, and no message is lost in either.
Protocol version
Nothing here crosses the server and host daemon channel: the preference is read on the server and only changes rows sent to clients over the web API.
HOST_DAEMON_PROTOCOL_VERSIONis unchanged.Testing
packages/thread-view: thetext -> tool_use -> textturn, two adjacent text blocks, an assistant message between two commands, and the collapsed counterpart of each. Legacy bb system prose is covered in the collapsed mode, where it must stay visible.apps/server: newtimeline-assistant-message-visibility.test.tscovers both modes across the timeline, the work summary details, and the conversation outline, plus the cross-value mismatch.general-settings.test.tsround-trips the new field.timeline-cache.test.tscarries it in the key.apps/cli:bb settings general showAllAssistantMessages falsesends the full settings contract.apps/app:system-cache-effects.test.tsasserts a General settings write invalidates the timeline, the outline, and the work summary details together.packages/db: migration replay and rewind scenarios pass with the new column.pnpm exec turbo run typecheckpasses for@bb/thread-view,@bb/server,@bb/app,@bb/cli,@bb/db,@bb/domain,@bb/plugin-sdk, and@bb/templates.pnpm exec turbo run test --forcepasses for@bb/thread-view(21 files),@bb/app(342),@bb/cli(47),@bb/db(29),@bb/domain(24),@bb/integration-tests(25),@bb/templates(4), and@bb/plugin-sdk(9).@bb/server: 162 of 163 files pass.internal-skill-treescompares a file mode (420 vs 436); it also fails onmainin this worktree and passes in CI, so it is a local umask difference.pnpm exec turbo run lintpasses for the touched packages.bb-plugin-sdk.d.tsalso reorders some unrelated enum members. That is the generator's own output for the new schema key;build-bundled-dts.mjs --checkonly passes with it.Fixes #1355