Address review findings in the app, server and tests

- app: startup shell, python and ssh probes run asynchronously so a slow
  shell profile can't freeze the window; PATH comes from the user's real
  login shell and a failed lookup isn't cached; a server that never
  answers is killed; the setup offer runs once per launch, only after the
  UI loads, and decides from HostName alone; connect.sh is started through
  `env ... zsh` so it works whatever the login shell is.
- server: volume validates the level before muting or changing anything.
- Steam: null-safe install-manager fields, http.client errors caught in
  store ratings, price fallback when a sale has no final price.
- tests: server output kept for diagnosis, any startup error retried, and
  captures asserted non-cacheable.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
saphidandClaude Opus 5.5 committed 2026-09-25 22:44:54 +10:00
1 parent d4486a7681
commit 99653151c4
13 files changed
+340 -38

No files matched your search

+2 -2
View File
@@ -142,8 +142,8 @@ OWNED_JS = r"""
"""
WIZARD_JS = """SteamClient.Installs.GetInstallManagerInfo().then(i => ({
state: i.eInstallState, app: i.currentAppID, need: i.nDiskSpaceRequired, free: i.nDiskSpaceAvailable,
error: i.eAppError, detail: i.errorDetail }))"""
state: i?.eInstallState ?? 0, app: i?.currentAppID ?? 0, need: i?.nDiskSpaceRequired || 0,
free: i?.nDiskSpaceAvailable || 0, error: i?.eAppError, detail: i?.errorDetail }))"""
def steam_url(url):
+2 -1
View File
@@ -6,6 +6,7 @@ Uses the store's public endpoints (no key, no login):
`frame_resolved_category` is the Frame's
Buying happens on the store page, signed in as the user; nothing here buys.
"""
import http.client
import json
import threading
import time
@@ -37,7 +38,7 @@ def frame_rating(appid):
r = _get("saleaction/ajaxgetdeckappcompatibilityreport", {"nAppID": appid, "l": "english"})
cat = int((r.get("results") or {}).get("frame_resolved_category") or 0)
cat = cat if 0 <= cat <= 3 else 0
except (OSError, ValueError, TypeError, AttributeError):
except (OSError, ValueError, TypeError, AttributeError, http.client.HTTPException):
return 0 # not cached, so the next search retries
with _lock:
_compat[appid] = (time.time(), cat)
+2 -1
View File
@@ -1024,7 +1024,8 @@ function price(p) {
if (!p) return "Free";
const f = n => { try { return new Intl.NumberFormat(undefined, { style: "currency", currency: p.currency }).format(n / 100); }
catch { return `${(n / 100).toFixed(2)} ${esc(p.currency || "")}`; } };
return p.initial > p.final ? `<s>${f(p.initial)}</s>${f(p.final)}` : f(p.final);
const fin = p.final ?? p.initial ?? 0;
return p.initial > fin ? `<s>${f(p.initial)}</s>${f(fin)}` : f(fin);
}
function gameButtons(id, name, free) {
+8 -4
View File
@@ -8,6 +8,7 @@ Usage: ui/server.py [--port 47810] (normally started by scripts/frame-ui.sh)
Env: FRAME_ALIAS (default frame)
"""
import argparse
import http.client
import json
import os
import re
@@ -204,7 +205,7 @@ def steam_frame(*args, timeout=40):
except Failure as e:
# frame_steam.py prints {"error": ...} on stdout when it fails, but ssh()
# reports stderr instead if there was any, so look in both.
for line in reversed([*getattr(e, "stdout", "").splitlines(), *str(e).splitlines()]):
for line in [*reversed(getattr(e, "stdout", "").splitlines()), *reversed(str(e).splitlines())]:
try:
raise Failure(json.loads(line)["error"]) from None
except (ValueError, KeyError, TypeError):
@@ -230,17 +231,20 @@ def steam_search(query):
raise Failure("cc must be a two-letter country code", 400)
try:
return {"results": frame_store.search((q.get("q") or [""])[0], cc)}
except (OSError, ValueError, TypeError, AttributeError) as e:
except (OSError, ValueError, TypeError, AttributeError, http.client.HTTPException) as e:
raise Failure(f"Steam store search failed: {e}")
def set_volume(body):
if "muted" in body:
ssh(f"wpctl set-mute @DEFAULT_AUDIO_SINK@ {1 if body['muted'] else 0}")
# Validate everything before touching the headset.
level = None
if "level" in body:
level = float(body["level"])
if not 0 <= level <= 1:
raise Failure("level must be 0..1", 400)
if "muted" in body:
ssh(f"wpctl set-mute @DEFAULT_AUDIO_SINK@ {1 if body['muted'] else 0}")
if level is not None:
ssh(f"wpctl set-volume @DEFAULT_AUDIO_SINK@ {level:.2f}")
return {"message": "Volume updated"}