* Attach and verify release SHA256SUMS with download guidance
* Write release checksums as exact LF bytes on Windows
* Read release safety text as UTF-8 on Windows
* Clarify checksum rollout and macOS signing status
---------
Co-authored-by: SAPHID <saphid@users.noreply.github.com>
* Add bounded desktop power and heat observations with offline downloads
* Fix desktop power tests on Python 3.9
---------
Co-authored-by: SAPHID <saphid@users.noreply.github.com>
* Add read-only power and heat sampler for issue 52
* Make power sampler IIO fixture portable on Windows
* test: normalize Windows fixture paths for power sampler
---------
Co-authored-by: SAPHID <saphid@users.noreply.github.com>
Brings in #38's native PC host. The only conflict was ui/index.html: main
moved the macview card under Tools, so this keeps main's layout; #38's
loadMacView changes applied cleanly. Scoping to Windows and the
Experimental opt-in follow in separate commits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ui/store.html, scripts/store-on-frame.sh and the /store route (built in
another thread and left uncommitted in the main checkout) join this branch,
rebuilt on /api/search: the same sources, categories, phone-only rule and
artwork as the Store, as a 1280x800 laser panel. A category rail with
Installed, eight art cards a page, a featured VR app on Home, a detail
sheet with screenshots, install progress and game data, the thumbstick
turning pages and the on-screen keyboard.
- Artwork: the proxy allowed four name lookups at once and failed the rest
immediately, so a page of cards lost most of its images until the retry.
Public answers are now kept for five minutes and a lookup waits for a slot
within its own budget.
- Phones: one-line shelf headings, a shorter store header, wider shelf
cards, and a two-column library.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From review: EET and WET link to Athens and Lisbon but name clock rules,
not Greece or Portugal, so they and the other rule names (CET, EST5EDT, …)
send nothing. Pacific/Johnston has left zone.tab, which made its Honolulu
group look only American; it's set to UM by hand.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From review: tzdata links an old name to any zone with the same clocks,
even in another country (Africa/Asmera, Eritrea, links to Africa/Nairobi).
An old name now gets a country only when every zone sharing its target is
in that country, or when it's a hand-checked old name for a place listed in
zone.tab. The rest (UTC, CET, GMT, …) send nothing. Every zone Node's ICU
reports has an entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From review: the time zone setting could hold any text (a TZ like
home/alex/private passed the name check), so the app now looks the zone up
in a table made from the tz database (scripts/timezones.py) and sends only
its two-letter country; anything not in the table sends nothing.
Turning a setting on no longer counts as having seen the new notice, and
the country is taken out at send time until it has been, so it can't
leave early whatever order things happen in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- publish-release.sh: the fallback takes only an untagged-... draft titled
exactly "Frame Control X.Y.Z" (optionally ": subtitle"); a pre-release's
draft ("9.8.7-rc.1") or one bound to another tag is refused.
- updater.js: ignore the page in update.json/the API entirely and always
link to releases/tag/v<version> built from the validated version, so a
path like tag/..\..\other/repo can't reach shell.openExternal.
- Tests for both, including backslash and %2e%2e traversal.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
publish-release.sh took update.json's "page" from the draft's url, which is
releases/tag/untagged-... and dies on publishing; 0.4.0's manifest shipped
that dead link. It also looked the draft up with `gh release view <tag>`,
which reports "release not found" while a draft's tag_name still reads
untagged-... (and uses rate-limited GraphQL).
- publish-release.sh: REST only. Checks the tag exists, finds the release by
tag_name or else the one untagged draft titled "Frame Control X.Y.Z",
keeps the 8-installer digest check, replaces update.json via the uploads
API, then PATCHes tag_name/draft/prerelease/make_latest. Adds --dry-run.
- updater.js: trust only this repo's releases/tag/<tag> pages (never
untagged-...); otherwise link to releases/tag/v<version>.
- release.yml: one draft job before the matrix (no racing creates), with
--verify-tag and tag_name set explicitly.
- Tests: tests/test_publish_release.py with a stand-in gh (tests/fakegh/gh),
and an updater test for the page.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On a timeout or a setup error the request now stops the scheduled task and
waits for it to end before releasing the mutex, so a stalled helper can't
act later inside another request.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Windows releases the mutex when the request's process ends, so there's no
stale lock to recover and no window where two requests can take each
other's. Requests sent together now run one after another.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
input.txt is now created with CreateNew, so only one request can hold it; a
lock older than 15 s is treated as left by a dead run. The outer PowerShell
stops on any error, says why in one line, releases the lock and exits 1.
docs/testing.md names both keys Set Up Connection can add to the headset.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- frame-key remove deletes only the exact line add wrote, through a private
temporary file, and leaves authorized_keys alone if it can't read or
rewrite it. add checks the VM's key is a plain ed25519 key, keeps a last
line without a newline intact, and doesn't duplicate a key the headset
already trusts.
- The input helper stops on any error before acknowledging, refuses a
second click while one is in flight, and coordinates are bounded.
- Host, container and user names are checked before they reach a remote
shell; PowerShell errors come back as plain text.
- docs/testing.md says what frame-key does and doesn't undo.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
scripts/windows-vm.sh drives a dockur/windows Windows 11 VM from the Mac over
ssh -J: start/stop, PowerShell, copy files in, screenshots via QEMU's
screendump, clicks and scrolls replayed by a scheduled task in the signed-in
session, QEMU key names, and a tagged, removable headset key for tests
against a real Frame. docs/testing.md explains setting it up (Windows from
Microsoft, loopback-only ports) and the pitfalls found reproducing the
Windows "RDP not working" report. docs/streaming.md records what Remote
Desktop from Windows does against the Frame's xrdp (verified 2026-09-30).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- checks: the runner's packages.microsoft.com apt source now 403s, failing
`apt-get update` and with it "Install zsh"; tolerate the refresh error
(Ubuntu's lists still update and the install still fails if it can't).
- e2e: a failed quiet image build is rebuilt with the full log, but the
script exited 2 even when that rebuild succeeded; only fail if it fails.
- ArtworkTests: a timed-out lookup gives its resolver slot back from its own
thread, which a busy macOS runner may schedule after the next test starts.
Wait for every slot before each test, and always return slots a test took,
so one late thread can't cascade into three failures.
- release: tagged builds no longer re-upload the installers as workflow
artifacts (they are attached to the release; the artifact finalize timed
out on v0.3.0). Pull requests build installers only when the Electron app,
the macOS helper it compiles, or the workflow changes.
- checks and release: a newer push to a pull request cancels the older run.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Integration review findings: the scripts they run ssh'd to whatever 'frame' means
in ~/.ssh/config. They now take FRAME_ALIAS and FRAME_SSH_OPTS from the server's route.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review round 12: a failed USB-C tunnel now retries the normal path; an
existing HostKeyAlias wins; --host with --usb is rejected; the Steam
desktop-streaming claim is now 'untested' (Valve documents the desktop
showing when a game loses focus); the Show/cleanup overlap test blocks
for real (it fails without the lock). Verified live: with the cable out,
the route is the normal path.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- 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>