Commit Graph
71 Commits
Author SHA1 Message Date
saphidandClaude Opus 5.5 49378b2c80 Devices: fixes from review round 24
- While the connector is taking a queued reconnect off its list, the old
  connection no longer counts as live, so no install starts on it.
- Importing a block without a Port takes the port ssh would really use
  (ssh -F <config> -G), e.g. one a later Host * sets.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 01:13:15 +10:00
saphidandClaude Opus 5.5 22863f2f87 Devices: match the host in ssh's login line case-insensitively (review round 23)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 01:01:29 +10:00
saphidandClaude Opus 5.5 b779376f5b Devices: fixes from review round 22
- An upload's answer arriving after a switch opens nothing; the APK
  alternatives dialog installs on the headset the APK was checked for.
- A handshake that goes silent (e.g. a jump host's forward hanging) moves on
  to the next address.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 00:53:18 +10:00
saphidandClaude Opus 5.5 8bd8d63546 Devices: fixes from review round 21
- Behind a jump host, a forward it couldn't open moves on to the next address;
  only a refused key stops (judged by ssh's words, not the step).
- Add a headset suggests an alias no Host in ~/.ssh/config already uses.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 00:44:18 +10:00
saphidandClaude Opus 5.5 42b51afc5a Devices: fixes from review round 20
- A jump host's own "Authenticated to" line no longer counts as the headset's,
  and a master that logs in but doesn't start lets the next address be tried.
- Devices tab changes refresh through the ordered list load, so a late answer
  can't undo a newer selection.
- A probe's time out starts after the name lookup: macOS can take 5 s to look
  up a .local name (found on the real Frame once its USB link went away).
- An attempt's ending is published from a method, not a return in finally.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 00:35:29 +10:00
saphidandClaude Opus 5.5 6597a90059 Devices: fixes from review round 19
- A switch the server refuses no longer drops answers the page is waiting for
  (an install's job id): only an actual change of headset does.
- Test now goes through a jump host when the alias uses one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 00:25:10 +10:00
saphidandClaude Opus 5.5 c3e3f2629c Devices: fixes from review round 18
- A set-up headset whose alias goes through a jump host (ProxyJump or
  ProxyCommand in ~/.ssh/config) is reached through it, address by address,
  still pinned per headset.
- A refused switch puts the header's switcher back on the headset in use.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 00:17:29 +10:00
saphidandClaude Opus 5.5 90fd2684ba Devices: fixes from review round 17
- A command that fails after a switch doesn't make the connector drop the new
  headset's connection.
- On first import, the app keeps using the `frame` headset even when Set Up
  Connection put another block above it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 00:08:36 +10:00
saphidandClaude Opus 5.5 fa05a4b310 Devices: fixes from review round 16
- Terminals, power and a reconnect's probes use the route commands have now
  (a pinned bare-alias destination, a login change still deferred).
- Saving port 22 keeps an explicit Port line where there was one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 00:00:00 +10:00
saphidandClaude Opus 5.5 93ebd34f2c Devices: fixes from review round 15
- A bare alias's route is pinned to where ~/.ssh/config sent it when it was
  routed (HostName, Port, User), so editing that file can't move an install.
- Renaming during an install is allowed: only a real user or port change waits.
- The Devices tab follows a network change even while the headset is offline.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 23:51:12 +10:00
saphidandClaude Opus 5.5 34e91988b1 Devices: fixes from review round 14
- Retry now (while connected) and Forget identity wait for running installs.
- Terminal windows get the headset's address by name, so a link-local IPv6
  zone never has to pass through Windows' console.
- Renaming the headset in use shows at once in the header.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 23:40:10 +10:00
saphidandClaude Opus 5.5 5874fe33f6 Devices: fixes from review round 13
- Every command to a set-up headset checks its pinned key
  (StrictHostKeyChecking=yes, whatever ~/.ssh/config says); only the
  connector's first handshake may save one.
- A reconnect during an install keeps the whole route it started with, also
  when a bare alias is set up meanwhile.
- Removing the headset FRAME_ALIAS named doesn't bring it back as a bare alias.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 23:31:22 +10:00
saphidandClaude Opus 5.5 98ef6944c9 Devices: fixes from review round 12
- A reconnect while an install runs keeps the login it started with; a new
  one from ~/.ssh/config applies after.
- Frame > Open SSH goes through the server, so it uses the same headset and
  address as the app and refuses when there's none.
- A bare frame alias in use when a headset is set up stays selectable.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 23:21:19 +10:00
saphidandClaude Opus 5.5 2e9bc8624b Devices: fixes from review round 11
- A headset set up while a bare alias is in use doesn't take over by itself;
  a login change from ~/.ssh/config waits for running installs.
- Saving a headset writes only the login fields that changed, and only if the
  block still holds the old ones.
- SSH, SFTP, power and remote desktop open with the same headset and address
  as every other command, and refuse when there's no address.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 23:12:41 +10:00
saphidandClaude Opus 5.5 c69ea6266f Devices: fixes from review round 10
- Removing or moving the active headset's address waits for running installs,
  like switching.
- A volume change still waiting to be sent goes to the headset whose slider
  it was, and a switch cancels it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 23:03:27 +10:00
saphidandClaude Opus 5.5 43e19d17f6 Devices: fixes from review round 9
- A title waiting its turn to be read stays with the headset it was dropped
  on, and is dropped if the app switches meanwhile.
- Probes still finishing from an earlier attempt can't overwrite the rows of
  a newer one.
- The FRAME_ALIAS the server started with stays on the list after switching
  away, so it can be picked again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 22:55:14 +10:00
saphidandClaude Opus 5.5 175c39a2b0 Devices: fixes from review round 8
- A batch of dropped files stays with the headset it was dropped on, and
  stops if the app switches.
- Removing or moving the address in use reroutes at once; a headset with no
  addresses reaches nothing rather than whatever ~/.ssh/config says.
- A late answer to an older device-list request is ignored.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 22:45:34 +10:00
saphidandClaude Opus 5.5 51ef5d8283 Devices: fixes from review round 7
- Removing every headset leaves none in use (commands fail at once) instead of
  falling back to the `frame` alias.
- ssh goes to the IP that answered for IPv6 too, with a link-local address's
  interface (verified: frame.local over fe80::…%en9 on the real Frame).
- Find results only show in the panel of the headset they were for.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 22:35:43 +10:00
saphidandClaude Opus 5.5 ba33d2ff40 Devices: fixes from review round 6
- The headset a change is meant for is checked and the work counted in one
  step, so a switch can't slip in between (uploads too).
- A sideloaded title read on one headset can't be installed on another; open
  confirmations close on a switch.
- The only headset can't be removed while its ssh alias stays behind.
- Answers about the previous headset are dropped without touching panels; the
  catalogue's Installed tags are rebuilt for the new headset.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 22:24:46 +10:00
saphidandClaude Opus 5.5 41ac28248a Devices: fixes from review round 5
- A switch publishes the new headset at once, so the page clears the old one's
  panels and the lists behind them (games, store, Android apps, screenshots).
- The page names the headset its changes are for (X-Frame-Device); the server
  refuses one meant for a headset it has switched away from (409).
- A rejected address edit changes nothing.
- Test now goes to the IPv4 address that answered, like the connection.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 22:14:25 +10:00
saphidandClaude Opus 5.5 d383a26746 Devices: fixes from review round 4
- A switch clears every headset-specific list and its buttons at once.
- SSH goes to the IPv4 address that answered the probe, not the name again.
- A rejected headset edit changes nothing.
- The SteamOS/Lepton builds recorded in reports are read again per headset.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 22:00:09 +10:00
saphidandClaude Opus 5.5 318bc3b84f Devices: make the pin folder before ssh saves a first-seen key into it
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 21:49:43 +10:00
saphidandClaude Opus 5.5 cb182dbce5 Devices: fixes from review round 3
- Attempts carry a generation: one overtaken by a switch, a removal or a login
  change routes nothing back to the old headset and can't report connected.
- Removing the active headset or changing its user/port reroutes at once,
  before anything that can fail.
- One known_hosts file per headset (~/.ssh/frame-control-hosts/<id>):
  forgetting one headset's key can't drop another's, whoever else writes.
- learn() checks, under the config lock, that the block is still what the
  attempt started from before writing to it.
- A switch stops live video and drops captures from the previous headset.
- A probe shares its time between the addresses a name resolves to.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 21:48:39 +10:00
saphidandClaude Opus 5.5 18334b5989 Devices: fixes from review round 2
- Switching headsets is serialized with the start of any install; background
  work counts as running from before its thread starts. use() reroutes every
  command at once and makes ensure() wait for the new headset.
- A new user or port reroutes commands even if the attempt then fails.
- ~/.ssh/config edits take a lock file shared with Set Up Connection
  (frame_connect.py and connect.sh, which now also writes atomically).
- A finished attempt no longer writes its older settings over a change Set
  Up Connection made meanwhile.
- Pin edits are locked and swapped atomically.
- A bare alias behind ProxyJump/ProxyCommand is left to ssh to reach.
- The page drops answers about the previous headset after a switch; the
  header switcher takes clicks in the macOS title bar.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 21:38:41 +10:00
saphidandClaude Opus 5.5 13eb65603b Devices: fixes from review round 1
- No switching headsets (or changing the active one's user/port, or removing
  it) while installs run: they read the ssh settings step by step.
- Switching reroutes every command to the new headset at once, even if it
  never answers.
- ~/.ssh/config edits are serialized, use unique temp files, and back off if
  another program wrote the file meanwhile.
- stop() ends a handshake in progress and joins the connector.
- Pinned keys are written unhashed (HashKnownHosts=no); hashed ones are still
  found and forgotten via ssh-keygen.
- Set Up Connection changing a headset's user or port updates the registry.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 21:25:41 +10:00
saphidandClaude Opus 5.5 a954fc83c9 Devices: several headsets, several addresses each, and live connection status
Frame Control can now manage more than one Steam Frame, and reach each at any
of several addresses (LAN IPs per network, its .local name, Tailscale). A
connector in the server tries them all at once, picks the best one that
answers, follows ssh -v through each stage (network, finding, SSH, identity,
login) and streams that to the page. The header shows it live; a new Devices
tab (key 5) manages headsets, addresses and network names.

- ui/frame_devices.py: registry in devices.json, imported from the managed
  ~/.ssh/config blocks; per-headset host key pinning; config block updates.
- ui/frame_network.py: gateway IP+MAC fingerprint, Wi-Fi name, Tailscale.
- ui/frame_link.py: the connector, Test now, Tailscale/mDNS discovery, API.
- server.py: ensure_master delegates to the connector; /api/connection,
  /api/connection/events (SSE), /api/devices.
- Electron: headset switcher and Devices item in the Frame menu.
- frame_connect.py --alias; FRAME_CONTROL_DATA_DIR / FRAME_CONTROL_SSH_DIR
  keep tests off real data.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 21:13:58 +10:00
saphidandClaude Opus 5.5 224340edc9 APK alternatives: refresh the catalogue after an install job; pin the test's premise
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 17:29:29 +10:00
saphidandClaude Opus 5.5 c814cb95d0 Merge main into apk-alternatives: install other versions as background jobs
Main now runs Android installs as background jobs and maps SSH failures to
one offline message. Alternative-version installs go through the same job,
the alternatives dialog waits on it with runJob, and send_error_json keeps
the apk blocker that opens the dialog.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 17:21:55 +10:00
saphidandClaude Opus 5.5 a1fa4ce140 Fix the cross-provider review's findings on APK alternatives
One malformed or unreachable repo no longer hides the others; skip bad
index entries; a refreshed raw index outdates its reduced copy; style the
dialog like the others; validate package ids with PKG_RE.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 15:15:11 +10:00
saphidandClaude Opus 5.5 50405ccf88 Add IzzyOnDroid to APK alternatives; keep the catalogue's index file
Reducing an index no longer deletes apk-catalog/data/index-v2.json, which
the catalogue build reads. Drop the measurement log from docs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 14:30:15 +10:00
saphid 32196b4260 Reduce F-Droid indexes and fetch APK alternatives asynchronously 2026-09-28 14:26:40 +10:00
saphid fbe7ba9575 Find installable APK alternatives in F-Droid main and archive 2026-09-28 14:16:43 +10:00
saphid 0016d9200c Merge remote-tracking branch 'origin/iphone-app' into iphone-app 2026-09-27 21:27:10 +10:00
saphidandClaude Opus 5.5 fe87a9d826 Keep the page clear of iOS safe areas everywhere; research typing, pointing and mirroring into the Frame
- Header, content, tab bar, status strip, drawer and toasts add the notch,
  Dynamic Island, rounded-corner and home-indicator insets on every side
  (zero on desktops). Phones on their side use the bottom tab bar layout.
- The phone tab bar hides while a text field has focus, instead of riding
  on the keyboard.
- DEBUG hook FRAME_TEST_LANDSCAPE for checking this in the Simulator.
- docs/streaming.md: iPhone mirroring (UxPlay, broadcast extension) and
  keyboard/mouse input (uinput needs no sudo on the Frame, verified).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 21:26:32 +10:00
saphidandClaude Opus 5.5 ca2a991f03 Sideloaded titles: use ids Steam's create-shortcut accepts
On the Frame, Steam refused to register titles whose id had a hyphen
(fc-smoke-exe) with "missing/invalid arguments", and registered the same
program as FCSmokeProbe (headset smoke test, 2026-09-27, BUILD_ID
20260922.6101926). Valve's client only allows ^[A-Za-z_][A-Za-z0-9_.]+$.

title_id now makes ids of letters, digits and _, not starting with a
digit, 2 to 64 long; new installs are checked against that, while titles
already on the Frame are still listed, launched and removed. Steam's error
text is trimmed before it's quoted, and the "install it again with Steam
running" hint only follows a Steam-not-running error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 17:40:09 +10:00
saphidandClaude Opus 5.5 aafd2dbda8 iPhone app: test pairing and power against a Holo Core stand-in
Valve publishes no Steam Frame OS image, so tests/frame-container builds the
Frame's SSH surface on Valve and Collabora's Holo Core aarch64 base: a
steamos user with a password and sudo, OpenSSH with keys and passwords,
Python, and a systemctl that only records requests.

Against it from the Simulator: password pairing, the host-key pin, the power
password check. Found and fixed: a changed host key or a refused login said
"Can't reach the Frame" and retried forever; they now say "Pair with the
Frame again" and offer that. The server's key rejection now says the header
may be wrong, not only missing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 15:44:42 +10:00
saphidandClaude Opus 5.5 14790ac768 Fix the follow-up review's findings
- Pairing saves nothing if it was cancelled while adding the key.
- The ssh stand-in runs under bash: dash refuses job control without a
  terminal, which cost stdin and the process group.
- A server that stops while the tunnel opens fails the attempt (and retries)
  instead of leaving it half-connected.
- The health probe answers within its deadline even when a dead link keeps
  the probe itself waiting.
- A cached version is deleted only if no server runs from it (servers now run
  by absolute path) and it's two weeks unused.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 11:40:17 +10:00
saphidandClaude Opus 5.5 a910f83ac1 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>
2026-09-27 11:30:40 +10:00
saphidandClaude Opus 5.5 13187e8f6a iPhone and iPad app: the same features, served from the Frame
An iPhone can't run Python or ssh, but the Frame can. The app (ios/, SwiftUI)
connects with its own SSH key (Citadel), copies the server and helpers to
~/.cache/frame-control/<version> on the Frame once per version, starts
ui/server.py there with FRAME_LOCAL=1 on the Frame's 127.0.0.1, and shows the
page through an SSH tunnel. The server exits when the phone disconnects.

Server: FRAME_LOCAL=1 puts ui/local-bin on PATH, whose ssh stand-in runs each
`ssh frame COMMAND` locally (and serves as rsync's transport), so desktop and
phone share one code path. Android display goes through podman exec there, as
the Frame has no adb. FRAME_UI_KEY replaces the fixed X-Frame-UI value with a
per-session key. Power actions take the Developer Mode password via sudo -S.
--port 0 now prints the port it took.

Page: a bottom tab bar and safe areas on phones, Play buttons visible on touch
screens, saving through the share sheet, SSH/SFTP/Steam Link/remote desktop
opening in their iOS apps, and a password dialog for power.

App: pairing with the Developer Mode password once (never stored) or with a
key the user adds; host key pinned on first use; plain-language connection
errors with quiet retries; frame-control://install links; alerts and confirms.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 11:18:15 +10:00
saphidandClaude Opus 5.5 0f770dc88a Tabs, one offline banner, background installs, drop anywhere
The page was one 6,800px scroll with nine nav links (hidden below 1150px).
It is now four tabs, Home, Games, Android and Tools, switched with 1-4; old
section links still land on the right tab.

When the Frame can't be reached, the server turns ssh's connection errors into
one plain message (503, offline: true), the page shows a single banner with
Retry and Set Up Connection, retries every 8 s, and reloads every panel when
the Frame answers. Panels say "Waiting for the Frame" instead of raw ssh text.

Flatpak and Android catalogue installs run as background jobs the page polls,
so a slow install no longer holds a request for up to 15 minutes or reports a
false failure; the bottom bar counts running installs.

Files can be dropped anywhere in the window, as the README already said.
Recent reports show the newest five, with Show all. Android display explains
an empty or failed read. A topped-up headset on a charger reads as not
charging rather than "still draining, using 0.0 W".

Fixes a race where the catalogue and reports loads wrote the compat-db
mirror's .tmp file at once and one failed with a 500.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 10:23:24 +10:00
saphid 2f5ba5c086 Revert "Interrupt stalled downloads on Windows too"
This reverts commit b86dd3b07d.
2026-09-26 23:16:31 +10:00
saphidandClaude Opus 5.5 b86dd3b07d Interrupt stalled downloads on Windows too
shutdown() from another thread doesn't wake a blocked recv on Windows, so
quitting mid-download waited out the 4 s deadline there (CI's Windows
server tests). Close the handle as well, detached first so the worker's own
close can't hit a reused handle.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 23:06:13 +10:00
saphidandClaude Opus 5.5 eb78eba0dc Give the install dialog its own snapshot of installed titles
A Refresh in flight could leave the shared list stale when the dialog
opened. The drop now fetches the list itself and hands it to the dialog.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 22:55:42 +10:00
saphidandClaude Opus 5.5 fd0a284942 Refuse APK entries Android can't read; ignore stale title lists
- Only stored and deflated entries are read: Python 3.9's bzip2 and lzma
  readers inflate without bound before trimming.
- loadTitles drops a response that a newer request has overtaken, so the
  install dialog's replace warning uses the fresh list.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 22:49:18 +10:00
saphidandClaude Opus 5.5 dfacf43e55 Fix the follow-up review's findings
- APK reader: read members with a bounded read, since ZipFile.read inflates
  a member fully before trimming it to a forged declared size; the reference
  walk counts every entry it examines, dead ends and cycles included.
- Titles: a path that exists under the root wins over stripping the archive
  prefix; the prefix is taken before a linked folder is staged elsewhere
  (another drive on Windows); the install dialog loads a fresh title list
  first and says so if it couldn't check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 22:44:07 +10:00
saphidandClaude Opus 5.5 39d28790a1 Fix the cross-provider review's findings
- APK reader: cap AndroidManifest.xml, resources.arsc and icon sizes before
  inflating them (APKs can come from install links), and follow resource
  references without cycles and with a result budget.
- Sideloading: reserve devkit-steam (SteamOS's sideloaded-client trampoline);
  the install dialog warns when a name replaces an installed title; a
  manifest's exe may name the program as it is in the archive, above the
  folder the installer steps into.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 22:35:16 +10:00
saphidandClaude Opus 5.5 1580dae42e Record headset results for sideloading and devkit pairing
Tested on a Frame (BUILD_ID 20260922.6101926):

- Devkit pairing: the service runs and answers properties.json, but /register
  refuses with "please put the Steam client in pairing mode" unless Steam is on
  Settings > Developer > Pair new host. Both setup paths now say so and keep
  asking for 2 minutes before falling back to the password.
- Sideloading: registering, launching, quoted start paths and Remove work; a
  Windows exe runs under Proton 11 through FEX. An aarch64 build starts but
  outside the runtime container, and an x86-64 Linux build doesn't start
  because the x86-64 Steam Linux Runtime 4.0 isn't installed. The inspect note
  for x86-64 Linux builds now says so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 22:31:05 +10:00
saphid ccf5f3e123 Merge remote-tracking branch 'origin/bundle-deps' into frame-sideload-features
# Conflicts:
#	app/main.js
2026-09-26 22:01:46 +10:00
saphidandClaude Opus 5.5 0478626061 Reject bad redirects cleanly and ignore any icon read error
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 20:55:41 +10:00
saphidandClaude Opus 5.5 2ab2ffa65a Fix the final review's findings on the combined features
- Links to localhost need FRAME_CONTROL_LOCAL_LINKS=1: otherwise any website's
  link could make the app fetch from services on this computer.
- Title staging folders (unzipped titles) carry the server's PID and are swept
  on the next start like download folders, so quitting mid-install doesn't leave
  gigabytes behind.
- An install from a link refreshes Sideloaded titles.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 20:49:21 +10:00