Skip to content

fix(mobile): every Duo header blurs under the vertical bar, from one place - #24671

Merged
juliusmarminge merged 1 commit into
t3code/settings-split-regressionsfrom
t3code/duo-content-under-bar
Oct 25, 2026
Merged

juliusmarminge merged 1 commit into
t3code/settings-split-regressionsfrom
t3code/duo-content-under-bar

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

On the iPhone Duo, the header blur stopped short of the vertical bar on every screen, leaving a hard edge before the rail. System Settings carries the blur all the way across.

In iOS 26 the header's blur is the scroll view's own scroll-edge effect, drawn inside the scroll view. Duo columns pad their content away from the vertical bar, so every scroll view, and its blur, stopped there.

Change

The fix lives in one place, so no screen can get it wrong:

  • Content: columns keep padding everything away from the bar, so nothing a screen renders can sit under it.
  • Blur: NativeScrollViewMarker, the one way to register a header scroll view, now wraps it in a native view (T3ScrollEdgeBleedView). Where the view sits flush against a side safe area its column padded away, it widens the native UIScrollView under that band. On the right, the content stays exactly where React Native laid it out. It never touches JS, so folds move the blur and the content in one UIKit layout pass.
  • Enforcement: lint rejects importing ScrollViewMarker from react-native-screens anywhere but the wrapper. A new screen gets the blur just by registering its scroll view, which it needs for header behavior anyway.
  • Lists: the new t3code/require-registered-list-scroll rule reports a LegendList, AnimatedLegendList or KeyboardAwareLegendList without a scroll component, so a screen's list can't silently skip registration. Archived threads, open source licenses, pull request files, the file tree and the new-task picker now render through NativeScrollView. Lists nested in another scroll surface, in sheets, or in the non-native sidebar suppress the rule with a reason.

The Chats, Pull requests and PR reviewers screens moved from the raw marker to the wrapper. The Usage account page and the unavailable-thread screen now use ScreenScrollView, so their main scroll view is registered too. docs/internals/mobile-navigation.md records the constraint.

This needs a native build. Older builds without the view fall back to the previous behavior.

Stacked on #24644, which touches the same Settings files.

Before After
Folded Duo Chats before: header blur stops at the vertical bar Folded Duo Chats after: header blur reaches under the vertical bar, rows stay inset

Unfolded Duo pull request after: header blur reaches the rail, content stays inset

How it stays out of React Native's way

  • React Native sizes the scroll view's component view, and the UIScrollView inside follows it by autoresizing, so the widened frame survives React Native's layout updates.
  • The component view's clipping is turned off while bleeding (the scroll view still clips its content). React Native only rewrites that when the overflow style changes.
  • Fabric recycles scroll views, so the view puts everything back when it leaves the window, when its content is unmounted, and on dealloc.
  • It searches only the few levels down to the scroll view and never walks into a scroll view's content.

Verification

iPhone Duo simulator, native build with the view:

  • Folded and unfolded: Chats, the Pull requests list, pull request detail, a thread, and Settings pages.
  • The blur reaches under the rail and the content stays inset. The theme grid layout is unchanged.
  • Stress run: three cycles through all four tabs, scrolling the thread feed, pull-to-refresh, and folding and unfolding repeatedly. The app stayed up and logged no crash reports.

vp run check:changed passes, along with the React Compiler coverage, native, home and pull-request tests. swiftlint --strict is clean, and the lint rule was checked against a probe file.

Not checked:

  • A bar on the left (a rotated Duo; the simulator doesn't rotate its panels). That path offsets the content with an inset, so a programmatic scrollTo could leave the content under a left-side bar until the next layout pass.
  • iPad and iPhone with this native build. The view does nothing there, since no side safe area is padded away.

Claude Opus 5.5 in Claude Code, running in T3 Code.

🤖 Generated with Claude Code

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Oct 25, 2026
Comment thread apps/mobile/src/Stack.tsx Outdated
// material reaches the bar; their content takes the bar's inset natively (ColumnInsetContent).
const LISTS_UNDER_DUO_BAR = { nativeContentInsetHorizontally: false };

const SETTINGS_PAGE_OPTIONS = { ...SHEET_GLASS_HEADER_OPTIONS, ...LISTS_UNDER_DUO_BAR };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium src/Stack.tsx:233

On iOS, SettingsArchive and SettingsOpenSourceLicenses render their LegendList rows beneath the Duo rail because this shared option removes their horizontal column padding without replacing it. Their lists do not use ColumnInsetContent, and automatic inset adjustment does not add the needed horizontal inset for these vertical lists; keep these routes padded until their list content applies the horizontal safe-area inset.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/mobile/src/Stack.tsx around line 233:

On iOS, `SettingsArchive` and `SettingsOpenSourceLicenses` render their `LegendList` rows beneath the Duo rail because this shared option removes their horizontal column padding without replacing it. Their lists do not use `ColumnInsetContent`, and automatic inset adjustment does not add the needed horizontal inset for these vertical lists; keep these routes padded until their list content applies the horizontal safe-area inset.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outdated: the per-route nativeContentInsetHorizontally: false option (LISTS_UNDER_DUO_BAR) this points at is gone. Columns pad every screen again, so no content sits under the rail. The blur now reaches the rail through a native wrapper on NativeScrollViewMarker that only widens the scroll view (ff6615d). Archived threads and open source licenses also now register their lists through NativeScrollView, and the new t3code/require-registered-list-scroll lint rule reports any list that doesn't (b74ff40).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b74ff40757e resolves this, but it is not yet on this PR’s branch. At ff6615d9aef, these two lists still do not use NativeScrollView, so the finding remains until that commit is included.

@macroscopeapp

macroscopeapp Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — This PR adds a new native iOS wrapper that changes existing scroll-view frames, insets, offsets, and clipping across multiple screens, and it introduces a lint suppression for the wrapper import. An unresolved Medium finding also reports Settings rows rendering beneath the Duo rail, so the change warrants human review.

Needs human approval because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

UIKit draws the header's scroll-edge blur inside the scroll view, and Duo columns
pad their content away from the vertical bar, so the blur stopped at the bar on
every screen.

NativeScrollViewMarker, the one way to register a header scroll view, now wraps
it in a native view that widens the scroll view under any side safe area its
column pads away and leaves the content where it was. Columns keep padding
everything else, so no screen can put content under the bar, and a new screen
gets the blur by registering its scroll view at all. Lint rejects importing
ScrollViewMarker from react-native-screens anywhere else.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge force-pushed the t3code/duo-content-under-bar branch from 95af5b6 to ff6615d Compare October 25, 2026 03:28
@juliusmarminge juliusmarminge changed the title fix(mobile): Duo header blur reaches the vertical bar like the system apps fix(mobile): every Duo header blurs under the vertical bar, from one place Oct 25, 2026
@juliusmarminge
juliusmarminge added this pull request to stack #24705 October 25, 2026 03:30
@github-actions github-actions Bot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Oct 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire — 5.0 KiB — 6.8 KiB ✅
Codex Thread snapshot wire — 3.8 KiB — 4.9 KiB ✅
Codex Live turn WebSocket wire — 1.2 KiB — 2.0 KiB ✅
Codex Live turn WebSocket decoded — 20.9 KiB — 29.3 KiB ✅
Codex Live turn messages — 2 — 8 ✅
Claude Total thread wire — 5.0 KiB — 6.8 KiB ✅
Claude Thread snapshot wire — 3.8 KiB — 4.9 KiB ✅
Claude Live turn WebSocket wire — 1.2 KiB — 2.0 KiB ✅
Claude Live turn WebSocket decoded — 21.2 KiB — 29.3 KiB ✅
Claude Live turn messages — 2 — 8 ✅

Baseline: unavailable · PR result: ff6615d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@juliusmarminge
juliusmarminge merged commit 90c455f into main Oct 25, 2026
28 of 30 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/duo-content-under-bar branch October 25, 2026 03:32
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Oct 25, 2026
## What's Changed
* perf(mobile): highlight code with native @pierre/highlights instead of Shiki by @juliusmarminge in pingdotgg/t3code#24544
* fix(desktop): support Linux distro protocol launchers by @juliusmarminge in pingdotgg/t3code#24591
* fix(provider-acp): force-kill terminal commands that ignore SIGTERM by @juliusmarminge in pingdotgg/t3code#24596
* fix(grok): invoke leading picked skills through native commands by @juliusmarminge in pingdotgg/t3code#24605
* fix(server): long agent turns no longer load whole when a thread opens by @t3dotgg in pingdotgg/t3code#24589
* fix(server): analytics stop counting reconnects, retries, and dev servers by @t3dotgg in pingdotgg/t3code#24601
* fix(acp): idle agent errors fail their turns by @juliusmarminge in pingdotgg/t3code#24615
* fix(provider-acp): Grok monitors show as monitors in background work by @t3dotgg in pingdotgg/t3code#24609
* feat(server): devices on the same network reach background services over LAN by @t3dotgg in pingdotgg/t3code#24588
* fix(codex): bound pending requests after app-server stdout closes by @juliusmarminge in pingdotgg/t3code#24600
* fix(cursor): discover built-in skills in the composer by @juliusmarminge in pingdotgg/t3code#24595
* fix(server): retry provider event writes after brief SQLite locks by @juliusmarminge in pingdotgg/t3code#24571
* fix(server): settle inactive threads with persisted open PR links by @juliusmarminge in pingdotgg/t3code#24617
* feat(mobile): Settings is a workspace tab, and tabs never open on an empty detail by @juliusmarminge in pingdotgg/t3code#24524
* feat(mobile): the folded Duo opens search from a search tab below the tab bar by @juliusmarminge in pingdotgg/t3code#24360
* revert(mobile): take the folded Duo search tab back out for now by @juliusmarminge in pingdotgg/t3code#24626
* fix(web): preview images outside the workspace by @juliusmarminge in pingdotgg/t3code#24627
* fix(threads): Working holds only background work the agent waits on by @t3dotgg in pingdotgg/t3code#24610
* fix(web): switched threads and Scroll to end land on the real end by @t3dotgg in pingdotgg/t3code#24622
* fix(web): attach DNG originals as generic files by @juliusmarminge in pingdotgg/t3code#24625
* fix(server): surface failed runtime request answer delivery by @juliusmarminge in pingdotgg/t3code#24616
* fix(preview): report navigation timeout accurately by @Ethereal49 in pingdotgg/t3code#24356
* fix(web): give PR row metadata room below the title by @juliusmarminge in pingdotgg/t3code#24553
* fix(mobile): center floating status actions by @PixPMusic in pingdotgg/t3code#24526
* fix(mobile): the iPhone new-chat button sits beside the tab bar on iOS 26.6 by @wojas in pingdotgg/t3code#24492
* fix(web): signed-out T3 Connect shows a Sign in button by @t3dotgg in pingdotgg/t3code#24636
* perf(mobile): stop menu fault logging and whole-list re-renders on Home by @juliusmarminge in pingdotgg/t3code#24646
* fix(mobile): preserve syntax and navigation in large diffs by @juliusmarminge in pingdotgg/t3code#24276
* perf(shell): cold connects skip idle subagent threads, a third of the thread list by @t3dotgg in pingdotgg/t3code#24648
* feat: share a thread with a guest who watches live and sends messages you approve by @t3dotgg in pingdotgg/t3code#24104
* fix(antigravity): say when sign-in cannot reach Google by @t3dotgg in pingdotgg/t3code#24652
* fix: canonicalize git.cafe pull request links by @maria-rcks in pingdotgg/t3code#22634
* feat(mobile): manage labels, edit PRs and comments, react, and refresh checks live by @juliusmarminge in pingdotgg/t3code#24602
* perf(web): highlight code with @pierre/highlights instead of Shiki by @maria-rcks in pingdotgg/t3code#23178
* perf(server): thread opens and live events send each message body once by @t3dotgg in pingdotgg/t3code#24647
* refactor: halve code comments and cap comment blocks at four lines by @maria-rcks in pingdotgg/t3code#21514
* chore: fit three comments to the four-line cap so lint passes on main by @t3dotgg in pingdotgg/t3code#24675
* fix(claude): ENABLE_TOOL_SEARCH=auto no longer costs 10x cache writes per session by @t3dotgg in pingdotgg/t3code#24654
* perf(orchestration): thread snapshots send each long ID once by @t3dotgg in pingdotgg/t3code#24643
* fix(mobile): keep visible diff files highlighted by @juliusmarminge in pingdotgg/t3code#24682
* fix(mobile): Settings split view keeps its selection and its header matches the page by @juliusmarminge in pingdotgg/t3code#24644
* fix(mobile): every Duo header blurs under the vertical bar, from one place by @juliusmarminge in pingdotgg/t3code#24671
* feat(settings): follow-ups steer the running turn by default by @maria-rcks in pingdotgg/t3code#24718
* fix(mobile): Duo threads no longer open shifted left by @juliusmarminge in pingdotgg/t3code#24732
* fix(mobile): PR links open in the app the same way they do on web by @juliusmarminge in pingdotgg/t3code#24738
* feat(web): command palette search finds options inside submenus by @maria-rcks in pingdotgg/t3code#24709

## New Contributors
* @Ethereal49 made their first contribution in pingdotgg/t3code#24356
* @wojas made their first contribution in pingdotgg/t3code#24492

**Full Changelog**: pingdotgg/t3code@v0.1.3-nightly.20261024.3643...v0.1.3-nightly.20261025.3655

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.1.3-nightly.20261025.3655
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant