- Title removal ran the Steam shortcut tidy-up before steamos-delete, which
finds the Proton prefix through that shortcut, so compatdata was left behind
(e2e caught it). steamos-delete runs first again; art/collection tidy-up after.
- Test fixtures are byte-exact: never convert line endings (a text-looking
fixture APK got CRLF on Windows and failed its SHA-256).
- Read index.html/artwork-settings.js as UTF-8 in tests; app-data backup and
OBB shell tests run only on POSIX (they exercise the Frame-side scripts).
- e2e expects the icon under artwork/ now.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Automatic backfill touches only entries marked art_pending at install (a
devkit title Steam registered later) and fills only slots Steam has no art
for: no name, exe, VR flag or icon changes, no clearing. Older installs
without the flag are left alone and refreshed only when the user asks.
- Android remove takes the install lock that install and refresh hold, so a
refresh in progress can't recreate a removed app; a refresh after removal
finds it not installed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- 'Refresh artwork' (settings) and the API's refresh-art --all cover devkit
titles as well as Android apps; frame_titles.py gains refresh-art ID|--all.
- Apps and titles without complete Steam artwork are flagged (art_missing):
the app shows 'Add artwork', and the CLIs' list prints the refresh command.
- When the app lists them and Steam answers, Frame Control re-applies their
art in the background (at most every five minutes), e.g. for a title Steam
registered after an install made while it wasn't running.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Remove: collections and artwork clearing are best effort in Steam's JS, and
the host carries on to delete the files when Steam isn't running (Android
apps and devkit titles).
- Devkit titles: the shortcut is found by devkit id, the saved id, or an
executable/start folder inside the title's folder; never by display name.
Steam's overviews don't carry devkit_gameid (checked on the Frame
2026-09-28), so 'list' now reads exe/start dir from app details.
- Photo-based grid/wide/hero slots render as JPEG (a noise-heavy 3840x1240
hero was over 12 MiB as PNG on the Frame); the logo stays transparent PNG.
Rendering gets 75 s and retries once with generated art. Each slot is
cleared before it is set, since Steam keeps .png and .jpg side by side.
- Devkit titles keep their own VR flag (vr=None skips SetShortcutIsVR) and
their Sideloaded collection.
- A failed title install's cleanup can't replace the original error.
- refresh_art for devkit titles (frame_titles.refresh_art).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
- 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>
- 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>
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>
- 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>
Dropping a game's .zip, folder or .exe on Send to Frame now adds it to the
headset's Steam library through Valve's SteamOS Devkit title path, with the
runtime picked from the program's header: Windows PE -> Proton Experimental
(steam_play=1), aarch64 ELF -> SteamLinuxRuntime_4-arm64, x86-64 ELF ->
SteamLinuxRuntime_4 (through FEX). Other architectures are refused.
- frame/devkit-utils: Valve's devkit-utils vendored unmodified (MIT,
steamos-devkit v0.20260925.1), synced to ~/devkit-utils by stamp, bundled in
the app and compiled in CI.
- ui/frame_titles.py: inspect (safe unzip, ELF/PE classification, launch
target ranking), install(path, name=None, exe=None, runtime=None,
progress=None), list, launch, remove, plus a CLI.
- ui/server.py: /api/titles (inspect/install/discard/launch/remove),
/api/titles/job progress, and an upload mode 'title'.
- ui/index.html: confirm dialog (name, launch target, runtime), install
progress, and a Sideloaded titles list with Launch and Remove. The app's
preload passes a dropped folder's path.
- tests and docs/sideloading.md. Device-side behaviour is inferred from
Valve's source; the headset was offline, so none of it has been checked on
a Frame yet.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>