iPhone app: verified against a Frame from the Simulator

Adds debug-only test hooks (open on a tab, run page JS, leave the tunnel URL in
Caches) used to drive the app in the Simulator, and records what was verified:
all four tabs with live data, capture and 31 fps live video in WKWebView, upload,
install jobs and the power password check through the app's tunnel.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
saphidandClaude Opus 5.5 committed 2026-09-27 15:26:23 +10:00
1 parent fd3a25434d
commit db75d1ca71
4 files changed
+36 -5

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

+18 -4
View File
@@ -67,7 +67,21 @@ signing team in Xcode (Signing & Capabilities).
## Verified ## Verified
In the iOS Simulator (iOS 26.5) and against a Frame (2026-09-27): the page and <img src="img/iphone-tabs.jpg" alt="The four tabs in the iPhone app, connected to a Frame" width="900">
all its reads, headset capture, live video, and file upload through the
on-Frame server. Not yet exercised: pairing with the password, Android display In the iOS Simulator (iOS 26.5) against a real Frame, 2026-09-27: the app connected
changes through podman, and power actions. with its key, copied the bundle over SFTP, started the server on the Frame and
showed all four tabs with live data. In the app's web view, Capture returned a
headset still and Live played H.264 video at 31 fps (WebCodecs works in
WKWebView). Through the app's tunnel: status, games, Steam library, Android apps,
screenshots, a file upload (checked on the Frame), a background install job, and
the power password check (a wrong password is refused). The server on the Frame
exits within seconds of the app closing.
Not yet exercised: pairing with the password (only the add-the-key route),
Android display changes through podman (no Android app was running), a real
sleep/restart/shut down, and a physical iPhone.
Debug builds have Simulator test hooks (`FRAME_TEST_HOST`, `FRAME_TEST_PAGE`,
`FRAME_TEST_JS`, and the tunnel URL in the app's Caches folder); release builds
don't.
+10 -1
View File
@@ -178,7 +178,16 @@ final class AppModel: ObservableObject {
self.server = server self.server = server
self.forwarder = f self.forwarder = f
readySince = Date() readySince = Date()
phase = .ready(URL(string: "http://127.0.0.1:\(f.localPort)/?key=\(key)")!) var page = "http://127.0.0.1:\(f.localPort)/?key=\(key)"
#if DEBUG
// Test hooks for the Simulator: open on a given tab, and leave the URL where
// a test can drive the same tunnel (`simctl get_app_container … data`).
if let tab = ProcessInfo.processInfo.environment["FRAME_TEST_PAGE"] { page += "#\(tab)" }
if let dir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first {
try? page.write(to: dir.appendingPathComponent("frame-test-url.txt"), atomically: true, encoding: .utf8)
}
#endif
phase = .ready(URL(string: page)!)
// Runs at once if it stopped in the moment since the check above. // Runs at once if it stopped in the moment since the check above.
server.whenExited { [weak self] tail in server.whenExited { [weak self] tail in
Task { @MainActor in self?.lost(mine, "Frame Control on the headset stopped. \(tail.suffix(200))") } Task { @MainActor in self?.lost(mine, "Frame Control on the headset stopped. \(tail.suffix(200))") }
+8
View File
@@ -144,6 +144,14 @@ struct WebShell: UIViewRepresentable {
(root.presentedViewController ?? root).present(sheet, animated: true) (root.presentedViewController ?? root).present(sheet, animated: true)
} }
#if DEBUG
/// Simulator test hook: FRAME_TEST_JS runs in the page once it has loaded.
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
guard let js = ProcessInfo.processInfo.environment["FRAME_TEST_JS"] else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + 4) { webView.evaluateJavaScript(js) }
}
#endif
// MARK: navigation: the app's page stays here; other sites open in Safari // MARK: navigation: the app's page stays here; other sites open in Safari
func webView(_ webView: WKWebView, decidePolicyFor action: WKNavigationAction, func webView(_ webView: WKWebView, decidePolicyFor action: WKNavigationAction,