Skip to content

fix(mobile): show a destructive confirm when discarding a draft on Android - #6512

Merged
iscekic merged 6 commits into
mainfrom
kwf/explorer-5-visual-defects-on-android-the-destructive-is-d-61f3b-0604
Sep 25, 2026
Merged

iscekic merged 6 commits into
mainfrom
kwf/explorer-5-visual-defects-on-android-the-destructive-is-d-61f3b-0604

Conversation

@iscekic

@iscekic iscekic commented Sep 21, 2026 •

Copy link
Copy Markdown
Collaborator

Changelog for users

  • Leaving a new coding task on Android with an unsaved draft now opens the app's own confirmation.
  • In it the destructive "Discard" is a red filled button beside a neutral outlined "Keep editing".
  • Both labels follow the active app language, and discarding still clears the draft and leaves the screen.

Changelog for maintainers

  • Finding 1 draft-discard-dialog-ur — accepted. Android's native AlertDialog paints every button with the theme accent — #6397 themed it to --primary — so Alert.alert's style: 'destructive' never reaches the screen. Both platforms now mount DestructiveConfirmDialog (one cross-platform implementation); the hook no longer calls Alert.alert.
  • Finding 2 home-ur-portrait / home-ur-night — rejected. The home CTA already inherits text-primary-foreground, and a disabled primary takes the muted bg-primary-disabled fill. Nothing in this diff touches the CTA.
  • Findings 3–5 model-search-deepseek, new-session-draft, repo-picker-ur — rejected. The composer shows its draft verbatim once with no undefined nodes, including behind the repository sheet; no product change was made.
  • useNewSessionDiscardGuard now returns discardConfirm, and the new-session body mounts it once. A caller that ignores the returned node loses the confirm on both platforms.
  • The hook stores the intercepted leave in a ref and closes the confirm before clearing. A failed clear stays on the screen and toasts discardFailed.
  • DestructiveConfirmDialog gained an optional cancelLabel. The new-session confirm passes common.keepEditing; no caller supplies one, so the default stays common.cancel.
  • Review the deferred navigation.dispatch first: that is where a double leave or a dropped action would surface. The mounted suite forks Platform.OS with a mutable mock and keeps both platforms in one file; use-new-session-discard-guard.platform.test.ts fails in node if the hook regains a platform branch or an Alert.alert call.
  • Two changes the original branch carried are dropped by the merge: the branded-splash containment fix (main fix(mobile): show branch control under label on new-session form #6447 landed the same change with its own rationale) and the theme-token opacity revert in permission-card, question-card, attachment-preview-strip, pr-review-overview-parts and the check-classes rule (that reverted fix(mobile): stop theme-token opacity modifiers from silently dropping #6344, which main keeps).
  • main also added two node source-parity tests that pin this guard to one shared Alert.alert call site (src/lib/alert-dialog-button-case.test.ts, src/lib/android-alert-title.test.ts). The cutover removes that call site, so those two clauses and their source reads are deleted; every assertion about the prebuild plugins stays, and their other Alert.alert callers remain.
  • main deleted DestructiveConfirmDialog and its sign-out caller in fix(mobile): theme Android alert dialogs with the app's surface colors #6397 (the Android alert is themed through withAndroidAlertDialogTheme instead); this PR reintroduces the dialog for the discard confirm only.

E2E proof

[e5] repo-sheet-composer-behind: with the repository sheet open, the prompt field behind the sheet still shows the typed sentence once — 'undefinedAdd' is absent — The scripted scene MISSed its first anchor (cold launch unpainted at 5 s, then e5's script-call cap was hit), so it was driven via the sim tools: the digest shows the composer EditText behind the open repository sheet holding 'Add a CONTRIBUTING.md section on running the local dev stack' exactly once with no 'undefined' node (e5-repo-sheet-open2.txt, screenshot e5-repo-sheet-open2.png).

[e5] repo-sheet-composer-behind: with the repository sheet open, the prompt field behind the sheet still shows the typed sentence once — 'undefinedAdd' is absent — prior/e5-repo-sheet-open2.png

[e1] platform:android discard-dialog-destructive-affordance: a dirty new-session leave opens the in-app confirm with a red destructive 'ضائع کریں' beside a neutral 'ترمیم جاری رکھیں' — the capture shows… — Android dirty-leave opened the in-app confirm, not the native alert: e1-dialog-digest.log carries android.view.View Discard draft?, android.widget.TextView Your prompt will be lost., android.widget.Button Keep editing and android.widget.Button Discard; the destructive-vs-neutral colour is the visual reviewer's call (e1-discard-confirm.png), Discard then navigated home and cleared the draft (e1-discard-accepted.log: android.widget.EditText What would you like to work on?), and with the clear forced to fail the screen stayed on New session with the draft (e1-discard-failed.log).

[e1] platform:android discard-dialog-destructive-affordance: a dirty new-session leave opens the in-app confirm with a red destructive 'ضائع کریں' beside a neutral 'ترمیم جاری رکھیں' — the capture shows… — prior/e1-discard-confirm.png

[e1] draft-restores-verbatim: a typed draft survives a forward tab leave and restores exactly once on return — no 'undefined' prefix, no duplicated truncated fragment — [android] A typed draft survives the leave/return and restores verbatim exactly once with no 'undefined': after Go back -> in-app 'Discard draft?' confirm -> 'Keep editing' the composer shows text="Add a CONTRIBUTING.md section on running the local dev stack" with showing-hint="false" and 0 occurrences of "undefined" (e1-restored.xml), the drive log records tapped "Keep editing" (jev-drive-1790033157272.log), and the scripted replay that also asserts a second Go back -> "/Discard/" cleanup reports "SCENE e1 OK" (e1-scene.log); replay saved at e1.replay.json; no behavioral UX-DEFECT observed.

[e1] draft-restores-verbatim: a typed draft survives a forward tab leave and restores exactly once on return — no 'undefined' prefix, no duplicated truncated fragment — e2e-mobile-app/e1-restored.png

[e4] draft-restores-verbatim: a typed draft survives a forward tab leave and restores exactly once on return — no 'undefined' prefix, no duplicated truncated fragment — android; a typed draft restores verbatim exactly once and never with an 'undefined' prefix: e4-restore-after-restart.txt has exactly one node text="Add a CONTRIBUTING.md section on running the local dev stack" (grep -c = 1) and 0 nodes containing 'undefined' after a cold app restart, corroborated by jev-drive-1790031074188.log ('tap e6 -> tapped "New coding task"', verified) and, after a forward leave to the repository picker and the intercepted Back -> Android confirm -> 'Keep editing', by e4-draft-restored.txt (jev-drive-1790030668115.log, jev-drive-1790030659952.log). The parked…

[e4] draft-restores-verbatim: a typed draft survives a forward tab leave and restores exactly once on return — no 'undefined' prefix, no duplicated truncated fragment — prior/e4-restore-after-restart.png

[e6] ux-check: android dark/Urdu dirty new-session — Back opens the discard confirm, Destructive vs Keep editing — android emulator-5606, app language Urdu (RTL) with Appearance Dark (content-desc="تاریک" ... checked="true", e6-appearance.txt); from signed-in-home a typed new-session draft plus Back produced the in-app Modal confirm — e6-scene.log digest shows android.view.ViewGroup مسودہ حذف کریں؟, آپ کا پرامپٹ ضائع ہو جائے گا۔ together with android.widget.Button ضائع کریں and android.widget.Button ترمیم جاری رکھیں (the cancelLabel=common.keepEditing prop only the new Android branch passes, per files/change.diff); capture e6.png for the visual reviewer — the red-fill vs neutral-outline glance…

[e6] ux-check: android dark/Urdu dirty new-session — Back opens the discard confirm, Destructive vs Keep editing — prior/e6.png

[e7] ux-check: keep-editing keeps the draft — After tapping Keep editing the digest shows android.view.View New session with android.widget.EditText Add a CONTRIBUTING.md section on running the l and no confirm node — the dialog closed, the composer kept the draft, and no navigation occurred (e7-keep-editing.png).

[e7] ux-check: keep-editing keeps the draft — prior/e7-keep-editing.png

ux-check: Empty composer (no draft, no unclaimed uploads): pressing Back leaves the screen with no confirm shown. — e10-composer-empty.png

E2E proof — log excerpts

[e1] draft-restores-verbatim -> pass :: [android] A typed draft survives the leave/return and restores verbatim exactly once with no 'undefined': after Go back -> in-app 'Discard draft?' confirm -> 'Keep editing' the composer shows text="Add a CONTRIBUTING.md section on running the local dev stack" with showing-hint="false" and 0 occurrences of "undefined" (e1-restored.xml), the drive log records tapped "Keep editing" (jev-drive-1790033157272.log), and the scripted replay that also asserts a second Go back -> "/Discard/" cleanup reports "SCENE e1 OK" (e1-scene.log); replay saved at e1.replay.json; no behavioral UX-DEFECT observed.
[e3] ux-check: light mode — session composer and new-session composer (incl. beh -> pass :: [android] Light mode (device-state night mode wanted no); no literal 'undefined' appears and the restored draft renders verbatim once — the new-session composer shows text="Add a CONTRIBUTING.md section on running the local dev stack" with 0 occurrences of "undefined" (e3-composer.xml), the same draft is present behind the open repository sheet ("Select repository", "Search repositories...") with 0 occurrences of "undefined" (e3-repo-sheet.xml), and the full replay (composer -> repo sheet -> launch -> draft restored -> model picker -> discard) reports "SCENE e3 OK" (e3-scene.log); the parked flow's post-picker step asserted "/Search models/", which this build does not render (the picker show
[e2] ux-check: Android dark mode Urdu (RTL) new-session with a typed draft — Bac -> pass :: android emulator-5604, app set to اردو (RTL) + Appearance=Dark, e2e account signed in; appium scene on files/replay-e2.json printed 'SCENE e2 OK' and the digest 'android.view.ViewGroup مسودہ حذف کریں؟, آپ کا پرامپٹ ضائع ہو جائے گا۔ tappable [55,1009][1025,1391]' with 'android.widget.Button ترمیم جاری رکھیں tappable [331,1230][638,1345]' and 'android.widget.Button ضائع کریں tappable [101,1230][304,1345]', so Back on the dirty new-session opened the in-app confirm with both choices present (e2-scene.log); keep-editing keeping the draft ('EditText "Add a CONTRIBUTING.md section on running the local dev stack"') and discard returning to Home were driven live (e2-live.log); destructive-fill vs ne
/home/igor_kilocode_ai/.local/share/kwf/sections/explorer-5-visual-defects-on-android-the-destructive-is-d-61f3b-0604/e2e-mobile-app/e1-scene.log
android.widget.Button Keep editing tappable [501,1230][765,1346]
android.widget.TextView Keep editing tappable [540,1264][725,1310]
android.widget.Button Discard tappable [792,1230][979,1346]
android.widget.TextView Discard tappable [828,1264][941,1310]
SCENE e1 OK
NOTE e1 assert '/What would you like to work on/' is not on screen word for word; jev read the outcome as shown (p=0.93)
android.widget.LinearLayout com.kilocode.kiloapp:id/action_bar_root tappable [0,0][1080,2400]
android.widget.FrameLayout android:id/content tappable [0,0][1080,2400]
android.widget.TextView 0 LIVE tappable [58,84][1024,121]
android.view.View Agents tappable [58,125][1024,231]
android.widget.Button See all tappable [877,263][1023,302]
android.widget.TextView SEE ALL tappable [877,263][1023,302]
android.widget.ScrollView centered-state tappable [0,342][1080,2400]
android.view.ViewGroup centered-state-content tappable [0,958][1080,1442]
android.widget.TextView Nothing running right now tappable [295,1123][786,1188]
android.widget.TextView Start a coding task from your phone. Your sessions will appear here. tappable [56,1197][1025,1289]
android.widget.Button New session tappable [379,1326][701,1442]
android.widget.TextView New session tappable [476,1360][664,1406]
android.view.View Home, tab, 1 of 3 tappable [0,2195][360,2337]
android.widget.TextView HOME tappable [13,2281][347,2320]
android.view.View Agents, tab, 2 of 3 tappable [360,2195][720,2337]
android.widget.TextView AGENTS tappable [373,2281][707,2320]
android.view.View Profile, tab, 3 of 3 tappable [720,2195][1080,2337]
android.widget.TextView PROFILE tappable [733,2281][1067,2320]
/home/igor_kilocode_ai/.local/share/kwf/sections/explorer-5-visual-defects-on-android-the-destructive-is-d-61f3b-0604/e2e-mobile-app/jev-drive-1790033157272.log
jev_drive goal="Open the new-session composer, type the draft text into the prompt field, tap the header \"Go back\" button, then in the \"Discard draft?\" confirm tap \"Keep editing\"." expect="The c
step 1: tap e8 draft (conf 1) -> tapped "New session"
step 2: type e3 draft (conf 0.74) -> typed draft
step 3: tap e1  (conf 0.57) -> tapped "Go back"
step 4: tap e4  (conf 0.99) -> tapped "Keep editing"
step 5: wait   (conf 0.74) -> waited 3 s (screen unchanged)
step 6: verified (conf 0.67)
jev_drive verified after 5 step(s); log /home/igor_kilocode_ai/.local/share/kwf/sections/explorer-5-visual-defects-on-android-the-destructive-is-d-61f3b-0604/e2e-mobile-app/jev-drive-1790033157272.log
/home/igor_kilocode_ai/.local/share/kwf/sections/explorer-5-visual-defects-on-android-the-destructive-is-d-61f3b-0604/e2e-mobile-app/e3-scene.log
android.widget.Button Done tappable [921,1237][1043,1338]
android.widget.TextView Done tappable [939,1259][1025,1315]
SCENE e3 OK
NOTE e3 assert '/What would you like to work on/' is not on screen word for word; jev read the outcome as shown (p=0.91)
android.widget.LinearLayout com.kilocode.kiloapp:id/action_bar_root tappable [0,0][1080,2400]
android.widget.FrameLayout android:id/content tappable [0,0][1080,2400]
android.view.View Kilo tappable [58,84][1022,189]
android.widget.TextView LIVE NOW tappable [36,217][537,254]
android.widget.Button See all tappable [555,216][1043,255]
android.widget.TextView SEE ALL tappable [555,216][1043,255]
android.widget.TextView Nothing running right now tappable [348,363][732,409]
android.widget.Button New coding task tappable [37,537][1043,653]
android.widget.TextView New coding task tappable [450,571][696,617]
android.widget.Button New task from a picture tappable [37,671][1043,787]
android.widget.TextView New task from a picture tappable [396,705][749,751]
android.widget.TextView EXPLORE tappable [36,822][1044,859]
android.widget.Button Code Reviewer, Automatic PR reviews tappable [37,878][1043,943]
android.widget.TextView Code Reviewer tappable [171,906][953,943]
android.view.View Home, tab, 1 of 3 tappable [0,2195][360,2337]
android.widget.TextView HOME tappable [13,2281][347,2320]
android.view.View Agents, tab, 2 of 3 tappable [360,2195][720,2337]
android.widget.TextView AGENTS tappable [373,2281][707,2320]
android.view.View Profile, tab, 3 of 3 tappable [720,2195][1080,2337]
android.widget.TextView PROFILE tappable [733,2281][1067,2320]
/home/igor_kilocode_ai/.local/share/kwf/sections/explorer-5-visual-defects-on-android-the-destructive-is-d-61f3b-0604/e2e-mobile-app/e2-scene.log
SCENE e2 OK
android.widget.FrameLayout android:id/content tappable [0,0][1080,2400]
android.view.ViewGroup مسودہ حذف کریں؟, آپ کا پرامپٹ ضائع ہو جائے گا۔ tappable [55,1009][1025,1391]
android.view.View مسودہ حذف کریں؟ tappable [100,1054][978,1110]
android.widget.TextView آپ کا پرامپٹ ضائع ہو جائے گا۔ tappable [100,1147][978,1193]
android.widget.Button ترمیم جاری رکھیں tappable [331,1230][638,1345]
android.widget.TextView ترمیم جاری رکھیں tappable [370,1264][598,1310]
android.widget.Button ضائع کریں tappable [101,1230][304,1345]
android.widget.TextView ضائع کریں tappable [137,1264][266,1310]
SCENE e2 OK
android.widget.FrameLayout android:id/content tappable [0,0][1080,2400]
android.view.ViewGroup مسودہ حذف کریں؟, آپ کا پرامپٹ ضائع ہو جائے گا۔ tappable [55,1009][1025,1391]
android.view.View مسودہ حذف کریں؟ tappable [100,1054][978,1110]
android.widget.TextView آپ کا پرامپٹ ضائع ہو جائے گا۔ tappable [100,1147][978,1193]
android.widget.Button ترمیم جاری رکھیں tappable [331,1230][638,1345]
android.widget.TextView ترمیم جاری رکھیں tappable [370,1264][598,1310]
android.widget.Button ضائع کریں tappable [101,1230][304,1345]
android.widget.TextView ضائع کریں tappable [137,1264][266,1310]
/home/igor_kilocode_ai/.local/share/kwf/sections/explorer-5-visual-defects-on-android-the-destructive-is-d-61f3b-0604/e2e-mobile-app/e2-live.log
# e2 live run transcript (android emulator-5604) — hand-driven steps after the scripted scene
# Setup before the scene: app language set to اردو (Urdu, RTL) via Profile -> Preferences -> Account -> Language;
# Appearance set to Dark via Profile -> Preferences -> Appearance. Scene: appium.sh emulator-5604 script files/replay-e2.json (SCENE e2 OK).
## composer after tapping "ترمیم جاری رکھیں" (Keep editing) — draft preserved
Button "واپس جائیں"
android.view.View "نیا سیشن"
EditText "Add a CONTRIBUTING.md section on running the local dev stack"
Button "سیشن شروع کریں" [disabled]
## Back pressed again — the discard confirm re-opens
android.view.ViewGroup "مسودہ حذف کریں؟, آپ کا پرامپٹ ضائع ہو جائے گا۔"
android.view.View "مسودہ حذف کریں؟"
TextView "آپ کا پرامپٹ ضائع ہو جائے گا۔"
Button "ترمیم جاری رکھیں"
TextView "ترمیم جاری رکھیں"
Button "ضائع کریں"
TextView "ضائع کریں"
## after tapping "ضائع کریں" (Discard) — back on Home, empty draft
Button "کوڈنگ کا نیا کام"
TextView "اس وقت کچھ بھی نہیں چل رہا"
android.view.View "ہوم، ٹیب، 3 میں سے 1" [checked]
Owner request

Surface: mobile-app

Explorer finding: 5 visual defects on android: The destructive "ضائع کریں" is drawn in ; The primary CTA "کوڈنگ کا نیا کام" is a ; The session composer renders the literal (+2 more)

The user-agent explorer found these 5 while using the app like a user. They are one kind of defect on one platform, so they are ONE item: the pull request must fix and prove every one of them.
One must-run scenario per finding: the plan needs 5 musts, one for each finding below.
The explorer never edits product code.

Evidence (from the device runs):

--- Finding 1 of 5: draft-discard-dialog-ur: The destructive "ضائع کریں" is drawn in the same teal and same weight as the safe "ترمیم جاری رکھیں", so the discarding choice carries no destructive affordance.
Flow: draft-discard-dialog-ur
Found on revision: f2181ae
Repro:

  1. set this state first: uimode night; rotate landscape; battery 22 discharging; relaunch; credits 20; the device in dark mode
  2. open the app on emulator-5602
  3. reach draft-discard-dialog-ur
  4. the capture shows the defect named below
    Observed: The destructive "ضائع کریں" is drawn in the same teal and same weight as the safe "ترمیم جاری رکھیں", so the discarding choice carries no destructive affordance.
    Expected: the screen renders without this defect

--- Finding 2 of 5: home-ur-portrait: The primary CTA "کوڈنگ کا نیا کام" is a dark olive pill with a dark label, so its text barely separates from its own fill.
Flow: home-ur-portrait
Found on revision: f2181ae
Repro:

  1. set this state first: uimode night; rotate landscape; battery 22 discharging; relaunch; credits 20; the device in dark mode
  2. open the app on emulator-5602
  3. reach home-ur-portrait, home-ur-night
  4. the capture shows the defect named below
    Observed: The primary CTA "کوڈنگ کا نیا کام" is a dark olive pill with a dark label, so its text barely separates from its own fill.
    Expected: the screen renders without this defect

--- Finding 3 of 5: model-search-deepseek: The session composer renders the literal string 'undefined' as its draft text instead of a prompt or placeholder.
Flow: model-search-deepseek
Found on revision: f2181ae
Repro:

  1. set this state first: uimode day; battery 15 discharging; reviews 2; credits 14; rotate reverse-portrait; relaunch; rotate portrait; the device in light mode
  2. open the app on emulator-5602
  3. reach model-search-deepseek
  4. the capture shows the defect named below
    Observed: The session composer renders the literal string 'undefined' as its draft text instead of a prompt or placeholder.
    Expected: the screen renders without this defect

--- Finding 4 of 5: new-session-draft: The prompt composer shows the developer word 'undefined' prepended to a duplicated, mid-word-truncated fragment ('Add a CONTRIBUTING.m') before the real task sentence.
Flow: new-session-draft
Found on revision: f2181ae
Repro:

  1. set this state first: uimode day; battery 15 discharging; reviews 2; credits 14; rotate reverse-portrait; relaunch; rotate portrait; the device in light mode
  2. open the app on emulator-5602
  3. reach new-session-draft
  4. the capture shows the defect named below
    Observed: The prompt composer shows the developer word 'undefined' prepended to a duplicated, mid-word-truncated fragment ('Add a CONTRIBUTING.m') before the real task sentence.
    Expected: the screen renders without this defect

--- Finding 5 of 5: repo-picker-ur: The new-session prompt field behind the sheet renders a broken, repeated string beginning 'undefinedAdd a CONTRIBUTING.m' before 'Add a CONTRIBUTING.md section on running the local dev stack, and fix broken README links'.
Flow: repo-picker-ur
Found on revision: f2181ae
Repro:

  1. set this state first: uimode night; rotate landscape; battery 22 discharging; relaunch; credits 20; the device in dark mode
  2. open the app on emulator-5602
  3. reach repo-picker-ur
  4. the capture shows the defect named below
    Observed: The new-session prompt field behind the sheet renders a broken, repeated string beginning 'undefinedAdd a CONTRIBUTING.m' before 'Add a CONTRIBUTING.md section on running the local dev stack, and fix broken README links'.
    Expected: the screen renders without this defect

[e12] ux-check: light mode — session composer and new-session composer (incl. — Light mode (content-desc="Light" ... checked="true", e12-appearance.txt); e12-scene.log OK with 0 occurrences of undefined and final digest android.widget.EditText Add a CONTRIBUTING.md section on running the local dev stack once after a cold relaunch and a model-search round trip (replay banks the launch step); e12-repo-sheet.txt shows the same sentence verbatim once behind the open repository sheet (text="Select repository") with 0 undefined; e12-session-composer.txt shows the agent-chat composer text="Message" hint="Message" showing-hint="true" with 0 undefined — findings 3-5…

[e12] ux-check: light mode — session composer and new-session composer (incl. — e12-appearance.png

[e8] ux-check (android) — In-app Android confirm (no AlertController markers) shows 'Discard draft?' / 'Your prompt will be lost.' / 'Discard' / 'Keep editing'; tapping 'Discard' left the composer (e8-after-discard.txt shows 'Nothing running right now', 0 draft matches) and re-entry shows the placeholder 'What would you like to work on?' with 0 draft matches (e8-reentry.txt); no UX-DEFECT observed.

[e8] ux-check (android) — e8-after-discard.png

[e11] ux-check: Dark-mode home CTA label — With Appearance set to Dark (RadioButton 'Dark' [checked]) the Home digest shows the enabled Button 'New coding task' with its label once and no disabled state; the pill/label contrast is the visual reviewer's call on e11-dark-home.png (digest e11-dark-home.txt).

[e11] ux-check: Dark-mode home CTA label — e11-dark-home.png

[e9] ux-check: failing draft clear — android: e9-toast-tap.log line 7 shows the composer still mounted with 'android.widget.EditText e9 draft text' and line 38 shows 'android.widget.TextView Could not discard the draft. Please try again.' after tapping Discard, so the confirm closed, the screen stayed and the toast rendered; the failing clear was created with a temporary staged edit in new-session-screen-body.tsx (reverted, worktree clean), and out/e9-toast.png is the capture for the visual reviewer. No UX-DEFECT on the visited composer/confirm/model-picker screens.

[e9] ux-check: failing draft clear — e9-toast.png

[e2] home-cta-legible (android) — Re-ran the edited parked e2 list after its screen MISS: 'SCENE e2 OK'; digest shows the enabled home primary CTA Button/TextView 'New coding task' with 'Nothing running right now' (English-locale device, so not 'کوڈنگ کا نیا کام'); fill/label contrast is the visual reviewer's call on e2.png, not claimed here.

[e2] home-cta-legible (android) — e2.png

[e3] ux-check: light mode — session composer and new-session composer (incl. — [android] Light mode (device-state night mode wanted no); no literal 'undefined' appears and the restored draft renders verbatim once — the new-session composer shows text="Add a CONTRIBUTING.md section on running the local dev stack" with 0 occurrences of "undefined" (e3-composer.xml), the same draft is present behind the open repository sheet ("Select repository", "Search repositories...") with 0 occurrences of "undefined" (e3-repo-sheet.xml), and the full replay (composer -> repo sheet -> launch -> draft restored -> model picker -> discard) reports "SCENE e3 OK" (e3-scene.log); the parked…

[e3] ux-check: light mode — session composer and new-session composer (incl. — e3-composer.png

Follow-ups (not changed here)

  • not proved live: home-cta-legible: the home primary CTA 'کوڈنگ کا نیا کام' renders with a contrasting label on its brand fill (muted fill only when disabled) — capture proves no dark-on-dark pill (no capture cited it)
  • not proved live: model-search-composer-no-undefined: after searching models, the session composer shows its placeholder — the literal 'undefined' is absent (no capture cited it)
  • not proved live: ux-check: Android, dark mode, Urdu (RTL), new-session with a typed draft: pressing Back opens the discard confirm, and 'Discard' is a filled red/destructive button while 'Keep editing' is a neutral outline button — the two are distinguishable at a glance (finding 1 / request: the destructive choice carries a destructive affordance). (no capture cited it)
  • not proved live: ux-check: Dark-mode home: the 'New coding task' primary CTA label is clearly legible against its pill fill (finding 2, refuted as fixed in base). (no capture cited it)
  • not proved live: ux-check: In that confirm, tap 'Keep editing': the dialog closes, the new-session composer is shown with the draft text still present, and no navigation occurred. (no capture cited it)
  • not proved live: ux-check: Light mode, session composer and new-session composer (including behind the repository sheet): no literal 'undefined' text appears, and a restored draft renders verbatim exactly once (findings 3-5, refuted). (no capture cited it)
  • not proved live: ux-check: Reopen the confirm and tap 'Discard': the composer empties and the screen leaves; re-entering new session shows no restored draft. (no capture cited it)
  • not proved live: ux-check: With a failing draft clear, tapping 'Discard' closes the confirm, the screen stays with the draft intact, and the toast 'Could not discard the draft. Please try again.' is visible. (no capture cited it)

Open findings (not fixed here)

  • not proved live: draft-discard-dialog-ur.png is no longer on the host that took it, so no publish can carry it
  • not proved live: home-ur-night.png is no longer on the host that took it, so no publish can carry it
  • not proved live: home-ur-portrait.png is no longer on the host that took it, so no publish can carry it
  • not proved live: model-search-deepseek.png is no longer on the host that took it, so no publish can carry it
  • not proved live: new-session-draft.png is no longer on the host that took it, so no publish can carry it
  • not proved live: repo-picker-ur.png is no longer on the host that took it, so no publish can carry it

e1-after-discard

E2E proof — log excerpts

[e1] new-session discard confirm (android) -> pass :: android: Go back opened the app's own confirm (e1-scene.log has 'SCENE e1 OK' and 'android.view.ViewGroup Discard draft?, Your prompt will be lost.', 'android.widget.Button Keep editing', 'android.widget.Button Discard' with no AlertDialog node), Keep editing restored the draft verbatim and once (e1b-scene.log 'SCENE e1b OK' + 'android.widget.EditText Add a CONTRIBUTING.md section on running the local dev stack'), and leaving again then Discard left the screen (e1-after-discard.xml 'text="Nothing running right now"') with the draft cleared (e1c-scene.log 'SCENE e1c OK' + 'android.widget.EditText What would you like to work on?').
[e2] uploads-specific discard confirm (android) -> pass :: android: an empty prompt with the unclaimed attach-test.png opened the same in-app confirm carrying only the uploads message (e2-dialog.xml 'text="Your prompt and any unclaimed uploads will be deleted."', 'content-desc="Keep editing"', 'content-desc="Discard"', and 0 occurrences of 'Your prompt will be lost.'), Discard left the screen (e2-after-discard.xml 'text="Nothing running right now"') and released the upload, which the next new session showed absent (e1-scene.log 'SCENE e1 OK').
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-platform-64de/e2e-mobile-app/e1-scene.log
SCENE e1 OK
android.widget.FrameLayout android:id/content tappable [0,0][1080,2400]
android.view.ViewGroup Discard draft?, Your prompt will be lost. tappable [55,1009][1025,1391]
android.view.View Discard draft? tappable [100,1054][978,1110]
android.widget.TextView Your prompt will be lost. tappable [100,1147][978,1193]
android.widget.Button Keep editing tappable [501,1230][765,1346]
android.widget.TextView Keep editing tappable [540,1264][725,1310]
android.widget.Button Discard tappable [792,1230][979,1346]
android.widget.TextView Discard tappable [828,1264][941,1310]
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-platform-64de/e2e-mobile-app/e1b-scene.log
android.widget.TextView Run on tappable [37,792][1045,838]
android.widget.Button Run on: Cloud Agent tappable [37,856][909,972]
android.widget.TextView Cloud Agent tappable [67,886][844,942]
android.widget.Button Refresh tappable [928,856][1043,972]
android.widget.TextView Run kilo remote on your computer, or /remote in a running CLI session, to control a local kilo process. tappable [36,991][1044,1065]
android.widget.TextView Repository tappable [37,1111][1045,1157]
android.widget.Button Repository: Select repository tappable [37,1175][1043,1291]
android.widget.TextView Select repository tappable [67,1205][978,1261]
android.widget.Button Connect GitLab tappable [76,1358][1004,1404]
android.widget.TextView Connect GitLab tappable [76,1358][944,1404]
android.widget.TextView Connect GitLab in your browser, then return here to pick a repository. tappable [76,1432][1004,1524]
android.widget.Button Open GitLab tappable [76,1552][870,1667]
android.widget.TextView Open GitLab tappable [175,1586][768,1632]
android.widget.Button Refresh repositories tappable [888,1552][1004,1667]
android.widget.TextView Changes tappable [37,1753][1045,1799]
android.view.View Changes tappable [37,1817][1043,1936]
android.widget.RadioButton Leave changes tappable [46,1826][540,1927]
android.widget.TextView Leave changes tappable [182,1853][403,1899]
android.widget.RadioButton Commit and push tappable [540,1826][1034,1927]
android.widget.TextView Commit and push tappable [656,1853][917,1899]
android.widget.TextView Environment tappable [37,1982][1045,2028]
android.widget.TextView Default environment tappable [37,2046][1045,2067]
android.widget.Button Start session [37,2122][1043,2237]
android.widget.TextView Start session tappable [442,2156][638,2202]
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-platform-64de/e2e-mobile-app/e1c-scene.log
android.widget.TextView Run on tappable [37,792][1045,838]
android.widget.Button Run on: Cloud Agent tappable [37,856][909,972]
android.widget.TextView Cloud Agent tappable [67,886][844,942]
android.widget.Button Refresh tappable [928,856][1043,972]
android.widget.TextView Run kilo remote on your computer, or /remote in a running CLI session, to control a local kilo process. tappable [36,991][1044,1065]
android.widget.TextView Repository tappable [37,1111][1045,1157]
android.widget.Button Repository: Select repository tappable [37,1175][1043,1291]
android.widget.TextView Select repository tappable [67,1205][978,1261]
android.widget.Button Connect GitLab tappable [76,1358][1004,1404]
android.widget.TextView Connect GitLab tappable [76,1358][944,1404]
android.widget.TextView Connect GitLab in your browser, then return here to pick a repository. tappable [76,1432][1004,1524]
android.widget.Button Open GitLab tappable [76,1552][870,1667]
android.widget.TextView Open GitLab tappable [175,1586][768,1632]
android.widget.Button Refresh repositories tappable [888,1552][1004,1667]
android.widget.TextView Changes tappable [37,1753][1045,1799]
android.view.View Changes tappable [37,1817][1043,1936]
android.widget.RadioButton Leave changes tappable [46,1826][540,1927]
android.widget.TextView Leave changes tappable [182,1853][403,1899]
android.widget.RadioButton Commit and push tappable [540,1826][1034,1927]
android.widget.TextView Commit and push tappable [656,1853][917,1899]
android.widget.TextView Environment tappable [37,1982][1045,2028]
android.widget.TextView Default environment tappable [37,2046][1045,2067]
android.widget.Button Start session [37,2122][1043,2237]
android.widget.TextView Start session tappable [442,2156][638,2202]

@kilo-code-bot

kilo-code-bot Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

The cross-platform in-app discard confirm is wired correctly: the intercepted leave is captured in a ref before the confirm closes, the draft clear is awaited before the captured action is replayed, a failed clear stays and toasts, and no listeners, timers, subscriptions, or effects are added that could leak. The iOS cutover from the native alert is deliberate and is held to the same one-implementation contract by the new node source-parity test.

Files Reviewed (8 files)
  • apps/mobile/src/components/agents/use-new-session-discard-guard.ts - one cross-platform confirm; holds the leave in pendingActionRef, closes before awaiting onDiscard, dispatches the captured action only after it resolves, no per-platform branch or native-alert fallback
  • apps/mobile/src/components/destructive-confirm-dialog.tsx - reintroduces the in-app dialog; optional cancelLabel with a common.cancel fallback; destructive fill vs neutral outline
  • apps/mobile/src/components/agents/new-session-screen-body.tsx - destructures and mounts discardConfirm once at the screen root; no other callers to migrate
  • apps/mobile/src/app/(app)/agent-chat/__tests__/use-new-session-discard-guard.mounted.test.tsx - drives both platform values, asserts no native alert, confirm labels, destructive fill, discard-before-dispatch ordering, failure toast, and cancel
  • apps/mobile/src/components/agents/use-new-session-discard-guard.platform.test.ts - node source-parity guard: no platform branch, no react-native import, no Alert.alert call
  • apps/mobile/src/components/destructive-confirm-dialog.mounted.test.tsx - covers the new cancelLabel and the unchanged default
  • apps/mobile/src/lib/alert-dialog-button-case.test.ts - drops the now-invalid native-alert clause and its source read; plugin assertions intact
  • apps/mobile/src/lib/android-alert-title.test.ts - drops the now-invalid native-alert clause; remaining imports still used
Previous Review Summary (commit 0b7b7b1)

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

Previous review (commit 0b7b7b1)

Status: No Issues Found | Recommendation: Merge

The Android-only in-app destructive confirm is wired correctly: the intercepted leave is captured before the dialog closes, the draft clear is awaited before the action is replayed, a failed clear toasts and stays, and no listeners, timers, or subscriptions are added that could leak.

Files Reviewed (6 files)
  • apps/mobile/plugins/branded-splash.test.ts - relaxed the splash color assertion to arrayContaining so introspection-merged colors from a prebuilt android/ tree do not fail it; the asserted entry is still pinned
  • apps/mobile/src/app/(app)/agent-chat/__tests__/use-new-session-discard-guard.mounted.test.tsx - iOS and Android paths exercised with a mutable Platform.OS mock; asserts alert suppression, confirm labels, discard-before-dispatch ordering, failure toast, and cancel
  • apps/mobile/src/components/agents/new-session-screen-body.tsx - mounts the returned discardConfirm once at the screen root; Modal overlay keeps the composer layout intact
  • apps/mobile/src/components/agents/use-new-session-discard-guard.ts - Android fork stores the captured action in a ref, closes the confirm synchronously before clearing (no double clear/dispatch), replays the action only after onDiscard resolves, and reuses the existing i18n keys
  • apps/mobile/src/components/destructive-confirm-dialog.mounted.test.tsx - covers the new optional cancelLabel and the unchanged default
  • apps/mobile/src/components/destructive-confirm-dialog.tsx - optional cancelLabel with common.cancel fallback; sign-out caller unaffected

Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

@iscekic
iscekic marked this pull request as draft September 22, 2026 00:11
@iscekic

iscekic commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Cross-platform fix. Pushed 98eab15 (kwf kwf-fix-platform-64de).

What changed:

  • Finding 1 draft-discard-dialog-ur — accepted. Android's native AlertDialog paints every button with the theme accent, so Alert.alert's style: 'destructive' never reached the screen. Both platforms now mount DestructiveConfirmDialog.
  • Finding 2 home-ur-portrait / home-ur-night — rejected. The home CTA already inherits text-primary-foreground, and a disabled primary takes the muted bg-primary-disabled fill. Nothing in this diff touches the CTA.
  • Findings 3–5 model-search-deepseek, new-session-draft, repo-picker-ur — rejected. The composer shows its draft verbatim once with no undefined nodes, including behind the repository sheet; no product change was made.
  • if (Platform.OS === 'android') { — accepted; removed. The intercepted leave now holds the action and opens the in-app confirm on every platform, so iOS and Android share one path.
  • Platform.OS === 'android' && confirmVisible — accepted; removed. discardConfirm is no longer platform-gated, so the dialog mounts on both.
  • A code comment names the capability gap behind the shared dialog: Android's native alert paints every button with the theme accent, so the destructive style never reaches the screen.
  • The hook drops its react-native import, the Alert.alert branch, and its styled buttons. Discard still clears before the deferred navigation.dispatch; a failed clear keeps the screen and toasts; a dismiss keeps the draft.
  • Tests: a node-only source-parity test fails if the hook regains a platform branch or an Alert.alert call; the mounted suite drives both platform values. Review the shared leave path and the deferred dispatch first.

@iscekic
iscekic force-pushed the kwf/explorer-5-visual-defects-on-android-the-destructive-is-d-61f3b-0604 branch from 98eab15 to a32ee0c Compare September 22, 2026 01:19
…-defects-on-android-the-destructive-is-d-61f3b-0604

# Conflicts:
#	apps/mobile/plugins/branded-splash.test.ts
#	apps/mobile/src/components/destructive-confirm-dialog.mounted.test.tsx
#	apps/mobile/src/components/destructive-confirm-dialog.tsx
…merge

The merged main removed the in-app dialog's sign-out caller (#6397 reverted
that surface to the themed native alert), so the dialog and guard comments
now describe the one remaining caller instead of a stale sign-out reference.
…r has

main added two source-parity guards that hold
use-new-session-discard-guard.ts to one shared Alert.alert call site: that
call site is gone, because the guard now mounts the in-app
DestructiveConfirmDialog so Android shows the destructive red fill. Delete
those two clauses (and the now-unused source reads) and keep every assertion
about the plugins themselves, whose other Alert.alert callers remain.
…onfirm

The button case now applies to the alerts still rendered natively.
@iscekic iscekic added the human-ready The PR is ready for human review. label Sep 25, 2026
@iscekic
iscekic marked this pull request as ready for review September 25, 2026 13:49
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.

2 participants