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

+6 -3
View File
@@ -68,8 +68,6 @@
.mobile-only { display: none; }
body.mobile .mobile-only { display: revert; }
body.mobile .desk-only { display: none; }
/* Touch screens can't hover: keep the library's name and Play button showing. */
@media (hover: none) { .capsule .over { opacity: 1; } .capsule:hover { transform: none; } }
/* ---- pages: one per tab; the header nav switches between them ---- */
@@ -322,6 +320,8 @@
.disp .seg button { padding: 0 10px; }
.disp input[type=number] { width: 72px; background: rgba(0,0,0,.28); color: var(--text); border: 1px solid transparent;
border-radius: 3px; height: 32px; padding: 0 8px; font: inherit; font-size: 13px; }
/* Touch screens can't hover: keep the library's name and Play button showing. */
@media (hover: none) { .capsule .over { opacity: 1; } .capsule:hover { transform: none; } }
/* ---- phones: tabs move to a bottom bar, as in iOS apps; the page clears the notch and home indicator ---- */
@media (max-width: 640px) {
/* No blur here: it would make the header the containing block of the fixed tab bar. */
@@ -798,7 +798,10 @@ async function api(path, body) {
method: "POST", headers: {"Content-Type": "application/json", "X-Frame-UI": UI_KEY}, body: JSON.stringify(body) };
let r;
try { r = await fetch(path, opts); }
catch { throw new Error("Frame Control's local server isn't running. Restart the app (Frame → Restart Server)."); }
catch {
throw new Error(window.frameApp?.platform === "ios" ? "Lost the connection to the Frame. The app reconnects on its own."
: "Frame Control's local server isn't running. Restart the app (Frame → Restart Server).");
}
const data = await r.json().catch(() => ({ error: `HTTP ${r.status}` }));
if (!r.ok) {
const err = new Error(data.error || `HTTP ${r.status}`);