iPad P0 acceptance — human-must test cases (v1.0.0-rc.8)
This document captures the human-side verification pass for the
iPad P0 ship of Chanora. The build artefact is the same
Runner.app produced for iPhone (TARGETED_DEVICE_FAMILY = "1,2"
in Runner.xcodeproj — both iPhone (1) and iPad (2)). Apple's
free Personal Team provisioning profile includes iPad in the
universal family, so the same sideload that installs on an iPhone
installs on an iPad.
The officially-tested environment is iPadOS 17 or later on a
physical iPad, signed with the developer's personal team
(DEC-025 covers iPhone; this document extends P0 coverage to
iPad). Simulator support is best-effort — microphone capture and
AirPlay 2 routing cannot be assessed in the simulator.
Automated tests (cargo check, cargo test --workspace --lib)
sign off the mechanical side. This checklist signs off the user-
experience side. A failure on any row blocks promotion of the
iPad build from rc.8.
Source: this checklist extends ios-p0-acceptance.md with the
three iPad-specific rows (split-view multitasking, on-screen
keyboard occlusion, AirPlay 2 audio route) and removes the rows
that don't apply (background-audio behaviour is identical
because both share the same UIBackgroundModes = ['audio']
declaration in Info.plist). SDD references: SDD-094..097 (v1
audio + PTT lifecycle), DEC-025 (target environment), DEC-027
(diagnostic privacy invariant), SRS-197 (iOS/iPadOS audio
routing contract).
iPad does not have a global PTT capability — Apple's iOS /
iPadOS sandbox model has no session-level event-tap analogue.
The desktop PTT backend ladder (SDD-081..088) collapses to
FocusedPttBackend and the capability badge advertises
L0Focused. PTT works only while Chanora is the foreground app
(or the audio-background mode keeps streaming when Chanora is
backgrounded; see TC-8 / TC-13).
0. Pre-flight
| # |
Step |
Expected |
| 0.1 |
Same Xcode signing setup as iOS: in Runner.xcworkspace, Runner target → Signing & Capabilities → personal team selected, unique bundle id. (If you already did this for iPhone the iPad uses the same provisioning profile.) |
No code-signing errors in Xcode. |
| 0.2 |
Connect iPad via USB and trust the development Mac. On iPad: Settings → General → VPN & Device Management → developer-cert → Trust. |
iPad appears in Xcode's device picker. |
| 0.3 |
Build for iPad: in Xcode pick the iPad as the run destination + Runner scheme → Cmd+R. (Or via CLI: flutter build ios --release then sideload with xcrun devicectl device install app.) |
App installs on iPad and launches. |
| 0.4 |
First launch: iPad prompts for Microphone access — Allow. The prompt string matches NSMicrophoneUsageDescription from ios/Runner/Info.plist. |
Mic permission granted. |
| 0.5 |
About / version label inside Chanora |
Shows v1.0.0-rc.8. |
TC-1: Connect + identity persistence (DEC-006, SRS-201)
| # |
Step |
Expected |
| 1.1 |
Enter a reachable TS3 server address, a nickname, blank password |
Connect button enabled. |
| 1.2 |
Tap Connect |
Server snapshot appears. |
| 1.3 |
Quit (background) + relaunch with the same nickname |
Server-visible UID is the same (identity persisted in Documents/identity.tskey). |
TC-2: Voice channel join (SRS-204, SDD-094)
| # |
Step |
Expected |
| 2.1 |
Tap a voice channel |
Join works; voice bar shows the channel name. Mic capture starts (iOS status bar shows the orange mic indicator on iPadOS 17+). |
| 2.2 |
Tap a different voice channel |
Smooth move; no audio dropout. |
TC-3: PTT press / release (Focused only — iPadOS limitation)
| # |
Step |
Expected |
| 3.1 |
Open Voice settings (gear icon), select Push to talk |
Capability badge clearly states L0 Focused with explanatory text that iPadOS does not permit global hotkeys. |
| 3.2 |
The "Bound key" UI is replaced by an on-screen Push-to-Talk button. |
Touch and hold the button → mic indicator turns "Mic on". Release → indicator returns "Mic off" after the configured release-tail. |
TC-4: Release tail (SDD-097)
| # |
Step |
Expected |
| 4.1 |
Set release-tail to 0 ms; tap-and-release the PTT button |
Sharp on/off transition. |
| 4.2 |
Set release-tail to 500 ms; tap-and-release |
Mic stays on for ~500 ms after release. |
TC-5: Continuous mode
| # |
Step |
Expected |
| 5.1 |
Switch to Continuous mode |
PTT button disappears; mic stays open while in the voice channel. |
TC-6: Mic mute
| # |
Step |
Expected |
| 6.1 |
Mute mic via the Voice Bar |
Mic icon shows muted state. Touching the PTT button does NOT unmute. |
TC-7: Speaker mute
| # |
Step |
Expected |
| 7.1 |
Speaker mute |
Other clients silenced locally. |
TC-8: Background audio (UIBackgroundModes = audio)
| # |
Step |
Expected |
| 8.1 |
While connected + in a voice channel + in Continuous mode, swipe Chanora to the App Switcher (or press the home button if your iPad has one). |
The iPadOS orange mic indicator stays on; voice continues to be received and sent. |
| 8.2 |
Return to Chanora |
Session intact; no reconnect cycle. |
| 8.3 |
Lock the iPad for 30 s, then unlock |
Session still intact (provided you stayed in the channel and on Wi-Fi/cellular). |
TC-9: AVAudioSession routing (SRS-197)
| # |
Step |
Expected |
| 9.1 |
Connect AirPods / Bluetooth headset; talk on Chanora |
Audio routes to the headset (.allowBluetooth + .allowBluetoothA2DP are set in AppDelegate.swift::application(_:didFinishLaunchingWithOptions:)). |
| 9.2 |
Disconnect the headset mid-call |
Audio routes back to the built-in speakers (.defaultToSpeaker). |
| 9.3 |
(If iPad has FaceTime configured) place a FaceTime call to yourself or have someone call. |
Chanora's audio session yields to the FaceTime call. |
| 9.4 |
End the FaceTime call |
Chanora resumes audio without manual reconnect. |
TC-10: Reconnect
| # |
Step |
Expected |
| 10.1 |
Toggle Airplane Mode on for 10 s, then off |
Connection-lost banner appears, then recovers. |
TC-11: Diagnostic export + privacy (DEC-027)
| # |
Step |
Expected |
| 11.1 |
Export a diagnostic bundle |
Allow-listed fields only; no raw key labels, no audio bytes. |
TC-12: Capability badge label
| # |
Step |
Expected |
| 12.1 |
Capability badge |
Shows L0Focused with explanation that iPadOS does not expose global hotkeys. |
TC-13: iPad-specific — wide-mode landscape layout
The Flutter UI uses a LayoutBuilder 840 dp breakpoint
(apps/chanora_flutter/lib/main.dart). On iPhone in portrait the
body is below 840 dp, so the layout is stacked (Voice Bar on top,
channel tree below). On iPad in landscape the body is well above
840 dp, so the layout splits into a 320 dp left column (banner +
Voice Bar) and the rest filled by the channel tree.
| # |
Step |
Expected |
| 13.1 |
Rotate iPad to landscape, connect to a server, double-click into a voice channel. |
Voice Bar pinned to a 320 dp left column. Channel tree fills the rest. The "not production ready" banner sits inside the left column above the Voice Bar (per the earlier left-column-banner change). |
| 13.2 |
Rotate iPad to portrait. |
Layout flips back to the stacked iPhone-style arrangement. Voice Bar on top full-width, channel tree below. Banner pins full-width at the top above the connection status. |
| 13.3 |
In landscape, join a channel with a very long name. |
The channel-name pill in the Voice Bar truncates with an ellipsis instead of overflowing under the channel tree. Mute icons stay anchored to the right edge of the left column. |
TC-14: iPad-specific — Split View / Slide Over
Multitasking with other iPad apps. Our scaffold currently sets
UIApplicationSupportsMultipleScenes = false in Info.plist,
so Split View is not supported in P0. This row exists to
document that and ensure the app doesn't crash when iPadOS tries.
| # |
Step |
Expected |
| 14.1 |
In landscape, swipe up from the iPad home indicator → drag Chanora to the right edge to enter Split View, or pick "Slide Over" from the iPadOS multitasking menu. |
iPadOS shows the Split View / Slide Over UI but Chanora remains full-screen and does not crash. The non-support is by design for P0; a P1 follow-up will switch to multi-scene. |
TC-15: iPad-specific — AirPlay 2 audio route
| # |
Step |
Expected |
| 15.1 |
While in a voice channel, open Control Center → AirPlay → pick an AirPlay 2 speaker (HomePod, Apple TV, AirPlay-compatible receiver). |
Audio routes to the AirPlay destination. The PTT-active indicator continues to work; the mic stays routed to the iPad built-in mic (AirPlay 2 receivers don't typically have a return mic channel). |
| 15.2 |
Reselect "iPad" as the audio route |
Audio returns to local routing. No restart needed. |
Auto-test sign-off
These rows do not require human interaction. The values are the
same as the iOS document — iPad uses the identical Rust + Flutter
build artefacts.
| Layer |
Result |
cargo check --workspace on macOS host |
clean |
cargo test --workspace --lib on macOS host |
unchanged (iPad-specific Rust code does not exist; iOS-targeted code is #[cfg(target_os = "ios")]-gated and applies identically to iPadOS) |
cargo build --release --target aarch64-apple-ios -p chanora_bridge |
clean — produces libchanora_bridge.a |
flutter build ios --release |
clean — produces Runner.app ≈ 17 MB (same artefact as iPhone) |
Runner.xcodeproj TARGETED_DEVICE_FAMILY = "1,2" |
Universal family confirmed via grep TARGETED_DEVICE_FAMILY ios/Runner.xcodeproj/project.pbxproj. |
Sign-off form
Known gaps (not blockers)
- Split View / Slide Over multitasking is intentionally
unsupported in P0.
UIApplicationSupportsMultipleScenes stays
false until we wire iPadOS multi-scene handling (P1).
- Apple Pencil + external keyboard shortcuts: not wired. A
paid Developer Program account would let us declare
GCKeyboard hardware-keyboard support for things like Cmd+M
to mute — P1 follow-up.
- Stage Manager (iPadOS 16+): app runs but the layout is not
optimised for resizable Stage Manager windows. P1.
- TestFlight + App Store distribution: requires the paid
$99/yr Apple Developer Program. P1 follow-up.
- Simulator audio: cpal-via-CoreAudio inside the iPad
simulator routes to the Mac's audio devices and is fine for
smoke-testing the UI, but real microphone capture / AEC
behaviour and AirPlay 2 routing cannot be assessed there.
Test on a physical iPad.
- VAD (
TransmitMode::VoiceActivity) is intentionally
disabled in P0 (DEC-030).