Skip to content

fix(mobile): keep recent-review and repository rows inside landscape side insets - #8504

Merged
iscekic merged 3 commits into
mainfrom
drain/mobile-recent-reviews-landscape-insets
Oct 22, 2026
Merged

iscekic merged 3 commits into
mainfrom
drain/mobile-recent-reviews-landscape-insets

Conversation

@iscekic

@iscekic iscekic commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Evidence

Evidence: demonstrated — the two lists were rendered with a seeded fake account on a leased iOS simulator lane (dev client loading the VM's Metro) and on the Android emulator, in landscape and portrait. On the base revision every row took the fixed gutter and sat under the sensor housing or display cutout (63px for the Android landscape row titles, 24pt for the iOS portrait gutter); on the head revision every row starts at the inset plus the gutter — 86pt on an 874pt iOS landscape screen, 189px on a 2400px Android landscape screen — with equal leading and trailing margins, and the same screen re-derived its offset when rotated back. Captures and per-row rects are in a bot: comment on this pull request.

What changed

Two mobile lists built their content container from a fixed 24pt horizontal gutter:
the recent-reviews list (apps/mobile/src/components/code-reviewer/review-list-screen.tsx)
and the security-agent repository picker
(apps/mobile/src/components/security-agent/repository-settings-screen.tsx).

On a device whose landscape safe area is wider than that gutter, every row drew its
leading characters under the sensor housing or the display cutout.

Both screens now derive the content-container padding from the safe-area side insets
and keep the 24pt gutter on top of them:

const insets = useSafeAreaInsets();
const listContentContainerStyle = useMemo(
  () => ({
    paddingTop: 16,
    paddingLeft: insets.left + LIST_CONTENT_GUTTER,
    paddingRight: insets.right + LIST_CONTENT_GUTTER,
    // picker only: paddingBottom from the tab-bar clearance
  }),
  [insets.left, insets.right]
);

One memoized style per insets value, so every row — freshly mounted or recycled —
starts at the same offset and a rotation re-derives the padding once. This matches
the two pr-review FlashLists, which already read useSafeAreaInsets for the same
reason; portrait insets are zero, so the portrait layout is unchanged.

End-to-end evidence

Run on this work VM against a local dev stack with a seeded fake account
(dev/seed/code-reviews/review-list.ts, eight terminal rows, and four cached
repository rows for the picker).

iOS — leased simulator lane, dev client of this branch loading JS from the VM's Metro:

State Row origin (app-reported rect) Row width
Landscape, recent reviews x = 86pt on every row 702pt of 874pt
Portrait after rotating back x = 24pt on every row 354pt of 402pt
Landscape, repository picker x = 86pt on every row 702pt of 874pt

86 = 62 (landscape side inset) + 24 (gutter), and 874 - 702 - 86 = 86, so the
leading and trailing margins are equal. Rotating back to portrait restored 24pt, so
the offset is re-derived on rotation rather than kept from the previous frame.

Android — emulator with a 136px display cutout on the left edge in landscape:

State Row title left edge
Portrait, recent reviews 63px = 24dp
Landscape, recent reviews 189px = 136px cutout + 53px gutter
Landscape, repository picker 189px

The titles stay clear of the cutout band in landscape and the gutter is unchanged in
portrait.

Unit coverage: full-surface-states.mounted.test.tsx and
repository-settings-screen.mounted.test.tsx assert the content-container padding for
portrait (24 / 24) and landscape (47 / 59 insets → 71 / 83). Both assertions fail
on the previous code and pass with this change; oxlint on the four touched files is
clean and tsc --noEmit reports no errors.

Not exercised at runtime: the picker's toggle and save paths (the list rendering, which
carries the padding, was exercised). Truncation of very long titles is unchanged — the
row text keeps numberOfLines={1}.

Proxy answers

The owner's proxy gave these answers in the owner's place.

  1. Question: Scope: The reported list is code-reviewer/review-list-screen.tsx. components/security-agent/repository-settings-screen.tsx has the identical fixed paddingHorizontal: 24 FlashList and no safe-area handling, so it has the same latent landscape clipping (not reported). Fix only the reported list, or both?
    Answer: Both lists in this PR
    Confidence: 0.70 · Precedent: Fix the root cause and the class of the issue on every affected surface.
  2. Question: Inset hook: Which inset source should the list content container use?
    Answer: Raw useSafeAreaInsets
    Confidence: 0.50 · Precedent: Keep similar elements consistent across surfaces; this matches the sibling pr-review FlashLists and keeps the gutters symmetric.
  3. Question: iOS proof: How much iOS-lane evidence should the PR carry? The lane loads JS from Metro on this VM, so a before/after needs a Metro restart with the file reverted.
    Answer: After-fix iOS + Android proof
    Confidence: 0.65 · Precedent: Do the least work possible with no drop in quality; the explorer's evidence already covers the base reproduction.

The recent-reviews list and the security-agent repository picker built their
content container from a fixed 24pt horizontal gutter. On a notched iPhone in
landscape the safe-area side inset is wider than that gutter, so rows whose
band overlaps the sensor housing drew their titles under it.

Derive the content container padding from the safe-area side insets and keep
the 24pt gutter on top of them, from one memoized style so recycled rows share
the offset and a rotation re-derives it.
@iscekic iscekic added drain Work item from the findings drain explorer:ios Finding surfaced by the iOS explorer labels Oct 21, 2026
@iscekic

iscekic commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Visual evidence from this VM against a local dev stack and a seeded fake account (eight terminal review rows, no production data).

  • iOS landscape: every row starts at the safe-area side inset plus the 24pt gutter (86pt on an 874pt-wide screen), so no title sits under the sensor housing.
  • iOS portrait after rotating back: the rows return to the 24pt gutter, so the padding is re-derived per orientation.
  • Android landscape: the row titles start past the left display-cutout band (199px = 136px cutout + 63px gutter); portrait keeps the 24dp gutter.

The security-agent repository picker received the same content-container change and is covered by its mounted test; reaching it at runtime needs a connected provider and repository fixture, so it has no capture here.

iOS landscape: uniform row inset clear of the sensor housing

iOS portrait: rows at the unchanged 24pt gutter

Android landscape: rows clear of the left display cutout

Android portrait: rows at the unchanged 24dp gutter

@kilo-code-bot

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

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The inset-derived content-container padding in both mobile lists is correct, memoized on primitive inset values, covered by portrait/landscape mounted tests, and introduces no double-padding, leak, or behavior change in portrait.

Assumptions: [INFERENCE] — runtime clearance depends on react-native-safe-area-context reporting display-cutout insets on Android, which the PR's emulator evidence supports; the list body uses raw useSafeAreaInsets rather than the shared useScreenInsets corner-floor helper, so in the asymmetric-cutout case its gutter can differ by up to 8dp from ScreenHeader's, which is the pre-existing header/list gutter difference and not a regression. The security-agent picker was not exercised at runtime (its mounted test covers the content-container padding).

Files Reviewed (4 files)
  • apps/mobile/src/components/code-reviewer/review-list-screen.tsx
  • apps/mobile/src/components/code-reviewer/full-surface-states.mounted.test.tsx
  • apps/mobile/src/components/security-agent/repository-settings-screen.tsx
  • apps/mobile/src/components/security-agent/repository-settings-screen.mounted.test.tsx

Reviewed by deepseek-flash-latest · Input: 98.4K · Output: 10.4K · Cached: 1M

Review guidance: REVIEW.md from base branch main

@iscekic iscekic added merge:blocked:insufficient-e2e Verification does not cover the change merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. labels Oct 21, 2026
@iscekic

iscekic commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Follow-up verification of the head commit on both lists, on the iOS simulator lane and the Android emulator, against a local dev stack and a seeded fake account (eight terminal review rows, four repository rows). No production data.

Measured row origins (app-reported accessibility rects, and the uiautomator node bounds for Android):

Surface Orientation Row origin Row width
Recent reviews (iOS, 874pt landscape) landscape 86pt on every row 702pt
Recent reviews (iOS, 402pt portrait) portrait 24pt on every row 354pt
Repository picker (iOS, 874pt landscape) landscape 86pt on every row 702pt
Recent reviews (Android, 2400px landscape) landscape title text at 189px row to 2337px
Recent reviews (Android, 1080px portrait) portrait title text at 63px row to 1017px
  • 86 = 62 (landscape side inset) + 24 (gutter); leading and trailing margins are equal at 874 - 702 - 86 = 86.
  • Rotating the same screen back to portrait restored 24pt, so the padding is re-derived per orientation rather than kept from the previous frame.
  • Android landscape rows start past the 136px display-cutout band (189 - 136 = 53px ≈ 24dp at this density); the same screen in portrait keeps the 63px (24dp) gutter.
  • The same screens rebuilt from the base revision put every row at the fixed gutter regardless of the cutout or inset (63px Android landscape, 24pt iOS portrait), which is the reported misalignment; the captures below show the fixed state.

Every row on both lists and in both orientations now starts at the same offset, recycled rows included, and the titles are fully readable.

Screenshots to follow in this comment's attachments.

Recent reviews list in landscape with every row at the same safe-area-aware inset

The same recent reviews list in portrait at the plain gutter

Repository picker list in landscape at the same inset

Recent reviews list on the Android emulator in landscape clear of the display cutout

Repository picker on the Android emulator in landscape

Recent reviews list on the Android emulator in portrait

@iscekic iscekic removed the merge:blocked:insufficient-e2e Verification does not cover the change label Oct 22, 2026
@iscekic
iscekic enabled auto-merge October 22, 2026 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drain Work item from the findings drain explorer:ios Finding surfaced by the iOS explorer merge:bot Bot-owned review; merge when ready once readiness passes and a review approves.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants