Skip to content

fix(mobile): harden offline and query integrity flows (w6c) - #5416

Open
iscekic wants to merge 14 commits into
mainfrom
audit-w6c-offline-query-05cb
Open

fix(mobile): harden offline and query integrity flows (w6c)#5416
iscekic wants to merge 14 commits into
mainfrom
audit-w6c-offline-query-05cb

Conversation

@iscekic

@iscekic iscekic commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

A live session that stops reconnecting now shows "Connection lost" with a Retry button, so the user can recover instead of watching "Reconnecting…" forever.

The Security Agent settings screen now shows a load error with a Retry button when it cannot reach the service, instead of telling an owner that only owners can manage the agent.


The SDK connection gains an additive reconnect-exhaustion contract: an optional maxReconnectAttempts cap, a two-edge onReconnectExhaustionChange callback, and a retryReconnect method. Exhaustion sets when the retry cap is reached or a terminal auth failure stops retries, and clears on every recovery path; refreshAndConnect now threads the attempt through so a live session that refreshes auth on reconnect still reaches the cap. All additions are optional, so web and extension consumers compile and behave unchanged.

Files
  • packages/cloud-agent-sdk/src/base-connection.ts — adds maxReconnectAttempts, onReconnectExhaustionChange, and retryReconnect; sets exhaustion at the retry cap and on a terminal auth failure, clears it on every recovery path, and threads the reconnect attempt through refreshAndConnect.
  • packages/cloud-agent-sdk/src/user-web-connection.ts — forwards maxReconnectAttempts, mirrors the exhaustion snapshot, exposes isReconnectExhausted, onReconnectExhaustionChange, and retryConnection, and resets the snapshot when the connection stops.

The session connection indicator gains an explicit exhausted state: a remote session whose reconnects are exhausted shows "Connection lost" with a Retry action instead of "Reconnecting…" forever. The state resolver consumes the exhaustion signal, and a new useUserWebConnectionHealth hook binds readiness and exhaustion reactively through the shared connection.

Files
  • apps/mobile/src/components/agents/session-connection-indicator-state.ts — adds the exhausted state and the reconnectExhausted input.
  • apps/mobile/src/components/agents/session-connection-indicator.tsx — renders the "Connection lost" label and a Retry pressable that calls retryConnection, with separate accessibility elements.
  • apps/mobile/src/lib/hooks/use-user-web-connection-state.ts — adds useUserWebConnectionHealth binding both readiness and exhaustion.

useSecurityAgentCapability returns a discriminated status (loading | error | denied | allowed) instead of a bare canManage boolean, so callers can tell a failed or pending permission load from a resolved denial. The settings overview screen renders distinct loading, error, offline, and denied states, so an owner never sees permission-denied copy on a network error or an offline launch. A settled role stays authoritative: a failed background refetch does not demote an already-resolved capability to error.

Files
  • apps/mobile/src/lib/hooks/use-security-agent.ts — adds the discriminated capability status and exposes hasData and isPending from the role query.
  • apps/mobile/src/components/security-agent/settings-overview-screen.tsx — branches on capability status and committed connectivity for the offline and permission-error screens.

The offline banner store now exposes a committed BannerState tri-state (online, offline, unknown) and notifies on every committed change, so screens can gate on a settled connectivity state. The hook layer keeps one module-level store for the whole app, so every consumer shares a single NetInfo subscription, and adds a useCommittedConnectivityStatus hook.

Files
  • apps/mobile/src/lib/offline-banner-state.ts — exports BannerState, adds the state snapshot, and notifies on every committed change.
  • apps/mobile/src/lib/hooks/use-offline-banner-state.ts — replaces the per-mount store lifecycle with one lazy shared store and adds useCommittedConnectivityStatus.

Every optimistic mutation now stamps its write with a per-cache generation from nextMutationGeneration and rolls back only when isLatestMutationGeneration confirms it is still the latest, so an older failure cannot stomp cache state a newer mutation owns. Mutations that share one cache serialize their network calls through a scope.id or a chainSave, and each changed mutation declares its onError policy in a comment. The accepted residual is that when two writes both fail, the older optimistic value can show until the settle invalidation refetches.

Files
  • apps/mobile/src/lib/hooks/mutation-generations.ts — new pure per-key generation counter and latest-generation check.
  • apps/mobile/src/lib/hooks/use-code-reviewer.ts — stamps toggle and save rollbacks with generations and chains both onto the shared scope+platform save key.
  • apps/mobile/src/lib/hooks/use-model-preferences.ts — stamps favorite rollbacks with generations and chains favorite writes on one key.
  • apps/mobile/src/lib/hooks/use-organization-mutations.ts — stamps member, rename, and balance rollbacks with generations and serializes org writes with a scope id.
  • apps/mobile/src/lib/hooks/use-security-agent-mutations.ts — stamps config save and toggle rollbacks with generations and serializes them with a scope id.
  • apps/mobile/src/lib/hooks/use-security-remediation.ts — stamps remediation-cancel rollbacks with generations.
  • apps/mobile/src/lib/hooks/use-session-mutations.ts — stamps session delete and rename rollbacks with generations after the cancel, and chains both per session.
  • apps/mobile/src/lib/pr-review/discussion/use-review-discussion-mutations.ts — stamps thread resolve, unresolve, and reaction rollbacks with a shared generation key, chains resolve and unresolve per thread, and scopes reactions per thread.
  • apps/mobile/src/components/notifications-screen.tsx — stamps push-token and preference rollbacks with generations and serializes both with static scope ids.

The global query client sets refetchOnWindowFocus to false, and a new useRouteForegroundRefresh hook invalidates only the active route's query prefixes on foreground and focus regain. Owner mounts across layouts refresh each route's domain, and the root layout keeps app-wide freshness for the signed-in user, organizations, and kilo-chat. The Agents list reads focus live via navigation.isFocused() so a frozen unfocused tab does not refetch on foreground.

Files
  • apps/mobile/src/lib/query-client.ts — sets refetchOnWindowFocus: false.
  • apps/mobile/src/lib/hooks/use-route-foreground-refresh.ts — new hook invalidating path-prefix keys on app-active and focus regain.
  • apps/mobile/src/app/(app)/_layout.tsx — adds the app-wide freshness mount.
  • apps/mobile/src/app/(app)/(tabs)/(0_home)/_layout.tsx — refreshes the active-sessions domain.
  • apps/mobile/src/app/(app)/(tabs)/(3_profile)/_layout.tsx — refreshes the user, organizations, reviewer, security-agent, and kilo-pass domains.
  • apps/mobile/src/app/(app)/pr-review/[owner]/[repo]/[number]/_layout.tsx — refreshes the PR-review domain.
  • apps/mobile/src/app/(app)/pr-review/index.tsx — refreshes the PR-review domain.
  • apps/mobile/src/app/(app)/agent-chat/[session-id].tsx — refreshes the session and model-preference domains.
  • apps/mobile/src/app/(app)/agent-chat/model-picker.tsx — refreshes the model-preference domain.
  • apps/mobile/src/app/(app)/device-sessions.tsx — refreshes the user domain.
  • apps/mobile/src/app/(app)/kilo-pass.tsx — refreshes the kilo-pass domain.
  • apps/mobile/src/components/agents/session-list-screen.tsx — gates the foreground refetch on tab focus and invalidates the active-sessions tray.

The credits card's own focus-based refetch is removed because the profile route mount now owns that refresh.

Files
  • apps/mobile/src/components/profile-credits-card.tsx — removes the focus-based refetch.

Tests: 25 test files updated — 20 in the mobile app and 5 in the SDK, adding chaos scenarios for offline flapping, process kill with persisted state, cold-launch offline, hibernation zombie-socket replacement, reconnect exhaustion, and mutation interleave.
Generated: none.


Verification

iOS ran three cases in one round; a fourth case was not run.

Case What it proves Platform Result
cold-launch-restore A relaunch renders the cached Agents session list without an error screen or a stuck skeleton. iOS passed
foreground-freshness The app refetches the session list on foreground return and shows no connection-lost label. iOS passed
security-capability-happy-path A personal-scope owner sees the Security Agent switch and settings rows, never denial copy. iOS passed
mutation-error-surface A failed Security Agent change rolls back and shows an error toast. iOS not run — the fresh account has no repositories, so the switch is disabled; the sandbox lacks a GitHub installation to seed one; the vitest/jest suite holds the rollback and toast evidence

The round reproduced no defect on the unfixed build; all three cases passed on the first run.
No recording exists.

Human steps: none.

Visual Changes

Agents tab (session list) after cold-launch restore

After a cold relaunch, the user sees the Agents tab with its session list restored from the cache as a settled empty state. The Agents header at the top and the No sessions yet block in the middle of the screen show the restored list.

02-agents-cold-restore.png

Agents tab after foreground return

When the app returns to the foreground, the Agents tab refreshes and shows no Connection lost or Reconnecting… label. The empty session list fills the app window, and no connection label appears on the screen. The capture catches the foreground transition, but the screen content stays readable.

01-agents-foreground-freshness.png

Security Agent settings (personal scope)

The owner sees a Settings screen with the Security Agent row and its switch, and no owner-denial copy. The Security Agent row and its switch sit under the Settings header, above the repository hint text.

01-security-capability-happy-path.png

Reviewer Notes

Notes: none.

iscekic added 10 commits August 21, 2026 03:25
Add an additive two-edge reconnect-exhaustion signal so mobile can show an explicit recovery action instead of an indefinite Reconnecting label. The auth-failure stop path fires the same terminal edge.
Render Connection lost with a Retry action when the user-web transport exhausts reconnect attempts, instead of an indefinite Reconnecting label.
The settings overview now renders a skeleton, a permissions error with retry, or the offline variant instead of permission-denied copy when the org-role query is unresolved or paused.
Add a per-key generation guard so a failing older mutation cannot roll back cache state a newer mutation owns, and serialize same-entity mutations with scope ids for security, org, and model-preferences hooks.
…tations

Add the generation guard and the named network-order mechanisms to the remaining mutation hooks: session list, code-reviewer, PR discussion threads, and push-token/notification preferences.
Add useRouteForegroundRefresh to invalidate route query keys on the focused AppState foreground transition and on focus regain after the first. Disable the blanket refetchOnWindowFocus default so frozen background tabs no longer refetch.
Mount useRouteForegroundRefresh at the app-wide, home-tab, profile-tab, and pushed-route surfaces from plan section 10. Each path-prefix key uses the nested tRPC form so invalidation prefix-matches tRPC v11 keys. Remove the profile credits card's bespoke focus invalidation, now subsumed by the profile-tab owner. Add a real-QueryClient key-matching test.
Gate the Agents list AppState foreground listener on the tab's focus state so a frozen background tab no longer refetches. The focused tab refreshes the stored list and invalidates the active-sessions tray. Add a mounted test covering focused, unfocused, and blur-after-mount foreground transitions.
Extend the offline banner, query-client lifecycle, and mutation outbox tests with EV-03-style chaos scenarios: NetInfo flapping drives onlineManager and the banner debounce correctly, and an outbox row survives a simulated relaunch with its operationKey reused.
@iscekic iscekic self-assigned this Aug 21, 2026
Comment thread packages/cloud-agent-sdk/src/base-connection.ts
Comment thread apps/mobile/src/lib/hooks/use-session-mutations.ts Outdated
@kilo-code-bot

kilo-code-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files)
  • apps/mobile/src/components/agents/session-list-screen.mounted.test.tsx
  • apps/mobile/src/components/agents/session-list-screen.tsx
  • apps/mobile/src/lib/hooks/use-session-mutations.ts
  • packages/cloud-agent-sdk/src/base-connection.test.ts
  • packages/cloud-agent-sdk/src/base-connection.ts
Previous Review Summaries (2 snapshots, latest commit 0010a39)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 0010a39)

Status: 3 Issues Found | Recommendation: Address before merge

Fix these issues in Kilo Cloud

Overview

Severity Count
CRITICAL 1
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
packages/cloud-agent-sdk/src/base-connection.ts 180 Live user-web reconnects never reach the exhaustion cap

WARNING

File Line Issue
apps/mobile/src/lib/hooks/use-session-mutations.ts 73 Generation stamped before cancelQueries can clobber a newer list write
apps/mobile/src/components/agents/session-list-screen.tsx 117 Frozen Agents tab never clears focusedRef, so foreground still refetches
Files Reviewed (56 files)
  • apps/mobile/src/app/(app)/(tabs)/(0_home)/_layout.tsx
  • apps/mobile/src/app/(app)/(tabs)/(3_profile)/_layout.tsx
  • apps/mobile/src/app/(app)/_layout.tsx
  • apps/mobile/src/app/(app)/agent-chat/[session-id].tsx
  • apps/mobile/src/app/(app)/agent-chat/model-picker.tsx
  • apps/mobile/src/app/(app)/device-sessions.tsx
  • apps/mobile/src/app/(app)/kilo-pass.tsx
  • apps/mobile/src/app/(app)/pr-review/[owner]/[repo]/[number]/_layout.tsx
  • apps/mobile/src/app/(app)/pr-review/index.tsx
  • apps/mobile/src/components/agents/session-connection-indicator-state.test.ts
  • apps/mobile/src/components/agents/session-connection-indicator-state.ts
  • apps/mobile/src/components/agents/session-connection-indicator.mounted.test.tsx
  • apps/mobile/src/components/agents/session-connection-indicator.tsx
  • apps/mobile/src/components/agents/session-list-screen.mounted.test.tsx
  • apps/mobile/src/components/agents/session-list-screen.tsx - 1 issue
  • apps/mobile/src/components/agents/user-web-connection-provider.mounted.test.tsx
  • apps/mobile/src/components/notifications-screen.mounted.test.tsx
  • apps/mobile/src/components/notifications-screen.tsx
  • apps/mobile/src/components/profile-credits-card.tsx
  • apps/mobile/src/components/security-agent/settings-overview-screen.mounted.test.tsx
  • apps/mobile/src/components/security-agent/settings-overview-screen.tsx
  • apps/mobile/src/lib/hooks/mutation-generations.test.ts
  • apps/mobile/src/lib/hooks/mutation-generations.ts
  • apps/mobile/src/lib/hooks/use-code-reviewer.test.ts
  • apps/mobile/src/lib/hooks/use-code-reviewer.ts
  • apps/mobile/src/lib/hooks/use-model-preferences.test.ts
  • apps/mobile/src/lib/hooks/use-model-preferences.ts
  • apps/mobile/src/lib/hooks/use-offline-banner-state.mounted.test.tsx
  • apps/mobile/src/lib/hooks/use-offline-banner-state.ts
  • apps/mobile/src/lib/hooks/use-organization-mutations.test.ts
  • apps/mobile/src/lib/hooks/use-organization-mutations.ts
  • apps/mobile/src/lib/hooks/use-route-foreground-refresh.mounted.test.tsx
  • apps/mobile/src/lib/hooks/use-route-foreground-refresh.ts
  • apps/mobile/src/lib/hooks/use-security-agent-mutations.test.ts
  • apps/mobile/src/lib/hooks/use-security-agent-mutations.ts
  • apps/mobile/src/lib/hooks/use-security-agent.test.ts
  • apps/mobile/src/lib/hooks/use-security-agent.ts
  • apps/mobile/src/lib/hooks/use-security-remediation.test.ts
  • apps/mobile/src/lib/hooks/use-security-remediation.ts
  • apps/mobile/src/lib/hooks/use-session-mutations.test.ts
  • apps/mobile/src/lib/hooks/use-session-mutations.ts - 1 issue
  • apps/mobile/src/lib/hooks/use-user-web-connection-state.ts
  • apps/mobile/src/lib/offline-banner-state.test.ts
  • apps/mobile/src/lib/offline-banner-state.ts
  • apps/mobile/src/lib/persist/mutation-outbox.test.ts
  • apps/mobile/src/lib/pr-review/discussion/use-review-discussion-mutations.test.ts
  • apps/mobile/src/lib/pr-review/discussion/use-review-discussion-mutations.ts
  • apps/mobile/src/lib/query-client-lifecycle.test.ts
  • apps/mobile/src/lib/query-client.ts
  • packages/cloud-agent-sdk/src/base-connection.test.ts
  • packages/cloud-agent-sdk/src/base-connection.ts - 1 issue
  • packages/cloud-agent-sdk/src/session-routing.test.ts
  • packages/cloud-agent-sdk/src/session-transport.test.ts
  • packages/cloud-agent-sdk/src/session.test.ts
  • packages/cloud-agent-sdk/src/user-web-connection.test.ts
  • packages/cloud-agent-sdk/src/user-web-connection.ts

Previous review

Status: 2 Issues Found | Recommendation: Address before merge

Fix these issues in Kilo Cloud

Overview

Severity Count
CRITICAL 1
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
packages/cloud-agent-sdk/src/base-connection.ts 180 Live user-web reconnects never reach the exhaustion cap

WARNING

File Line Issue
apps/mobile/src/lib/hooks/use-session-mutations.ts 73 Generation stamped before cancelQueries can clobber a newer list write
Files Reviewed (57 files)
  • apps/mobile/src/app/(app)/(tabs)/(0_home)/_layout.tsx
  • apps/mobile/src/app/(app)/(tabs)/(3_profile)/_layout.tsx
  • apps/mobile/src/app/(app)/_layout.tsx
  • apps/mobile/src/app/(app)/agent-chat/[session-id].tsx
  • apps/mobile/src/app/(app)/agent-chat/model-picker.tsx
  • apps/mobile/src/app/(app)/device-sessions.tsx
  • apps/mobile/src/app/(app)/kilo-pass.tsx
  • apps/mobile/src/app/(app)/pr-review/[owner]/[repo]/[number]/_layout.tsx
  • apps/mobile/src/app/(app)/pr-review/index.tsx
  • apps/mobile/src/components/agents/session-connection-indicator-state.test.ts
  • apps/mobile/src/components/agents/session-connection-indicator-state.ts
  • apps/mobile/src/components/agents/session-connection-indicator.mounted.test.tsx
  • apps/mobile/src/components/agents/session-connection-indicator.tsx
  • apps/mobile/src/components/agents/session-list-screen.mounted.test.tsx
  • apps/mobile/src/components/agents/session-list-screen.tsx
  • apps/mobile/src/components/agents/user-web-connection-provider.mounted.test.tsx
  • apps/mobile/src/components/notifications-screen.mounted.test.tsx
  • apps/mobile/src/components/notifications-screen.tsx
  • apps/mobile/src/components/profile-credits-card.tsx
  • apps/mobile/src/components/security-agent/settings-overview-screen.mounted.test.tsx
  • apps/mobile/src/components/security-agent/settings-overview-screen.tsx
  • apps/mobile/src/lib/hooks/mutation-generations.test.ts
  • apps/mobile/src/lib/hooks/mutation-generations.ts
  • apps/mobile/src/lib/hooks/use-code-reviewer.test.ts
  • apps/mobile/src/lib/hooks/use-code-reviewer.ts
  • apps/mobile/src/lib/hooks/use-model-preferences.test.ts
  • apps/mobile/src/lib/hooks/use-model-preferences.ts
  • apps/mobile/src/lib/hooks/use-offline-banner-state.mounted.test.tsx
  • apps/mobile/src/lib/hooks/use-offline-banner-state.ts
  • apps/mobile/src/lib/hooks/use-organization-mutations.test.ts
  • apps/mobile/src/lib/hooks/use-organization-mutations.ts
  • apps/mobile/src/lib/hooks/use-route-foreground-refresh.mounted.test.tsx
  • apps/mobile/src/lib/hooks/use-route-foreground-refresh.ts
  • apps/mobile/src/lib/hooks/use-security-agent-mutations.test.ts
  • apps/mobile/src/lib/hooks/use-security-agent-mutations.ts
  • apps/mobile/src/lib/hooks/use-security-agent.test.ts
  • apps/mobile/src/lib/hooks/use-security-agent.ts
  • apps/mobile/src/lib/hooks/use-security-remediation.test.ts
  • apps/mobile/src/lib/hooks/use-security-remediation.ts
  • apps/mobile/src/lib/hooks/use-session-mutations.test.ts
  • apps/mobile/src/lib/hooks/use-session-mutations.ts - 1 issue
  • apps/mobile/src/lib/hooks/use-user-web-connection-state.ts
  • apps/mobile/src/lib/offline-banner-state.test.ts
  • apps/mobile/src/lib/offline-banner-state.ts
  • apps/mobile/src/lib/persist/mutation-outbox.test.ts
  • apps/mobile/src/lib/pr-review/discussion/use-review-discussion-mutations.test.ts
  • apps/mobile/src/lib/pr-review/discussion/use-review-discussion-mutations.ts
  • apps/mobile/src/lib/query-client-lifecycle.test.ts
  • apps/mobile/src/lib/query-client.ts
  • packages/cloud-agent-sdk/src/base-connection.test.ts
  • packages/cloud-agent-sdk/src/base-connection.ts - 1 issue
  • packages/cloud-agent-sdk/src/session-routing.test.ts
  • packages/cloud-agent-sdk/src/session-transport.test.ts
  • packages/cloud-agent-sdk/src/session.test.ts
  • packages/cloud-agent-sdk/src/user-web-connection.test.ts
  • packages/cloud-agent-sdk/src/user-web-connection.ts

Reviewed by grok-4.6 · Input: 103.8K · Output: 13.3K · Cached: 288.6K

Review guidance: REVIEW.md from base branch main

Comment thread apps/mobile/src/components/agents/session-list-screen.tsx Outdated
Thread the reconnect attempt through refreshAndConnect so reconnects that refresh auth before connecting no longer reset the attempt counter to zero. Live user-web sessions now reach the exhaustion cap and fire the two-edge signal, instead of showing Reconnecting forever.
Stamp the generation inside snapshotAndUpdate after cancelQueries resolves, so the stamp order matches the write order and an older mutation cannot clobber a newer list write.
Read navigation.isFocused() in the AppState callback instead of a focusedRef that a frozen tab never updates. A frozen unfocused Agents tab no longer refetches or invalidates the active-sessions tray on foreground.
@iscekic iscekic added the human-ready The PR is ready for human review. label Aug 21, 2026
@iscekic iscekic changed the title fix(mobile): harden offline and query integrity flows fix(mobile): harden offline and query integrity flows (w6c) Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant