Repository navigation
Adapt the app for iPhone Duo - #172
Open
cmcmanus96 wants to merge 47 commits into
Open
cmcmanus96 wants to merge 47 commits into
cmcmanus96 wants to merge 47 commits into
Conversation
On a window of at least 840pt (an iPhone Duo's inner display held sideways) the log, notifications, remotes, profile, search and commit pages draw their lists in a centred 672pt column instead of running across the whole display, and onboarding puts the lockup and the sign-in actions side by side. Narrower windows, including the closed display, keep the full-width layouts. The column takes a fixed expanded width rather than max-w: EDGE stretches a w-full child across its parent regardless of items-center, and its scroll-view lays children out leading, so the commit page wraps its column in a centring one. Co-authored-by: Connor McManus <cmcmanus@termbid.com> Amp-Thread-ID: https://ampcode.com/threads/T-01a1a7bf-8563-75c2-9973-a03b3476f392
On an iPhone Duo the system's rail and status column give a horizontal pager a trailing safe-area inset. NativeUIPagerRenderer sized pages by the ScrollView's width but measured progress against visibleRect, which takes in the inset, so the tab underline lagged further behind each later tab, landscape Notifications could select the wrong filter, and the next page drew under the rail. Apply a patch to nativephp/mobile-ui 0.9.0 through cweagans/composer-patches: divide by the ScrollView's bounds width and clip the pager's sides only, leaving the bottom extension under a tab bar and vertical pagers as they were. Co-authored-by: Connor McManus <cmcmanus@termbid.com> Amp-Thread-ID: https://ampcode.com/threads/T-01a1a7bf-8563-75c2-9973-a03b3476f392
Preserve the side-inset width and clipping fixes while reconciling horizontal pager geometry after background resizing. Retain recovery through tracking, accept genuine inactive swipe completion, and reconcile late native acknowledgements without overriding newer selection intent. Validated exact cand12 renderer SHA1: 5659b5b4395cdb01f710483cf2f7a2b79683d654. Independent Astra source and native-evidence review found no additional concrete blocker. Controlled iOS 27.2 lifecycle races, clean Log/Profile warm resume, 30 extracted-handler checks, and the PHP suite were verified. Physical iOS 27.3 and active fold/rotation retention remain unverified. Amp-Thread-ID: https://ampcode.com/threads/T-01a1ac67-a129-7066-90e3-bf362ac34436 Co-authored-by: Connor McManus <cmcmanus@termbid.com>
Classify geometry before selection reporting and retain resize intent across late clamps with bounded, cancellable corrections. Preserve warm-resume recovery and side-inset paging fixes. Amp-Thread-ID: https://ampcode.com/threads/T-01a1ac67-a129-7066-90e3-bf362ac34436 Co-authored-by: Connor McManus <cmcmanus@termbid.com>
Use an iOS adaptive native host with stable conversation slots, compact navigation, pane-local composer insets and revision-checked native commands. Coordinate retained panes for outbox settlement, optimistic stars, deletion and failed-draft recovery without duplicating side effects. Preserve exact attached child receivers for async completion through a focused mobile patch. Cover retained state, stale commands, shared writes and recovery/resubmission with component regressions. Amp-Thread-ID: https://ampcode.com/threads/T-01a1ac67-a129-7066-90e3-bf362ac34436 Co-authored-by: Connor McManus <cmcmanus@termbid.com>
Preserve the Duo readable-column constant alongside the upstream timezone fix, and retain both dependency sets with a Composer-generated combined content hash. Duo workspace and display checks pass (58 tests). Eight new upstream Remotes pan API tests also fail on an unchanged upstream control with the same locked packages; they remain intact and recorded separately. Amp-Thread-ID: https://ampcode.com/threads/T-01a1ac67-a129-7066-90e3-bf362ac34436 Co-authored-by: Connor McManus <cmcmanus@termbid.com>
Recover malformed native tab-container layout after transitions, preserve retained-page geometry, and add unselected conversation guidance with background-only bottom coverage. Amp-Thread-ID: https://ampcode.com/threads/T-01a1ac67-a129-7066-90e3-bf362ac34436 Co-authored-by: Connor McManus <cmcmanus@termbid.com>
Preserve keyboard avoidance and retained control insets while allocating the full container to the adaptive workspace. Amp-Thread-ID: https://ampcode.com/threads/T-01a1ac67-a129-7066-90e3-bf362ac34436 Co-authored-by: Connor McManus <cmcmanus@termbid.com>
Who they are, their activity and their level scroll in a leading column and the tabs and their commits in a trailing one, with the gap centred on the fold. The closed display keeps the single column.
… the buttons' margin
The split now changes mode on the horizontal size class instead of a width breakpoint and an orientation check, and parts its columns on the fold's division region, an even split when no region crosses it. The workspace fills the window, so UIKit no longer suppresses the vertical bar for its navigation controllers: on the outer display the Log's search and compose and a commit's back button move into the trailing bar like every other screen. Each column measures its own horizontal safe area, since a navigation controller passes the window's insets to pages that never reach the bar. The feed row whose conversation is open beside it is selected, the split drops its Close button, and the empty detail is a themed EDGE placeholder with an icon, title and hint.
…ar's readable column, and label the muted sheet button on its circle Android resolves a percentage against the parent, so the 40% variant shrank its lockup; it is iOS-only now. Android seats a bottom bar's root at the leading edge, so the reply bar keeps a full-width root and centres a readable child. An a11y-label on a stack already hides its children from VoiceOver, so the muted sheet button needs no wrapper.
The hinge handler re-read layout only when the hinge status changed, assuming the division region turns active at the same moment. It now compares the fold gap with the one last applied and lays out only when it differs. The placeholder tag shares the detail renderer instead of a copy, the safe-area comment describes the vertical bar as observed behavior, and the selected-row docblocks cover the stacked layout too.
The two-column profile drew the header twice, a hidden copy for each layout, so every profile, phones included, sent it twice on every render and every ref in it appeared twice. The pager now draws its own header beside the pages from the `expanded` breakpoint up (iOS), splitting at the fold it reads from reservedRegions, or evenly without one, so the header is in the tree once and the 11:9 weights and the hairline go away. Android keeps the header above the tabs.
Crossing the header-beside breakpoint (unfolding, Split View) switched the pager between two layouts, so SwiftUI rebuilt its paging scroll view and every page's list. The pager now has one layout that shows or hides the header column and the tab row around the same scroll view, and the header's scroll tracking forgets the lists while the header is beside them, so it finds them again where they are once it is back. The columns now meet on the fold's midline while the device is flat and stop at the fold's margins only when it is partly folded, as the feed workspace does. The breakpoint falls back to the available width before the window has one, as the responsive classes do, so a cold start lays out once. The reservedRegions call no longer needs a compiler gate.
Moving the header beside the pages took the header out of each page's list and switched the list's modifiers to another branch, so SwiftUI made a new UICollectionView for every list on each fold and unfold, and each list opened at the top. The pages now get the header either way and only draw it while it is in the list: the header row and the tabs' section stay, empty while the header is beside the pages, so the list keeps its rows, its sections and its scroll view, as it does at base. After the header is back, a list found before the one the header follows waits for it, instead of being levelled against a header the reset has put back at the top.
Lists, scroll views and pagers stopped at the safe area, so beside the iPhone Duo's vertical bar their indicator sat just inside the bar, where Apple's Settings and News put it at the screen's edge. A vertical list or scroll view now runs under the horizontal safe area beside it and hands that area back to its content as safe area: rows, separators and controls stay where they were, while the indicator and full-bleed backgrounds reach the edge. A horizontal pager does the same for its pages, which span the bar's inset, so its lists can too; it clips its sides at those edges. The Duo workspace gives each column its measured insets as safe area instead of padding, so the same applies inside it. A column that stops at the fold has no inset there and keeps its indicator at its own edge.
The workspace parted its columns at the unrounded middle of a display without a fold, while the pager rounded it. Both now round, and each copy of the fold rule names the other, since the app's plugin shouldn't depend on a symbol a vendor patch adds.
The division region can turn active just after a hinge update, which left the columns parted at the flat fold until something else laid them out.
cmcmanus96
marked this pull request as ready for review
November 8, 2026 04:42
Regular width alone split the unfolded display in portrait into two skinny columns. Like Apple's arrangements, weigh the window's aspect ratio as well: only a window wider than it is tall gets the split.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adapts the app to iPhone Duo, following Apple's guidance for its two displays.
Android is unchanged apart from the Remotes empty-state wording.
Vendor patches
Five Composer patches change NativePHP code: two small fixes to
mobile, and three changes to themobile-uipager and scroll views (about 1,100 lines). The last two add behaviour that belongs in mobile-ui, so we'd like to send them upstream and drop the patches here.Testing
main.Screenshots
iPhone Duo simulator (iOS 27.2) and Pixel 9 emulator.
log,profileandremotesare unfolded;log-foldedis the outer display.log
log-folded
profile
remotes
Earlier rounds
Split-view and conversation captures use
f2ff64e; toolbar captures use56a57fa(before:f2e38d0). Earlier before images show an intermediate contribution build, not upstream; its exact commit is unknown. Android captures and some dark before images are unavailable.Landscape split view
Portrait conversation
Landscape → portrait → tap Back to Log: light video · dark video.
Toolbar alignment
Composer, rail and shared-header follow-up
Composer recordings use
1188ded; rail captures useea3f91a.Composer swipe dismissal: before · after light / dark.
Shared header surfaces (
b143b20); Profile/Remotes before images are user-provided header crops.Light captures: Notifications, Remotes, Profile.
Separator sweep
After captures use
b32201b; before images are user-provided crops (New commit: dark; New remote/New org: light).Ordinary-iPhone keyboard validation
iPhone 17 simulator, iOS 26.5,
b32201b; unsent Reply retains text and caret through rotation and stays above the keyboard. This does not verify the Duo rail or folding transitions.