Skip to content

fix(mobile): finished threads no longer stay hidden in the Working shelf - #28224

Closed
juliusmarminge wants to merge 2 commits into
mainfrom
fix/issue-24997
Closed

juliusmarminge wants to merge 2 commits into
mainfrom
fix/issue-24997

Conversation

@juliusmarminge

Copy link
Copy Markdown
Member

Problem

On mobile Home (iOS and Android), a thread whose run finishes stays hidden inside the collapsed Working shelf instead of returning to the list after the 2-second exit grace, so a thread you just created from the phone appears to vanish until the app is relaunched or something else rebuilds the list. Reproduced on main (15fe283272) in the T3 Code dev client on an iPhone 17 Pro simulator against a fresh dev server with Codex: New chat → Demo → send "Reply with the single word echo." → Back to Home while it runs. The server reports the run completed, but Home keeps showing Working (1) with the thread hidden for as long as nothing else changes (40+ s in the capture below).

Change

useThreadListV2 memoized threadListInboxReturns.observe(...) with inboxVersion in its dependency list, but the callback never used the version. The React Compiler memoizes on the values a callback uses, so the compiled cache guard was if ($[0] !== threads || $[1] !== workingShelfEnabled): when the grace timer fired and bumped the tracker version, the hook re-rendered with the stale observation that still said "working". The hook now passes the version into the observation call, so the compiled guard is if ($[0] !== inboxVersion || $[1] !== t0) and the timer's version bump re-observes. Web's Sidebar already passes the version into its partition (clockKey) and is unchanged.

Scope and approval

Fixes #24997

Verification

  • vp test run --project react src/features/threads/useThreadListV2.test.tsx (apps/mobile): the new test renders the real hook through the React Compiler, finishes a Working thread and advances past WORKING_EXIT_GRACE_MS. On main it fails with the list still shows the finished thread as working: expected 1 to be +0 while the tracker itself already reports the thread as not working; it passes with the fix.
  • vp run check:changed: format, lint, knip, typecheck (2 packages), the test beside the change and the React Compiler check pass.
  • Compiled output of useThreadListV2.ts with the mobile Babel config: before if($[0]!==threads||$[1]!==workingShelfEnabled), after if($[0]!==inboxVersion||$[1]!==t0).

Evidence

Real T3 Code Dev client on pool simulators/emulators, fresh dev server and database per side (T3CODE_DEV_SEED=0, own --home-dir), Codex GPT-6-Astra, same native fingerprint on both sides; each side's JS from its own checkout's Metro. Recordings are timestamped screenshot sequences (~1.5–2 s apart), no touch on the list between completion and the return. Data is synthetic: a Demo project with one finished "Older inbox" comparator thread.

R1: iPhone 17 Pro, portrait, dark, Home. Before: 17:08:16 send "Reply with the single word echo.", 17:08:17 Back; the completed thread stays under Working (1) through 17:09:08. After: 17:10:58 send, 17:11:00 Back; Echo appears above Older inbox and the Working header disappears by 17:11:06 with no input.

R1 iPhone 17 Pro portrait dark Before After Diff0 (any change, ×32, status bar masked) DiffT (>16/255, status bar masked)
SHA 15fe283272 2843373207 15fe283 → 2843373 15fe283 → 2843373
Recording R1 before: completed Echo stays hidden under Working (1) R1 after: Echo returns and Working disappears without input
Endpoint R1 endpoints, before left, after right R1 diff, any change, status bar masked R1 diff, threshold 16, status bar masked

What else differs in the R1 endpoints: the intended Echo row (Done badge) at the top with the rows below shifted down and Working (1) removed, and New thread's age label (4m vs 6m, wall-clock). The top 50 px status bar (clock, battery fill) is masked in both diffs; with it masked, 4,570 of 351,348 pixels differ at zero threshold and 3,122 above 16, all in the list rows.

The GIFs show sampled frames with the time each screenshot returned; frame delays are not exact wall-clock intervals. The R1 after run lost the device connection from 17:11:16 to 17:11:34 after the return, so that stretch has no frames; Echo had already returned at 17:11:06.

After-only rows, head 2843373:

Row Steps Recording
R2 iPad Pro 13-inch (M5), landscape, dark, native primary column, target unselected 17:18:06 send "ipad", 17:18:08 select Older inbox; target hidden under Working (1), then returns to the top on its own R2 after: iPad unselected thread returns
R3 Pixel Tablet, landscape, light, split sidebar (ThreadNavigationSidebar), target unselected 17:23:01 send a 25 s shell-sleep prompt, Older inbox selected; Working (1) disappears and the thread returns on its own R3 after: Android tablet sidebar thread returns
R4 Pixel Tablet, another run still working Working (2) → Working (1): the finished thread returns, the running one stays R4 after: Working 2 to 1, finished thread returns
R5 iPhone 17 Pro, Working expanded the finished card moves from the expanded shelf to the list, no duplicate R5 after: expanded Working, card moves to the list
R6 iPad, target selected selected row stays visible during grace, then becomes an ordinary row R6 after: selected thread becomes an ordinary row
R8 Pixel 9, portrait, light, Home same return as R1 on Android phone R8 after: Android phone Home thread returns
Driver and seed used for the captures (scratch, not committed)
# Each side from its own checkout, fresh home:
# T3CODE_DEV_SEED=0 vp run dev:server --host 0.0.0.0 --home-dir <fresh home>
# cd apps/mobile && REACT_NATIVE_PACKAGER_HOSTNAME=<tailnet host> vp run dev:client
# seed: project add Demo <scratch repo>; thread start --title 'Older inbox' 'Reply with the single word comparator.' --wait
# agent-device connect proxy ... ; agent-device open --platform ios --device "iPhone 17 Pro"; vp run mobile:install ios --session <s>
# load Metro, then .agents/skills/test-t3-mobile/scripts/pair-client.sh <port> <home> http://<tailnet host>:<port> agent-device --session <s>
A() { agent-device --session "$s" "$@"; }
A press 'label="New chat"'; sleep 2
# press [button] "Demo" until the nav bar reads "New thread" (re-snapshot each time)
A fill "$composer_ref" "Reply with the single word echo."
A press 'label="Send"'; sleep 0.5
A press 'label="Back"'
# then screenshot every ~1.5 s for 55 s without touching the list

Not checked:

  • Before recordings for R2–R8 (the iPhone pair shows the defect; the other rows show the fixed behaviour only).
  • Working shelf disabled (no change expected: the tracker is bypassed).
  • Physical devices. The recordings are sampled screenshots, so they show the return happens without input, not an exact 2,000 ms interval.

Models and harnesses: Claude Opus 5.5 through Claude Code (lead, test, implementation, review B); GPT-6.1-Sol through Codex in T3 Code (review A, proof audits, captures).

juliusmarminge and others added 2 commits October 31, 2026 16:45
…shelf

Renders useThreadListV2 through the React Compiler (react test project),
moves a thread from running to finished, and advances past the exit grace.
The tracker reports the thread as no longer working, but the hook keeps the
stale observation because the compiled memo ignores the inbox version, so
the thread stays inside the collapsed Working shelf. Fails on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…grace

The React Compiler memoizes on what a memo callback uses, so it dropped the
inbox tracker version from the observation's cache guard and the grace
timer's re-render reused the stale observation. Pass the version into the
observation so the compiled guard depends on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Oct 31, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 2843373

Macroscope's review found this PR approvable — The change refreshes mobile thread placement when the existing Working-shelf grace period expires, preventing finished threads from remaining hidden. Its effects are limited to list presentation, with no changes to product defaults or persisted thread state.

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

@juliusmarminge juliusmarminge added bugfix Fix PR opened by an issue lead; enters the PR loop pr:gate-failed A land gate failed labels Oct 31, 2026
@coderabbitai

coderabbitai Bot commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 15fe283 and 2843373.

Files selected for processing: 3
  • apps/mobile/src/features/threads/useThreadListV2.test.tsx
  • apps/mobile/src/features/threads/useThreadListV2.ts
  • apps/mobile/vite.config.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.

Walkthrough

The mobile thread-list hook now routes inbox observation through a helper and includes inboxVersion in its memo dependencies. A regression test verifies that a completed thread remains counted as working during the exit-grace period, then leaves that count while remaining listed. The test is added to the mobile Vite React test list.

Changes: Mobile thread-list inbox refresh

Layer / File(s) Summary
Refresh inbox observation with the inbox version
apps/mobile/src/features/threads/useThreadListV2.ts, apps/mobile/src/features/threads/useThreadListV2.test.tsx, apps/mobile/vite.config.ts
The hook passes inboxVersion through a local observation helper. The regression test checks the working count before and after WORKING_EXIT_GRACE_MS, and the Vite React test list includes the test.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Possibly related issues

  • Issue 24901 describes a completed thread that remains in the mobile Working shelf after the exit-grace period. This change tests that behavior and updates inbox observation when its version changes.

Possibly related PRs

  • pingdotgg/t3code#24907: Adds the version argument to threadListInboxReturns.observe; this change routes the version through the hook’s observation helper.
  • pingdotgg/t3code#28169: Fixes stale Working-shelf observation after the grace period; this change adds regression coverage for that behavior.
  • pingdotgg/t3code#25400: Passes inboxVersion into observe; this change uses that input in the hook’s memoized observation path.

Suggested reviewers: t3dotgg

Merge Risk: Minimal · up to 28433

The change refreshes the Working-shelf observation after the grace timer. No merge-blocking risk was identified.

Pre-merge checks: Passed 3 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Linked Issues check Warning Directly linked issue #24997 requires the mobile thread list to match the desktop sidebar for the same environments. Reported symptoms: recent threads from two online environments are missing from the… Choose one of two paths. (a) Change the PR to reference only the Working-shelf defect, for example 'Relates to #24997' or no closing reference, so the linked issue is not claimed as fixed. (b) Keep 'Fixes #24997' and add coding changes and …
Passed Passed checks: 3 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the primary mobile fix: completed threads no longer remain hidden in the Working shelf.
Description check Passed The description includes all template sections. It gives a specific problem and reproduction, explains the implementation, links the issue, documents focused tests and observed results, includes UI ev…
Out of Scope Changes check Passed All changed files relate to one objective: the finished-thread Working-shelf return after the exit grace. useThreadListV2.ts changes only the inbox observation call and its memoization input, which re…
Full details: Description check

Explanation

The description includes all template sections. It gives a specific problem and reproduction, explains the implementation, links the issue, documents focused tests and observed results, includes UI evidence, and lists verification limits.

Full details: Out of Scope Changes check

Explanation

All changed files relate to one objective: the finished-thread Working-shelf return after the exit grace. useThreadListV2.ts changes only the inbox observation call and its memoization input, which removes the stale observation. The new test in useThreadListV2.test.tsx covers that path (running to completed, then past WORKING_EXIT_GRACE_MS). The vite.config.ts change adds the new React test file to the reactTests list, so it runs under the react project. No unrelated refactoring, dependency change, or web Sidebar change appears in the diff. The change set is small and matches the stated scope of the PR description. The unchanged web Sidebar is not an out-of-scope change.

Full details: Linked Issues check

Explanation

Directly linked issue #24997 requires the mobile thread list to match the desktop sidebar for the same environments. Reported symptoms: recent threads from two online environments are missing from the list, including threads created on the phone. Force-quit does not fix it. Toggling an environment fixes it. The reporter suspects a saved list copy is kept instead of the full loaded snapshot. The PR diff (merge base 15fe283 to head 2843373) changes only useThreadListV2.ts: an observeInboxReturns helper that forwards an unused _version argument so the React Compiler re-observes the Working-shelf inbox tracker after the exit grace timer. It also adds a test in useThreadListV2.test.tsx and registers that test in apps/mobile/vite.config.ts. None of these changes touch shell snapshot loading, environment subscriptions, or the thread list data source. The fixed defect is a finished thread that stays hidden in the collapsed Working shelf after WORKING_EXIT_GRACE_MS. The issue's missing-thread reports do not mention the Working shelf. The PR's own description limits its claim to that narrower case and says it does not verify the multi-day, multi-environment scenario. The PR still states 'Fixes #24997'. The Working-shelf fix may be a valid, separate fix, but the reviewed evidence does not show it resolves the issue's listed requirement.

Resolution

Choose one of two paths. (a) Change the PR to reference only the Working-shelf defect, for example 'Relates to #24997' or no closing reference, so the linked issue is not claimed as fixed. (b) Keep 'Fixes #24997' and add coding changes and automated tests for the reported behavior. Those tests must show that threads from multiple online environments, including threads created from mobile, appear in the Home and sidebar lists after a shell snapshot or subscription interruption, without an environment toggle. Also show that the list does not keep a stale saved copy in place of the loaded snapshot.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

unmanbearpig added a commit to unmanbearpig/t3code-unmbp that referenced this pull request Nov 1, 2026
Integrate upstream PR pingdotgg#28224, including its React Compiler regression test. Confirmed that the test fails before the fix and passes afterward.
@t3dotgg

t3dotgg commented Nov 1, 2026

Copy link
Copy Markdown
Member

Note

🤖 GPT-6.1 Terra [1m] responding on behalf of Theo

Closing because main passes the grace-expiry version into the mobile inbox observation, so finished threads leave Working when the timer fires. #28169 useThreadListV2.ts

@t3dotgg t3dotgg closed this Nov 1, 2026
@juliusmarminge juliusmarminge removed the pr:gate-failed A land gate failed label Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Fix PR opened by an issue lead; enters the PR loop size:S 10-29 changed lines (additions + deletions). 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.

[Bug]: iOS 2.1.0 thread list misses days of threads from online environments; survives force-quit, fixed by toggling the environment

2 participants