Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
xcodebuild -resolvePackageDependencies -workspace swiftchan.xcodeproj/project.xcworkspace -scheme swiftchan -clonedSourcePackagesDirPath build/SourcePackages -onlyUsePackageVersionsFromResolvedFile

- name: Build and test
timeout-minutes: 20
timeout-minutes: 25
run: bash ci_scripts/test_with_diagnostics.sh

- name: Upload test results
Expand Down
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@

Swiftchan is a SwiftUI imageboard app using MVVM, SwiftData, and Swift Package Manager. The app requires iOS 18.0 or later. Video playback uses KSPlayer and FFmpegKit; CocoaPods and MobileVLCKit are no longer used.

## iPad testing

Read [docs/ipad-testing.md](docs/ipad-testing.md) for regular-width reading/workspaces, native orientation preparation, fixture safety, and simulator videos. Check iPad mini portrait and iPad Pro landscape, and adapt to the available window. Capture native main display 1 and verify actual window/PNG geometry. Use `uv run` for Python helpers; keep `CLAUDE.md` linked to this file.

## iPhone Duo testing

Read [docs/iphone-duo-testing.md](docs/iphone-duo-testing.md) before Duo layout, pose, or recording work. Native `agent-device@0.21.20` hinge control is verified for open, book, and closed; use the scoped Duo toolchain and confirm angle plus app-visible geometry. A manual tabletop quarter-turn is verified; automated physical rotation remains unverified. Capture the lit panel explicitly and distinguish live continuity from saved-state reopening. Use `uv run` for Python helpers. Keep `CLAUDE.md` as the compatibility symlink to this file.

Keep the catalog's OP card grid in both compact navigation and board/thread workspaces. Compact phones, the closed Duo, and an active native Duo division use two columns. With the Duo flat or on iPad, derive additional columns from the catalog pane's available width; a narrow iPad sidebar may still fit only two. Check the real two-to-more-to-two transition without relaunching or losing the selected thread and visible reply.

## Setup and validation

- Use Xcode 26.6 (CI uses this version); updated packages require at least Swift 6.2 / Xcode 26.
Expand Down Expand Up @@ -43,5 +53,6 @@ KSPlayer is intentionally pinned to `bb6d367c02f3247ef3f328dc0cfea34e77aef7c5` f
- UI tests live in `swiftchanUITests/`. The shared scheme runs `BrowsingUITests` for following/editing generals, link validation, reading restoration, and unread navigation. These tests use a debug-only in-memory favorites store (`--ui-testing`); Thread UI tests opt into a deterministic debug-only fixture with `--ui-thread-fixture`; older live-network UI tests remain excluded.
- Add focused regressions for behavior fixes. Prefer fixtures and injected loaders over live network dependencies.
- GitHub Actions builds and runs unit and focused UI tests for pull requests.
- The build/test step has a 25-minute limit within the 30-minute job, allowing cold dependency compilation plus the full UI suite. Gallery paging returns to the post associated with the selected media; test that post after dismissing the gallery instead of expecting the first post to remain onscreen.
- `bundle exec fastlane beta` signs and uploads a TestFlight release, then bumps the build number. Run it only when releasing is requested.
- Certificates are managed with Fastlane match (`make renew_certs` / `make get_certs`).
32 changes: 32 additions & 0 deletions docs/ipad-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# iPad layout and simulator review

Read Apple's complete [Designing for iPadOS](https://developer.apple.com/design/human-interface-guidelines/designing-for-ipados) guidance and [Supporting multiple windows on iPad](https://developer.apple.com/documentation/uikit/supporting-multiple-windows-on-ipad) before changing the layout. Design for the available window, size class, safe areas, and text size, rather than treating device orientation as the layout model. Check portrait, landscape, and narrow windows. These apps have not gained new multiple-window support in this pass.

## Native simulator capture

The review uses iPad mini (A17 Pro) portrait (744 x 1133 pt) and iPad Pro 13-inch (M5) landscape (1376 x 1032 pt), on iPadOS 27.0. Standard iPad uses main display 1. Verify the device with `xcrun simctl list devices` and its displays with `xcrun simctl io <UDID> enumerate`; do not reuse Duo inner display 3.

An XCTest preparation sets `XCUIDevice.shared.orientation` to `.portrait` or `.landscapeLeft`, launches the app, and waits until the actual app window has the requested aspect ratio before starting recordVideo. Then the walkthrough navigates the real app. Native screenshots must have the same requested orientation: mini portrait 1488 x 2266 pixels, Pro landscape 2752 x 2064 pixels. Do not rotate a PNG/video to claim a native orientation passed.

```sh
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcrun simctl io <UDID> recordVideo --codec=h264 --display=1 review.mp4
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcrun simctl io <UDID> screenshot --display=1 review.png
```

Use a scoped DEVELOPER_DIR; do not change global xcode-select. Native Duo APIs require the 27.1/27.2 toolchain described in [iphone-duo-testing.md](iphone-duo-testing.md). The iPad walkthroughs may use that same built binary but run on an ordinary iPad simulator. A passing CLI orientation command alone does not prove app geometry.

Python capture/package helpers run with `uv run --with pillow python`. Run one UI walkthrough per simulator at a time. Package only actual nonempty passing test cases and passing selected suites; reject assertions, empty test restarts, missing/black screenshots, or incomplete videos. Keep original pixels for PNGs. A review MP4 can be scaled for size, without rotating, cropping, or recreating the UI. Verify videos with a full FFmpeg decode and native AVFoundation playback/frame decode.

## App behavior and validation

Regular-width windows at least 580 pt wide use a board and thread workspace. Compact windows retain pushed thread navigation. Keep catalog selection visible, the selected thread readable, and the catalog title inline in the workspace. The sidebar uses the same OP card grid as compact navigation; the detail shows the selected thread and its media/actions. Compact phones and active Duo divisions keep two catalog columns. Flat Duo and regular iPad layouts add columns when the actual catalog pane has room, with approximately 140 pt per card; a narrow iPad sidebar still uses two. Selected cards have an accent outline. On a native horizontal Duo division, the catalog sits above the thread, with the system division and both 20 pt margins kept clear. iPad uses the balanced NavigationSplitView. Thread scrolling tracks post identity with scrollPosition(id:anchor:), so a window resize can keep the reading post visible. Build 1.3 (4) is the app version.

Use debug-only in-memory catalog/thread/media fixtures. Check switching threads, returning to boards, gallery next-image/filmstrip/dismissal, favorites, settings, following a general, and post actions. The normal regression suite passes 95 unit tests and 20 UI tests on iPhone Air 26.5; pose-only cases skip there. After adding the post-ID scroll binding, 95 unit tests and eight focused quote, jump, resume, unread, recent-thread, and linked-post UI regressions pass on the final source. Mini portrait and Pro landscape walkthroughs both pass on that source.

Those counts describe build 3. Build 4 adds `testCompactCatalogKeepsTwoColumnsAndOpensThreads`, which checks real card frames and pushed navigation, and the opt-in `testDuoCatalogGridExpandsAndRefoldsWithoutLosingThread`. The native grid transition passes with two columns at 130 degrees, four at 180 degrees, and two after returning to 130 degrees, while thread 200 and reply 205 remain visible. Use `--ui-catalog-grid-fixture` for twelve invented catalog cards. Do not enable pose-test flags on ordinary CI phones.

## Review artifacts

The shared workspace review is `../iphone-duo-review/2026-10-04-current/review/`, with per-app `*-ipad-mini` and `*-ipad-pro` MP4/JSON/PNG packets. `validation.json` records passing cases and source provenance; use it to distinguish historical Duo footage from the current build. The local gallery is http://127.0.0.1:8179/ when its server is running. These artifacts do not indicate an App Store Connect or TestFlight submission.

The follow-up catalog grid review is `../iphone-duo-review/2026-10-04-grid/`. Keep its build 4 clips and validation separate from the earlier build 3 Swiftchan footage.
98 changes: 98 additions & 0 deletions docs/iphone-duo-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# iPhone Duo simulator testing

Verified locally on 2026-10-04 with Xcode Duo 27.1 (24A94403), the iOS 27.1 simulator (24A94401), and `agent-device@0.21.20`. This is local simulator evidence; physical-device and hosted-CI pose control have not been verified.

## Toolchain and tool versions

Scope the Duo toolchain to each command. Keep the normal Xcode selection intact:

```sh
export DEVELOPER_DIR=/Applications/Xcode-Duo-beta.app/Contents/Developer
xcrun simctl list devices available
```

Select the intended simulator by its full UDID. Our review simulator is `3C2C0377-69D7-432C-A82A-0DEA70E77A5B`; another machine must use its own. Swiftchan's current dependency build additionally needs the installed 27.2 beta toolchain; pose commands use the Duo 27.1 toolchain.

The previously installed XcodeBuildMCP CLI was 2.6.2. Upstream 2.7.0 adds Xcode 27 Device Hub support; 2.7.1 renames the package to **MobileBuildMCP**. Pinned `npx --yes mobilebuildmcp@2.7.1 --version` and its simulator inventory worked here. Its inspected tool inventory has no fold/pose setter. Updating that package alone does not provide hinge control. No global MCP configuration change is required for the commands below.

## Working native hinge control

`agent-device@0.21.20` sends a simulator HID hinge event and verifies the resulting angle with CoreDevice. It does not need Device Hub accessibility or host UI scripting. The upstream headless amendment supersedes its older Device Hub accessibility instructions.

First open an installed app to establish the session. A fold request without an active session was refused even when supplied a UDID:

```sh
npx --yes agent-device@0.21.20 open APP_BUNDLE_ID \
--session duo-review --platform ios --udid SIMULATOR_UDID --foreground --json
npx --yes agent-device@0.21.20 fold open --session duo-review --json
npx --yes agent-device@0.21.20 fold half-open --session duo-review --json
npx --yes agent-device@0.21.20 fold closed --session duo-review --json
```

Replace both uppercase placeholders. Run mutations sequentially. A successful local readback was:

| Command | Measured hinge angle | Lit panel | App viewport observed by the native probe |
| --- | --- | --- | --- |
| `fold open` | 180 degrees | `LCD-1` / display 3 | 951 x 669 pt; division inactive |
| `fold half-open` | 130 degrees | `LCD-1` / display 3 | 951 x 669 pt; active vertical division |
| `fold closed` | 0 degrees | `LCD` / display 1 | 466 x 678 pt; no division |

The book-pose division measured x=455.5, width=40, height=669 pt, with 20 pt leading/trailing margins. These numbers describe this test device, not layout constants. Production layouts must use the native reserved-region frame and margins. Re-query app window geometry and accessibility elements after every pose change; old coordinates and references are stale. CoreDevice's native panel size (669 x 951 pt here) is not the oriented app viewport.

When finished, end the session without shutting down the simulator:

```sh
npx --yes agent-device@0.21.20 close --session duo-review --json
```

The hinge helper depends on private simulator HID behavior and the selected simulator SDK. Keep the version pinned and verify readback after toolchain updates. Do not substitute `simctl io screenConfig power`: switching display power does not change the actual hinge state.

## Book, flat, and tabletop

**Flat** means fully open. **Book** means partially open with the hinge vertical in the app. **Tabletop** is the laptop-like pose with the lower half lying flat, the upper half raised, and the hinge horizontal. It is not a separate verified CLI preset: it needs a partial fold plus the corresponding physical quarter-turn.

`orientation portrait` and `orientation landscape-right` returned success here, but the live native probe still reported the same 951 x 669 pt viewport and active vertical division. Therefore automated tabletop rotation is **unverified**. Do not label a book capture as tabletop, or treat an orientation command's success message as proof. A tabletop check must observe an active horizontal division in the app and inspect the actual rendered controls. Use the simulator's physical rotation control when available, and retain this manual requirement until app-visible geometry confirms a working automation path.

The user's physical quarter-turn was verified on 2026-10-04: the native viewport became 669 x 951 pt, with an active horizontal division at y=455.5, height=40, width=669 pt, and 20 pt top/bottom margins. This proves the manual tabletop geometry; it does not prove an automated rotation command. Capture harnesses must preserve that orientation instead of setting landscape again during setup. Native arrangement views can then place primary and secondary content above and below the fold.

## Screenshots, video, and continuity evidence

Always name the lit display. On this simulator:

```sh
# Inner display: fully open or book.
xcrun simctl io SIMULATOR_UDID screenshot --display=3 inner.png
xcrun simctl io SIMULATOR_UDID recordVideo --codec=h264 --display=3 inner.mp4
# Outer display: closed.
xcrun simctl io SIMULATOR_UDID screenshot --display=1 outer.png
```

Stop `recordVideo` with SIGINT so the file is finalized. An implicit screenshot can capture the dark inner panel while closed and still exit successfully. Inspect actual image content and decode the video before delivering it. Use `uv run` for Python capture and validation helpers.

A single-panel recording can show a live flat/book transition, but does not prove a continuous inner/outer transition after the active panel switches. Record the correct panel(s) and disclose any recording gap. Verify a real passing test case and the final test suite, not just a zero exit code or a preparation screenshot. If a timed test has already ended, a later pose change cannot make its earlier video a continuity pass.

Distinguish these outcomes: a prepared state; saved state restored after reopening; an existing process reactivated after a pose change; and the same app staying foreground throughout a recorded fold. Assert visible content and interaction, not only an offscreen DOM title or a retained model identifier.

## Physical quarter-turn verification

On 2026-10-04 a scoped Xcode 27.1 XCTest set XCUIDevice.shared.orientation to landscapeLeft while the Duo was in Tabletop. Its real landscape-window expectation failed. The fresh native probe still reported viewport 669 x 951 and an active horizontal division at y=455.5, height 40, with top/bottom margins 20. Standard iPad XCTest orientation preparation passes; do not infer Duo physical rotation support from it. Native agent-device hinge changes remain verified. Use the Simulator's manual quarter-turn for Duo, then read native region geometry. This establishes the tested limitation of these current commands, not a claim that every future automation path is unavailable.

## App-specific checks

Use the workspace. Verify the board catalog beside the selected thread, a visible scrolled reply, and background/foreground restoration. Background snapshot geometry must not push a standalone thread route. Once the device exposes a division (including inactive regions), retain the native ArrangementView container across folding and background snapshots. Keep the remembered workspace mode while inactive. Use a post-ID scroll position binding so pane resizing keeps the reading post visible rather than preserving a stale pixel offset. Keep navigation outside the arrangement.

The live native Tabletop-to-flat check passes with thread 200 and reply 205 visible before and after background reactivation. All four app process IDs remain unchanged; the other assertions cover Bible chapters 3/4, Mango reading page 2, and advancing real Earmark playback. The Tabletop tour also passes catalog/thread switching, all primary routes, and media actions.

Build 1.3 (4) preserves the OP card catalog grid in the workspace. The closed outer display and an active native division keep two columns. Flat Duo uses the actual catalog pane width to add columns. `testDuoCatalogGridExpandsAndRefoldsWithoutLosingThread` passes a foreground 130-to-180-to-130-degree transition: two columns, four columns, then two, with thread 200 and reply 205 still visible. Run it with `TEST_RUNNER_CATALOG_GRID_CAPTURE=1`, preserve the verified manual tabletop quarter-turn, and use native hinge watchers at `CATALOG_GRID_READY_TO_UNFOLD` and `CATALOG_GRID_READY_TO_FOLD`. The twelve-card fixture is debug-only. The follow-up videos and native PNGs live in `../iphone-duo-review/2026-10-04-grid/`; earlier build 3 clips retain their original provenance.

Installed app bundle ID: `vanities.swiftchan`.

## Sources

Read the complete relevant Apple documents and upstream tool instructions when changing this workflow:

- [Apple: Designing for iPhone Duo](https://developer.apple.com/design/human-interface-guidelines/designing-for-iphone-duo)
- [Apple: Configuring the environment of a simulated device](https://developer.apple.com/documentation/xcode/configuring-the-environment-of-a-simulated-device)
- [Apple: Interacting with your app in the iOS or iPadOS Simulator](https://developer.apple.com/documentation/xcode/interacting-with-your-app-in-the-ios-or-ipados-simulator)
- [MobileBuildMCP 2.7.0](https://github.com/getsentry/MobileBuildMCP/releases/tag/v2.7.0) and [2.7.1](https://github.com/getsentry/MobileBuildMCP/releases/tag/v2.7.1)
- [Agent Device foldable-panel ADR](https://github.com/callstack/agent-device/blob/main/docs/adr/0025-foldable-apple-panels.md), especially the 2026-09-22 headless amendment and the accepted quarter-turn evidence gap
Loading
Loading