orca/mobile/app
OrcaWin fb1259a09d
fix(mobile): keep cached workspace counts across a transient RPC failure (#12408)
* fix(mobile): keep cached workspace counts across a transient RPC failure

The Home host card showed "12 worktrees · 2 active" until any worktree.ps
failed — a backgrounded app, a Wi-Fi→cellular handoff, or a sleep/resume
that kills the socket mid-request. Two things then went wrong:

- render dropped the counts: `markHomeWorktreeCatalogUnavailable` kept the
  proven numbers in state, but the card only rendered them when
  `catalogUnavailable` was unset, so the line collapsed to "Worktree list
  unavailable" even though the last successful counts were right there.
- nothing re-drove the fetch: the per-host wiring latched a `statsFetched`
  boolean on the first connect, and the logical client survives socket
  drops, so its reconnect never re-read the catalog. The card stayed wrong
  until the user navigated away and back.

Keep the proven counts and flag them stale (`staleCounts`), rendered as
"Last known: 12 worktrees · 2 active"; a host whose catalog never loaded
still reads "Worktree list unavailable" (STA-3123). Replace the one-shot
latch with createHostConnectRefetchGate, which fires on each transition
INTO 'connected' — one refetch per reconnect, no polling timer — mirroring
useWorktreeResync on the host screen. fetchHomeHostWorktreeInfo moves out
of app/index.tsx so its rejection path is covered by tests.

* fix(mobile): bound "Last known" counts and survive a path cutover

Review found two ways the home host card's stale-count fix misbehaves.

1. A migrateTo cutover (relay->direct probe, forced replacement) rejects
   in-flight requests with LogicalClientCutoverError and republishes
   'connected' from 'connected', so the connect gate never re-arms and the
   card latched on "Last known: ..." with nothing left to clear it.
   worktree.ps now re-issues on the authenticated replacement, bounded,
   like runtime-capability-probe and worktree-create-retry already do.

2. "Last known: N worktrees" had no age bound. The home snapshot is
   persisted, so a cold start whose first worktree.ps failed rendered
   counts proven days ago exactly like counts proven seconds ago - the case
   STA-3123 deliberately rendered as "Worktree list unavailable". Counts now
   carry countsProvenAt and expire out of the "last known" wording after
   10 minutes; counts persisted by an older build count as expired.

Also, per review: the card derives its own worktree line from
HostWorktreeInfo, so a caller can no longer re-gate the counts away (that
was the original defect), and the derivation is covered by a render test -
mobile/vitest.config.ts never collected *.test.tsx, so component tests
were silently dead. Home stats are keyed by host and summed instead of
letting whichever desktop replied last overwrite the shared header row,
which the per-reconnect refetch made churn on flaky links.

* fix(mobile): age bounds liveness, not the counts; scope the header total to paired hosts

Round-2 review follow-up.

Age bound was anchored on proof time inside the failure branch only, so a
session connected past the window that then hit one failed refresh rendered
the pre-fix "Worktree list unavailable" — the exact case this PR exists for —
while identically aged counts still rendered unlabeled as live whenever the
refresh was merely pending. Age now decides live vs "Last known" and the
failure branch keeps whatever the host last proved; "Worktree list unavailable"
is reserved for a catalog that never loaded.

Header stats summed every entry ever cached, so removing a desktop left its
lifetime numbers in the total for the rest of the session. totalHomeStats now
sums the hosts still paired, which also covers removal from the host screen.

wireHostSubscriptions is the effect body moved verbatim out of useEffect;
react-doctor's effect-needs-cleanup false-positives on `subscribe` inside one
and the changed-code gate has no working suppression path (an inline directive
reads as unused to the plugin-less scan).

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-08-03 23:26:02 -07:00
..
h fix(sidebar): keep each project's entry-point workspace visible under "Hide sleeping" (#12257) 2026-08-03 22:55:17 -07:00
_layout.tsx Add unified mobile onboarding for session view and notifications (#9478) 2026-07-20 11:11:23 -07:00
about.tsx
browser-settings.tsx fix(mobile): add terminal link routing setting 2026-06-16 17:05:12 -07:00
connection-log.tsx Add Orca Relay desktop and mobile transport (#8536) 2026-07-14 11:47:05 -07:00
index.tsx fix(mobile): keep cached workspace counts across a transient RPC failure (#12408) 2026-08-03 23:26:02 -07:00
mobile-onboarding.tsx Add unified mobile onboarding for session view and notifications (#9478) 2026-07-20 11:11:23 -07:00
native-chat-settings.tsx rename: rebrand user-facing Native chat to Chat UI (#10036) 2026-07-22 19:14:44 -07:00
notification-opt-in.tsx fix(mobile): restore notification opt-in route compatibility (#9675) 2026-07-20 21:15:39 -07:00
notifications.tsx fix(mobile): harden notification opt-in onboarding (#8792) 2026-07-14 19:20:36 -07:00
pair-confirm.tsx Add unified mobile onboarding for session view and notifications (#9478) 2026-07-20 11:11:23 -07:00
pair-scan.tsx Add unified mobile onboarding for session view and notifications (#9478) 2026-07-20 11:11:23 -07:00
pair.tsx Add mobile terminal shortcut bar customization (#3012) 2026-05-29 16:39:52 -07:00
settings.tsx rename: rebrand user-facing Native chat to Chat UI (#10036) 2026-07-22 19:14:44 -07:00
terminal-settings.tsx Redesign mobile driver overlay layout and clarify action copy (#5647) 2026-06-17 19:21:42 -07:00
troubleshoot.tsx mobile: per-host connection log screen with copy-diagnostics (#7984) 2026-07-09 16:20:07 -07:00
voice-settings.tsx perf(mobile): gate dictation setup progress polling on foreground + single-flight (#9892) 2026-07-22 16:22:01 -07:00