Skip to content

fix(mobile): persist the last tool summary translation choice - #8266

Open
iscekic wants to merge 4 commits into
mainfrom
drain/mobile-preference-latest-choice
Open

iscekic wants to merge 4 commits into
mainfrom
drain/mobile-preference-latest-choice

Conversation

@iscekic

@iscekic iscekic commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

Change

A persisted preference now reaches the disk before the app shows it, and a burst of choices no longer queues one write per choice.

createSecureStorePreference (apps/mobile/src/lib/hooks/secure-store-preference.ts) is the store behind every SecureStore preference, including the tool-summary translation switch and the voice-input gateway transcription switch. It now keeps:

  • one write in flight per key; a newer choice made meanwhile replaces the queued target, so only the newest choice is written and an earlier write can never land after a newer one;
  • a rendered value that moves only when its write lands, so the switch never shows a value the disk does not hold; a failed write keeps the last stored value and shows the existing "could not save setting" toast;
  • getRequested(), the newest requested value (queued or being written), for code that derives the next value from the current one;
  • whenPersisted(), so a caller or a test can await durability instead of polling;
  • a write epoch so clear() (sign-out) cannot be repainted by a write already on its way, and mergeOnLoad still reconciles a choice made before the disk read settles.

PreferenceRow accepts resolveValue, and the tool-summary translation and voice-input gateway switches pass the store's newest requested value, so a burst of taps flips once per tap instead of repeating the value of the last render.

Getters and list mutators that build a value from the current one (chat backends, remote MCP servers and their OAuth state, trusted hosts and images, the agent model map, the settings registry) read getRequested(); React snapshots keep the durable value.

Verification

  • pnpm --filter kilo-app test — 1194 files, 17900 tests pass.
  • New cases in apps/mobile/src/lib/hooks/secure-store-preference.test.ts:
    • a choice is visible only after its write lands;
    • five rapid choices produce two writes and the newest one last;
    • a failed write toasts once and keeps the value the disk still holds.
  • pnpm --filter kilo-app typecheck and pnpm --filter kilo-app lint are clean.
  • Android end to end (emulator omp36, local dev stack, dev client, owned fake account):
    • tool-summary translation off → on, then am force-stop and relaunch: the switch reads on;
    • five rapid taps (on → off), then am force-stop and relaunch: the switch reads off;
    • voice-input gateway transcription off → on, then am force-stop and relaunch: the switch reads on.

Proxy answers

The owner's proxy gave these answers in the owner's place.

  1. Question: Measured on this emulator: one SecureStore write is about 20 ms; the write is async and fire-and-forget, so a teardown right after the tap loses the choice while the switch already shows it. Which semantics should the shared preference store use?
    Answer: Coalesce + durability-first
    Confidence: 0.65
  2. Question: The two settings named in the brief both use createSecureStorePreference, which also backs about 15 other preferences. Where should the fix land?
    Answer: Shared store
    Confidence: 0.90
  3. Question: The settings row computes the next value as !value from its last render, so taps that land before a re-render can repeat the same value. Should the row compute from the store's latest requested value instead?
    Answer: Compute from the store
    Confidence: 0.85
  4. Question: With durability-first the rendered value lags a write, so a burst of taps must compute the next value from the latest requested value. How should the row get it?
    Answer: Store exposes pending target; PreferenceRow uses it
    Confidence: 0.85
  5. Question: What should happen when the disk write fails?
    Answer: Toast and drop the failed target
    Confidence: 0.60
  6. Question: The store's set() is used by about 15 modules and their tests. What signature should it keep?
    Answer: void set() plus whenPersisted()
    Confidence: 0.65

The shared SecureStore preference store wrote fire-and-forget: set() moved the
value the app shows and started a write in the background, so a burst of
toggles queued one write per toggle and the newest choice sat behind the older
ones. A process teardown right after a choice left the app showing a value the
disk did not hold.

The store now keeps one write in flight, writes only the newest requested
target, and applies a value only when its write lands. A failed write toasts
and leaves the value on the last stored one. clear() fences an in-flight write
so a sign-out cannot repaint the reset value, and mergeOnLoad still reconciles
a choice made before the disk read settles.
A preference now becomes visible when its write lands, so a read that derives
the next value has to start from the newest requested one instead of the
rendered one. Getters and list mutators (chat backends, remote MCP servers and
their OAuth state, trusted hosts and images, the agent model map, the settings
registry) read that value, and the two settings switches resolve it at press
time through PreferenceRow's new resolver, so rapid taps each flip the state
the person last asked for.
@iscekic iscekic added drain Work item from the findings drain explorer:android Finding surfaced by the Android explorer labels Oct 20, 2026
@iscekic

iscekic commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Android end-to-end proof on emulator omp36 against the local dev stack with an owned fake account.

Exercised on the branch head:

  1. Tool-summary translation: switch off → tap → on; am force-stop; relaunch; Profile → Preferences → Translate tool summaries reads on.
  2. Five rapid taps on the same row (on → off), then am force-stop and relaunch: the screen reads off, matching the last tap.
  3. Voice-input gateway transcription: switch off → tap → on; am force-stop; relaunch; the switch reads on.

The uiautomator state read at each capture: on / on / off / off / off / on / on. The model-catalogue block below the switch in the voice-input screen is an unrelated local-stack error, cropped out of those two captures.

Tool summary translation switch on

Same screen after force-stop and relaunch, switch still on

Switch off after five rapid taps

Same screen after force-stop and relaunch, switch still off

Voice input gateway transcription switch off

Voice input gateway transcription switch on

Voice input screen after force-stop and relaunch, switch still on

Comment thread apps/mobile/src/lib/hooks/secure-store-preference.ts
Comment thread apps/mobile/src/lib/hooks/secure-store-preference.ts Outdated
Comment thread apps/mobile/src/components/ui/preference-row.tsx Outdated
Comment thread apps/mobile/src/lib/hooks/use-trusted-hosts.ts
@kilo-code-bot

kilo-code-bot Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental change only renames the nullable-store test so its name matches the behavior it exercises; the failure-release path remains covered by the following case, and all prior findings are resolved at HEAD (be825a9f).

Files Reviewed (1 file)
  • apps/mobile/src/lib/hooks/secure-store-preference.test.ts
Previous Review Summaries (2 snapshots, latest commit 2ef918f)

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

Previous review (commit 2ef918f)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The four previously reported issues (stranded queued choice, nullable null target, native switch bypassing the resolver, and revokeHost using the durable list) are all fixed at HEAD; the only remaining finding is a test name that claims failure coverage it does not exercise.

Overview

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

SUGGESTION

File Line Issue
apps/mobile/src/lib/hooks/secure-store-preference.test.ts 175 The test named "...and releases waiters after a failure" never makes a write fail, so it does not verify the failure release path (the following test does).
Files Reviewed (5 files)
  • apps/mobile/src/components/ui/preference-row.mounted.test.tsx
  • apps/mobile/src/components/ui/preference-row.tsx
  • apps/mobile/src/lib/hooks/secure-store-preference.test.ts - 1 issue
  • apps/mobile/src/lib/hooks/secure-store-preference.ts
  • apps/mobile/src/lib/hooks/use-trusted-hosts.ts

Fix these issues in Kilo Cloud

Previous review (commit 8f1b17e)

Status: 4 Issues Found | Recommendation: Address before merge

Executive Summary

The new durable SecureStore write queue can strand the newest queued choice and permanently hang whenPersisted() when a write fails while a newer choice is queued.

Overview

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

WARNING

File Line Issue
apps/mobile/src/lib/hooks/secure-store-preference.ts 141 A rejected write breaks out of the drain loop while a newer set() has re-populated requested. That target is never written or dropped, getRequested() keeps reporting a value the disk does not hold, and releaseDrainWaiters() refuses to release, so every whenPersisted() waiter (including setAsync) hangs and retains its resolver.
apps/mobile/src/lib/hooks/secure-store-preference.ts 89 currentRequested uses ??, so an in-flight or queued null target in the nullable stores (voice-input language, gateway transcription model, run-on destination) falls through to the previous durable value for the duration of the write.

SUGGESTION

File Line Issue
apps/mobile/src/components/ui/preference-row.tsx 57 The native Switch still calls onValueChange from the lagging rendered value, bypassing resolveValue, so thumb taps and row taps behave differently during a burst.
apps/mobile/src/lib/hooks/use-trusted-hosts.ts 56 revokeHost still builds from store.get() while trustHost uses the newest requested list, so a revoke during an in-flight trust write drops the newly trusted host.

Fix these issues in Kilo Cloud

Files Reviewed (31 files)
  • apps/mobile/src/components/tool-summary-translation-settings-screen.tsx
  • apps/mobile/src/components/ui/preference-row.tsx - 1 issue
  • apps/mobile/src/components/voice-input-settings-screen.tsx
  • apps/mobile/src/lib/chat/backend-store.ts
  • apps/mobile/src/lib/chat/remote-mcp-oauth-store.ts
  • apps/mobile/src/lib/chat/remote-mcp-store.ts
  • apps/mobile/src/lib/chat/settings-tools-switch.test.ts
  • apps/mobile/src/lib/chat/settings-tools-switch.ts
  • apps/mobile/src/lib/chat/sign-out.test.ts
  • apps/mobile/src/lib/hooks/secure-store-preference.test.ts
  • apps/mobile/src/lib/hooks/secure-store-preference.ts - 2 issues
  • apps/mobile/src/lib/hooks/use-collapsed-connect-ctas-preference.test.ts
  • apps/mobile/src/lib/hooks/use-collapsed-connect-ctas-preference.ts
  • apps/mobile/src/lib/hooks/use-condense-tool-calls-preference.test.ts
  • apps/mobile/src/lib/hooks/use-condense-tool-calls-preference.ts
  • apps/mobile/src/lib/hooks/use-hide-balance-preference.ts
  • apps/mobile/src/lib/hooks/use-hide-thinking-preference.ts
  • apps/mobile/src/lib/hooks/use-keep-screen-on-preference.ts
  • apps/mobile/src/lib/hooks/use-language-preference.ts
  • apps/mobile/src/lib/hooks/use-live-activity-preference.test.ts
  • apps/mobile/src/lib/hooks/use-live-activity-preference.ts
  • apps/mobile/src/lib/hooks/use-persisted-agent-model.ts
  • apps/mobile/src/lib/hooks/use-pr-review-footer-preference.ts
  • apps/mobile/src/lib/hooks/use-reasoning-preference.ts
  • apps/mobile/src/lib/hooks/use-return-sends-message-preference.ts
  • apps/mobile/src/lib/hooks/use-theme-preference.ts
  • apps/mobile/src/lib/hooks/use-trusted-hosts.ts - 1 issue
  • apps/mobile/src/lib/hooks/use-trusted-image-hosts.ts
  • apps/mobile/src/lib/tool-summary-translation/tool-summary-translation-preference.ts
  • apps/mobile/src/lib/voice-input/gateway/gateway-transcription-preference.ts
  • apps/mobile/src/lib/voice-input/voice-input-language-preference.ts

Reviewed by deepseek-v4.1-flash-1017 · Input: 33.9K · Output: 1.7K · Cached: 174.1K

Review guidance: REVIEW.md from base branch main

Review follow-ups on the preference store:

- a failed write now drops the queued target as well as the in-flight one, so
  the queue releases its waiters instead of stranding a newer choice forever;
- the newest-value lookup compares undefined explicitly, so a nullable store
  (the voice-input language "auto", no run-on destination) reports a requested
  null instead of the previous stored value;
- revokeHost builds from the newest value like trustHost already does;
- the row and the native switch share one flip, so both controls move the value
  the person last asked for.

Each edge has a case in secure-store-preference.test.ts or
preference-row.mounted.test.tsx.
Comment thread apps/mobile/src/lib/hooks/secure-store-preference.test.ts Outdated
@iscekic

iscekic commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Re-verified on the review-fix head 2ef918f1 (the row flip and the store edge cases changed in that commit).

  • Tool-summary translation: switch off → tap → on, then am force-stop and relaunch: the switch reads on.
  • Voice-input gateway transcription: toggled off and back on, then am force-stop and relaunch: the switch reads on.

Same emulator, local dev stack, and owned fake account as the earlier captures; switch state read with uiautomator at each capture.

Tool summary translation switch on after the tap

Same screen after force-stop and relaunch, switch still on

Voice input gateway transcription switch on

Voice input screen after force-stop and relaunch, switch still on

The nullable test asserts the requested null; the failure release is covered by
the following case, so the name no longer claims both.
@iscekic iscekic added the merge:human A human reviews and merges this pull request. label Oct 20, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drain Work item from the findings drain explorer:android Finding surfaced by the Android explorer merge:human A human reviews and merges this pull request.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant