Skip to content

fix(mobile): repaint the picked language row in the language picker - #8765

Merged
iscekic merged 7 commits into
mainfrom
drain/mobile-language-picker-selection
Oct 25, 2026
Merged

iscekic merged 7 commits into
mainfrom
drain/mobile-language-picker-selection

Conversation

@iscekic

@iscekic iscekic commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Evidence

Evidence: demonstrated — on the pre-fix build the picker's choice never reached the screen for an RTL language: the preference persisted and the native relaunch started, but the app came back in the previous direction (the earlier run on this dev client recorded the frame staying blank while the entry point re-ran). With this branch, on an Android emulator dev client built from this branch and on an iOS simulator build from this branch, tapping the Arabic row moved it from "radio button, unchecked" to "radio button, checked", and Done relaunched the app once into the Arabic login screen with a right-to-left layout, which a cold start keeps.

What changed

The selection reaches the list rows

The picker's list only repaints a mounted cell when one of its comparison
inputs changes identity. The sheet handed the row its selection through an
inline renderItem closure and gave the list no extraData, so the repaint
depended on that closure being recreated on every parent render: any hoist of
renderItem would silently leave a tapped row showing the previous check mark
and accessibility state, while every parent render still re-rendered all
mounted rows.

  • language-picker-sheet.tsx: pass the selection and the in-flight apply state
    to the list as a memoized extraData ({ selected, disabled: busy }), and
    hoist renderItem in a useCallback.
  • language-picker-row.tsx: take selected as the row's own boolean and wrap
    the row in React.memo, so a selection change repaints only the row that
    lost the check mark and the one that gained it.

The forced direction survives the relaunch

A relaunch is the only way to apply a forced native direction, and the app was
relaunching for it on every launch: the direction the picker forced was reset
by the localization module's template defaults before React rendered, so the
cold start saw the previous direction again and relaunched. The app never
painted in the chosen direction, and a launch that came back wrong repeated
forever.

  • patches/expo-localization@58.0.4.patch: the module only writes
    allowRTL/forceRTL when the merged Android string resource (or the iOS
    Info.plist key) actually carries a boolean. Both packaged resources are the
    literal unset, and the app does not configure the config plugin, so
    toBooleanStrictOrNull() returns null and the app stays the only writer of
    the direction.
  • apps/mobile/package.json: list expo-localization under
    expo.autolinking.android.buildFromSource. Android links the package's
    published AAR unless the module is listed, so without this the patched Kotlin
    never compiled, and the coupling is recorded beside the patch entry in
    pnpm-workspace.yaml.
  • i18n/direction-attempt.ts: record the language whose direction the last
    launch relaunched for, and clear it once the direction matches.
  • i18n/startup-direction.ts and i18n/startup-direction-run.ts: the cold
    start's decision (apply in place, relaunch once, or show the retry screen) and
    the effects that run it. A second automatic relaunch for the same language is
    refused, so a direction that never takes effect cannot loop; the retry control
    drops the record first, because a retry is the user asking again.
  • i18n/apply-language.ts: write the record before the picker's own relaunch,
    and report restart-skipped when the record could not be written.
  • language-picker-sheet.tsx: keep which step failed. Retry reloads after a
    failed reload, and re-runs the apply path after a skipped restart, so the
    record write is retried instead of reloading into the same refused launch.

Tests

  • language-picker-sheet.mounted.test.tsx: a tap marks the row selected, the
    list's extraData moves to the tapped language, Done applies it, and Retry
    after a skipped restart re-runs the apply path (record written twice, direction
    forced once, one reload). The retry assertion fails without the change.
  • startup-direction-run.test.ts: the apply, relaunch, refused second relaunch,
    write-failure and failed-relaunch branches, and that the retry drops the
    record before reloading.
  • apply-language.test.ts: the record is written before the relaunch and a
    failed write skips it.

End-to-end verification

VM: fresh drain work VM. Android: local dev stack, emulator omp36 (default
density and font scale), CI dev-client APK built from this branch's head
(mobile-native-android-a338432871d9f43f201e0315bb0e22f2806223a2, so the module
compiled from source with the patch) and Metro serving this branch.

Android flow, from the login screen:

  • Language → search arabic → the row reported
    checked="false"; after the tap the row's radio node reported
    checked="true".
  • Done → the app relaunched once (no blank frame) into the Arabic login screen:
    مرحبًا بك في Kilo, عنوان البريد الإلكتروني, المتابعة عبر البريد الإلكتروني.
  • The layout is mirrored, not only the copy: the terms row runs right to left
    (leading text rightmost, then الشروط, و, سياسة الخصوصية, and the final
    stop leftmost), the sheet's header put Cancel on the right and Done on the
    left, and RCTI18nUtil_forceRTL=true is persisted after the relaunch.
  • A cold start (am force-stop, relaunch) came back on the same Arabic screen
    without another relaunch and without the retry screen.
  • The reverse direction was exercised too: picker → English → Done relaunched
    once into the English login screen.

iOS: ios_app reused the mobile-native-build artifact of this checkout's
native fingerprint (7126375e…, simulator), Metro and the backend on the lane's
port offset, driven through the lane's agent-device session on the login screen:

  • Language → search arabic → the cell's accessibility value read
    radio button, unchecked before the tap and radio button, checked after it.
  • Done → the app came back on مرحبًا بك في Kilo (بالمتابعة، توافق على الشروط و سياسة الخصوصية., المزيد من خيارات تسجيل الدخول).
  • Mirrored layout on iOS too: in the Arabic sheet Cancel sits at x≈342 (right
    edge) and Done at x≈21 (left edge), the mirror of the English sheet.
  • Relaunching the app came back on the same Arabic screen.

Checks: vitest for the picker, row, startup-direction, direction-attempt and
apply-language suites; tsgo --noEmit, oxlint and oxfmt --list-different
for apps/mobile.

Production prerequisites

None.

Proxy answers

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

  1. Question: Repaint wiring: How should the picker list make a tap reach its rows?
    Answer: Memoized row + boolean selected + memoized extraData (Recommended)
    Confidence: 0.80 · Precedent: Engineering conventions / Performance reviewer: 'check performance on large tables and hot paths'. The brief asks for bounded row re-renders, and this option follows the repo's list convention.
  2. Question: Busy state: Should the same repaint path cover the rows' disabled/busy dim while an apply is in flight?
    Answer: Yes, include disabled in extraData (Recommended)
    Confidence: 0.85 · Precedent: Fix the class of the issue: 'if you saw a gap, fix it'; 'fix class of issue'.
  3. Question: iOS proof: The change touches no iOS-only code and no Platform/keyboard/safe-area/text logic. How should iOS be covered?
    Answer: Attempt an ios_lane lease anyway
    Confidence: 0.75 · Precedent: Verify on both platforms: 'Both iOS and Android need to be verified'. Agent requests the iOS lane: 'iOS lane must specifically be asked for by the agent'. The brief names ios_lane explicitly. If the lease is refused, treat the refusal as a workflow bug, not as an end state.
  4. Question: Test placement: Where does the new mounted assertion live?
    Answer: Extend language-picker-sheet.mounted.test.tsx (Recommended)
    Confidence: 0.80 · Precedent: Least work, no drop in quality: 'do the least amount of work possible, with no drop in quality'. Reuse the existing mock harness instead of duplicating it.
  5. Question: RTL apply defect: pressing Done with Arabic left the app in a permanent reload loop on a blank screen even though the preference persisted and RCTI18nUtil_forceRTL was written. How should this PR handle it?
    Answer: Fix it in this PR. The brief requires Arabic or Urdu chosen from login to reload into an RTL layout, and a permanent reload loop bricks the app. Find the root cause of the direction not surviving. First rule out a dev-client artifact with a local release build, then fix the real cause. Also add the bounded guard: at most one direction reload per launch, then fall back to the existing Retry/Continue screen, so the app can never loop. Prove Arabic and Urdu end to end on Android and on iOS (ios_lane) from the login screen.
    Confidence: 0.60 · Precedent: Autonomy: 'Fix the root cause and the class of the issue'.
  6. Question: Follow-up item: If the RTL-apply defect stays out of this PR, should it be filed as a drain work item?
    Answer: File a follow-up work item with the measured evidence
    Confidence: 0.55 · Precedent: Product scope: 'Send every work item through triage, including manual items'.
  7. Question: RTL fix scope: no JS-only change can make Arabic or Urdu render RTL, because the forced direction never survives a relaunch; the correct fix needs native code and new native builds. How should this PR proceed?
    Answer: Implement the native restart in this PR
    Confidence: 0.55 · Precedent: Autonomy: fix the root cause and the class of the issue, and never accept blocked as an end state. Proof: build native iOS in CI.

The sheet's FlashList only repaints a mounted cell when its data or extraData changes identity, and the row received the selection through a renderItem closure. Pass the selection and the in-flight busy state through a memoized extraData, hoist renderItem, and memoize the row on its own selected flag so a tap repaints only the rows that changed.
@iscekic iscekic added drain Work item from the findings drain explorer:ios Finding surfaced by the iOS explorer labels Oct 23, 2026
@iscekic

iscekic commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator Author

bot: Android emulator (1080x2400, CI dev-client APK, this branch's JavaScript through Metro), fresh local dev stack with no signed-in account.

Exercised on the login screen: Language control → search the language → tap the row → Done.

  • The tapped row reports checked="false" before the tap and checked="true" immediately after it, with the soft keyboard still shown, and Done closes the sheet and applies that language.
  • Captures below: the picker with the tapped row selected, and the login screen after Done, now in that language.
  • The same flow was repeated for an RTL language: the row reports checked="true" and Done goes through the direction-change restart. That restart does not render on this dev client (the app loops on a blank frame), so RTL layout is not shown; the existing unit suite covers the restart path.

Language picker with the tapped row marked selected

Login screen rendered in the applied language after Done

@kilo-code-bot

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

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

I re-verified the whole diff at 830e621 and found no remaining defect: the picker repaint wiring, the one-relaunch direction guard, the per-outcome Retry, and the expo-localization patch are consistent with their callers and tests (19 files).

Assumptions: [INFERENCE] Tests, tsgo, oxlint and oxfmt were not run in this read-only session, so the PR's reported results are taken as stated; node_modules is absent, so the packaged ExpoLocalization_supportsRTL/forcesRTL values and the patched native sources could not be inspected here (the author's reply and the cold-start evidence on both platforms cover that gap, and app.config.ts confirms the plugin is not configured). The language effect still re-runs when navigationReady flips and now awaits a SecureStore read before deciding, so two overlapping prepareLanguage runs could in principle both reach reloadAppAsync; I could not demonstrate that ordering, so this is noted rather than flagged. This range adds no subscriptions, timers, listeners or retained references, so there is no memory-leak risk in the changed code.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0

Both earlier suggestions are resolved at this head: applyLanguagePreference returns restart-skipped before syncRtl/reload when the attempt record cannot be written, and the picker's Retry now re-runs the apply path for that outcome (covered by the mounted test), while the cold start's apply/relaunch/refusal/write-failure branches are covered by startup-direction-run.test.ts.

Files Reviewed (19 files)
  • apps/mobile/package.json
  • apps/mobile/src/app/_layout.tsx
  • apps/mobile/src/components/language-picker-row.tsx
  • apps/mobile/src/components/language-picker-row.mounted.test.tsx
  • apps/mobile/src/components/language-picker-sheet.tsx
  • apps/mobile/src/components/language-picker-sheet.mounted.test.tsx
  • apps/mobile/src/i18n/apply-language.ts
  • apps/mobile/src/i18n/apply-language.test.ts
  • apps/mobile/src/i18n/direction-attempt.ts
  • apps/mobile/src/i18n/startup-direction.ts
  • apps/mobile/src/i18n/startup-direction.test.ts
  • apps/mobile/src/i18n/startup-direction-run.ts
  • apps/mobile/src/i18n/startup-direction-run.test.ts
  • apps/mobile/src/lib/settings/language-catalog.ts
  • apps/mobile/src/lib/settings/registry.test.ts
  • apps/mobile/src/lib/storage-keys.ts
  • patches/expo-localization@58.0.4.patch
  • pnpm-workspace.yaml
  • pnpm-lock.yaml (generated)
Previous Review Summaries (5 snapshots)

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

Previous review

Status: 1 Suggestion Found | Recommendation: Address before merge

Executive Summary

The incremental commit fixes the earlier Retry gap: restart-skipped now re-runs the apply path so the direction is forced only after the attempt record is written, leaving the unverified native expo-localization RTL patch as the only open suggestion.

Assumptions: [INFERENCE] Tests, tsgo, oxlint and oxfmt were not run in this read-only session, so the PR's reported results are taken as stated. The packaged expo-localization string-resource defaults and a cold start in an RTL language could not be verified without node_modules or a client rebuilt from this branch. This range only changes the picker's restart-failure state and its mounted test; it adds no subscriptions, timers, listeners or retained references, so there is no memory-leak risk in the changed code.

Overview

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

SUGGESTION

File Line Issue
patches/expo-localization@58.0.4.patch 8 Carried: the toBooleanStrictOrNull() guards skip only when the merged resource is unset/non-boolean; confirm the packaged ExpoLocalization_supportsRTL/forcesRTL defaults are unset rather than true/false. apps/mobile/package.json lists the module for a source build, but no client has been built from that entry: the APK named in the PR predates the patch, so the direction fix stays unverified on both platforms.
Files Reviewed (3 files)
  • apps/mobile/src/components/language-picker-sheet.tsx - 0 issues (the restart-skipped Retry now re-runs handleDone, retries the attempt record, then forces the direction and reloads)
  • apps/mobile/src/components/language-picker-sheet.mounted.test.tsx - 0 issues
  • patches/expo-localization@58.0.4.patch - 1 suggestion (carried, targeted check)

Fix these issues in Kilo Cloud

Previous review (commit 975ade2)

Status: 2 Suggestions Found | Recommendation: Address before merge

Executive Summary

The three new commits resolve both earlier suggestions and add real test coverage for the startup-direction branches; the remaining gaps are the shared Retry for the new restart-skipped outcome and the still-unverified native RTL patch.

Assumptions: [INFERENCE] reloadAppAsync restarts the JS context, and expo-localization's packaged RTL string-resource defaults could not be inspected (no node_modules in this read-only session); tests, tsgo, oxlint and oxfmt were not run, so the PR's reported results are taken as stated. This range adds no subscriptions, timers, listeners or retained references, so there is no memory-leak risk in the changed code.

Overview

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

SUGGESTION

File Line Issue
patches/expo-localization@58.0.4.patch 8 Carried: the toBooleanStrictOrNull() guards skip only when the merged resource is unset/non-boolean; confirm the packaged ExpoLocalization_supportsRTL/forcesRTL defaults are unset rather than true/false. apps/mobile/package.json now lists the module for a source build, but no client has been built from that entry: the APK named in the PR predates the patch, so the direction fix stays unverified on both platforms.
apps/mobile/src/components/language-picker-sheet.tsx 168 restart-skipped shares the reload-failed Retry, but its Retry only calls reloadAppAsync(): no attempt record and no syncRtl, so the direction needs a second cold-start relaunch, or a stale record sends the user to the root retry screen.
Files Reviewed (17 files)
  • apps/mobile/package.json - 0 issues
  • apps/mobile/src/app/_layout.tsx - 0 issues (earlier test-gap suggestion resolved by startup-direction-run tests)
  • apps/mobile/src/components/language-picker-sheet.tsx - 1 suggestion
  • apps/mobile/src/components/language-picker-sheet.mounted.test.tsx - 0 issues
  • apps/mobile/src/i18n/apply-language.ts - 0 issues (earlier write-result suggestion resolved)
  • apps/mobile/src/i18n/apply-language.test.ts - 0 issues
  • apps/mobile/src/i18n/direction-attempt.ts - 0 issues (context)
  • apps/mobile/src/i18n/rtl.ts - 0 issues (context)
  • apps/mobile/src/i18n/startup-direction-run.ts - 0 issues
  • apps/mobile/src/i18n/startup-direction-run.test.ts - 0 issues
  • apps/mobile/src/i18n/startup-direction.ts - 0 issues (context)
  • apps/mobile/src/i18n/startup-direction.test.ts - 0 issues (context)
  • apps/mobile/src/lib/auth/secure-store-value.ts - 0 issues (context)
  • apps/mobile/src/lib/settings/language-catalog.ts - 0 issues
  • apps/mobile/src/lib/settings/registry.test.ts - 0 issues
  • pnpm-workspace.yaml - 0 issues
  • patches/expo-localization@58.0.4.patch - 1 suggestion (carried, targeted check)

Fix these issues in Kilo Cloud

Previous review (commit 1d49990)

Status: 3 Suggestions Found | Recommendation: Merge

Executive Summary

The new expo-localization native patch is the only change since the previous review; its Android guard may never trigger, no device run covers the patch, and the two earlier suggestions (ignored attempt-write result, untested startup-direction wiring) remain.

Assumptions: [INFERENCE] reloadAppAsync restarts the JS context without a native relaunch, and expo-localization's packaged RTL string-resource defaults could not be inspected in this read-only session (no node_modules); tests, tsgo, oxlint and oxfmt were not run, so the PR's reported results are taken as stated.

Overview

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

SUGGESTION

File Line Issue
patches/expo-localization@58.0.4.patch 8 The toBooleanStrictOrNull() guard only skips when the merged string resource is unset/non-boolean; this app does not configure the expo-localization plugin, so confirm the packaged defaults are unset rather than true/false. Also patches/** is part of the dev-client native fingerprint, so the APK named in the PR predates this native change and the direction fix is unverified on both platforms.
apps/mobile/src/i18n/apply-language.ts 67 writeDirectionAttemptAsync result is ignored; a failed record write leads to a second automatic relaunch from the cold start, contrary to direction-attempt.ts's documented contract.
apps/mobile/src/app/_layout.tsx 476 The fallback, write-failure, clear-on-apply and retry-clear branches have no test; only the pure resolver and the apply-time write are covered.
Files Reviewed (5 files)
  • patches/expo-localization@58.0.4.patch - 1 suggestion
  • pnpm-workspace.yaml - 0 issues
  • pnpm-lock.yaml - 0 issues (generated)
  • apps/mobile/src/i18n/apply-language.ts - 1 suggestion (carried, re-verified)
  • apps/mobile/src/app/_layout.tsx - 1 suggestion (carried, re-verified)

Previous review (commit 6f5321a)

Status: 2 Suggestions Found | Recommendation: Merge

Executive Summary

The picker repaint change is unchanged and remains sound; the new direction-attempt guard is logically consistent (one automatic relaunch per language, then the retry screen), with two non-blocking gaps: the apply path ignores a failed attempt record and the _layout loop-protection wiring has no test.

Assumptions: [INFERENCE] expo's reloadAppAsync restarts the JS context without a native relaunch, so I18nManager.isRTL can return the pre-reload value on the next cold start — the case this change guards; that is the behavior described in the PR body's blank-frame observation, not code I read. Tests, tsgo, oxlint and oxfmt were not run in this read-only session, so the PR's reported results are taken as stated. The PR description still covers only the picker files and does not describe the new direction-attempt/startup-direction behavior.

Overview

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

SUGGESTION

File Line Issue
apps/mobile/src/i18n/apply-language.ts 67 writeDirectionAttemptAsync result is ignored; a failed record write leads to a second automatic relaunch from the cold start, contrary to direction-attempt.ts's documented contract.
apps/mobile/src/app/_layout.tsx 476 The fallback, write-failure, clear-on-apply and retry-clear branches have no test; only the pure resolver and the apply-time write are covered.
Files Reviewed (8 files)
  • apps/mobile/src/app/_layout.tsx - 1 suggestion
  • apps/mobile/src/components/language-picker-sheet.mounted.test.tsx - 0 issues
  • apps/mobile/src/i18n/apply-language.test.ts - 0 issues
  • apps/mobile/src/i18n/apply-language.ts - 1 suggestion
  • apps/mobile/src/i18n/direction-attempt.ts - 0 issues
  • apps/mobile/src/i18n/startup-direction.test.ts - 0 issues
  • apps/mobile/src/i18n/startup-direction.ts - 0 issues
  • apps/mobile/src/lib/storage-keys.ts - 0 issues

Notes: resolveLanguageStartupStep reduces to isRtl === shouldBeRtl ? 'apply' : lastAttempt === language ? 'fallback' : 'reload' and every branch matches the intent (a given language gets at most one automatic relaunch, and a different language still reloads); the record uses the existing SecureStore preference convention and is app-wide, like the language preference. No new subscriptions, timers, listeners or retained references were added, so there is no memory-leak risk in the changed code.

Previous review (commit b2fae4d)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The change correctly moves the picker selection and busy state into a memoized extraData and hoists renderItem, and the row's selected boolean derivation matches the previous per-row comparison on every item kind; the added mounted test pins the repaint contract. No correctness, security, or leak issues found in the four changed files.

Assumptions: [INFERENCE] FlashList 2.3.3 extraData behaves as the repo's existing extraData call sites assume (e.g. finding-list-screen.tsx); tests and typecheck were not run in this read-only session, so the PR's reported vitest/tsgo/oxlint results are taken as stated.

Files Reviewed (4 files)
  • apps/mobile/src/components/language-picker-sheet.tsx - 0 issues
  • apps/mobile/src/components/language-picker-row.tsx - 0 issues
  • apps/mobile/src/components/language-picker-sheet.mounted.test.tsx - 0 issues
  • apps/mobile/src/components/language-picker-row.mounted.test.tsx - 0 issues

Notes: the selected derivation covers all three LanguagePickerItem kinds, onSelect={setSelected} stays referentially stable so React.memo can bail out on unaffected rows, and no new subscriptions, timers, or retained references were introduced (no memory-leak risk).


Reviewed by deepseek-flash-latest · Input: 115.9K · Output: 28.7K · Cached: 1.5M

Review guidance: REVIEW.md from base branch main

@iscekic iscekic added merge:blocked:insufficient-e2e Verification does not cover the change merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. labels Oct 23, 2026
The cold start forced the native layout direction and relaunched whenever the
running direction did not match the stored language. A relaunch is the only way
to apply a forced direction, but it is not guaranteed to come back with it: the
app then relaunched again on every launch and stayed on a blank frame, and the
chosen language never reached the screen.

Record the language whose direction the last launch relaunched for, and refuse a
second automatic relaunch for the same language. The app shows its existing
retry screen instead, whose retry clears the record so the user can ask for the
attempt again. The picker's own apply path writes the same record before it
relaunches, so it cannot feed the loop either.
@iscekic iscekic removed merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. merge:blocked:insufficient-e2e Verification does not cover the change labels Oct 24, 2026
Comment thread apps/mobile/src/i18n/apply-language.ts Resolved
Comment thread apps/mobile/src/app/_layout.tsx Resolved
@iscekic iscekic added merge:blocked:insufficient-e2e Verification does not cover the change merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. and removed merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head labels Oct 24, 2026
expo-localization reset the React Native RTL preferences from its build-time
template values on every React instance, and this app does not set those
values: the unset string resolves to the template default, so each new instance
forced the direction back to LTR before React rendered. An app-chosen RTL
language then never took effect, and the cold-start relaunch repeated on every
launch.

Leave the preference alone when the template value is unset on both platforms;
an explicitly configured value still applies.
@iscekic iscekic removed merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. merge:blocked:insufficient-e2e Verification does not cover the change labels Oct 24, 2026
Comment thread patches/expo-localization@58.0.4.patch Resolved
@iscekic iscekic added merge:blocked:insufficient-e2e Verification does not cover the change merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. and removed merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head labels Oct 24, 2026
The picker's apply path wrote the relaunch record and reloaded regardless of
the write result, so a failed write still relaunched and the next launch had
no record to refuse a second attempt. Honour the documented contract: when the
record cannot be written, keep the current direction and show the picker's
saved-language retry state instead of reloading.
Move the direction step's effects out of the root layout into one function with
injected effects, so the branches that decide between an in-place start, one
relaunch, and the retry screen are covered: a missing record, a refused record
write, a failed reload, and the retry control dropping the record first.
The package publishes a prebuilt AAR, and Gradle links it unless the module is
listed for a source build, so the layout-direction guard this branch patches
into the module's Kotlin never compiled on Android. List the module for a
source build and record the coupling beside the patch entry.
@iscekic iscekic removed merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. merge:blocked:insufficient-e2e Verification does not cover the change labels Oct 25, 2026
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:blocked:insufficient-e2e Verification does not cover the change merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. labels Oct 25, 2026
One Retry served two different restart failures. A failed reload had already
forced the direction and written the attempt record, so reloading again is
exactly the step to repeat. A skipped restart never reached either: the record
write failed, so reloading there came back in the previous direction with still
no record, and the direction then needed a second restart to arrive.

Keep which step failed and re-run the apply path for the skipped one, so Retry
retries the record write and only then relaunches. The mounted test drives the
skipped case through Retry and asserts the second write, the forced direction
and one reload.
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:blocked:insufficient-e2e Verification does not cover the change merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. and removed merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. merge:blocked:insufficient-e2e Verification does not cover the change merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head labels Oct 25, 2026
@iscekic

iscekic commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Runnable proof of the language picker on both platforms, from the login screen with no account signed in.

Android emulator, dev client built from this branch:

  1. After tapping the Arabic row the picker keeps it marked as the selected language.
  2. Done relaunches the app once onto the login screen in Arabic, laid out right to left.

iOS simulator, dev client built from this branch:
3. Same tap on the Arabic row, with the row marked selected (its accessibility value went from "radio button, unchecked" to "radio button, checked").
4. After Done, the login screen in Arabic with the right-to-left layout.

A cold start of each app comes back on the same Arabic screen without another relaunch.

Language picker with the Arabic row marked selected

Login screen in Arabic after the single relaunch

iOS language picker with the Arabic row marked selected

iOS login screen in Arabic after the relaunch

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:ios Finding surfaced by the iOS explorer merge:bot Bot-owned review; merge when ready once readiness passes and a review approves.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants