Fix the iPhone review's findings

- Cancelling (Change headset, Forget) invalidates the attempt in flight; a
  connection only becomes the app's once every step finished for it.
- A server that stops while the tunnel opens is noticed (exit callbacks are
  synchronised and replayed), and the app isn't left showing a dead page.
- The port is read only once the digits are complete, and range-checked.
- Other versions in ~/.cache/frame-control stay unless untouched for 14 days,
  so a second phone or iPad isn't cut off.
- The key is appended on its own line even if authorized_keys lacks a final
  newline.
- The app checks every 20 s, and on returning to the foreground, that the SSH
  session still answers, and reconnects if not.
- The ssh stand-in runs the command as its own process group and passes a
  TERM on to all of it, so a live-video ffmpeg stops with its stream.
- Touch screens show the library's Play buttons (the rule now follows the
  base one); the failure screen only says it's retrying when it is; the page
  says the app reconnects rather than naming a desktop menu.

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 11:30:40 +10:00
1 parent 13187e8f6a
commit a910f83ac1
7 files changed
+174 -54

No files matched your search

+12
View File
@@ -80,6 +80,18 @@ final class FrameLink: @unchecked Sendable {
var isConnected: Bool { client.isConnected }
/// Whether the Frame answers a trivial command within a few seconds.
func answers(within seconds: Double = 6) async -> Bool {
guard client.isConnected else { return false }
return await withTaskGroup(of: Bool.self) { group in
group.addTask { (try? await self.run("true").status) == 0 }
group.addTask { try? await Task.sleep(nanoseconds: UInt64(seconds * 1e9)); return false }
let first = await group.next() ?? false
group.cancelAll()
return first
}
}
func close() async {
try? await client.close()
}