Skip to content

fix(mobile): enlarge icon-only touch targets to 44pt - #6101

Merged
iscekic merged 1 commit into
mainfrom
kwf/janitor-2026-09-12-experience-and-design-14e1
Sep 14, 2026
Merged

fix(mobile): enlarge icon-only touch targets to 44pt#6101
iscekic merged 1 commit into
mainfrom
kwf/janitor-2026-09-12-experience-and-design-14e1

Conversation

@iscekic

@iscekic iscekic commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

kwf trial run. This PR came from a workflow trial request, not from a tracked work item. Review it as you would any other PR; the label kwf-trial marks where it came from.

Changelog for users

  • The device-sessions sign-out button now accepts taps across a 44x44pt area, up from roughly 32x32pt.
  • The image viewer's close and share buttons now accept taps across 44x44pt areas, up from 40x40pt.
  • The sign-out, close, and share glyphs keep their previous sizes and colors.
  • Device sessions rows no longer change height when loading skeletons are replaced by loaded rows.

Changelog for maintainers

  • device-sessions-screen.tsx: the sign-out Pressable uses min-h-[44px] min-w-[44px] shrink-0 items-center justify-center; hitSlop={8} is unchanged, so the effective area is larger than the visible 44pt box.
  • image-viewer-modal.tsx: the close and share Pressables swap h-10 w-10 for min-h-[44px] min-w-[44px] shrink-0; neither has hit slop, so the box itself must provide the 44pt minimum.
  • The implementation uses [44px] rather than the requested min-h-11 min-w-11: the native rem is 14pt, so min-h-11 resolves to 38.5pt, under the app's minimum. The px form matches button.tsx's icon size.
  • The loading placeholder now wraps its Skeleton in a 44x44 View, so the skeleton row and the loaded row have the same height.
  • Review hint: the main layout risk is the image-viewer header; keeping shrink-0 on both new boxes prevents the controls from reflowing when the title grows.
  • Tests: image-viewer-modal.mounted.test.tsx adds a close/share 44px assertion; device-sessions-screen.mounted.test.tsx asserts the 44px class names, hitSlop 8, and three reserved skeleton boxes.
  • trusted-hosts-screen.tsx still uses min-h-11 min-w-11, which resolves below 44pt natively; it is left unchanged as out of scope.

E2E proof

Recording of the verified flow (waits trimmed)

e1-devsess.mp4.trim.mp4

Recording of the verified flow (waits trimmed)

e1-viewer.mp4.trim.mp4

Recording of the verified flow (waits trimmed)

e1-load.mp4.trim.mp4

Recording of the verified flow (waits trimmed)

p3-share.mp4.trim.mp4

Recording of the verified flow (waits trimmed)

p2-run.mp4.trim.mp4

Recording of the verified flow (waits trimmed)

p3-share-flow.mp4.trim.mp4

E2E proof — log excerpts

[e1] layout stability: device-sessions skeleton -> loaded rows; image-viewer hea -> pass :: Android emulator-5554: 'skeleton: row 1 box [55,315][1025,486] w=970 h=171' / 'skeleton: row 2 box [55,513][1025,683] w=970 h=170' (centres y=400/598) versus loaded 'Sign out this device tappable [882,343][997,458]' / 'Sign out this session tappable [882,541][997,657]' (centres y=400/599) gives zero vertical jump, and e1-viewer.log shows 'Close demo tappable [37,142][152,257]' with Share hidden vs 'Share e2e-viewer.png tappable [928,142][1043,257]' with the same Close box [37,142][152,257] when Share is shown, both 115px=44pt.
[p4] ux-check: device-sessions outer-edge sign-out and inter-row gap -> pass :: Android emulator-5554: outer-edge taps at (889,400) and (889,599) opened 'Sign out this device? tappable [133,1065][947,1136]' and 'Sign out this session?' while the gap tap at (540,500) left 'Sign out this device tappable [882,343][997,458]' and 'Sign out this session tappable [882,541][997,657]' both present with no alert.
[p1] device sessions: outer edge of the 44pt sign-out area opens the confirmatio -> pass :: Android emulator-5554 (density 420, 44pt=115.5px). p1.log: 'Button Sign out this device tappable [882,343][997,458]' (115x115px = 44pt) and 'Button Sign out this session tappable [882,541][997,657]'; outer-edge tap (adb input tap 890 400 / 890 599, ~29px inside the box edge, outside the 42px glyph) opened 'Sign out this device?' and 'Sign out this session?' (both [133,1065][947,1136]); recording p1-devsess.mp4 + shots p1.png/p1-alert1.png/p1-alert2.png for the visual reviewer. Baseline (emulator-5556) before-state not re-run: per-run raw-drive cap reached (drive 6 refused); before-state from files/change.diff (hitSlop 8 + 16pt glyph ~32pt). No functional UX defect in the digest; visual audit
/home/igor_kilocode_ai/.local/share/kwf/sections/janitor-2026-09-12-experience-and-design-14e1/e2e-mobile-app/e1-devsess-layout.log
--- skeleton row boxes parsed from e1-skeleton2.xml (uiautomator dump at skeleton time) ---
skeleton: row 1 box [55,315][1025,486] w=970 h=171
skeleton: row 2 box [55,513][1025,683] w=970 h=170
skeleton: row 3 box [55,711][1025,881] w=970 h=170
skeleton: row 1 vertical centre y=400
skeleton: row 2 vertical centre y=598
skeleton: row 3 vertical centre y=796
--- loaded digest after recover+Retry (e1-devsess-loaded.digest) ---
hierarchy: /tmp/kilo-hierarchy.0V3Vuk
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.Button Go back tappable [0,149][101,250]
android.view.View Device sessions tappable [111,167][1044,232]
android.widget.TextView okhttp tappable [157,343][252,389]
android.widget.TextView THIS DEVICE tappable [288,347][446,384]
android.widget.TextView Signed in 9/12/2026 · Last seen 9/12/2026 tappable [157,393][855,430]
android.widget.Button Sign out this device tappable [882,343][997,458]
android.widget.TextView Web browser tappable [157,541][350,587]
android.widget.TextView Signed in 9/9/2026 · Last seen 9/10/2026 tappable [157,591][855,628]
android.widget.Button Sign out this session tappable [882,541][997,657]
android.widget.TextView Only Kilo app sign-ins appear here. Editor and CLI sign-ins are not listed. tappable [55,711][1025,748]
--- loaded row anchors (from the digest above) ---
loaded: row 1 Sign out this device box [882,343][997,458] vertical centre y=400
loaded: row 2 Sign out this session box [882,541][997,657] vertical centre y=599
/home/igor_kilocode_ai/.local/share/kwf/sections/janitor-2026-09-12-experience-and-design-14e1/e2e-mobile-app/e1-viewer.log
=== e1 image-viewer header: Share hidden vs shown (android emulator-5554) ===
--- hidden (image not loaded; only Close) digest e1-viewer-a.digest ---
hierarchy: /tmp/kilo-hierarchy.WRG40s
android.widget.FrameLayout android:id/content tappable [0,0][1080,2400]
android.widget.Button Close demo tappable [37,142][152,257]
--- shown (image loaded; Close + Share) digest e1-viewer-shown.digest ---
hierarchy: /tmp/kilo-hierarchy.dI4Aqy
android.widget.FrameLayout android:id/content tappable [0,0][1080,2400]
android.widget.Button Close e2e-viewer.png tappable [37,142][152,257]
android.widget.Button Share e2e-viewer.png tappable [928,142][1043,257]
--- header geometry: Close box [37,142][152,257] in BOTH states -> header height unchanged; Share box [928,142][1043,257]; both 115x115px = 44pt at density 420 ---
/home/igor_kilocode_ai/.local/share/kwf/sections/janitor-2026-09-12-experience-and-design-14e1/e2e-mobile-app/p4-signout.log
androidx.appcompat.widget.LinearLayoutCompat com.kilocode.kiloapp:id/parentPanel tappable [70,1018][1010,1447]
android.widget.LinearLayout com.kilocode.kiloapp:id/topPanel tappable [70,1018][1010,1157]
android.widget.TextView Sign out this session? tappable [133,1065][947,1136]
android.view.View com.kilocode.kiloapp:id/titleDividerNoCustom tappable [70,1136][1010,1157]
android.widget.FrameLayout com.kilocode.kiloapp:id/contentPanel tappable [70,1157][1010,1283]
android.widget.ScrollView com.kilocode.kiloapp:id/scrollView tappable [70,1157][1010,1263]
android.widget.TextView Web browser will be signed out. It can keep API access for up to an hour. tappable [70,1157][1010,1263]
android.widget.ScrollView com.kilocode.kiloapp:id/buttonPanel tappable [70,1283][1010,1447]
android.widget.Button CANCEL tappable [550,1294][752,1436]
android.widget.Button SIGN OUT tappable [752,1294][978,1436]
--- tap gap between the two session rows at (540,500): digest p4-gap.digest (no alert) ---
hierarchy: /tmp/kilo-hierarchy.H9NcOs
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.Button Go back tappable [0,149][101,250]
android.view.View Device sessions tappable [111,167][1044,232]
android.widget.TextView okhttp tappable [157,343][252,389]
android.widget.TextView THIS DEVICE tappable [288,347][446,384]
android.widget.TextView Signed in 9/12/2026 · Last seen 9/12/2026 tappable [157,393][855,430]
android.widget.Button Sign out this device tappable [882,343][997,458]
android.widget.TextView Web browser tappable [157,541][350,587]
android.widget.TextView Signed in 9/9/2026 · Last seen 9/10/2026 tappable [157,591][855,628]
/home/igor_kilocode_ai/.local/share/kwf/sections/janitor-2026-09-12-experience-and-design-14e1/e2e-mobile-app/p1.log
android.widget.Button SIGN OUT tappable [752,1294][978,1436]
shot: /home/igor_kilocode_ai/.local/share/kwf/sections/janitor-2026-09-12-experience-and-design-14e1/e2e-mobile-app/p1-alert1.png
appium.sh: 20 scene calls in this round. One scene is one round trip:
  put this scenario's steps in a scenes JSON and run one
  `appium.sh <device> script <scenes.json> --out /home/igor_kilocode_ai/.local/share/kwf/sections/janitor-2026-09-12-experience-and-design-14e1/e2e-mobile-app` instead.
--- adb -s emulator-5554 shell input tap 890 599  (outer edge of Web browser sign-out box [882,541][997,657]) ---
--- appium.sh scene p1-alert2 (other session row) ---
SCENE p1-alert2 OK
android.widget.FrameLayout com.kilocode.kiloapp:id/action_bar_root tappable [70,1018][1010,1447]
android.widget.FrameLayout android:id/content tappable [70,1018][1010,1447]
androidx.appcompat.widget.LinearLayoutCompat com.kilocode.kiloapp:id/parentPanel tappable [70,1018][1010,1447]
android.widget.LinearLayout com.kilocode.kiloapp:id/topPanel tappable [70,1018][1010,1157]
android.widget.TextView Sign out this session? tappable [133,1065][947,1136]
android.view.View com.kilocode.kiloapp:id/titleDividerNoCustom tappable [70,1136][1010,1157]
android.widget.FrameLayout com.kilocode.kiloapp:id/contentPanel tappable [70,1157][1010,1283]
android.widget.ScrollView com.kilocode.kiloapp:id/scrollView tappable [70,1157][1010,1263]
android.widget.TextView Web browser will be signed out. It can keep API access for up to an hour. tappable [70,1157][1010,1263]
android.widget.ScrollView com.kilocode.kiloapp:id/buttonPanel tappable [70,1283][1010,1447]
android.widget.Button CANCEL tappable [550,1294][752,1436]
android.widget.Button SIGN OUT tappable [752,1294][978,1436]
  • proved live: device sessions: on iOS and Android, with the current-device row present (and, needs:seed, at least one other signed-in session listed), tap the OUTER EDGE of the 44pt sign-out area — a few pt… — android emulator-5554: outer-edge tap (890,550; 8px inside the 44pt box [882,541][997,657], outside the 16pt glyph) opened the confirm alert — 'android.widget.TextView Sign out this session? tappable [133,1065][947,1136]' (also p1-device-edge.digest: 'Sign out this device?' for tap 890,352, box [882,343][997,458]); labels/dates/copy in the digest are unchanged ('okhttp THIS DEVICE Signed in 9/12/2026 · Last seen 9/12/2026', 'Web browser Signed in 9/9/2026 · Last seen 9/10/2026', 'Only Kilo app sign-ins appear here...'); recording p1-signout-edge.mp4 captured, expanded-area background is a…
  • proved live: image viewer: on iOS and Android, open an image attachment from a session/comment, tap the OUTER EDGE of the 44pt close area (not the 20pt glyph) — the viewer closes and the underlying screen is… — android emulator-5554; opened 'e2e-viewer.png' in session 'HTML image tag demo', viewer close box [37,142][152,257] = 115px = 44pt (p2-viewer-open.log: 'android.widget.Button Close e2e-viewer.png tappable [37,142][152,257]'); outer-edge tap at (39,199) — 21pt from the glyph centre, outside the 20pt glyph and outside the old 40pt box — closed the viewer, and the after digest is identical to the pre-open session digest ('SAME underlying session digest', p2-after-close.digest); recording p2-run.mp4, replay p2.replay.json re-ran 'SCENE p2 OK'. Visual glyph-size judgement is the visual reviewer's.…
  • proved live: image viewer: on iOS and Android, open an image attachment, tap the OUTER EDGE of the 44pt share area (not the 20pt glyph) — the native share flow opens and no layout shifts in the header…
  • proved live: layout stability: on iOS and Android, record device-sessions loading skeleton -> loaded rows (no vertical jump larger than the pre-change baseline) and the image-viewer header with Share hidden vs…
  • proved live: ux-check: Device sessions loading -> loaded transition shows no visible jump or new truncation of the session label compared with the previous release. — Loading digest (Device sessions, skeleton) and loaded digest captured with nextjs stalled then resumed: header bounds [111,167][1044,232] identical in both; labels render untruncated; sign-out box 115px = 44pt and loaded bounds match the pre-restart run. Visual jump judged by the visual reviewer on p7-load.mp4/p7-loading.png/p7-loaded.png.
  • proved live: ux-check: No visible regression in glyph size/position or header layout: the sign-out LogOut icon stays 16pt, the viewer X/Share icons stay 20pt, and neither 44pt control is clipped by the 56pt… — android emulator-5554; Device sessions shows both controls at 115px = 44pt ('Sign out this device tappable [882,343][997,458]', 'Sign out this session tappable [882,541][997,657]'), and outer-edge taps (884,599)/(884,400) each opened the confirmation alert ('SCENE p6-signout-alert OK' → 'Sign out this session?' and 'Sign out this device?' with CANCEL/SIGN OUT); viewer Close/Share stayed 44pt and on-screen in portrait ([37,142][152,257] / [928,142][1043,257]) and landscape ([128,77][243,192] / [2248,77][2363,192]), and a landscape outer-edge tap closed the viewer; captures for the visual…
  • proved live: ux-check: iOS and Android: in Settings > Device sessions (with another signed-in session), tapping the outer edge of the sign-out touch area — not the centered glyph — opens the sign-out confirmation…
  • proved live: ux-check: iOS and Android: opening an image attachment in the viewer, tapping the outer edge of the 44pt close area dismisses the viewer, and tapping the outer edge of the 44pt share area opens the…
Owner request

Surface: the mobile app (apps/mobile).

Problem

Two icon-only controls on the mobile app fall below the app's own 44pt touch-target convention, making them harder to hit (the device-sessions control is destructive).

  • apps/mobile/src/components/device-sessions-screen.tsx:61-76: the sign-out control rendered as <LogOut size={16} /> inside a Pressable has className="shrink-0 active:opacity-70" and hitSlop={8}, so its effective touch area is roughly 32x32pt (16pt glyph + 8pt slop per side), well under the 44pt the app uses elsewhere.
  • apps/mobile/src/components/image-viewer-modal.tsx:180-199: the close and share controls are h-10 w-10 (40pt) with no hitSlop, so both stay under 44pt.

The intended pattern already exists in this codebase: apps/mobile/src/components/trusted-hosts-screen.tsx:68-78 gives the same style of trailing icon action className="min-h-11 min-w-11 shrink-0 items-center justify-center active:opacity-70", and apps/mobile/src/components/ui/button.tsx:22-31 documents 44pt as the minimum for the icon size. apps/mobile/src/components/sheet-header.tsx:66-79,110-118 uses the same min-h-11 min-w-11 treatment for icon-only sheet controls.

Requested behavior

Make the device-sessions sign-out control and the image-viewer close/share controls meet the same 44pt minimum as the existing icon controls, without changing the visible glyph sizes or the surrounding layout intent.

  • In apps/mobile/src/components/device-sessions-screen.tsx, give the sign-out Pressable the same min-h-11 min-w-11 items-center justify-center treatment used by trusted-hosts-screen.tsx, keeping hitSlop at or above the current value so the effective area is at least 44x44pt.
  • In apps/mobile/src/components/image-viewer-modal.tsx, raise the close and share Pressable dimensions (or add hit slop) so each has an effective touch area of at least 44x44pt, again matching the existing icon-control pattern.

Exclusions

  • Do not change any accessibility labels, copy, or translations.
  • Do not change the icon glyph sizes, colors, or the visual design beyond the minimum touch area.
  • Do not touch Kilo Claw (apps/mobile/src/app/**/kiloclaw/**, apps/mobile/src/components/kiloclaw/**, apps/mobile/src/lib/kiloclaw/**, (1_kiloclaw) routes) or Kilo Chat (apps/mobile/src/components/kilo-chat/**, apps/mobile/src/app/**/(4_chat)/**).
  • Do not change dependencies or add new components; keep both iOS and Android in scope (the change is shared code).

Acceptance checks

  1. Update apps/mobile/src/components/device-sessions-screen.mounted.test.tsx so it renders one session (the existing query.data fixture) and asserts the single sign-out Pressable carries a minimum touch dimension of 44pt (for example min-h-11 and min-w-11 on className). This must fail before the change and pass after it.
  2. Update apps/mobile/src/components/image-viewer-modal.mounted.test.tsx so it asserts the close and share controls each carry an effective minimum touch dimension of 44pt (the same kind of className/hit-slop assertion). This must fail before the change and pass after it.
  3. From apps/mobile/: pnpm typecheck, pnpm lint, pnpm format:check, pnpm check:unused, and pnpm test all pass.
  4. Local end-to-end proof through the normal workflow on both iOS and Android: open Settings > Device sessions with at least one other signed-in session, then tap the outer edge (not the center) of each 44pt sign-out area and confirm the confirmation alert opens; open an image attachment to launch the viewer, then tap the outer edge of the close and share areas and confirm each activates. Record both runs. If recording is unavailable, provide screenshots of each control's expanded area and the resulting action. No other screen behavior, layout, or copy changes.

Prerequisites for the checks

  • The normal mobile E2E workflow running the backend and Metro.
  • An account with at least two device sessions for the device-sessions check, and a session/comment with an image attachment for the viewer check.
  • Recording tooling for both iOS and Android; screenshots are the fallback only if recording is unavailable.

Notes

  • The queued workflow implements this after classification.

Surface: the mobile app (apps/mobile).

# Problem

Two icon-only controls on the mobile app fall below the app's own 44pt touch-target convention, making them harder to hit (the device-sessions control is destructive).

- `apps/mobile/src/components/device-sessions-screen.tsx:61-76`: the sign-out control rendered as `<LogOut size={16} />` inside a `Pressable` has `className="shrink-0 active:opacity-70"` and `hitSlop={8}`, so its effective touch area is roughly 32x32pt (16pt glyph + 8pt slop per side), well under the 44pt the app uses elsewhere.
- `apps/mobile/src/components/image-viewer-modal.tsx:180-199`: the close and share controls are `h-10 w-10` (40pt) with no `hitSlop`, so both stay under 44pt.

The intended pattern already exists in this codebase: `apps/mobile/src/components/trusted-hosts-screen.tsx:68-78` gives the same style of trailing icon action `className="min-h-11 min-w-11 shrink-0 items-center justify-center active:opacity-70"`, and `apps/mobile/src/components/ui/button.tsx:22-31` documents 44pt as the minimum for the `icon` size. `apps/mobile/src/components/sheet-header.tsx:66-79,110-118` uses the same `min-h-11 min-w-11` treatment for icon-only sheet controls.

# Requested behavior

Make the device-sessions sign-out control and the image-viewer close/share controls meet the same 44pt minimum as the existing icon controls, without changing the visible glyph sizes or the surrounding layout intent.

- In `apps/mobile/src/components/device-sessions-screen.tsx`, g
@iscekic iscekic added the kwf-janitor-trial Nightly mobile janitor request label Sep 12, 2026
@kilo-code-bot

kilo-code-bot Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

The PR raises the device-sessions sign-out and image-viewer close/share touch targets to a 44pt minimum using the repo's established [44px] pattern, and the accompanying tests and skeleton reservation match the loaded layout; no security, correctness, performance, or migration issues found in the changed lines.

Files Reviewed (4 files)
  • apps/mobile/src/components/device-sessions-screen.tsx
  • apps/mobile/src/components/device-sessions-screen.mounted.test.tsx
  • apps/mobile/src/components/image-viewer-modal.tsx
  • apps/mobile/src/components/image-viewer-modal.mounted.test.tsx

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

Review guidance: REVIEW.md from base branch main

@iscekic iscekic added the human-ready The PR is ready for human review. label Sep 12, 2026
@iscekic iscekic self-assigned this Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review. kwf-janitor-trial Nightly mobile janitor request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants