Repository navigation
fix(mobile): keep session header trailing controls inside the viewport - #6513
Conversation
Surface: mobile-app Explorer finding: session-thread: The session header toolbar overflows the viewport and the Copy link control is clipped at the right screen edge, leaving only a partial glyph visible; the same clipped control appears in session-composer-kb-up.png and session-sending.png. The user-agent explorer found this while using the app like a user. One finding per item; the explorer never edits product code. Flow: session-thread Found on revision: f2181ae Repro: 1. set this state first: credits 20; session permission; uimode night; battery 22 discharging; relaunch; the device in light mode 2. open the app on emulator-5602 3. reach session-thread 4. the capture shows the defect named below Observed: The session header toolbar overflows the viewport and the Copy link control is clipped at the right screen edge, leaving only a partial glyph visible; the same clipped control appears in session-composer-kb-up.png and session-sending.png. Expected: the screen renders without this defect Evidence (from the device run): - ~/.local/share/kwf/findings/explorer-session-thread-the-session-header-toolbar-overfl-2809abd2/session-thread.png
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Reviewed the Files Reviewed (7 files)
Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0 Review guidance: REVIEW.md from base branch |
… cluster The merge of main exposed a real conflict of intent. This branch sized the header's trailing slot to its content (`ms-3 shrink-0`) so fixed-width controls stop painting past a clamped box. Main (#6513, pinned by #6522) relies on that slot's `max-w-[50%] min-w-0 shrink` cap so the session header's context pill truncates a long total cost inside the cap. Without the cap the pill's natural width pushes the title to zero and can paint past the screen edge. 21509eb rewrote main's assertion to match the uncapped slot, which hid that regression. Add `headerRightShrinks` to ScreenHeader: a cluster that shrinks on its own keeps the half-row cap, and fixed-width clusters stay content-sized. Both session headers (the loaded detail and the loading skeleton) opt in. Restore main's assertion unchanged.
…t, and sign-in screens (#6511) * fix: fix reviewer blocking findings from patch (kwf explorer-10-visual-defects-on-android-the-errored-questio-778f4-6366/patchf) * fix: kwf-fix-review-8c9c patch delivery * fix: fix the failed mobile-app verification gate (kwf kwf-fix-review-8c9c/gr1) * fix(mobile): size the session header's trailing cluster to its content The header clamped the trailing slot to half the row while its fixed-width children kept painting at full width, so the last control's glyphs ran past the right screen edge (session-compose-kbup). The cluster now sizes to its content and never shrinks; the flex-1 min-w-0 heading absorbs the squeeze and a window too narrow for both drops the actions to their own row. A variable-width headerRight still caps itself: Submit review and the Security Agent Save button carry 140 dp. * test(mobile): align the header tests with the content-sized cluster findInlineActionsWrapper no longer keys on the removed half-row cap; the stacked-row helper keys on the cluster's own class, and the cluster assertions check the wrapper's content instead of a superseded cap. * fix(mobile): stack header actions when the trailing cluster squeezes the title The stacking rule read the window width and the font scale only, so a wide trailing cluster stayed on the title's row. At 320 dp with a font scale of 2 the PR review header's Share, Submit review and Merge controls (236 dp of fixed-width buttons) left the title a few characters. `ScreenHeader` now reserves the width a caller declares for its `headerRight` cluster, and the decision subtracts the `ms-3` gap as well. The PR review screen declares the widths of the controls it actually renders, so a narrower cluster keeps the title beside it. * test(mobile): read the declared header width through findElement The test read `props` off the element it found, which the type checker sees as unknown, and it referenced a constant scoped to another suite. It now asserts the width through `findElement` and declares its own mergeable overview. * test(mobile): fix the declared-width stacking assertions The 236dp case at 390dp leaves the title 98dp, below its 120dp minimum, so the actions stack. The narrower-cluster test also inherited the previous test's 320dp / scale 2 window, so it now sets its own geometry. * fix(mobile): align the session header cluster test with the content-sized slot The screen-header change on this branch dropped the `max-w-[50%]` cap from the trailing slot, but session-detail-content.test.ts still asserted the capped wrapper and went red. Assert the new `ms-3 shrink-0` slot with the uncapped `min-w-0 shrink` cluster inside it. * fix(mobile): keep the half-row cap for the session header's shrinking cluster The merge of main exposed a real conflict of intent. This branch sized the header's trailing slot to its content (`ms-3 shrink-0`) so fixed-width controls stop painting past a clamped box. Main (#6513, pinned by #6522) relies on that slot's `max-w-[50%] min-w-0 shrink` cap so the session header's context pill truncates a long total cost inside the cap. Without the cap the pill's natural width pushes the title to zero and can paint past the screen edge. 21509eb rewrote main's assertion to match the uncapped slot, which hid that regression. Add `headerRightShrinks` to ScreenHeader: a cluster that shrinks on its own keeps the half-row cap, and fixed-width clusters stay content-sized. Both session headers (the loaded detail and the loading skeleton) opt in. Restore main's assertion unchanged. * chore(mobile): drop the node_modules symlink committed by mistake
Changelog for users
Changelog for maintainers
ScreenHeadercaps the trailing slot atmax-w-[50%], but React Native defaultsflexShrinkto 0; the trailing cluster now opts in withmin-w-0 shrinkso the cap actually constrains it.numberOfLines={1}so a squeezed pill ellipsizes rather than spilling.shrink/min-w-0tokens on the cluster, the pill, the inner row, and each text, plusnumberOfLines={1}.findAllelement walker moved tosession-context-metrics-test-helpers.tsand is imported by the metrics test.colors.xml.E2E proof
[e1] ux-check: Open a session and observe the loading-header -> loaded-header swap: the context pill's reserved box does not move the title or body, and one pill is shown throughout. — One context pill, unclipped: e1-scene.log 'android.widget.Button Context 10,942 of 1,310,720 tokens, 1% used, cost 0.39 cents. Tap to view context details. tappable [741,123][1043,238]' (right edge 1043 < 1080) beside title 'android.view.View Greeting tappable [111,147][714,212]', both identical in the cold-app re-open (e1-cold2.log) and one pill line per digest, so the header did not shift and no UX-DEFECT appeared; the hidden reserved-box frame resolves inside the capture window so the box was already populated at the earliest capture (e1.png).
[e6] ux-check: Tap the compressed context pill: the context sheet still opens, and the pill's touch target is unchanged (>=44pt tall). — android emulator-5554: SCENE e6 OK — tapping the pill opened the context sheet (digest shows session-page-sheet-surface, 'Context usage', 'COPY LINK, https://app.kilo.ai/cloud/sessions/ses_f3a016c7dfff5Q3xYtv7R001kD' in e6-scene.log); the pill pressable bounds [910,123][1044,238] = 115px = 43.8dp at the emulator's 420dpi/2.625x, i.e. the unchanged h-[44px] 44dp token, with right edge 1044 < 1080 so nothing is clipped (e6-touch-target.log, e6-hierarchy.xml); UX audit of the header and the context sheet shows no defect; screenshots for the visual reviewer: e6.png, e6-hierarchy.png. Declared…
[e4] ux-check: on a narrow device in night mode, the session-thread header trailing controls (PR badge + context pill with ring + percentage + cost) stay fully inside the viewport — android emulator-5554, night mode on (adb shell cmd uimode night -> 'Night mode: yes'). Loaded session-thread header: PR badge 'Updating, open pull request #1' [617,158][722,205] and context pill 'Context 10,941 of 1,310,720 tokens, 1% used, cost 0.39 cents. Tap to view context details.' [741,123][1043,238] with inner '1%' [863,162][900,199] and '$0.0039' [909,162][1013,199]; the root window is [0,0][1080,2400] so the trailing cluster's right edge 1043 is 37px inside the viewport and no glyph is cut off ('SCENE e4 OK', e4-scene.log; still e4.png, tree e4-header.txt/.png). CAVEAT: the assigned…
[e5] ux-check: header trailing cluster (PR badge + context pill) at raised font scale, title readable — android emulator-5604; font scale raised to the emulator's Largest preset 2.0 (Settings seekbar text="2.0" content-desc="Font size", e5-fontscale-set.txt; 1.3x is not a position on this image and no harness switch sets a custom scale — papercut obstacle:device-state-font-scale). Session ses_f39dabee8ffd70D4OXvVRM8XcT opened from Agents with a seeded PR link (e5-pr-link2.log) shows badge and pill. e5-scene.log:7 badge 'pull request #5896' [540,224][706,278] and e5-scene.log:9 pill [724,193][1043,309]: the cluster is capped at the row's right 50% and the pill's right edge is x=1043 < 1080, so…
[e5] ux-check: header trailing cluster (PR badge + context pill) at raised font scale, title readable
[p2] needs:fault: a stalled session-metadata response holds the route's loading header (the reserved context-pill box) and nothing is clipped at the right edge — android emulator-5554; nextjs stalled (p2-stall.log: "fault.sh: stalled pids [205850] for 300s") and the route header held its loading state — p2-loading.txt shows the fallback title text="Session" and the reserved pill box content-desc="Tap to view context details." resource-id="session-context-metrics" bounds="[910,123][1044,238]" (right edge 1044 within the 1080-wide root) — while after fault.sh recover + Retry the same session shows content-desc="Rename session: Greeting" and content-desc="Context 11,076 of 1,310,720 tokens, 1% used, cost 0.49 cents. Tap to view context details."…
E2E proof — log excerpts
/home/igor_kilocode_ai/.local/share/kwf/sections/explorer-session-thread-the-session-header-toolbar-overfl-2809a-fc4d/e2e-mobile-app/e1-scene.log/home/igor_kilocode_ai/.local/share/kwf/sections/explorer-session-thread-the-session-header-toolbar-overfl-2809a-fc4d/e2e-mobile-app/e1-cold2.log/home/igor_kilocode_ai/.local/share/kwf/sections/explorer-session-thread-the-session-header-toolbar-overfl-2809a-fc4d/e2e-mobile-app/scripted-e1.logOwner request
[e3] android pill squeeze — Android emulator-5606, temporary fixture widened the seeded session's pill to 342% / cost >= $1.00 plus a linked PR badge: packed build keeps it inside the screen (pill [741,189][1043,304] in e3-scene.log; compressed pill [712,189][1043,304] with cost text truncated inside it at [890,228][1014,265] in e3-packed.log), while the pre-fix baseline (same fixture, added shrink/min-w-0 and numberOfLines removed) paints to the right edge (pill [712,189][1080,304], cost text [912,228][1080,265] in e3-baseline-bounds.log); fixture reverted (git status clean); captures e3.png, e3squeeze-packed.png…
[e3] android pill squeeze
[p3] needs: a session with no associated PR and no context usage renders the title plus the reserved (invisible) pill box, nothing clipped at the right edge — android emulator-5554, night mode. Seeded a bare cli_sessions_v2 row for the e2e user via db.sh (no pr_url, no messages) and opened it: session-thread header rendered title 'p3 no-pr no-context' [111,147][883,212] plus the reserved invisible pill box 'Tap to view context details.' [910,123][1044,238] with no text and no PR badge; right edge 1044 < 1080 so nothing is clipped (p3-header.txt; still p3-header.png). p3 is advisory and its state is transient: a later capture of the same header box (p3-scene.log) shows stale context text 'Context 4,480,000 of 1,310,720 tokens, 342% used, cost 1…
[p2] needs:fault: a stalled session-metadata response holds the route's loading header (the reserved context-pill box) and nothing is clipped at the right edge — android emulator-5554; nextjs stalled (p2-stall.log: "fault.sh: stalled pids [205850] for 300s") and the route header held its loading state — p2-loading.txt shows the fallback title text="Session" and the reserved pill box content-desc="Tap to view context details." resource-id="session-context-metrics" bounds="[910,123][1044,238]" (right edge 1044 within the 1080-wide root) — while after fault.sh recover + Retry the same session shows content-desc="Rename session: Greeting" and content-desc="Context 11,076 of 1,310,720 tokens, 1% used, cost 0.49 cents. Tap to view context details."…
[e3] android pill squeeze
Follow-ups (not changed here)
Open findings (not fixed here)