fix(mobile): restore live tool shimmer and add a Thinking row#10173

merged2
main
t3code/fix-mobile-tool-shimmer-thinking-row
openedSep 5, 2026byJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 3d ago

Problem

Two mobile regressions against the web chat view:

  1. The running tool row stopped shimmering. #9106 narrowed the shimmer from "the trailing live run" to "a call whose lifecycle is still inProgress". Codex only reports inProgress on item.started, which the work log drops, so in practice the mobile summary row lost its shine the moment a call completed while the turn kept going. Web keeps shining until the latest call fails, is declined, or is stopped (#9098, restored again in #9777 after the perf pass).
  2. No "Thinking" row. Web shows a shimmering Thinking live activity whenever a turn is working with no shimmering tool row (before the first call, and after a failed one, per #9165). Mobile never had it, so a working turn with no tools rendered nothing above the composer.

Fix

Mirror web's live-row rules in deriveThreadFeedPresentation:

  • The trailing tool group shimmers while its latest call is running or succeeded. A failed, declined, or stopped call turns the shine off.
  • When a turn is working and no tool row shimmers, append one thinking feed entry. It renders through the existing ShimmeringWorkContent with a brain icon (SF brain, Tabler IconBrain on Android) and uses the same fixed row height as the group toggle, so the list's size estimates stay exact.

The thinking row object is reused across derivations while createdAt/turnId are unchanged, so LegendList keeps the row instance during streaming.

Verification

  • vp test run apps/mobile/src/lib/threadActivity.test.ts — 90 passed. Updated the completed/failed lifecycle expectations to the new rules and added a test for the standalone Thinking row (present while working, stable identity, absent when idle).
  • tsc --noEmit for apps/mobile, targeted vp lint and vp fmt --check on the changed files (only pre-existing warnings in ThreadFeed.tsx).
  • iOS Simulator (iPhone 17 Pro, iOS 26.5) against a disposable base dir seeded with three running turns. Before is main at 62ed748, after is this branch, same dev client, same seeded data.

Working turn, no tool calls yet

Before: nothing above the composer. After: a shimmering Thinking row with a brain icon.

Latest tool call completed, turn still running

The label is identical in both; the difference is motion, so here is the recording (before on the left, after on the right):

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/3b2347f283343702/pair-completed.mp4

Before and after stills of the Running vp row

Latest tool call failed, turn still running

Before: only the failed row. After: the failed row stays and a Thinking row appears below it.

Android was not exercised (no emulator on the verification host); the only Android-specific change is the Tabler icon mapping.

Implemented by Claude Fable 5 in the Claude Code harness.

Note

Restore live tool shimmer and add a thinking row to mobile thread feed

  • Adds a thinking entry variant to the thread feed type union and renders it as a new ThreadThinkingRow component with a brain icon and shimmering label.
  • The presentation builder appends one thinking row at the tail when a turn is actively working and no work-toggle row is already shimmering; existing thinking rows are filtered out before rebuild to avoid duplicates.
  • A cached thinking entry keeps object identity stable across repeated derivations unless the active-work timestamp or turn id changes.
  • Updates work-group shimmer logic in appendToolGroupRows so a trailing work group shimmers when it has an active activity or when its latest activity succeeded, restoring the live-tool shimmer.
  • Maps the brain SF Symbol to the Tabler brain icon in the Android fallback so ThreadThinkingRow renders correctly on Android.
  • Risk: appendPresentedFeedEntry and appendToolGroupRows in threadActivity.ts now exclude and recompute shimmer for thinking entries; any out-of-tree callers of these helpers will need to handle the new thinking variant.

Macroscope summarized 51cc1ec.

Added 1 commit

  • fix(mobile): restore live tool shimmer and add a Thinking rowcfd143c
Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.6 KiB13.6 KiB+15 B (+0.1%)15.1 KiB✅
CodexThread snapshot wire7.0 KiB7.0 KiB+4 B (+0.1%)7.3 KiB✅
CodexLive turn WebSocket wire6.6 KiB6.6 KiB+11 B (+0.2%)7.8 KiB✅
CodexLive turn WebSocket decoded57.1 KiB57.1 KiB0 B (0.0%)66.4 KiB✅
CodexLive turn messages10100 (0.0%)21✅
ClaudeTotal thread wire13.6 KiB13.6 KiB+21 B (+0.2%)15.1 KiB✅
ClaudeThread snapshot wire7.0 KiB7.0 KiB−4 B (−0.1%)7.3 KiB✅
ClaudeLive turn WebSocket wire6.5 KiB6.6 KiB+25 B (+0.4%)7.8 KiB✅
ClaudeLive turn WebSocket decoded57.8 KiB57.9 KiB+44 B (+0.1%)66.4 KiB✅
ClaudeLive turn messages910+1 (+11.1%)21✅

Baseline: 62ed748 · PR result: 51cc1ec · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.8 KiB
  • Claude decoded thread snapshot: 114.5 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Synced from GitHub

Approvability

Verdict: Approved at 51cc1ec

Macroscope's review found this PR approvable — This is a focused mobile thread-feed bug fix that restores live tool shimmer and adds a small Thinking indicator during active turns. Its impact is confined to presentation logic with targeted state and identity coverage, and it introduces no schema, default, deployment, or sensitive-data changes.

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

Added 1 commit

  • test(mobile): cover the streaming-tail Thinking handoff51cc1ec