fix(web): apply PR #32009 review feedback#32010
Merged
Merged
Conversation
PR #32009 feedback follow-up: * sanitize-display-messages.ts - Trim header comment: drop the parenthetical about `window._vellumDebug.chat.tail()` (tail() no longer touches the pipeline directly — see debug-api change below). - Replace the redundant JSDoc + sequential lets in `sanitizeDisplayMessages` with a `pipeline.reduce` over the three named steps — the implementation now is the spec. - Drop the local `sortByTimestamp` wrapper; the imported `sortedByTimestamp` is the named pipeline step directly. - Update Hack #2 docblock + SHORT TERM line to use the codebase's 'assistant' terminology and drop the inline ticket reference. * debug-api.ts - `tail()` is now logic-free: it reads from a new `sanitizedMessagesRef` populated by the render path. DevTools mirrors the UI without re-running the sanitize pipeline. Drops the `sanitizeDisplayMessages` import inside debug-api entirely. * chat-page.tsx + chat-route-content.tsx - Own `sanitizedMessagesRef` in the composition root, thread it through `ChatRouteRefs`, populate it right after the `useMemo(() => sanitizeDisplayMessages(messages))`. Rename `sortedMessages` → `sanitizedMessages` to match what it actually is. * build-items.ts - Remove the comment claiming `messages` must already be sanitized. The function takes whatever it's given; sanitization isn't its contract. * debug-api.test.ts - Migrate the existing tail() tests to populate `sanitizedMessagesRef` (the new contract). Add a contract test that pins 'tail() reads from sanitizedMessagesRef, NOT raw messagesRef' so the two refs can't be silently swapped back.
dvargasfuertes
approved these changes
May 25, 2026
Comment on lines
+221
to
+226
| /** | ||
| * Mirror of the post-`sanitizeDisplayMessages` array, populated below right | ||
| * after the render-boundary `useMemo`. Read by `useChatDebugApi`'s `tail()` | ||
| * so DevTools sees exactly what the transcript renders without re-running | ||
| * the sanitize pipeline. | ||
| */ |
Comment on lines
795
to
+797
|
|
||
| // Mirror into a ref so `useChatDebugApi`'s `tail()` can read what the | ||
| // UI is actually rendering without re-running the pipeline. |
Contributor
There was a problem hiding this comment.
Suggested change
| // Mirror into a ref so `useChatDebugApi`'s `tail()` can read what the | |
| // UI is actually rendering without re-running the pipeline. |
| // UI is actually rendering without re-running the pipeline. | ||
| sanitizedMessagesRef.current = sanitizedMessages; | ||
|
|
||
| const transcriptItems = useMemo( |
Contributor
There was a problem hiding this comment.
introduce another ref to track transcript items
Comment on lines
+251
to
+254
| // Populated by `chat-route-content.tsx` right after the render-boundary | ||
| // `useMemo(() => sanitizeDisplayMessages(messages))`. Owned here so | ||
| // `useChatDebugApi` (mounted in this component) can read the same array | ||
| // the UI is rendering without re-running the pipeline. |
Comment on lines
249
to
250
| const messagesRef = useRef<DisplayMessage[]>(messages); | ||
| messagesRef.current = messages; |
Contributor
There was a problem hiding this comment.
Do we need this ref anymore?
Comment on lines
-56
to
+46
| function sortByTimestamp(messages: DisplayMessage[]): DisplayMessage[] { | ||
| return sortedByTimestamp(messages); | ||
| } | ||
| // (Implementation: `sortedByTimestamp`, imported at the top.) |
Contributor
There was a problem hiding this comment.
Ah ok we are using this in reconcileMessages, which we plan to cleanup, simplify, and remove in another convo. for now, let's add back the wrapper method we had here until we are ready to delete reconcileMessages
Contributor
Author
|
Follow-up shipped as #32013 — Option C from the chat thread. This PR's feedback → resolution:
Plus from chat (Option C):
CI on #32013. |
dvargasfuertes
pushed a commit
that referenced
this pull request
May 25, 2026
…tTranscriptItems() (#32013) Follow-up to PR #32010 review feedback. Vargas chose Option C from the DisplayMessage vs TranscriptItem design conversation: keep both arrays visible side-by-side via the debug API before deciding which transcript items should be promoted to messages or whether wrapper kinds can be dropped. Changes ------- - Rename `chat.tail(n?)` → `chat.getClientMessages(n?)`. Same shape (`DisplayMessage[]`), same default limit, same source ref. The new name disambiguates "what the client thinks the message list is" from the server-side `serverMessages()` view. - Add `chat.getTranscriptItems(): TranscriptItem[]`. The full virtualized row list — messages plus the thinking indicator, pending prompts, queued marker, error notices, and the onboarding-choice row. Logic-free reader of a new `transcriptItemsRef` populated by `chat-route-content.tsx` right after `buildTranscriptItems`. - Migrate the one in-tree consumer: `share-feedback-modal.tsx` now attaches `clientMessages` + `transcriptItems` to the triage tar. Review-feedback resolutions on PR #32010 ---------------------------------------- - chat-route-content.tsx:226 — deleted `sanitizedMessagesRef` JSDoc. - chat-route-content.tsx:797 — deleted "Mirror into a ref so …" comment. - chat-route-content.tsx:800 — added `transcriptItemsRef` to `ChatRouteRefs`, populated in the same spot as `sanitizedMessagesRef`. - chat-page.tsx:254 — deleted the JSDoc above `sanitizedMessagesRef`. - chat-page.tsx:250 ("Do we need messagesRef anymore?") — kept. `messagesRef` is consumed by 5+ stream/send/secondary-action hooks (`use-send-message`, `use-stream-event-handler`, `use-conversation-secondary-actions`, `interaction-handlers`, `surface-handlers`) and a contract-pin test asserts the debug API reads sanitized, not raw. Migrating those consumers is its own follow-up. - sanitize-display-messages.ts:46 — restored the local `sortByTimestamp` wrapper. `reconcileMessages` still imports `sortedByTimestamp` directly; the wrapper keeps this file's pipeline self-contained until the reconcile cleanup lands. Tests ----- - 64 tests pass across `debug-api.test.ts` + `sanitize-display-messages.test.ts`. - New `createChatDebugApi.getTranscriptItems` describe block covers: empty case, reference-identity (`expect(result).toBe(items)`), full discriminated-union surface, and a contract pin asserting the impl reads from `transcriptItemsRef` (NOT derived from `sanitizedMessagesRef`). - Renamed contract pin: `"reads from sanitizedMessagesRef, NOT raw messagesRef"` still applies to `getClientMessages`. - Help-text test now asserts both `.getClientMessages(` and `.getTranscriptItems(` appear. Verification ------------ - `bun test`: 64/64 pass. - `bun run lint`: clean on all 6 touched files. - `bun run audit:cross-domain:check`: clean. - `bun run typecheck`: 10 errors, identical set to `origin/main` baseline (settings/ai, platform_actor_token, provisioned_storage_gib — all in untouched domains). Refs: #32010, #32009 Co-authored-by: vellum-apollo-bot[bot] <220448527+vellum-apollo-bot[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #32009. Addresses each piece of review feedback and applies the suggested refactor pattern.
Closes review threads on #32009:
sanitize-display-messages.tsline 4) — dropped the parenthetical aboutwindow._vellumDebug.chat.tail(), sincetail()no longer touches the pipeline directly (seedebug-api.tsbelow).sanitizeDisplayMessagesbody (line 23) — adopted thepipeline.reducepattern Vargas suggested. The JSDoc that enumerated the three steps was indeed redundant with the implementation, so the JSDoc is gone and the array literal is now the spec.sortByTimestampwrapper (line 57) — deleted. The importedsortedByTimestampfrommessage-sorting.tsis the pipeline step directly. Added a one-line(Implementation: sortedByTimestamp, imported at the top.)under the Hack feat: initialize Next.js app in /web directory #1 header so the docblock still says "here's the named step."removeInvalidMessagesdocblock (lines 72, 75) — switched "daemon" → "assistant" for the runtime that synthesisesunknowntool calls, dropped the inline ticket reference.build-items.tsline 58— removed the comment claimingmessagesmust already be sanitized.buildTranscriptItemstakes whatever it's given; sanitization isn't its contract.debug-api.ts— the meatier oneVargas: "This method is doing too much logic, and it should be just reading from the same ref that eventually is looped over the react components."
Done. The flow is now:
sanitizedMessagesRefis created inchat-page.tsxnext tomessagesRef, threaded down throughChatRouteRefs, populated bychat-route-content.tsxright after the render-boundaryuseMemo, and passed intouseChatDebugApi.tail()is now a one-liner that slicessanitizedMessagesRef.current. ThesanitizeDisplayMessagesimport is gone fromdebug-api.tsentirely.sortedMessages→sanitizedMessagesinchat-route-content.tsxto match what the variable actually holds (it's not "just sorted" anymore).Tests
debug-api.test.ts— everytail()test now populatessanitizedMessagesRefinstead ofmessagesRefto reflect the new contract. Added one new contract test (reads from sanitizedMessagesRef, NOT raw messagesRef) so the two refs can't be silently swapped back later.sanitize-display-messages.test.ts(24 tests) is untouched —pipeline.reduceis wire-compatible with the oldlet result = …; result = …chain.Local checks
bun teston touched files: 170 passing, 0 failing.bun run linton touched files: clean.bun run audit:cross-domain:check: clean.bunx tsc --noEmit: identical pre-existing errors as main (all insettings/ai+Assistanttype, unrelated to this PR).Codex P1 on
line 181Acknowledged Vargas's reply on the merged PR — the position-for-position
toolName+resultmatcher is intentional. No change here.Broader design question
Discussed separately in chat. tl;dr:
DisplayMessage[]is the logical conversation model;TranscriptItem[]is the virtualized render list (a discriminated union of 10 kinds, only one of which ismessage). They're not interchangeable. The duplication we just eliminated wasn't between them — it was between two independent computations of "sanitized DisplayMessage[]" at the render boundary and insidetail(). With this PR there's now a single computation, and a ref bridges it to the debug API.