Skip to content

fix(web): a mounted chat view no longer keeps every thread copy it replaced - #36390

Merged
juliusmarminge merged 2 commits into
mainfrom
factory/issue-36383
Nov 13, 2026
Merged

juliusmarminge merged 2 commits into
mainfrom
factory/issue-36383

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Nov 13, 2026 •

Copy link
Copy Markdown
Member

No change needed this round. PR #36390 at f88d1a1 passed all 37 checks. Macroscope approved it with no line comments, and CodeRabbit generated no actionable comments. CodeRabbit marked its Linked Issues pre-merge check inconclusive because the issue also names optimistic-message, settings-persistence, lifecycle and send-hook callbacks as possible retention edges. That is not a high-rated finding. The diagnosis this fix answers to pins the leak on the compiler-memoized focusComposer/scheduleComposerFocus closures, and the full-app WeakRef retention test from the repro branch fails without this change and passes with it, so the reproduced chain is settled by the focus callbacks alone. CodeRabbit's note that the PR description's Evidence section is a placeholder is for the PR-writing step. The fix has no visible UI change, and the browser test is its proof. The fix moves focusComposer and scheduleComposerFocus into a useComposerFocus hook so they no longer close over ChatView's render scope, and ChatView passes only the stable versions on.

Fixes #36383

Reproduction on main

On origin/main f8dc37d I added the WeakRef retention test from open PR #31464 to apps/web/src/components/ChatView.browser.test.tsx. It renders the whole AppRoot in real Chromium with only the socket faked. For six rounds it opens a thread, streams a turn-item.updated event into it, takes a WeakRef to the thread's projection, and switches to another thread; then it forces a GC through CDP. vp test run src/components/ChatView.browser.test.tsx -t "lets go of the thread structures" failed in both runs on main: the projections from rounds 0, 1, 2 and 3 were still alive (expected []). With #31464's fix applied temporarily (stable focusComposer/scheduleComposerFocus in a useComposerFocus hook), the same test passed. Then I reverted the fix and committed only the test. I did not reproduce the reporter's own setup (desktop dev build, Replay threads, heap snapshots); the retention is shown through this full-app browser test.

Evidence

Real development web app in Chromium, 1280×800, light theme, fresh synthetic data on each commit. Stream six Replay turns while switching between two threads and typing drafts, keep chat mounted, then force CDP garbage collection. The displayed diagnostic reads WeakRefs to the actual thread projections; the latest two are excluded.

Before: f8dc37d0b7 After: f88d1a1130
Result Four older projections retained; expected none All four older projections collected
Historical projections after GC Before: 4/4 retained After: 0/4 retained
Assertion-point capture Base failure capture Passed
Recording Before recording After recording

Recordings mask chat history to hide local workspace paths and append the inspected, unmasked result screenshot. Thread navigation, composer input, and generation controls remain visible.

Scenario source · Chromium launcher · Runner configuration

The scenario comparison exited 0: failsOnBase=true, passesOnHead=true.

Not shown:

  • The reporter's desktop Electron setup and heap-snapshot retaining paths were not inspected; this proof directly demonstrates historical projection retention and collection in the real development web client.

Tests

Previous round: vp test run src/components/ChatView.browser.test.tsx passed 8/8, including the retention test, which failed before the fix; vp run check:changed passed. This round: CI on f88d1a1 passed all 37 checks.

Not in this change

Who did the work

  • Written by Claude Opus 5.5, in Claude Code
  • Reviewed by GPT-6.1 Sol, in Codex, and by Claude Opus 5.5, each on its own
  • Proof captured by GPT-6.1 Sol and checked by Claude Opus 5.5

Run by the t3code factory in T3 Code.

juliusmarminge and others added 2 commits November 13, 2026 20:33
Fails on main: ChatView's compiler-memoized focus callbacks chain old
render scopes, so replaced thread structures survive a GC (#36383).
Test taken from #31464.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…placed

ChatView defined focusComposer and scheduleComposerFocus in its own render,
so the React Compiler memoized them there and each one pinned that render's
scope, which held the previous render's handlers. Old renders and their
thread projections formed a chain freed only on unmount. Make both
callbacks in a small useComposerFocus hook instead, and pass only the
stable versions on, including to useComposerSelection.

Fixes #36383

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@github-actions github-actions Bot added vouch:trusted Author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Nov 13, 2026
@macroscopeapp

macroscopeapp Bot commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at f88d1a1

Macroscope's review found this PR approvable — The change moves composer-focus callbacks into a dedicated hook to reduce retained chat-render memory while preserving existing focus behavior and model-picker protection. Its effects are limited to client-side focus handling and memory lifetime.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

ChatView moves composer focus callbacks into useComposerFocus and uses its immediate and scheduled callbacks. A browser test checks thread-structure projection reachability after repeated updates and thread switches.

Changes: Chat focus and retention

Layer / File(s) Summary
Composer focus callbacks and retention test
apps/web/src/components/chat/useComposerFocus.ts, apps/web/src/components/ChatView.tsx, apps/web/src/components/ChatView.browser.test.tsx
useComposerFocus provides immediate and scheduled focus callbacks. ChatView uses these callbacks for artifact-template insertion and composer selection. The browser test checks which thread-structure projections remain reachable after repeated updates, thread switches, and garbage collection.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: Minimal · up to f88d1

This change moves composer focus handling into a dedicated hook so that it no longer retains obsolete thread data. Focus behavior is intended to stay the same, and no merge-blocking risk was identified.

Pre-merge checks: Passed 3 · Inconclusive 1

Failed Failed checks: 1 inconclusive

Check name Status Explanation Resolution
Linked Issues check Inconclusive [#36383] The issue asks that long-lived callbacks keep their semantics without retaining a growing chain of obsolete render data. It names several retention edges: focus, optimistic-message and settin… Provide evidence that the named optimistic-message, settings-persistence, lifecycle, and send-hook callbacks no longer retain obsolete ChatView render scopes at head, or that they are not retention paths in the reproduced workload. Alternat…
Passed Passed checks: 3 passed

Check name Status Explanation
Title check Passed The title "fix(web): a mounted chat view no longer keeps every thread copy it replaced" matches the main change. The PR moves focusComposer and scheduleComposerFocus into a useComposerFocus hook so th…
Out of Scope Changes check Passed The whole-PR diff from merge base f8dc37d to head f88d1a1 touches three areas. ChatView.tsx replaces the local focusComposer and scheduleComposerFocus closures with the values returned by useCompose…
Description check Passed The description states the problem as a retention leak in ChatView focus callbacks, with a repro on main, a fix that moves focusComposer and scheduleComposerFocus into useComposerFocus, and a Fixes li…
Full details: Title check

Explanation

The title "fix(web): a mounted chat view no longer keeps every thread copy it replaced" matches the main change. The PR moves focusComposer and scheduleComposerFocus into a useComposerFocus hook so that ChatView no longer retains replaced thread projections. The title is a conventional commit, is specific, and is one sentence. It is somewhat long at 75 characters.

Full details: Out of Scope Changes check

Explanation

The whole-PR diff from merge base f8dc37d to head f88d1a1 touches three areas. ChatView.tsx replaces the local focusComposer and scheduleComposerFocus closures with the values returned by useComposerFocus, and removes the now-unused isModelPickerOpen import. The new useComposerFocus.ts hook contains the same focus logic moved out of the component. ChatView.browser.test.tsx adds the retention regression test. Each change directly supports the focusComposer retention fix or its test. No unrelated behavior change appears in the diff summary.

Full details: Description check

Explanation

The description states the problem as a retention leak in ChatView focus callbacks, with a repro on main, a fix that moves focusComposer and scheduleComposerFocus into useComposerFocus, and a Fixes link to issue #36383. It also lists tests run and the model and harness used. It is mostly complete. The Problem section is not explicit; the text starts with status notes and an argument about CodeRabbit and Macroscope comments, which is off-template. The Verification section has long evidence tables, but the Evidence section is a placeholder in the author's own words for a later step. The Scope section does not explain why the bug is a fix and not a feature, and it does not state a maintainer request. The description includes non-template material such as review-bot status and an 'Not in this change' list that concedes unaudited paths.

Full details: Linked Issues check

Explanation

[#36383] The issue asks that long-lived callbacks keep their semantics without retaining a growing chain of obsolete render data. It names several retention edges: focus, optimistic-message and settings-persistence callbacks, and lifecycle and send-hook callbacks. The PR changes only the focus path. At head, apps/web/src/components/chat/useComposerFocus.ts defines stable focusComposer and scheduleComposerFocus callbacks, and ChatView.tsx passes the stable versions to useComposerSelection, insertArtifactTemplate, and the subscription. Focus behavior matches the removed code: focusAtEnd on the current handle, and a requestAnimationFrame deferral that skips focus when isModelPickerOpen() is true. The author reports a full-app browser test (ChatView.browser.test.tsx, 'lets go of the thread structures') that failed before the change and passed after it. That test was not run in this review. The PR does not address or audit the optimistic-message, settings-persistence, lifecycle, or send-hook callback edges named in the issue. The issue's desktop Replay workload and production ownership are also unverified by the PR.

Resolution

Provide evidence that the named optimistic-message, settings-persistence, lifecycle, and send-hook callbacks no longer retain obsolete ChatView render scopes at head, or that they are not retention paths in the reproduced workload. Alternatively, confirm the issue scope is limited to the focusComposer edge. Include the result of running the full-app browser retention test at head, since it was not run here.

Finishing Touches

Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 4115648 and f88d1a1.

Files selected for processing: 3

  • apps/web/src/components/ChatView.browser.test.tsx
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/chat/useComposerFocus.ts

Limit details: You’ve used all 10 included reviews currently available.

@juliusmarminge
juliusmarminge merged commit 7ea6db4 into main Nov 13, 2026
38 checks passed
@juliusmarminge
juliusmarminge deleted the factory/issue-36383 branch November 13, 2026 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:trusted Author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Development chat retains a chain of historical render contexts during streaming

1 participant