Skip to content

feat(site/src/pages/AgentsPage): keep agent attachments in working blocks and list them under Attachments - #30788

Merged
ibetitsmike merged 14 commits into
mainfrom
mike/chat-artifacts-panel
Oct 19, 2026
Merged

ibetitsmike merged 14 commits into
mainfrom
mike/chat-artifacts-panel

Conversation

@ibetitsmike

@ibetitsmike ibetitsmike commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

In /agents chats with Collapse assistant steps on, files the agent attaches (attach_file, computer-use screenshots, the propose_plan plan file) rendered as the answer after a Working block: the block closed at the attachment, and a turn that kept working split into several blocks. They now fold into the Working block of the step that made them and show when it is expanded. Rows outside every block (an uncollapsible propose_plan or ask_user_question row, a parked running tool) hide their files, and an agent row left with nothing else is not rendered. With the preference off, the transcript is unchanged, except that download cards get the focus ring the text-preview button beside them already has.

To keep those files reachable, the right panel's Summary tab gets a collapsible Attachments (N) section, open by default. It lists the files the agent attached in the loaded messages, newest first, as the transcript's download cards (expired tile included); user uploads stay in their messages. While older history is unloaded, the section says so and offers Load earlier messages. Until the first message page reaches the chat store, the header shows no count, so a chat with files never flashes "(0)", and live messages do not make the list blink.

Frontend only: production code changes 366 lines; the rest is tests, stories, and fixtures. This overlaps #30656, which renders attached files below the Working block; this PR keeps them inside the block, as the newer request asks, so whichever lands second reconciles workingBlockGrouping.ts and ConversationTimeline.tsx.

Collapse on. The turn's files stay in the collapsed Working block, and the Summary panel lists them under Attachments (3), newest first.

Collapsed Working block beside the open Attachments section

Block expanded. Each file follows the step that attached it.

Expanded Working block with each file after its step

Expired file. After old.zip expires, the panel shows the expired tile in its place.

Attachments section with an expired tile

Follow-ups, out of scope: split ConversationTimeline.tsx (795 lines before, 805 now); keep focus on load-more buttons while they fetch (HistorySidebar, InboxPopover, this one); show the fetch error in both message pagers; share probe-learned expiry between the transcript and the panel; flag evicted files newer than the newest linked one, such as an evicted newest file (deriveEvictedFileIds cannot yet tell them from files the chat has not linked yet); name the file on the expired tile.

Testing

  • pnpm lint, the AgentsPage unit tests (122 files, 2133 tests), and the story tests for the touched area (13 files, 261 tests) pass on 30007ac.
  • New unit tests cover a step's attachment staying in its block, rows outside blocks dropping files, isAgentAttachmentOnlyRow, getAgentAttachments (agent files only, newest first), the loading gate (a live message does not blank the list), and the load button. Each fails when its behavior breaks.
  • New stories: ChatAttachments (12 states), four ChatSummaryPanel states, and three working-block states.
  • Remote UAT round 4: PASS on 3e47d30, on a dev deployment built from the branch. It checked expired files on both surfaces, mouse and Enter downloads against the source files' sha256, no "(0)" on reload or chat switch, one count change per live attachment, and the focus ring. 30007ac changes only a doc comment after it, and the screenshots come from this round. Rounds 1 to 3 passed on earlier heads; round 3 covered Load earlier messages, rows outside blocks, and 18 real-model runs on four models.
Review record
  • 11 rounds of coder-frontend-review (8 global roles), frontend-deep-review (8 project roles), and frontend-review (FE1 to FE10), read-only. Round 9 ran all 17 roles on b53897c; rounds 10 and 11 re-checked the comment-only fixes on 3e47d30 and 30007ac.
  • Final verdicts: every checkpoint PASS except FE5 and FE6 (justified below). RP6's PASS requires the ConversationTimeline.tsx split follow-up listed above.
  • Test audit: PASS through b53897c. Every finding not listed below was fixed.
  • Codex: four rounds on 30007ac. Each of its five P2 findings was declined in its thread with evidence: a spinner for the one-commit hydration gap, the file name on the shared expired tile, the copy action for a text-and-file message that chatd never stores, gating the load button on hydration, and the ChatAttachments doc. Round 4 found no major issues.

Justified FAILs:

  • FE5, no error text for a failed page: the transcript's pager shows the same retry-only state for the same query; follow-up.
  • FE6 and SA6, the button disables while fetching and loses focus: the repo's load-more pattern (InboxPopover, HistorySidebar); follow-up for all three.

Dropped findings (finding: reason):

  • RO6 stack split: the component is 94 lines, and the hiding needs the panel, so the halves are not independent.
  • RP6 and RO5, split ConversationTimeline.tsx now: the PR adds call sites, a prop, and two flags; follow-up, and fix(site): split Working blocks at assistant text and scroll to their end on expand #30656 edits it.
  • RO1 SidebarSectionHeader: a sidebar h2 with no aria-controls; the section uses the Radix Collapsible in SectionHeader.
  • RO1 isFileRenderBlock: inline block.type checks are the grouping module's idiom.
  • RO2 shared retry button: the two share only variant, size, and label.
  • RO3 every helper: a one-line idiom with different predicates in two modules.
  • RO3 shared guard with getMemberRow: two conditions, and fix(site): split Working blocks at assistant text and scroll to their end on expand #30656 rewrites getMemberRow.
  • RO3 Omit over Pick: the Pick reuses the exported EarlierMessagesProps types.
  • RO3 page-level spread: AgentChatPageView passes every prop explicitly.
  • RO4 move isAgentAttachmentOnlyRow: it takes the grouping module's row type and sits beside getRowBlocks.
  • RO5 transcript focus ring: intended; it matches the text-preview button.
  • SD1 inline single-use story constants, and repeated fixture values: FE9 asks for named spread constants.
  • SD2 { message } wrapper: retyping moves the map into ConversationTimeline.tsx, which fix(site): split Working blocks at assistant text and scroll to their end on expand #30656 edits.
  • SA2 cva for fullWidth: one boolean in a pre-existing component.
  • TN4 inline type imports: the repo's style for mixed imports.
  • TN4 ChatStoreHandle: ChatStore is the exported type; the handle is a file-local alias.
  • TN4 named FileCardProps: the file types its components inline.
  • TN4 GetRowBlocksOptions: named like GroupWorkingBlocksOptions.
  • TN5 AgentAttachment beside readAgentAttachmentText: that one reads user uploads, in another module.
  • TN6 copy: the repo's paged empty state (SessionTimeline.tsx) and the transcript's retry label.
  • CD1 getAgentAttachments doc: it states the ordering and the upload exclusion.
  • CD1 trim exported docs: AGENTS.md requires docs on exported symbols.
  • CD4 isHydratingMessages comment: pre-existing and consistent with its source.
  • CD5 consumers in the EarlierMessagesProps doc: the doc states the contract.
  • splitRowBlocks doc: the base text stays; a comment at the file condition gives the reason.
  • Hydration comment placement: it sits on the operand it explains.
  • Deleted NotVisible comment: this PR made it false.
  • TS1 and TS5 NotVisible assertions: pre-existing.
  • TS7 panel story with hasMoreMessages: tsc enforces the forward; WithEarlierMessages is the visual.
  • TS7 streamed-file story: live rows share the getRowBlocks call.
  • TS7 text-plus-file case and ChatError story: the grouping test fails on the same break; no new state.
  • TS7 raster image fixture: grouping treats every file part alike.
  • TS7 text and a file in one message: chatd persists a file as its own message; the unit test covers the branch.
  • Play-function assertions and a collapse test: plays drive state, and Radix owns collapsing.
  • useChatMessages unit test: a moved derivation with no new logic.
  • Drop LoadingMessages and LiveMessageArriving: each catches a call-site break (test audit).
  • A store per story, built in render: as ChatPageContent.stories does.
  • NotVisible capture shows the section: it renders whatever isVisible is.
  • RP4 hoist the .map callback: a single-use wrapper (site/AGENTS.md).
  • RP5 useChatMessages as a function: it owns two subscriptions that three components share.
  • RP8 pre-existing render helpers: unchanged; the split follow-up covers them.
  • Per-render derivations and a second subscription: pure derivations; only the queries are gated.
  • useFileProbes() before the branches: hooks cannot be conditional, and the default context is safe.
  • DF5 share probes with the panel: FileCard never probes; both read chat.files, polled every 5 s while the chat runs.
  • DF5 every loaded file evicted: a pre-existing deriveEvictedFileIds gap; follow-up.
  • DF5 empty before query data: the page shows its loading view then.
  • Spinner before the store fills: a one-commit window, so it would flash.
  • Previous chat after a switch: the page remounts per chat with its own store.
  • void onFetchMoreMessages(): fetchNextPage reports errors in its result, and the transcript calls it the same way.
  • Later parts first within a message: later parts are newer.
  • Up chevron when open: as ChatSectionHeader does.
  • messageHelpers.ts imports chatAttachments.ts: the page already loads it; dpdm finds no cycle.
  • Live status on a live row whose file is hidden: intended.
  • m-0 on the empty state: index.css sets paragraph margins.
  • FileCard's name omits "Download file", and the expired tile names no file: pre-existing; naming the file is a follow-up.

UAT observations, not failures:

  • A file in a message that also holds text after the tool call is listed only under Attachments, as for any row outside a block. Round 3 first reported it as a FAIL; only DB-seeded rows had that shape, and 18 real-model runs never did.
  • An evicted newest file keeps its link until a newer linked file exists (follow-up above).
  • The panel scroller clips the bottom of a card's focus ring at its bottom edge.
  • The load button has no aria-busy, and after a failure two buttons read "Retry loading earlier messages".
  • One parallel step with two files renders both tool rows before both cards.
  • The transcript's expired tile is 112px wide beside 384px cards, and its tooltip is mouse-only (pre-existing).

Xum opened this PR on behalf of @ibetitsmike.

…ocks and list them under Artifacts

With collapsed assistant steps, file parts the agent attaches now fold into the working block of the step that made them instead of ending the block, and rows outside any block drop them. The Summary tab gains an Artifacts section that lists assistant file parts from the loaded messages, newest first, with a button to load earlier messages.
…rtifacts section

Tighten comments, require hideAttachments, drop an unreachable href filter, seed the plan story through its query key, and prefix new fixtures with mock.
…chment-only rows outside working blocks

chatd commits a local tool's attachments as their own assistant message
after the call. When that message falls outside every working block (for
example after propose_plan), hiding its files left an empty transcript
item and an extra gap. Skip such rows while steps collapse, and model the
persisted shape in the grouping test and the attachment story fixtures.
… artifacts on hydration

- Move the attachment-only check next to the working-block grouping as
  isAttachmentOnlyRow, and skip such rows with one condition in the
  timeline.
- Add useChatMessages to the chat store and use it in ChatPageContent and
  ChatSummaryPanel, so every caller keeps the desync warning.
- Hide the Artifacts empty copy while fetched pages are still hydrating
  the store, so it does not flash "No artifacts yet." on mount.
- Resolve each artifact's href in getChatArtifacts (getAttachmentHref now
  lives in utils/chatAttachments), and give FileCard a fullWidth prop in
  place of the className override.
- Type onFetchMoreMessages as a promise, as the page passes it, and pick
  the panel's paging props from ChatArtifacts.
… Attachments section

The Summary panel section now reads "Attachments (N)" and collapses
from its header, matching the name the transcript already uses. The
count is hidden while fetched pages hydrate the store.

Also applies the second frontend review round: the section picks its
paging props from EarlierMessagesProps, disables history paging while
messages hydrate, and Summary panel stories build a store per story.
…on steady while messages hydrate

Every live durable message reaches the store before the query cache, so
isHydratingMessages is true for one commit after each one. The section
hid its count, empty copy, and paging button during that commit, which
made them blink on every message. The panel now treats only an empty
store during hydration as loading, and the paging button is disabled
only while a page is loading.

Also applies the third frontend review round: the header uses
SectionHeader with the animated chevron, the attachment fixtures share
a step helper, and stories cover the loading and live-message states.
…ts list and build attachment fixtures from tool steps

Render the Attachments list, its empty text, and the paging button as
three sibling branches instead of a fragment inside a ternary.

Give download cards the focus ring that the sibling text preview button
already has.

Build attachment fixtures on workingStepMessages, so each attached file
sits between its tool call and its result. The Summary panel stories
now use the same builder, so their files also have tool results. Add a
story for a paging retry that is in flight.

Correct comments: hydration blocks only the transcript's automatic
pager, and only agent attachments hide outside working blocks. Restore
the splitRowBlocks doc and the NotVisible story note, and delete story
comments that repeat the loading-gate comment.
…ogic into grouping helpers

Move the timeline's section split and attachment filter into
getRowBlocks beside splitRowBlocks, so ChatMessageItem gains a call
instead of new logic. Rename isAttachmentOnlyRow to
isAgentAttachmentOnlyRow, since it matches only agent rows.

Expose the paging button's spinner to assistive technology, as the
other load-more buttons do (InboxPopover, HistorySidebar).

Name files beside sources in the splitRowBlocks doc, and move the
hydration note from the shared EarlierMessagesProps field to the
transcript gate that applies it.

Pass attachmentStepMessages one options object and type its tool fields
from the generated parts. Add stories for the focused header and
download card, and for an expired attachment in the Summary panel.
…gh getRowBlocks

Unexport splitRowBlocks, whose only outside caller after the previous
commit was its test, and run the split tests through getRowBlocks. A
new case covers hideAttachments dropping a row's files.

Say in the getRowBlocks doc why it drops files, shorten the
isAgentAttachmentOnlyRow and fixture docs, and name the Summary panel's
paging rest props earlierMessages, as ChatMessageScroller does.

Build the ExpiredAttachment story's file metadata from
MockChatFileMetadata.
…ents loading gate

Move the Summary panel's loading gate into getLoadedAgentAttachments
beside getAgentAttachments and cover it with a unit test: only an empty
store during hydration leaves the list undefined, so a live message
does not blank it.

Seed the new ChatSummaryPanel stories through parameters.queries
instead of API spies, and build their store with createAttachmentStore.

Name getRowBlocks' options type, keep the docs to what the names omit,
and drop the hideAttachments prop comment that the getRowBlocks doc now
covers.
…y to FileCard

FileCard takes an expired prop instead of reading the FileProbe context,
so the Attachments section passes expiry from chat.files and drops its
second FileProbeProvider, and the transcript's AttachmentBlock reads the
context it already sits in.

Keep comments to what the code does not say: the getRowBlocks doc
describes hideAttachments, the collapse rule moves to its derivation in
ConversationTimeline, the splitRowBlocks doc is back to its original
text with a one-line reason at the file check, and the
getLoadedAgentAttachments, EarlierMessagesProps, and fixture docs are
shorter. Delete a test comment that pinned a commit count and the
NotVisible story comment that restated its assertions.
State getRowBlocks' section contract and where hidden files go, and
drop the timeline comment that narrated the hideAttachments condition.
Say that the Attachments list stays undefined until the first page
reaches the store, that FileCard never probes expiry, and that every
consumer of the paging props sees a page any of them loads.
…c to agent rows

User rows render their uploads from the message display state, so only
the files hidden from agent rows show up under Attachments.
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

Xum posted this on behalf of @ibetitsmike.

@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: 30007ac6a4

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread site/src/pages/AgentsPage/components/ChatAttachments.tsx
Comment thread site/src/pages/AgentsPage/components/ChatAttachments.tsx
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T09:53:55.244373Z 30007ac Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

Xum posted this on behalf of @ibetitsmike.

@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: 30007ac6a4

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread site/src/pages/AgentsPage/components/ChatAttachments.tsx
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

Xum posted this on behalf of @ibetitsmike.

@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: 30007ac6a4

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread site/src/pages/AgentsPage/components/ChatAttachments.tsx
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

Xum posted this on behalf of @ibetitsmike.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: 30007ac6a4

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ibetitsmike
ibetitsmike marked this pull request as ready for review October 15, 2026 09:49
@ibetitsmike
ibetitsmike requested a review from a team as a code owner October 15, 2026 09:49
@pr-queue-manager
pr-queue-manager Bot requested a review from jaaydenh October 15, 2026 09:50
@ibetitsmike
ibetitsmike merged commit 8e76aec into main Oct 19, 2026
39 checks passed
@ibetitsmike
ibetitsmike deleted the mike/chat-artifacts-panel branch October 19, 2026 22:39
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 19, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants