Skip to content

fix(mobile): a finished thread leaves the Working section after its grace - #877

Merged
Jacksondr5 merged 2 commits into
j5/mainfrom
j5code/mobile-working-section-release
Nov 1, 2026
Merged

Jacksondr5 merged 2 commits into
j5/mainfrom
j5code/mobile-working-section-release

Conversation

@Jacksondr5

@Jacksondr5 Jacksondr5 commented Nov 1, 2026 •

Copy link
Copy Markdown
Owner

Problem

In the mobile Chats list, a thread could stay under the Working section after its turn ended. It came back only when some other thread changed, for example after visiting another chat. Jackson hit it on his iPhone at Checkpoint 1, and two of three Android debug runs reproduced it. Closes #842.

Cause

useThreadListV2 memoized the inbox tracker's observation like this:

useMemo(() => threadListInboxReturns.observe(...), [threads, workingShelfEnabled, inboxVersion])

inboxVersion was listed but never read inside the callback. The React Compiler memoizes on the values a computation reads, not on the listed dependencies. Compiling the file with Metro's release settings shows the memo guarded only by threads and workingShelfEnabled. When a finished thread's 2 s exit grace expired, the tracker bumped its version and Chats re-rendered, but it reused the old observation, which still said "working". The thread stayed there until a thread shell changed and invalidated the memo.

That's why it was intermittent. In the debug run that didn't reproduce, the thread's title regeneration happened to land after the grace and freed it. The debug logs (#842) showed the tracker firing correctly, with one listener, every time.

What changed

  • Upstream's own fix, taken ahead of the pin. fix(mobile): finished threads leave Working once the grace ends pingdotgg/t3code#28169 (be58795d37, merged 2026-10-31) fixed the same bug the same way: an observeInbox(threads, inboxVersion) helper that reads the version. This PR applies that hunk unchanged, so useThreadListV2.ts equals upstream's file at that commit (blob 1a0c67062f), and the next advance resolves trivially. The give-back agent found it.
  • Records: FORK.md, as a temporary in-place patch whose exit is "take upstream's file at the next advance".
  • Web is unaffected: it isn't compiled with the React Compiler, and calls observe on every render.

Tests

useThreadListV2.test.tsx runs under the release React Compiler (it's added to reactTests in the mobile vite.config.ts). It renders the hook with a working thread, then the finished thread, then advances only the grace timer. On trunk it fails ("expected 1 to be 0": the thread is still in Working). With the fix it passes. It also passes against upstream's version of the hook. The existing thread-list tests pass.

UI changes

This changes timing in the Chats list. A thread that finishes its turn now leaves the Working section after the 2 s grace, instead of waiting for another thread to change.

Android, recorded by a crew tester on the same test home: a follow-up message ("Say hello again.") sent from web to an existing Haiku thread, with Chats left untouched. A follow-up was used because a new thread's title regeneration can free the thread by accident.

Before (j5/main): the finished thread stays under Working.

android-working-before.mp4

After (this branch): Working empties within seconds of the turn ending.

android-working-after.mp4
Before After
Bottom of Chats, 50+ s after the turn ended, Android Working section still holding the finished thread, expanded, Android, before No Working section after the turn ended, Android, after

Working (1) still counting the finished thread at 53 s, Android, before
The finished thread back at the top of Chats after 60 s untouched, Android, after

On the before run, Working was collapsed, then expanded at 53 s to show the thread. The after run kept the expanded preference. iOS waits for Checkpoint 2.

Live testing

Android: PASS at 10c502c (wave-1a tester, report test-reports/j5code-mobile-wave1a-tester/10c502c88b.md). It used the same existing Haiku thread and the same prompt on both heads, with no new-thread title creation, and the focused tests pass. The compiled test is the fix's proof; the videos are the live check.

Upstream impact

Upstream's product behaviour doesn't change; the shelf now works as upstream designed it.

Checklist

  • One concern: the description has no "also"
  • Tests cover the changed behavior: a compiled hook test that fails on trunk
  • UI changes: Android before/after videos and screenshots above; iOS waits for Checkpoint 2
  • Live testing: the section above says what ran live and what did not
  • Upstream-owned files: each one is recorded in FORK.md (temporary patch and file-table rows) in this PR
  • Upstream product: no change to what upstream's product does
  • Surfaces: mobile Chats list on iOS and Android; web unaffected (explained above)
  • Docs: none

Built by Claude Opus 5.5 in Claude Code (J5 Code crew seat builder-composer).

🤖 Generated with Claude Code

@vercel

vercel Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
j5-code Ready Ready Preview Nov 1, 2026 8:17pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

  • Run on-demand review

This review includes 4 billable files and costs up to $1.00.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 8 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used all 3 included reviews currently available. Your 43 included PR review attempts over the past 7 days set your current allowance at 3 reviews per hour.

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between edc4350 and f4b2a7c.

Files selected for processing: 4
  • FORK.md
  • apps/mobile/src/features/threads/useThreadListV2.test.tsx
  • apps/mobile/src/features/threads/useThreadListV2.ts
  • apps/mobile/vite.config.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@Jacksondr5 Jacksondr5 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Combined panel review (reviewer-opus + reviewer-sol) at ebc3853

Verdict: no correctness findings, and the panel approves the source. One evidence gate stays open (below).

  • Cause confirmed. The React Compiler memoizes on the values a computation reads, so a useMemo that lists inboxVersion without reading it lost that dependency in the release build. When the 2 s exit grace expired, Chats re-rendered with the old observation. observeInbox(threads, inboxVersion) makes the version an argument of a module-level call. The compiler keys its cache on that argument and can't see that the body ignores it, so a bumped version recomputes. Dropping the useMemo is safe uncompiled too: observe is what web calls on every render.
  • Failing test first. useThreadListV2.test.tsx joins reactTests, so the production hook is transformed by the release compiler. reviewer-opus swapped trunk's useThreadListV2.ts back in: it fails with "expected 1 to be +0". It passes on the branch.
  • Zone 2, recorded. The upstream edit is the smallest one possible. It's recorded as a FORK.md temporary in-place patch with a watch signal and an exit, has ledger rows for both files and an upstreamSeams.test.ts reach, and is listed for give-back in #276. Jackson hit this on device, so the patch has the direct impact our rule asks for.
  • Reviewer-run: the new test and the seams test pass (48), mobile vp run typecheck exits 0, and lint is clean on both files.

Evidence gate (raised by reviewer-sol)

The PR ticks "UI changes: none", but this changes what the Chats list shows over time: a finished thread now leaves Working once its grace ends. Jackson's rule calls for before/after evidence on every UI change, and a short MP4 for timing. Please have the tester record a before/after clip on Android (trunk: the thread stays under Working; branch: it leaves after the grace), state that iOS waits for Checkpoint 2, and update the checklist to match.

Reviewed by Claude Opus 5.5 (reviewer-opus) and Sol 6.1 (reviewer-sol) in J5 Code.

@github-actions github-actions Bot added the size:S 10-29 effective changed lines (test files excluded in mixed PRs). label Nov 1, 2026

@Jacksondr5 Jacksondr5 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Panel delta check (reviewer-opus + reviewer-sol) at 10c502c

Verdict: no findings, and the panel's source approval stands. The video gate from our first review is unchanged: an Android before/after MP4 is needed, and the "UI changes" line needs fixing.

  • Upstream's fix taken unchanged. useThreadListV2.ts is now blob 1a0c67062f, identical to upstream's file at be58795d37 (pingdotgg#28169). That commit touches only this file, so nothing else is missing. The fix is the same approach: an observeInbox helper that takes the version as an argument, inside a useMemo.
  • Record. The FORK.md bullet now records an upstream fix taken ahead of the pin, with the exit "take upstream's at the next advance". The seams-test row is removed, because no J5 code remains in the file. #276 marks the entry as fixed upstream.
  • Test. J5's release-compiled useThreadListV2.test.tsx passes against upstream's version (both reviewers). With the seams test, 47 pass. Mobile vp run typecheck exits 0.

Reviewed by Claude Opus 5.5 (reviewer-opus) and Sol 6.1 (reviewer-sol) in J5 Code.

@github-actions github-actions Bot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Nov 1, 2026
Jacksondr5 and others added 2 commits November 1, 2026 15:16
…race

The Chats list kept a thread under Working after its turn until some
thread shell changed. useThreadListV2 memoized the inbox tracker's
observation with the tracker's version as an unread useMemo dependency;
the React Compiler memoizes on what a computation reads, so the compiled
memo dropped the version, and the re-render after the 2 s grace reused the
old answer. The version is now an argument to the observation. The new
test runs under the release compiler and fails on trunk.

Recorded as a temporary in-place patch in FORK.md, with a seams-test row.

Closes #842

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Upstream fixed the same bug in pingdotgg#28169 (be58795). The
hook now matches upstream's file at that commit byte for byte, so the next
advance resolves trivially; the FORK.md entry records it as upstream's
fix taken ahead of the pin, and the seams-test row is gone because no J5
code is involved. J5's compiled test stays and passes.

Refs #842

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Jacksondr5
Jacksondr5 force-pushed the j5code/mobile-working-section-release branch from 10c502c to f4b2a7c Compare November 1, 2026 20:16
@Jacksondr5
Jacksondr5 merged commit 7fc0f2b into j5/main Nov 1, 2026
32 checks passed
@Jacksondr5
Jacksondr5 deleted the j5code/mobile-working-section-release branch November 1, 2026 20:52

This branch was successfully deployed

1 active deployment
Preview — f4b2a7c9 Deployed Nov 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S 10-29 effective changed lines (test files excluded in mixed PRs). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mobile (Android): Chats list doesn't show threads created elsewhere until you navigate

1 participant