Repository navigation
Fix iOS 27 reader defects and speed up CI - #851
Merged
Merged
Conversation
- History: move "Load Earlier 200 Lines" out of the stack of selectable text rows. On iOS 27 a button in that stack gets no taps. - Live reading: restore a pane's position from a row anchor (row and offset in the row), not from an absolute scroll offset. On iOS 27, LazyVStack estimates the heights of rows that are not laid out, so the same offset showed different text after a pane switch. The document view is now one flat LazyVStack of rows, with each chunk of a long text block as a row, and it reports the row tops.
- Discard the result of resignFirstResponder() explicitly. - Make the test UIKey on the main actor and inject it into the test UIPress. UIPress is not isolated to the main actor, so its key getter cannot make a UIKey.
- Add the test-progress heartbeat to the iOS mirror test targets and keep the raw xcodebuild log next to the result bundle. xcsift reports only at the end, so a long CI run showed no output for 20 minutes. - Upload the result bundle and the log also when the job is cancelled, for example at its timeout. - select_ios_simulator.py: evaluate annotations lazily. The Python 3.9 that macOS includes failed on `X | None`.
- Move the iPhone initial split-view layout decision into ContentView.initialLayout(for:) and test it with a unit test. The UI test for it could run only on iPhone and is removed. The other UI tests that ran on iPhone also pass on iPad. - The fixture Host confirmed a held scroll after 2 seconds, and on a slow runner the test checked the loading state after that. Now the fixture holds each scroll until the test posts a Darwin notification (--mirror-ui-scroll-hold-fixture). MirrorSession takes the scroll confirmation timeout as a parameter, and the fixture makes it longer.
- Move the iOS and Android mirror jobs to their own workflows. They run for changes to their client (and Mirror/Shared for iOS) and for v* release tags. The Mac app workflow does not run for changes to only the mirror clients. - CI runs only the iOS mirror unit tests (make test-mirror-ios-unit). The UI suite takes minutes on a hosted runner and runs locally with make test-mirror-ios, on one iPad simulator. - Turn off simulator diagnostics collection for the mirror tests. It waited 10 minutes for the simulator after a passing run.
- The compilation cache step cached ~/Library/Developer/Xcode/ DerivedData/CompilationCache.noindex, but with -derivedDataPath Xcode keeps the CAS in build/ci-derived-data. The step never restored or saved anything, so each miss of the App incremental cache compiled everything (about 10 minutes). Remove it. When the incremental cache misses, restore the newest App build state for the same Xcode and keep only its CAS, so that the clean build replays compiler outputs. - Restore caches with actions/cache/restore and save them at the end of the job with the new save-macos-caches action. Only main saves the large SwiftPM, CLI and App entries: pull request entries filled the 10 GB quota and evicted the entries of main. Pull requests still save the small Mise and GhosttyKit entries.
After an incremental build, the first xcodebuild test pass on CI waits 1.5 to 2 minutes before its first test suite runs; later passes start in seconds. make test-app now exports each pass's test action log, and the CI summary shows when the test runner launch and the first test suite start, so that the cause of the wait can be found and watched.
They need no build, so they run in the parallel step with the script tests (the new checks task) instead of before the build.
Add 016.008: the dead CAS cache path, the CAS fallback, the cache save scope, the mirror client workflows and the measured job times. Correct the CAS note in 016.007. The test action log shows that the apparent wait before the first test suite was output buffering.
Release tags are lightweight. git push --follow-tags does not push them, and gh release create makes them on GitHub, which sends no tag push event. The v2026.9.29 release shows only the main push and the release event.
make test runs only the Mac app tests, but AGENTS.md described it as all tests. test-all runs the script, CLI, Mac app, iOS mirror (unit and UI) and Android mirror suites one after another, so that make -j cannot start two xcodebuild runs in the same checkout.
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.
Summary
Fixes two iOS 27 defects in the iOS mirror reader, and makes CI faster and cheaper: the mirror clients run only when they change, CI runs no iOS UI tests, and the Mac app job gets a working compilation cache fallback.
iOS 27: "Load Earlier 200 Lines" does not respond
On iOS 27, a
Buttonin the same stack as.textSelection(.enabled)text rows gets no taps. The history view put the button as the first child of theLazyVStackthat holds the selectable lines (a plainVStackalso fails; removing the text selection makes it work). The button now sits outside the stack of selectable rows. Text selection is unchanged.iOS 27: live reading position moves after a pane switch
The reading view restored an absolute
ScrollPosition(y:), butLazyVStackestimates the heights of rows above the visible area that it has not laid out. On iOS 27 the same offset then shows different text (17–31 pt off; exact on iOS 26.5).The live reader now stores an anchor: a document row and the distance from the top of that row to the top of the visible area.
MirrorDocumentViewis one flatLazyVStackof rows: a code or table block, a short text block, or one 32-line / 4096-scalar chunk of a long text block. Each row reports its top in the scroll content space.MirrorSession.liveReadingAnchorreplacesliveReadingOffset; it is@ObservationIgnoredbecause the reader writes it on every scroll.main).The history view still restores an absolute offset (single-line rows, so the estimates are exact for the fixture).
iOS mirror tests
userInterfaceIdiom == .phonebranch of the initial split-view layout, which cannot run on iPad. That decision is nowContentView.initialLayout(for:)with a unit test, and the UI test is removed. The other "iPhone" UI tests pass on iPad, so all UI tests run on one iPad simulator.testRemoteScrollShowsLoadingAndKeepsHistoryIndependentwas flaky: the fixture Host confirmed the scroll after 2 s (and the app gives up after 5 s), and on a slow runner each XCUITest query took about 3 s. The fixture now holds each scroll until the test posts a Darwin notification (--mirror-ui-scroll-hold-fixture), and its confirmation timeout is longer.MirrorSessiontakes the timeout as a parameter (default 5 s).make test-mirror-iosruns the unit and UI tests on iPad;make test-mirror-ios-unitruns only the unit tests. Both print a timestamped heartbeat and keep the raw log next to the result bundle.select_ios_simulator.pyworks with the Python 3.9 that macOS includes.resignFirstResponder()result; a main-actorUIKeyinit in a testUIPressoverride).CI
test(Mac app)Mirror/iOS/,Mirror/Android/or their workflowsmirror-iosMirror/iOS/,Mirror/Shared/or its workflow, and each published release. Unit tests onlymirror-androidMirror/Android/or its workflow, and each published releaseThe UI suite takes minutes on a hosted runner (each test launches the app; the runner is 3–5x slower than a Mac mini), so it runs locally with
make test-mirror-ios.Mac app job:
~/Library/Developer/Xcode/DerivedData/CompilationCache.noindex. Because the build uses-derivedDataPath, Xcode keeps the CAS inbuild/ci-derived-data, so that path never existed and the step never restored or saved anything. Each miss of the strict App incremental cache (changed Makefile, workflow, file roster, …) therefore compiled everything without a cache: about 10 minutes in the previous run of this PR. The step is removed. When the incremental cache misses, the job now restores the newest App build state for the same Xcode, keeps only itsCompilationCache.noindex, and builds clean against that warm CAS. This uses existing cache entries and needs no more cache space.actions/cache/restoreand saved at the end of the job by a newsave-macos-cachesaction. The large SwiftPM, CLI and App entries are saved only frommain. Each pull request run used to save about 1.6 GB, which pushed the repository over its 10 GB quota and evicted the entries ofmainthat all pull requests restore. It also removes about 50 s of cache upload from pull request runs. Mise and GhosttyKit entries are small and slow to rebuild, so pull requests still save them.make lintandmake check-localizationrun in the parallel step beside the build (newcheckstask with the script tests).make test-appexports each pass's test action log; the CI summary shows when the test runner launch and the first test suite start. (The test-progress heartbeat looked like a 2-minute wait before the first suite; the action log shows it is xcodebuild output buffering.)docs-ai/016-dev-build-and-ci-workflow/008-cas-fallback-and-cache-scope.mdrecords the cache findings and measurements, and corrects the CAS note in 016.007.Measured
buildjob on this pull request (both runs missed the strict App incremental key, because this pull request changes the Makefile and the workflows):buildjobVerification
MirrorDocumentTestsrow test,MirrorReadingAnchorTests(3),MirrorInitialLayoutTests(2);MirrorSessionTestschecks that a connection change clears the anchor.make test-mirror-ios, iPad, iOS 27): 86 passed, 0 failed (unit and UI).make test-mirror-ios-unit: 67 passed in 28 s.main, with the same per-test durations.make check,make test-scripts,actionlinton the changed workflows.build,mirror-iosandmirror-androidpass.