Skip to content

fix(mobile): shell-own login keyboard pad, un-nest status fades - #4850

Merged
iscekic merged 7 commits into
mainfrom
login-ui-d051
Jul 29, 2026
Merged

fix(mobile): shell-own login keyboard pad, un-nest status fades#4850
iscekic merged 7 commits into
mainfrom
login-ui-d051

Conversation

@iscekic

@iscekic iscekic commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

What — two login-screen keyboard defects and one intermittent-render mitigation in apps/mobile, confined to login-screen.tsx and login/email-otp-form.tsx:

  1. iOS OTP screen double-compensated the keyboard. The root KeyboardAvoidingView behavior="padding" and the OTP form's local keyboardHeight + 16 spacer stacked, pushing the form 194–376pt above the keyboard and clipping the logo under the Dynamic Island. Fix: the OTP form's spacer machinery is deleted outright; the iOS KAV stays as the single iOS compensation.
  2. Android had zero working keyboard compensation. On API 35+ the window never resizes for the IME (EDGE_TO_EDGE_ENFORCED), so the root KAV was inert; on a 720×1280 phone the email input and Send code were fully covered by the IME. Fix: the login shell consumes keyboardDidShow/keyboardDidHide itself (via the shared, already-tested reducer) and applies paddingBottom = endCoordinates.height + safe-area bottom inset on a wrapper View outside the ScrollView. This lands in the same commit as the spacer deletion so Android never has a zero-compensation window.
  3. Intermittent invisible login screen — repaired after the first mitigation failed its E2E gate. Reproduced once on iOS at baseline (logout→login remount parked the login branch invisibly for 60s+, recovering only on relaunch). The first mitigation (un-nesting the parent layout={LinearTransition} from the per-branch fades) did not hold: E2E round e1 measured 2/2 deterministic remount parking at washed-out ~40–50% alpha (pixel-measured, stable 3+ minutes, interactive underneath). The per-branch entering/exiting fades were the remaining interrupted-animation surface, so this PR now removes them entirely — the status branches render as plain Views and status swaps are instant. With no animation on these branches there is no animation state to park. Cost: the branch fade (200/150ms, decorative) is gone; the plan's "transitions stay visibly animated" goal is deliberately dropped — a login screen that can park invisible is worse than an instant swap.

Why — users on small Android phones could not see the field they were typing into nor the Send code button; iOS OTP users lost the logo and had the form pushed up to half a screen too high.

How — one working keyboard compensation per platform, owned by the login shell; not two stacked, and no third mechanism. The shell shares kilo-chat's pure reducer (resolveAppAwareKeyboardPadding + resolveKeyboardPaddingEventsForPlatform); kilo-chat's reducer and AppAwareKeyboardPaddingView are byte-untouched. The pad formula is endCoordinates.height + useSafeAreaInsets().bottom = WindowInsets.ime().bottom exactly: RN 0.86 reports endCoordinates.height minus the nav bar, and screenY is not the IME top under adjustResize (vendored ReactRootView.java:962,974-977; math-locked on pixel9: 704px + 63px = 767px = the dumpsys IME frame). The pad applies only while the keyboard is up, so the resting layout is unchanged.

Also commits four E2E learnings from the repro runs to .kilo_workflow/learnings/.

Verification

Reproduced on the unmodified baseline (quantified; screenshots/dumps retained in run artifacts):

  • iOS (iPhone 17 Pro), OTP + number pad: gap between lowest control and keyboard top 194pt, growing to 376pt after scroll (healthy class 0–60); logo clipped; instruction text scrolled to y=−20. Spacer measured ≈307–322pt ≈ keyboardHeight+16 → double-count confirmed.
  • Android small phone (720×1280), idle + email IME: IME top y=682; email input and Send code [817–894] fully covered; taps on the covered button landed on the IME and typed into the field; drags dismissed the IME instead of scrolling.
  • r0b mechanism lock: API-35 window frames byte-identical with/without the IME on both devices (the OS never resizes); the OTP screen's 373px shift on Android was the in-content spacer growing the form by 746px and re-centering by half — not an OS resize.

Post-fix device verification (three verifier rounds on the final code; evidence in run artifacts):

iOS keyboard matrix (e1, PASS):

  • idle-main + keyboard: gap 128.3pt, controls visible/tappable — PASS
  • OTP + number pad: gap 121.0pt (spec 90–150pt; defect was 194–376pt); logo/instruction fully visible, zero clipping; real Maestro taps landed on Verify/Resend/Back (Verify → Home) — PASS
  • OTP scroll, pending-with-code, error state (device-auth 500 ×2): PASS
  • Dynamic Type XXXL, OTP + pad: gap 112.3pt, no occlusion — PASS (pre-existing label desync persists: RN labels do not scale with Dynamic Type at all — documented, not a fix target)
  • Post-repair spot check (e2): OTP gap 92.3pt, no clipping — PASS

Android matrix (e2, all PASS — bounds are uiautomator vs dumpsys IME frame):

  • idle-main + email IME, pixel9: IME top 1541; input b975, Send code b1103, More b1232 — all fully above; Send-code tap landed → OTP screen
  • idle-main + email IME, small phone (720×1280): IME top 682; input b450, Send code b549 fully above + tap landed; More fully above (centre 608.5) — baseline defect (both fully covered) fixed
  • OTP + number pad, both devices: all controls above the pad (pixel9 gap 373px ≤ 385px baseline class); real Verify taps landed on both
  • pending-with-code, both devices: renders correctly — PASS
  • resting, keyboard hidden, both devices: byte-exact parity with the plan baseline (small phone: logo top 288, Send code [42,817][678,894], More 915–992)

Transparency recurrence loops (e2 + e3, repaired build, PASS):

  • 10/10 logout→login remounts FULL_ALPHA (pixel-probed against e1's parked reference) — PASS
  • 3× status-flip with real device-auth codes: 7/7 branch renders FULL_ALPHA (3 pending + 3 idle + 1 remount) — PASS
  • cold-launch series: full alpha at first poll — PASS
  • e1's failure mode (2/2 remounts parked at ~40–50% alpha for 3+ minutes) never recurred

Documented deviations (accepted, not failures):

  • Small-phone logo clip with the IME up measured 57px (plan predicted ~11px from static centering): RN's focus auto-scroll moves 121px inside the 150px scroll range to reveal the tapped field, pushing the logo past the top — scroll-recoverable decoration; every functional criterion exceeds spec (input/Send code clear by 232/133px; "More sign-in options" has zero covered sliver, better than the accepted trade-off; resting layout byte-exact).
  • e1's Android matrix was environment-blocked (global white-render wedge) and the status-flip pending cell was blocked by stuck per-IP device-auth rows (reconciled via the product's real poll endpoint — not a mock; learning committed). Both cells were rerun and passed in e2/e3.

Checks in apps/mobile: pnpm format, pnpm typecheck, pnpm lint (0/0), pnpm check:unused, pnpm test (277 files, 2346 tests) — all green.

  • E2E verification matrix passed on iOS + Android (both devices) — rounds e1/e2/e3, verdicts and bounds above

Visual Changes

Before After
iOS OTP + number pad: logo clipped under the Dynamic Island; lowest control 194–376pt above the keyboard Logo fully visible; gap 121.0pt (e1) / 92.3pt (post-repair spot check) between the lowest control and the keyboard top; all taps land
Android small phone, email IME up: email input and Send code fully covered Input (b450) and Send code (b549) fully above the IME (top 682) and tappable; "More sign-in options" fully clear (centre 608.5); logo clip with IME up 57px, scroll-recoverable (documented deviation); resting layout byte-exact vs baseline

Reviewer Notes

  • The keyboard-move commit (spacer deletion + iOS-only KAV + Android shell pad) is deliberately ONE commit: splitting it leaves Android OTP with zero working compensation in between.
  • app-aware-keyboard-padding-state.ts and kilo-chat's AppAwareKeyboardPaddingView are intentionally untouched: the view pads by raw endCoordinates.height on both platforms, which under-clears by the nav inset on Android and would re-create the double-count on iOS. Sharing only the pure reducer is the deliberate amount of reuse.
  • The transparency repair is evidence-driven: the first mitigation failed its own gate (2/2 deterministic recurrence in E2E e1), so the decorative branch fades are removed outright rather than replaced with a third animation mechanism. The e1 pixel numbers are in Verification.
  • Pre-existing quirk, documented not fixed: on iOS Dynamic Type XXXL the OTP labels' accessibility bounds desync from their glyphs once the keyboard appears (clipped "Verify co"-style labels, persisting after dismiss). Reproduced on the unmodified baseline; unrelated to the double compensation.
  • Android pending state did not show the "Your sign-in code" dev display within 12s in repro (iOS does) — observation only, out of scope.
  • e1 secondary observation (documented, not a defect): the device-auth error banner remains visible when the user continues into the OTP form from the error branch — that is the designed error state (error text + sign-in form render together per the E2E matrix's error row).
  • No new dependencies, no auth/backend changes, no apps/mobile/e2e/ changes, no new test files (the change deletes UI wiring and consumes an already-tested pure reducer).

@iscekic iscekic self-assigned this Jul 29, 2026
@kilo-code-bot

kilo-code-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

This round only adds/updates .kilo_workflow/learnings/*.md process notes (device-auth stuck pending rows, iOS consent sheet, shared IP-limit update) with no changes to login-screen.tsx or email-otp-form.tsx; no code was reviewed as none changed since the last pass.

Files Reviewed (3 files)
  • .kilo_workflow/learnings/mobile-device-auth-ip-limit-shared-sections.md - documentation/process notes, out of scope for code review
  • .kilo_workflow/learnings/mobile-device-auth-stuck-pending-rows.md - documentation/process notes, out of scope for code review
  • .kilo_workflow/learnings/mobile-ios-device-auth-consent-sheet.md - documentation/process notes, out of scope for code review

Note: apps/mobile/src/components/login-screen.tsx and apps/mobile/src/components/login/email-otp-form.tsx are unchanged since the previous review (commit 5597326) and were not re-reviewed.

Previous Review Summaries (3 snapshots, latest commit 5597326)

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

Previous review (commit 5597326)

Status: No Issues Found | Recommendation: Merge

Executive Summary

This round only adds two new .kilo_workflow/learnings/*.md process notes (device-auth IP-limit sharing, OTP outbox race) with no changes to login-screen.tsx or email-otp-form.tsx; no code was reviewed as none changed since the last pass.

Files Reviewed (2 files)
  • .kilo_workflow/learnings/mobile-device-auth-ip-limit-shared-sections.md - documentation/process notes, out of scope for code review
  • .kilo_workflow/learnings/mobile-otp-outbox-race-parallel-phases.md - documentation/process notes, out of scope for code review

Note: apps/mobile/src/components/login-screen.tsx and apps/mobile/src/components/login/email-otp-form.tsx are unchanged since the previous review (commit af1e544) and were not re-reviewed.

Previous review (commit af1e544)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Reviewed the follow-up change that fully removes the Reanimated fade animations (previously just un-nested) from the login status branches and the earlier login-shell keyboard-pad ownership change; no high-confidence security, correctness, or logic issues found in the changed code.

Files Reviewed (3 files)
  • apps/mobile/src/components/login-screen.tsx - fade animations removed entirely (no leftover Animated/FadeIn/FadeOut imports or references); status branches now render as plain View
  • apps/mobile/src/components/login/email-otp-form.tsx - unchanged since last review
  • .kilo_workflow/learnings/*.md - documentation/process notes added this round, out of scope for code review

Previous review (commit a506987)

Status: No Issues Found | Recommendation: Merge

Executive Summary

Reviewed the login-shell keyboard-compensation ownership change and the animation un-nesting in apps/mobile; no high-confidence security, correctness, or logic issues found in the changed code.

Files Reviewed (2 files)
  • apps/mobile/src/components/login-screen.tsx
  • apps/mobile/src/components/login/email-otp-form.tsx

Note: .kilo_workflow/learnings/*.md files added by this PR are documentation/process notes, not application code, and were out of scope for this review.


Reviewed by claude-sonnet-5 · Input: 24 · Output: 4.9K · Cached: 473.2K

Review guidance: REVIEW.md from base branch main

@iscekic iscekic added the human-ready The PR is ready for human review. label Jul 29, 2026
@iscekic
iscekic merged commit a51d049 into main Jul 29, 2026
22 checks passed
@iscekic
iscekic deleted the login-ui-d051 branch July 29, 2026 10:29
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