Skip to content

fix(mobile): keep session header trailing controls inside the viewport - #6513

Merged
iscekic merged 1 commit into
mainfrom
kwf/explorer-session-thread-the-session-header-toolbar-overfl-2809a-fc4d
Sep 22, 2026
Merged

iscekic merged 1 commit into
mainfrom
kwf/explorer-session-thread-the-session-header-toolbar-overfl-2809a-fc4d

Conversation

@iscekic

@iscekic iscekic commented Sep 21, 2026 •

Copy link
Copy Markdown
Collaborator

Changelog for users

  • The session header's context pill and PR badge stay inside the screen on narrow phones instead of painting past the right edge.
  • A context pill whose token, percentage, or cost label is too wide now truncates with an ellipsis instead of overflowing the header.

Changelog for maintainers

  • ScreenHeader caps the trailing slot at max-w-[50%], but React Native defaults flexShrink to 0; the trailing cluster now opts in with min-w-0 shrink so the cap actually constrains it.
  • The context pill and its inner text row also opt into shrinking, and both labels set numberOfLines={1} so a squeezed pill ellipsizes rather than spilling.
  • The same cluster fix is applied to the agent-chat route's loading header, so its reserved pill box cannot overflow before session data arrives.
  • Unit tests assert the shrink/min-w-0 tokens on the cluster, the pill, the inner row, and each text, plus numberOfLines={1}.
  • The shared findAll element walker moved to session-context-metrics-test-helpers.ts and is imported by the metrics test.
  • The branded-splash config-plugin test now introspects against a fresh temporary Android project root, so it no longer reads a developer's local prebuild colors.xml.
  • No catalog, schema, or config changes; review the header layout first, then the pill's text truncation.

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).

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…

e6.png

[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…

e4.png

[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.png

[e5] ux-check: header trailing cluster (PR badge + context pill) at raised font scale, title readable

e5-agents.png

[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."…

p2-loaded.png

E2E proof — log excerpts

[e1] ux-check: Open a session and observe the loading-header -> loaded-header sw -> pass :: 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).
[e1] session-open: the loaded session header keeps its whole trailing cluster (P -> pass :: jev read the digest: pass (confidence 1.00)
/home/igor_kilocode_ai/.local/share/kwf/sections/explorer-session-thread-the-session-header-toolbar-overfl-2809a-fc4d/e2e-mobile-app/e1-scene.log
android.widget.Button Add attachment tappable [28,2214][101,2288]
android.widget.EditText Message tappable [126,2191][800,2311]
android.widget.Button Start voice input tappable [835,2205][926,2297]
android.widget.Button Send message [926,2188][1052,2314]
SCENE e1 OK
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,130][101,231]
android.widget.Button Rename session: Greeting tappable [111,84][714,277]
android.view.View Greeting tappable [111,147][714,212]
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]
android.widget.TextView 1% tappable [863,162][900,199]
android.widget.TextView $0.0039 tappable [909,162][1013,199]
android.widget.TextView 11:44 PM tappable [466,1713][614,1752]
android.view.ViewGroup say hello, User message tappable [0,1771][1080,1909]
android.widget.TextView say hello tappable [847,1808][1011,1871]
android.widget.Button Preparation complete tappable [40,1930][1042,2031]
android.widget.TextView Preparation complete tappable [188,1957][506,2003]
android.view.ViewGroup Hello, Assistant message tappable [0,2052][1080,2143]
android.widget.TextView Hello tappable [37,2066][1045,2129]
android.widget.Button Add attachment tappable [28,2214][101,2288]
android.widget.EditText Message tappable [126,2191][800,2311]
android.widget.Button Start voice input tappable [835,2205][926,2297]
android.widget.Button Send message [926,2188][1052,2314]
/home/igor_kilocode_ai/.local/share/kwf/sections/explorer-session-thread-the-session-header-toolbar-overfl-2809a-fc4d/e2e-mobile-app/e1-cold2.log
android.widget.Button Add attachment tappable [28,2214][101,2288]
android.widget.EditText Message tappable [126,2191][800,2311]
android.widget.Button Start voice input tappable [835,2205][926,2297]
android.widget.Button Send message [926,2188][1052,2314]
SCENE e1 OK
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,130][101,231]
android.widget.Button Rename session: Greeting tappable [111,84][714,277]
android.view.View Greeting tappable [111,147][714,212]
android.widget.Button Context 11,061 of 1,310,720 tokens, 1% used, cost 0.4 cents. Tap to view context details. tappable [741,123][1043,238]
android.widget.TextView 1% tappable [863,162][900,199]
android.widget.TextView $0.0040 tappable [909,162][1013,199]
android.widget.TextView 11:38 PM tappable [466,1712][614,1751]
android.view.ViewGroup say hello, User message tappable [0,1770][1080,1908]
android.widget.TextView say hello tappable [847,1807][1011,1870]
android.widget.Button Preparation complete tappable [40,1929][1042,2030]
android.widget.TextView Preparation complete tappable [188,1956][506,2002]
android.view.ViewGroup Hello!, Assistant message tappable [0,2051][1080,2143]
android.widget.TextView Hello! tappable [37,2065][1045,2128]
android.widget.Button Add attachment tappable [28,2214][101,2288]
android.widget.EditText Message tappable [126,2191][800,2311]
android.widget.Button Start voice input tappable [835,2205][926,2297]
android.widget.Button Send message [926,2188][1052,2314]
/home/igor_kilocode_ai/.local/share/kwf/sections/explorer-session-thread-the-session-header-toolbar-overfl-2809a-fc4d/e2e-mobile-app/scripted-e1.log
android.widget.Button Greeting, Idle, kilo/brisk-frame-pbc, PANON-DEKING, and cost 0.4 cents, 9 minutes ago tappable [40,753][1042,958]
android.widget.TextView PANON-DEKING tappable [87,787][518,824]
android.widget.TextView $0.0040 · 9 MINUTES AGO tappable [562,787][949,824]
android.widget.TextView Greeting tappable [87,831][950,877]
android.widget.TextView kilo/brisk-frame-pbc tappable [87,887][950,924]
android.widget.Button New coding task tappable [37,1016][1043,1132]
android.widget.TextView New coding task tappable [450,1050][696,1096]
android.widget.Button New task from a picture tappable [37,1150][1043,1266]
android.widget.TextView New task from a picture tappable [396,1184][749,1230]
android.widget.TextView EXPLORE tappable [36,1301][1044,1338]
android.widget.Button Code Reviewer, Automatic PR reviews tappable [37,1357][1043,1501]
android.widget.TextView Code Reviewer tappable [171,1385][953,1431]
android.widget.TextView Automatic PR reviews tappable [171,1435][953,1472]
android.widget.Button Security Agent, Find and remediate vulnerabilities tappable [37,1519][1043,1663]
android.widget.TextView Security Agent tappable [171,1547][953,1593]
android.widget.TextView Find and remediate vulnerabilities tappable [171,1597][953,1634]
android.widget.Button PR Review, Review pull requests on mobile tappable [37,1682][1043,1822]
android.widget.TextView PR Review tappable [171,1710][953,1756]
android.widget.TextView Review pull requests on mobile tappable [171,1760][953,1797]
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]
Owner request

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):

[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 — e3-baseline.png

[e3] android pill squeeze

[e3] android pill squeeze — e3squeeze-packed.png

[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…

[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 — p3-header.png

[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."…

[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 — p2-loading.png

[e3] android pill squeeze

[e3] android pill squeeze — e3.png

Follow-ups (not changed here)

Open findings (not fixed here)

e6-hierarchy

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
@kilo-code-bot

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

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Reviewed the shrink/min-w-0 opt-in on the session-header trailing cluster, the context pill and its inner text row, the numberOfLines={1} truncation, the relocated findAll test helper, and the branded-splash test's fresh Android project root. The RN flexShrink: 0 default makes the added classes the correct counterpart to ScreenHeader's max-w-[50%] min-w-0 shrink trailing slot (apps/mobile/src/components/screen-header.tsx:294), and I found no new correctness, security, or layout-regression issues in the changed code. The branding test helper already registers temporary roots for afterEach cleanup (apps/mobile/plugins/branded-splash.test.ts:18), and projectRoot remains used by the other cases, so no leak or unused-variable issue. SessionContextMetrics is not mocked in session-detail-content.test.ts and is rendered in exactly one place, so the new findByProps({ testID: 'session-context-metrics' }) assertion is unambiguous.

Files Reviewed (7 files)
  • apps/mobile/plugins/branded-splash.test.ts
  • apps/mobile/src/app/(app)/agent-chat/[session-id].tsx
  • apps/mobile/src/components/agents/session-context-metrics-test-helpers.ts
  • apps/mobile/src/components/agents/session-context-metrics.test.ts
  • apps/mobile/src/components/agents/session-context-metrics.tsx
  • apps/mobile/src/components/agents/session-detail-content.test.ts
  • apps/mobile/src/components/agents/session-detail-content.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 22, 2026
@iscekic iscekic self-assigned this Sep 22, 2026
@iscekic
iscekic merged commit 8a9692b into main Sep 22, 2026
29 checks passed
@iscekic
iscekic deleted the kwf/explorer-session-thread-the-session-header-toolbar-overfl-2809a-fc4d branch September 22, 2026 12:14
iscekic added a commit that referenced this pull request Sep 25, 2026
… 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.
iscekic added a commit that referenced this pull request Sep 26, 2026
…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
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