mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-26 03:01:59 +01:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d87b863aa3 | ||
|
|
ebb1091a37 | ||
|
|
e6430c39ed | ||
|
|
b518f8c741 | ||
|
|
93a4ae95fa | ||
|
|
f2e73d98f4 | ||
|
|
78d284b154 | ||
|
|
2c4581f122 | ||
|
|
65fee8ca8d | ||
|
|
4372c913a6 | ||
|
|
5239dae1a8 | ||
|
|
9bc5294f36 | ||
|
|
7ed08c5bbd | ||
|
|
449146bbdb | ||
|
|
30fbadbb23 |
No files matched your search
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "frame-control",
|
||||
"version": "0.4.36",
|
||||
"version": "0.4.38",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frame-control",
|
||||
"version": "0.4.36",
|
||||
"version": "0.4.38",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"electron": "^44.4.5",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "frame-control",
|
||||
"productName": "Frame Control",
|
||||
"version": "0.4.36",
|
||||
"version": "0.4.38",
|
||||
"description": "Desktop app for managing a Valve Steam Frame over SSH",
|
||||
"private": true,
|
||||
"main": "main.js",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# What we have tested
|
||||
|
||||
Current for Frame Control **v0.4.36**, 25 October 2026. “Real Frame + physical PC” means an actual headset and a non-virtual computer. “VM only” means a Windows virtual machine, sometimes connected to the real Frame. A CI test or a simulated device is **not** a physical hardware test. The date/version column is the last relevant check, not a promise that every new release was retested on that platform. See each link for the exact procedure and limits.
|
||||
Current for Frame Control **v0.4.38**, 26 October 2026. “Real Frame + physical PC” means an actual headset and a non-virtual computer. “VM only” means a Windows virtual machine, sometimes connected to the real Frame. A CI test or a simulated device is **not** a physical hardware test. The date/version column is the last relevant check, not a promise that every new release was retested on that platform. See each link for the exact procedure and limits.
|
||||
|
||||
| Feature | Real Frame + physical PC tested | VM only | Untested or limited | Last check and proof |
|
||||
|---|---|---|---|---|
|
||||
| Core title smoke test | Linux lxso2 installed, launched and removed reserved native/Proton titles on the real Frame; all 13 steps passed | No | Unworn headset; visible rendering and controller input not checked. Other feature paths retain their separate limits below | 25 Oct, v0.4.36 tag `1701e62`, Frame OS 0.5.6 build `20261022.6153909`: [release smoke evidence](evidence/v0436-release-smoke-2026-10-25.md); [earlier detailed results](evidence/round2-smoke-2026-10-25.json) |
|
||||
| Core title smoke test | Linux lxso2 installed and removed reserved native/Proton titles on the real Frame; all 13 steps passed. ARM64 and Windows titles launched | No | The x86_64 title could not launch because Steam Linux Runtime 4.0 was not installed. The headset was unworn; visible rendering and controller input were not checked. Other feature paths retain their separate limits below | 26 Oct, v0.4.37 tag `5239dae`, Frame OS 0.5.6 build `20261022.6153909`: [release smoke evidence](evidence/v0437-release-smoke-2026-10-26.md); [earlier detailed results](evidence/round2-smoke-2026-10-25.json) |
|
||||
| Windows desktop in the headset | No physical Windows PC test | Windows 11 VM sent a whole desktop to a real Frame; this used an intermediate fix, not the final native DLL | Physical GPU, capture and controller input; individual windows unavailable in bundled DXGI build | 24 Oct, v0.4.10 installer + candidate fix: [VM log](evidence/windows-vm-2026-10-24.md), [PC guide](pc-in-headset.md) |
|
||||
| Linux desktop in the headset | No | No | Native Linux PC capture is disabled; lxso2 lacked a desktop portal and PipeWire session. The separate Steam `-pipewire` helper is not Frame Control's PC capture | 24 Oct, v0.4.17: [host audit](evidence/linux-pc-host-lxso2-2026-10-24.md), [Steam helper PR #163](https://github.com/saphid/frame-control/pull/163) |
|
||||
| Mac desktop in the headset | A physical Mac sent its screen/window to a real Frame | No | Current v0.4.26 Mac installer and worn-headset controller input have not been repeated | 28 Sep, pre-v0.4 / Frame OS 0.4.1: [Mac guide and measurement](streaming.md#b-show-the-macs-desktop-inside-the-frame) |
|
||||
@@ -22,8 +22,8 @@ Current for Frame Control **v0.4.36**, 25 October 2026. “Real Frame + physical
|
||||
| Passthrough in recordings | Real room pixels in OBS and a production MP4 after safe SteamVR wake | No | **Yes** for the checked 0.5.6 mirror. Raw cameras are not opened. Other games/modes and Steam clips may differ | 25 Oct: [validation](evidence/obs-validation-2026-10-25.md), [recording guide](recording.md#does-record-include-passthrough) |
|
||||
| Open at login | No Frame needed; Linux autostart file toggled on a physical Linux host with an isolated config directory | No | A full login session and physical Windows/macOS host login have not been checked; OS API calls were unit tested | 25 Oct, v0.4.26: [PR #189](https://github.com/saphid/frame-control/pull/189) |
|
||||
| Local Frame Control extensions | Example panel displayed live Frame status in a browser preview; the real Frame served a read-only example in the experimental headset panel | No | No wearer-visible extension view; third-party extensions and physical Windows/macOS extension folders untested | 25 Oct, v0.4.29: [PR #194](https://github.com/saphid/frame-control/pull/194), [PR #201](https://github.com/saphid/frame-control/pull/201), [extension guide](extensions.md) |
|
||||
| Experimental Frame Control SteamVR panel | Real Frame OS 0.5.6 installed its user service, registered an overlay, served authenticated live status and a read-only extension, saved a screenshot and short MP4, and removed owned resources | No | Headset was unworn in standby: overlay reported `not_visible`, stereo capture was dark; wearer-visible pixels and controller input are unverified. The new large read-only status screen has browser layout and packaging checks only | 25 Oct, v0.4.29 device check and v0.4.31 status screen: [PR #198](https://github.com/saphid/frame-control/pull/198), [PR #201](https://github.com/saphid/frame-control/pull/201), [PR #220](https://github.com/saphid/frame-control/pull/220), [panel guide](headset-app.md) |
|
||||
| Experimental Decky Loader manager | Real Frame OS 0.5.6 ARM64 user-service install, status, update and remove completed; seven official-store plugin backends loaded, and ProtonDB Badges completed managed store install/update/remove | No | v0.4.33 shared transaction lock and stopped-loader plugin removal have Linux and CI tests, but no live recovery test on the Frame. v0.4.35 treats `_root` store tags as root-required and hides duplicate IDs; this has test coverage, not a live store check. v0.4.36 stops a store install or update when the catalog hash changed after review; this was tested with a fake Frame, not with a live store change on the headset. Plugin pages, settings, routes, toasts and controller input in VR remain unverified; some plugins need root, x86_64 binaries or Deck-specific hardware | 25 Oct, v0.4.31 hardware and v0.4.36 tests: [PR #208](https://github.com/saphid/frame-control/pull/208), [PR #230](https://github.com/saphid/frame-control/pull/230), [PR #234](https://github.com/saphid/frame-control/pull/234), [PR #246](https://github.com/saphid/frame-control/pull/246), [PR #253](https://github.com/saphid/frame-control/pull/253), [compatibility table](decky-frame.md) |
|
||||
| Experimental Framey VR plugin panels and store | No live real-Frame panel test | No | ProtonDB Badges settings rendered and sent a setting call in local Chromium. Official Steam Status rendered in local Chromium; a session-only cache avoided its earlier storage errors. A later local browser check restored a bounded per-plugin cache after panel reopen; live Decky and offline behavior were not tested. Fake Frame tests cover setting read/write through update and reinstall and install/remove two pinned official API v2 archives plus the modern API v1 Storage Cleaner archive. Page tests cover the official JSX runtime, disabled controls, text fields, sliders, grouped dropdowns and an author sample bundle with a settings tab and read-only backend quick action. The sample is not published in the store. The 11-archive manifest audit found only ProtonDB Badges API v2 in that set. Storage Cleaner 1.4.0 showed an empty-data panel and four disabled cleanup buttons in local Chromium with a stub backend; no cleanup action was tested. HLTB for Deck 2.0.12 built a shallow settings view and Bluetooth 3.1.1 built its initial panel in local frontend tests without effects or backend calls. Their badges, search, pairing and Bluetooth changes remain unverified. A hash match, local panel and fake Frame install do not prove VR use. Wearer-visible pixels, controller input and live settings persistence remain unverified | 25 Oct, v0.4.32–0.4.36: [#223](https://github.com/saphid/frame-control/pull/223), [#224](https://github.com/saphid/frame-control/pull/224), [#225](https://github.com/saphid/frame-control/pull/225), [#226](https://github.com/saphid/frame-control/pull/226), [#227](https://github.com/saphid/frame-control/pull/227), [#229](https://github.com/saphid/frame-control/pull/229), [#231](https://github.com/saphid/frame-control/pull/231), [#232](https://github.com/saphid/frame-control/pull/232), [#233](https://github.com/saphid/frame-control/pull/233), [#236](https://github.com/saphid/frame-control/pull/236), [#237](https://github.com/saphid/frame-control/pull/237), [#238](https://github.com/saphid/frame-control/pull/238), [#239](https://github.com/saphid/frame-control/pull/239), [#240](https://github.com/saphid/frame-control/pull/240), [#241](https://github.com/saphid/frame-control/pull/241), [#243](https://github.com/saphid/frame-control/pull/243), [#244](https://github.com/saphid/frame-control/pull/244), [#245](https://github.com/saphid/frame-control/pull/245), [#247](https://github.com/saphid/frame-control/pull/247), [#248](https://github.com/saphid/frame-control/pull/248), [#251](https://github.com/saphid/frame-control/pull/251), [#252](https://github.com/saphid/frame-control/pull/252), [#253](https://github.com/saphid/frame-control/pull/253), [browser/fake Frame evidence](evidence/framey-browser-2026-10-25.md), [guide](framey-loader.md) |
|
||||
| Experimental Frame Control SteamVR panel | Frame OS 0.5.6 registered and served the opt-in overlay. A later unattended check safely woke SteamVR and found the panel in stereo compositor and mirror captures; it removed its temporary service and restored power settings | No | No person wore the headset for that check. Physical controller input, scrolling, VR keyboard, comfort and legibility remain unverified. The large status view has browser and packaging checks, not a wearer review | 25 Oct, v0.4.29–0.4.37: [panel PR #201](https://github.com/saphid/frame-control/pull/201), [unattended evidence](evidence/framey-vr-2026-10-25/README.md), [worn checklist](framey-vr-check.md) |
|
||||
| Experimental Decky Loader manager | Frame OS 0.5.6 ARM64 user-service install/status/update/remove and managed ProtonDB install/update/remove passed earlier. A private Decky 3.3.0 service returned read-only ProtonDB and Steam Status setting RPCs; a later locked check verified the official Notebook default drawing/save/reopen flow and Simple Timer backend events in local Chromium | No | The loader remains opt-in. Installs/updates reject an archive whose Decky name collides with another installed plugin before download and under the headset transaction lock; this safety path has host/fake Frame tests, not a new real-headset collision test. The exact-version official catalog covers 116 plugins: 1 works (Notebook default flow only), 44 partial, 60 broken and 11 not applicable. Most official backends and VR controls remain unverified. Physical controller use and wearer-visible official panels remain unverified | 26 Oct, v0.4.31–0.4.38: [managed store PR #208](https://github.com/saphid/frame-control/pull/208), [name-collision PR #269](https://github.com/saphid/frame-control/pull/269), [catalog PR #266](https://github.com/saphid/frame-control/pull/266), [backend event evidence](evidence/framey-events-backend-2026-10-26.json), [compatibility table](framey-compatibility.md) |
|
||||
| Experimental Framey VR plugin panels and store | An unattended Frame OS 0.5.6 run showed the Framey sample panel in stereo compositor and mirror captures. Synthetic OpenVR clicks changed a live sample setting, read its Decky backend file and confirmed it after a fresh iframe reopen | No | This was the repository sample, not an official store plugin. Physical controllers, native scrolling, VR keyboard, wearer comfort and legibility remain unverified. CSS Loader and SteamGridDB exact archives passed fake Frame install/remove and panel packing; this does not establish theme or artwork controls in VR. Panel-local backend events and React toasts have local/real-backend checks, but no wearer review. Pause Games and Tailscale Control settings render in local Chromium; their live actions are unverified. The 116-plugin catalog had 60 fake installs/removals and 56 expected blocks; 98 local Chromium initializers were ready and 18 failed. Notebook is marked works only for its default drawing/save/reopen flow | 26 Oct, v0.4.38: [unattended sample evidence](evidence/framey-vr-2026-10-25/README.md), [backend event PR #270](https://github.com/saphid/frame-control/pull/270), [settings/cache PR #271](https://github.com/saphid/frame-control/pull/271), [worn checklist](framey-vr-check.md) |
|
||||
|
||||
Release notes must preserve these limits until a newer recorded check changes them. When a feature is retested, add its date, exact app/Frame version, host type and proof here. The older [v0.3.1 matrix](support-matrix.html) is a historical snapshot and does not describe current coverage.
|
||||
+5
-2
@@ -88,8 +88,11 @@ entries. Install, update, and remove need an explicit confirmation. They only
|
||||
change Frame Control owned plugin folders under
|
||||
`~/.local/share/frame-control/decky/plugins`. The installer checks the store
|
||||
hash and archive paths, blocks root plugins and non-ARM64 binaries, and rolls
|
||||
back an update if the service restart fails. The current VR shell does not
|
||||
provide Decky's Steam Deck Quick Access Menu. Frame Control's headset panel
|
||||
back an update if the service restart fails. An update is also refused if its
|
||||
new plugin name is already used by another installed plugin, because Decky
|
||||
routes backend calls by name. This collision check has local tests only.
|
||||
The current VR shell does not provide Decky's Steam Deck Quick Access Menu.
|
||||
Frame Control's headset panel
|
||||
shows installed plugins and store actions. Its separate Framey iframe hosts
|
||||
API v2 pages and API v1 bundles that use Decky's modern frontend initializer,
|
||||
with settings, local routes, and panel toasts where supported.
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"checkedAtUtc": "2026-10-26T00:29:00Z",
|
||||
"scope": "Exact archived frontends only. Production Framey host with bounded SteamClient JSON cache, image fallback and inert Steam subscriptions/context. Opaque iframe; all backend calls and network denied. No headset or plugin backends used in this follow-up.",
|
||||
"baseHostCommit": "52a0974",
|
||||
"before": {
|
||||
"ready": 96,
|
||||
"failed": 20
|
||||
},
|
||||
"after": {
|
||||
"ready": 98,
|
||||
"failed": 18
|
||||
},
|
||||
"regressions": [],
|
||||
"observations": [
|
||||
{
|
||||
"id": 13,
|
||||
"name": "Pause Games",
|
||||
"ready": true,
|
||||
"error": null,
|
||||
"logs": [],
|
||||
"methods": [
|
||||
"load_settings"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"name": "Tailscale Control",
|
||||
"ready": true,
|
||||
"error": null,
|
||||
"logs": [],
|
||||
"methods": [
|
||||
"get_tailscale_state"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"name": "Steamback",
|
||||
"ready": false,
|
||||
"error": "Cannot read properties of undefined (reading 'm_CurrentUser')",
|
||||
"logs": [],
|
||||
"methods": []
|
||||
},
|
||||
{
|
||||
"id": 66,
|
||||
"name": "PlayTime",
|
||||
"ready": false,
|
||||
"error": "Cannot read properties of undefined (reading 'm_mapApps')",
|
||||
"logs": [
|
||||
{
|
||||
"kind": "error",
|
||||
"message": "%c PlayTime %c ERROR background: #16a085; color: black; background: #FF000040; Impossible to get \"RunningApps\" observe. Events will not be attached."
|
||||
},
|
||||
{
|
||||
"kind": "error",
|
||||
"message": "%c PlayTime %c ERROR background: #16a085; color: black; background: #FF000040; Unable to get settings, saving defaults Error: Not found"
|
||||
}
|
||||
],
|
||||
"methods": [
|
||||
"fetch_playtime_information",
|
||||
"statistics_for_last_two_weeks"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 120,
|
||||
"name": "Reroll",
|
||||
"ready": true,
|
||||
"error": null,
|
||||
"logs": [],
|
||||
"methods": [
|
||||
"get_setting",
|
||||
"version"
|
||||
]
|
||||
}
|
||||
],
|
||||
"tailscaleFocusedCheck": {
|
||||
"rendered": [
|
||||
"Settings",
|
||||
"Toggle Tailscale",
|
||||
"Exit Node IP",
|
||||
"Toggle LAN Access",
|
||||
"Advanced Settings",
|
||||
"Device Status",
|
||||
"Tailscale Setup"
|
||||
],
|
||||
"assetError": "Hardcoded localhost:1337 QR SVG blocked by image CSP",
|
||||
"actions": "Inspected only; no toggles, exit nodes, installs or connection actions"
|
||||
},
|
||||
"pauseGamesFocusedCheck": {
|
||||
"rendered": [
|
||||
"Pause before Suspend",
|
||||
"Pause on focus loss"
|
||||
],
|
||||
"actions": "Inspected only; no process signaling or suspend actions"
|
||||
},
|
||||
"storageChecks": [
|
||||
"Per-plugin cache survives panel reopen",
|
||||
"Missing key rejects Not found",
|
||||
"JSON serialization and cache quota enforced",
|
||||
"Malformed and stale-session cache updates rejected"
|
||||
],
|
||||
"unverified": [
|
||||
"Real backends for these plugins",
|
||||
"Tailscale network state/actions",
|
||||
"Live games and pause/resume",
|
||||
"Worn VR/controller/keyboard"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# v0.4.37 release smoke on a real Frame
|
||||
|
||||
On 26 October 2026 at 00:27 UTC, a physical Linux host ran the release
|
||||
`scripts/frame-smoke.sh` against a real Steam Frame. The smoke scripts were
|
||||
byte-identical to tag `v0.4.37` (`5239dae1a82c35635ef89f59eac9721c5014e2ed`).
|
||||
The headset reported Frame OS 0.5.6, build `20261022.6153909`.
|
||||
|
||||
The test acquired `/tmp/frame-test.lock` with a unique owner marker before
|
||||
reading the headset. SteamVR activity stayed at 3 (standby); initial proximity
|
||||
raw was 6.3125 and battery was 99%. The test released only its own lock after
|
||||
cleanup. Battery read 100% afterward.
|
||||
|
||||
All **13 of 13** smoke steps passed. ARM64 and Windows test titles installed,
|
||||
launched, and were removed. The x86_64 title installed and was removed; its
|
||||
launch reported that Steam Linux Runtime 4.0 was found but not installed, so
|
||||
this result does **not** establish x86_64 game execution. Devkit properties and
|
||||
status reads passed. No key was paired. The final cleanup removed the reserved
|
||||
test titles and processes. The private result remains under
|
||||
`/tmp/frame-v0437-smoke-results/`; this summary omits headset identifiers and
|
||||
device status details.
|
||||
|
||||
Because the headset was in standby, this test did not verify wearer-visible
|
||||
pixels, physical controller input, a Framey panel, or official Decky plugin
|
||||
settings in VR. Those checks remain on the [worn checklist](../framey-vr-check.md).
|
||||
@@ -4,7 +4,7 @@ Snapshot: 2026-10-25T23:19:48.854504+00:00; 116 current visible official plugins
|
||||
|
||||
This table and both store views use [`ui/framey-compatibility.json`](../ui/framey-compatibility.json). Results apply only to the exact store ID, version and SHA-256. A changed digest/version is **unverified**. Regenerate this document with `python3 scripts/framey-compatibility-doc.py`; use `--check` in review.
|
||||
|
||||
**Works: 1 · Partial: 42 · Broken: 62 · N/A: 11.**
|
||||
**Works: 1 · Partial: 44 · Broken: 60 · N/A: 11.**
|
||||
|
||||
- **Works** requires verified useful plugin behavior with its real backend. Read the reason for the verified scope and remaining limits; it does not imply worn-VR verification.
|
||||
- **Partial** means a limited frontend/settings surface initialized or rendered. Backend-dependent functionality remains unverified; it is not an end-to-end success claim.
|
||||
@@ -27,7 +27,7 @@ Open the browser audit URL in local Chromium, run `runAudit()` and inspect/downl
|
||||
|
||||
The current published digests are pinned in the JSON; if the store changed, retain/download those exact archives for reproduction. Audit downloads permit larger ZIPs for inspection; production installer limits remain 50 MiB ZIP / 150 MiB unpacked / 30 MiB per member.
|
||||
|
||||
After the VR job released the shared lock, this audit acquired it and used a private temporary Decky 3.3.0 ARM64 user service. Read-only ProtonDB `get_setting` and Steam Status `get_settings` calls succeeded through the live Steam CEF websocket. The service and private base were removed and the lock released; normal managed services stayed inactive, battery was 100 → 99, and no power setting was changed. [Redacted results](evidence/framey-official-backend-2026-10-25.json) record exactly what ran. A [2026-10-26 follow-up](evidence/framey-events-backend-2026-10-26.json) verified default Notebook drawing/save/reopen across a real backend restart and Simple Timer start/cancel, countdown/completion events, a visible toast and persisted recents/subtle mode. Only private test settings were written. Simple Timer remains partial because its own expired-timer startup raises a missing `timer_task` error; sound/suspend are unsupported. All other official backends remain unexecuted. The separate VR job proves Frame Control/sample compositor rendering, not any official plugin. No worn-VR official-plugin result is claimed. Earlier focused checks cover [ProtonDB](evidence/framey-browser-2026-10-25.md), [Steam Status/cache](evidence/framey-cache-browser-2026-10-25.md), [Storage Cleaner](evidence/framey-api-v1-browser-2026-10-25.md), and [legacy MusicControl](evidence/framey-legacy-2026-10-25.md).
|
||||
After the VR job released the shared lock, this audit acquired it and used a private temporary Decky 3.3.0 ARM64 user service. Read-only ProtonDB `get_setting` and Steam Status `get_settings` calls succeeded through the live Steam CEF websocket. The service and private base were removed and the lock released; normal managed services stayed inactive, battery was 100 → 99, and no power setting was changed. [Redacted results](evidence/framey-official-backend-2026-10-25.json) record exactly what ran. A [2026-10-26 follow-up](evidence/framey-events-backend-2026-10-26.json) verified default Notebook drawing/save/reopen across a real backend restart and Simple Timer start/cancel, countdown/completion events, a visible toast and persisted recents/subtle mode. Only private test settings were written. Simple Timer remains partial because its own expired-timer startup raises a missing `timer_task` error; sound/suspend are unsupported. All other official backends remain unexecuted. The separate VR job proves Frame Control/sample compositor rendering, not any official plugin. No worn-VR official-plugin result is claimed. A [local UI follow-up](evidence/framey-ui-fallbacks-2026-10-26.json) rendered Pause Games and Tailscale Control settings with all backend/network actions denied; it does not verify process pausing or Tailscale changes. Earlier focused checks cover [ProtonDB](evidence/framey-browser-2026-10-25.md), [Steam Status/cache](evidence/framey-cache-browser-2026-10-25.md), [Storage Cleaner](evidence/framey-api-v1-browser-2026-10-25.md), and [legacy MusicControl](evidence/framey-legacy-2026-10-25.md).
|
||||
|
||||
## Every current plugin
|
||||
|
||||
@@ -40,7 +40,7 @@ The API column shows manifest / actual bundled frontend API. Many manifests say
|
||||
| 10: vibrantDeck 2.0.1 | legacy / legacy | N/A | Not supported on Frame: Changes X11 gamescope color properties, not the Frame SteamVR compositor. | static archive, local browser, fake-Frame |
|
||||
| 11: Fantastic 0.5.1 | legacy / legacy | N/A | Not supported on Frame: Targets Steam Deck fan hardware; no Frame fan-control mapping is provided. | static archive, local browser, fake-Frame |
|
||||
| 12: MusicControl 1.1.6 | legacy / legacy | partial | Legacy media controls initialize; player discovery and playback are unverified. | static archive, local browser, fake-Frame |
|
||||
| 13: Pause Games 1.0.3 | 1 / 2 | broken | Frontend audit failed: SteamClient.System.UI.RegisterForSystemKeyEvents is not a function. | static archive, local browser, fake-Frame |
|
||||
| 13: Pause Games 1.0.3 | 1 / 2 | partial | Settings render locally with inert system-key subscriptions and empty detached Steam context. Live-game discovery, process pause/resume and suspend integration are unsupported/unverified; no backend actions ran. | static archive, local browser, fake-Frame |
|
||||
| 14: ProtonDB Badges 1.3.4 | 2 / 2 | partial | Settings render locally and read-only get_setting RPC returned the default through real Decky 3.3.0 on ARM64 Frame. Steam game-page badges have no patch target; live writes and VR controls unverified. | static archive, local browser, fake-Frame, real Frame SSH |
|
||||
| 16: Audio Loader 1.6.1 | legacy / legacy | broken | This plugin needs root and is not supported by the Frame user service. Frontend audit failed: Cannot read properties of null (reading 'm_GamepadUIAudioStore'). | static archive, local browser, fake-Frame |
|
||||
| 18: Bluetooth 3.1.1 | 1 / 2 | partial | Controls render with fixture replies; actual Bluetooth discovery, labels and connections remain unverified. | static archive, local browser, fake-Frame |
|
||||
@@ -59,9 +59,9 @@ The API column shows manifest / actual bundled frontend API. Many manifests say
|
||||
| 37: Discord Status 1.5.0 | 1 / 2 | broken | RunningApps and appStore are absent; Discord activity integration fails after initialization. | static archive, local browser, fake-Frame |
|
||||
| 38: Volume Mixer 1.2.1-1 | legacy / legacy | partial | Initializer succeeds; user PulseAudio/PipeWire mixer backend and volume changes unverified. | static archive, local browser, fake-Frame |
|
||||
| 40: Controller Tools 2.0.2 | 1 / 2 | broken | Plugin contains a non-ARM64 Linux binary: ControllerTools/bin/backend. Frontend audit failed: Audit blocks WebSocket. | static archive, local browser, fake-Frame |
|
||||
| 43: Tailscale Control 0.1.3 | legacy / legacy | broken | Initializer succeeds but React rejects a missing component (#130); no usable control claim. | static archive, local browser, fake-Frame |
|
||||
| 43: Tailscale Control 0.1.3 | legacy / legacy | partial | Settings render locally after the SuspensefulImage fallback. Tailscale daemon/state, connection and exit-node changes unverified; bundled localhost QR asset remains unavailable. No network settings were changed. | static archive, local browser, fake-Frame |
|
||||
| 44: DeckMTP 1.0.5 | legacy / legacy | broken | Plugin contains a non-ARM64 Linux binary: DeckMTP/bin/umtprd. | static archive, local browser, fake-Frame |
|
||||
| 46: Steamback 1.0.0-1 | legacy / legacy | broken | Unsafe plugin archive member. Frontend audit failed: App is not defined. | static archive, local browser, fake-Frame |
|
||||
| 46: Steamback 1.0.0-1 | legacy / legacy | broken | Frontend audit failed: Cannot read properties of undefined (reading 'm_CurrentUser'). Steam account/app context is unavailable. | static archive, local browser, fake-Frame |
|
||||
| 48: Quick Launch 1.2.1 | legacy / legacy | broken | This plugin needs root and is not supported by the Frame user service. | static archive, local browser, fake-Frame |
|
||||
| 49: Decky Cloud Save 1.4.2 | legacy / legacy | broken | Unsafe plugin archive member. | static archive, local browser, fake-Frame |
|
||||
| 50: Storage Cleaner 1.4.0 | 1 / 2 | partial | Empty-data panel rendered; cleanup was deliberately not run because it can remove local data. | static archive, local browser, fake-Frame |
|
||||
@@ -76,7 +76,7 @@ The API column shows manifest / actual bundled frontend API. Many manifests say
|
||||
| 63: Free Loader 1.6.0 | 1 / 2 | broken | Unsafe plugin archive member. | static archive, local browser, fake-Frame |
|
||||
| 64: Syncthing 0.2.7 | legacy / legacy | broken | Plugin contains a non-ARM64 Linux binary: decky-syncthing/bin/decky-syncthing-watchdog. | static archive, local browser, fake-Frame |
|
||||
| 65: Decky Terminal 0.5.3 | 1 / 2 | partial | Local routes initialize after parameter-route support; no shell commands were run. | static archive, local browser, fake-Frame |
|
||||
| 66: PlayTime 3.2.5 | 1 / 2 | broken | Frontend audit failed: Cannot read properties of undefined (reading 'GetJSON'). | static archive, local browser, fake-Frame |
|
||||
| 66: PlayTime 3.2.5 | 1 / 2 | broken | Private SteamClient JSON cache now works; initialization still fails on missing Steam appStore.m_mapApps. Live app data and MobX game observations are unavailable; backend actions were denied. | static archive, local browser, fake-Frame |
|
||||
| 67: Battery Tracker 0.2.0 | legacy / legacy | broken | This plugin needs root and is not supported by the Frame user service. | static archive, local browser, fake-Frame |
|
||||
| 68: Wine Cellar 0.2.1 | 1 / 2 | broken | Plugin contains a non-ARM64 Linux binary: decky-wine-cellar/bin/backend. Frontend audit failed: Audit blocks WebSocket. | static archive, local browser, fake-Frame |
|
||||
| 70: MicroSDeck 0.11.2 | legacy / 2 | broken | Plugin contains a non-ARM64 Linux binary: MicroSDeck/bin/backend. | static archive, local browser, fake-Frame |
|
||||
@@ -126,7 +126,7 @@ The API column shows manifest / actual bundled frontend API. Many manifests say
|
||||
| 117: DeckyParty File Manager 1.0.0 | legacy / legacy | partial | Initializer succeeds; privileged file operations and backend are unverified. No file changes were run. | static archive, local browser, fake-Frame |
|
||||
| 118: Speed Test 1.0.2 | 1 / 2 | partial | Initializer succeeds; automatic bandwidth tests were blocked by the audit network policy. | static archive, local browser, fake-Frame |
|
||||
| 119: Decky Clipboard 1.0.1 | 1 / 2 | broken | Plugin contains a non-ARM64 Linux binary: decky-clipboard/bin/xclip. | static archive, local browser, fake-Frame |
|
||||
| 120: Reroll 0.2.1 | 1 / 2 | broken | Plugin contains a non-ARM64 Linux binary: Reroll/bin/memscan.so. Frontend audit failed: window.SteamClient.Apps.RegisterForGameActionUserRequest is not a function. | static archive, local browser, fake-Frame |
|
||||
| 120: Reroll 0.2.1 | 1 / 2 | broken | Frontend now initializes with inert game-action subscriptions, but the archive contains x86_64 memscan.so and requests root. No process-memory access was run. | static archive, local browser, fake-Frame |
|
||||
| 121: DeckPass 1.1.1 | 1 / 2 | partial | Initializer succeeds; KeePass Flatpak discovery and secrets integration were not exercised. | static archive, local browser, fake-Frame |
|
||||
| 123: Steam Status 1.2.3 | 2 / 2 | partial | Settings/public status rendered locally; cache errors fixed. Read-only get_settings RPC returned actual Decky 3.3.0 defaults on ARM64 Frame. Persistence, live backend diagnostics and VR controls unverified. | static archive, local browser, fake-Frame, real Frame SSH |
|
||||
| 124: AlarMe 1.6.3 | 1 / 2 | partial | Initializer succeeds; timers, reminders, suspend inhibition and audio are unverified. | static archive, local browser, fake-Frame |
|
||||
@@ -301,7 +301,7 @@ All published Linux native payloads in this snapshot are x86_64; no published aa
|
||||
| Decky-Framegen | bin/nvngx.dll, bin/dlssg_to_fsr3_amd_is_better.dll, bin/nvapi64.dll | Windows DLL payload targets x86 game translation rather than a native ARM backend; archive is 75,140,408 bytes, beyond the 50 MiB installer limit. |
|
||||
| HueSync | bin/hello, py_modules/portio.so | portio and handheld LED backends depend on x86 I/O/unsupported hardware; ARM64 compilation alone is insufficient. |
|
||||
| Decky Clipboard | bin/xclip | xclip needs ARM64 X11 libraries/server; root and gamescope assumptions also block this archive. |
|
||||
| Reroll | bin/memscan.so | memscan.so needs ARM64 process-memory support and ABI testing; root requirement and missing Steam hook remain. |
|
||||
| Reroll | bin/memscan.so | memscan.so needs ARM64 process-memory support and ABI testing; root requirement remains. Frontend initializes but no memory access was tested. |
|
||||
| Decky Localsend | bin/localsend-core | LocalSend-Go commit 66edc509a28b9f877e7374504892dcbc653a4da4 cross-compiled successfully with CGO_ENABLED=0 GOOS=linux GOARCH=arm64. Static aarch64 ELF SHA-256 e79238f6051ce34f172f765ae918d3d902b819e27377989a55e5b1eba10e46f9. Not executed; source tip is not proven to match the shipped helper/protocol. Official ZIP is still x86_64. |
|
||||
| Launch Options | bin/hello | Remove the template x86 bin/hello or publish an ARM64 rebuild; missing Steam module mapping also prevents frontend initialization. |
|
||||
| Decky-Clipper | bin/gstreamer-1.0/libgstproxy.so, bin/gstreamer-1.0/libgstmidi.so, bin/gstreamer-1.0/libgstmpegtsmux.so, bin/gstreamer-1.0/gst-plugin-scanner, bin/gstreamer-1.0/libgstivfparse.so, … 115 total; full list in JSON | Rebuild all bundled GStreamer libraries/plugins for ARM64 and verify capture/codec ABI against Frame SteamVR; gamescope capture differs. |
|
||||
|
||||
@@ -113,6 +113,17 @@ work. IndexedDB is hidden because the browser exposes it but denies access in
|
||||
an opaque iframe; localForage can then select Framey's bounded localStorage.
|
||||
Direct IndexedDB users remain unsupported.
|
||||
|
||||
`SuspensefulImage` renders a native image. SteamClient `Storage.GetJSON` and
|
||||
`SetObject` use the same bounded per-plugin cache, including panel reopen
|
||||
persistence; they do not read Steam's account/global JSON store. Missing keys
|
||||
reject with `Not found`. Detached `SteamUIStore.RunningApps` is a frozen empty
|
||||
list, and the suspend store has no methods. Optional Steam app/account globals
|
||||
are undefined. These fallbacks let settings render without inventing live
|
||||
games or account identity. [The exact-bundle follow-up](evidence/framey-ui-fallbacks-2026-10-26.json)
|
||||
verifies Pause Games and Tailscale Control settings in local Chromium; their
|
||||
primary backend actions remain unverified. PlayTime still requires Steam's
|
||||
real app store and game observers.
|
||||
|
||||
The fake Frame integration test now writes a Decky setting through Framey's
|
||||
scoped backend endpoint, reads it in a new CEF request, updates the plugin,
|
||||
then checks it again. It also confirms removal blocks method calls and a
|
||||
|
||||
@@ -54,6 +54,8 @@ def render(data):
|
||||
'All other official backends remain unexecuted. '
|
||||
'The separate VR job proves Frame Control/sample compositor rendering, not any official plugin. '
|
||||
'No worn-VR official-plugin result is claimed. '
|
||||
'A [local UI follow-up](evidence/framey-ui-fallbacks-2026-10-26.json) rendered Pause Games and Tailscale Control settings '
|
||||
'with all backend/network actions denied; it does not verify process pausing or Tailscale changes. '
|
||||
'Earlier focused checks cover [ProtonDB](evidence/framey-browser-2026-10-25.md), '
|
||||
'[Steam Status/cache](evidence/framey-cache-browser-2026-10-25.md), '
|
||||
'[Storage Cleaner](evidence/framey-api-v1-browser-2026-10-25.md), and '
|
||||
|
||||
@@ -20,7 +20,7 @@ const window={React,ReactDOM:{createRoot:node=>{
|
||||
const listeners=[];const messages=[];const parent={postMessage:message=>messages.push(message)};
|
||||
const context=vm.createContext({window,document:{getElementById:id=>{
|
||||
if(!elements.has(id))elements.set(id,new Element());return elements.get(id);},createElement:()=>new Element()},
|
||||
navigator:{language:'en'}, parent,addEventListener:(type,handler)=>listeners.push({type,handler}),
|
||||
navigator:{language:'en'}, parent,DOMException,addEventListener:(type,handler)=>listeners.push({type,handler}),
|
||||
setTimeout:()=>1,clearTimeout(){}});
|
||||
vm.runInContext(testSource,context);
|
||||
// Official @decky/rollup bundles use the automatic JSX runtime, including keyed lists.
|
||||
@@ -175,6 +175,25 @@ assert.equal(unsubscribe.unregister,unsubscribe);
|
||||
unsubscribe.unregister();unsubscribe();assert.equal(eventCount,0);
|
||||
assert.equal(window.SteamClient.System.ShutdownPC,undefined);
|
||||
assert.equal(window.DFL.Router.MainRunningApp,undefined);
|
||||
assert.equal(window.DFL.Router.RunningApps.length,0);
|
||||
assert.equal(window.DFL.Router.RunningApps,window.SteamUIStore.RunningApps);
|
||||
assert.ok(Object.isFrozen(window.SteamUIStore));
|
||||
assert.ok(Object.isFrozen(window.SuspendResumeStore));
|
||||
assert.equal(window.SuspendResumeStore.InitiateSleep,undefined);
|
||||
for(const name of ['App','appStore','appInfoStore','collectionStore'])assert.equal(window[name],undefined);
|
||||
for(const register of [window.SteamClient.System.UI.RegisterForSystemKeyEvents,
|
||||
window.SteamClient.Apps.RegisterForGameActionUserRequest]){
|
||||
const dispose=register(()=>eventCount++);assert.equal(dispose.unregister,dispose);dispose();
|
||||
}
|
||||
assert.equal(eventCount,0);
|
||||
const image=window.DFL.SuspensefulImage({src:'data:image/svg+xml,<svg/>',alt:'Setup',style:{width:50}});
|
||||
assert.equal(image.type,'img');assert.equal(image.props.alt,'Setup');assert.equal(image.props.style.width,50);
|
||||
await assert.rejects(window.SteamClient.Storage.GetJSON('missing'),/Not found/);
|
||||
await window.SteamClient.Storage.SetObject('settings',{enabled:false,count:3});
|
||||
assert.equal(await window.SteamClient.Storage.GetJSON('settings'),'{"enabled":false,"count":3}');
|
||||
assert.equal(window.localStorage.getItem('steam-client:settings'),'{"enabled":false,"count":3}');
|
||||
await assert.rejects(window.SteamClient.Storage.SetObject('large','x'.repeat(65537)),/Framey plugin cache is full/);
|
||||
await assert.rejects(window.SteamClient.Storage.SetObject('undefined',undefined),/JSON value/);
|
||||
assert.equal(window.indexedDB,undefined); // localForage must use bounded plugin storage.
|
||||
const unpatch=window.DFL.beforePatch({},'method',()=>{});
|
||||
assert.equal(unpatch.unpatch,unpatch);unpatch.unpatch();
|
||||
|
||||
@@ -38,7 +38,7 @@ function iframe(){
|
||||
for(const message of queued)onParent({source:proxy,data:message});
|
||||
live=true;
|
||||
const send=message=>onParent({source:proxy,data:message});
|
||||
return {storage:frameWindow.localStorage,send};
|
||||
return {storage:frameWindow.localStorage,steamStorage:frameWindow.SteamClient.Storage,send};
|
||||
}
|
||||
let first=iframe();
|
||||
assert.equal(first.storage.getItem('status-cache'),null);
|
||||
@@ -46,6 +46,7 @@ first.storage.setItem('status-cache','{"online":true}');
|
||||
assert.equal(values.get('framey-plugin-cache-v1:14'),'{"status-cache":"{\\"online\\":true}"}');
|
||||
first.storage.setItem('temporary','discard');
|
||||
first.storage.removeItem('temporary');
|
||||
await first.steamStorage.SetObject('playtime-settings',{count:3});
|
||||
const oldSession=session;
|
||||
vm.runInContext('openFrameyPanel(14,"Steam Status")',parent);
|
||||
session=vm.runInContext('frameySession',parent);
|
||||
@@ -57,13 +58,15 @@ assert.equal(reopenedURL.searchParams.get('session'),session);
|
||||
assert.equal(reopenedURL.hash.slice(1),session);
|
||||
let reopened=iframe();
|
||||
assert.equal(reopened.storage.getItem('status-cache'),'{"online":true}');
|
||||
assert.equal(reopened.storage.length,1);
|
||||
assert.equal(reopened.storage.length,2);
|
||||
assert.equal(reopened.storage.key(0),'status-cache');
|
||||
assert.equal(await reopened.steamStorage.GetJSON('playtime-settings'),'{"count":3}');
|
||||
assert.throws(()=>reopened.storage.setItem('huge','x'.repeat(65537)),/Framey plugin cache is full/);
|
||||
// A malformed or over-limit message from a plugin must not poison saved data.
|
||||
const savedCache=values.get('framey-plugin-cache-v1:14');
|
||||
reopened.send({type:'framey:cache:save',session,entries:{huge:'x'.repeat(65537)}});
|
||||
assert.equal(values.get('framey-plugin-cache-v1:14'),'{"status-cache":"{\\"online\\":true}"}');
|
||||
assert.equal(values.get('framey-plugin-cache-v1:14'),savedCache);
|
||||
reopened.send({type:'framey:cache:save',session:oldSession,entries:{poison:'yes'}});
|
||||
assert.equal(values.get('framey-plugin-cache-v1:14'),'{"status-cache":"{\\"online\\":true}"}');
|
||||
assert.equal(values.get('framey-plugin-cache-v1:14'),savedCache);
|
||||
reopened.storage.clear();
|
||||
assert.equal(values.get('framey-plugin-cache-v1:14'),'{}');
|
||||
@@ -181,6 +181,27 @@ class StoreTransaction(unittest.TestCase):
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertFalse(self.folder.exists())
|
||||
|
||||
def test_remote_apply_rechecks_plugin_name_under_transaction_lock(self):
|
||||
data, _ = archive()
|
||||
other = self.folder.parent / 'Other'
|
||||
other.mkdir()
|
||||
(other / 'plugin.json').write_text('{"name":"Example"}')
|
||||
result = self.apply('install', data)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('different plugin with this name', result.stderr)
|
||||
self.assertFalse(self.folder.exists())
|
||||
self.assertEqual((other / 'plugin.json').read_text(), '{"name":"Example"}')
|
||||
self.assertEqual(list(self.folder.parent.glob('.frame-control-stage-*')), [])
|
||||
|
||||
(other / 'plugin.json').write_text('{"name":"Distinct"}')
|
||||
self.assertEqual(self.apply('install', data).returncode, 0)
|
||||
(other / 'plugin.json').write_text('{"name":"Example"}')
|
||||
result = self.apply('update', data)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('different plugin with this name', result.stderr)
|
||||
self.assertTrue(self.folder.exists())
|
||||
self.assertEqual(list(self.folder.parent.glob('.frame-control-stage-*')), [])
|
||||
|
||||
|
||||
class StoreHost(unittest.TestCase):
|
||||
def test_reviewed_archive_changed_before_install_does_not_download(self):
|
||||
@@ -219,6 +240,36 @@ class StoreHost(unittest.TestCase):
|
||||
'install', 42)
|
||||
network.assert_not_called()
|
||||
|
||||
def test_update_rejects_name_taken_by_another_plugin_before_download(self):
|
||||
state = {'managed': True, 'running': True, 'plugins': [
|
||||
{'storeId': 42, 'folder': 'Example', 'name': 'Old Name', 'version': '1.0'},
|
||||
{'storeId': 43, 'folder': 'Other', 'name': 'Example', 'version': '1.0'}]}
|
||||
catalog = [{'id': 42, 'name': 'Example', 'root': False,
|
||||
'sha256': 'a' * 64, 'version': '2.0'}]
|
||||
with patch.object(frame_decky_store.frame_decky, 'status', return_value=state), \
|
||||
patch.object(frame_decky_store, 'catalog', return_value=catalog), \
|
||||
patch.object(frame_decky_store.urllib.request, 'urlopen',
|
||||
side_effect=AssertionError('No download expected')):
|
||||
with self.assertRaisesRegex(ValueError, 'different plugin with this name'):
|
||||
frame_decky_store.change(lambda *args, **kwargs: self.fail('No headset write expected'),
|
||||
'update', 42)
|
||||
|
||||
def test_update_allows_its_own_existing_name(self):
|
||||
data, digest = archive()
|
||||
state = {'managed': True, 'running': True, 'plugins': [
|
||||
{'storeId': 42, 'folder': 'Example', 'name': 'Example', 'version': '1.0'}]}
|
||||
catalog = [{'id': 42, 'name': 'Example', 'root': False,
|
||||
'sha256': digest, 'version': '2.0'}]
|
||||
calls = []
|
||||
with patch.object(frame_decky_store.frame_decky, 'status', return_value=state), \
|
||||
patch.object(frame_decky_store, 'catalog', return_value=catalog), \
|
||||
patch.object(frame_decky_store.urllib.request, 'urlopen', return_value=io.BytesIO(data)):
|
||||
result = frame_decky_store.change(lambda command, **kwargs: calls.append(command) or '{}',
|
||||
'update', 42)
|
||||
self.assertEqual(result, {})
|
||||
self.assertEqual(len(calls), 3) # Upload, apply, and temporary archive cleanup.
|
||||
self.assertIn('update', calls[1])
|
||||
|
||||
def test_owned_plugin_can_be_removed_without_store_network(self):
|
||||
state = {'managed': True, 'running': True, 'plugins':
|
||||
[{'storeId': 42, 'folder': 'Example', 'name': 'Example', 'version': '1.0'}]}
|
||||
|
||||
+19
-3
@@ -175,6 +175,19 @@ try:
|
||||
z.extractall(stage)
|
||||
unpacked=stage/folder
|
||||
if not unpacked.is_dir(): raise RuntimeError('Missing plugin folder')
|
||||
# Recheck under the transaction lock. Another store operation may
|
||||
# have installed a colliding name while this archive downloaded.
|
||||
proposed_name=json.loads((unpacked/'plugin.json').read_text()).get('name')
|
||||
for peer in base.iterdir():
|
||||
if peer==target or peer.is_symlink() or not peer.is_dir(): continue
|
||||
manifest=peer/'plugin.json'
|
||||
if manifest.is_symlink() or not manifest.is_file(): continue
|
||||
try:
|
||||
if manifest.stat().st_size>16384: continue
|
||||
peer_data=json.loads(manifest.read_text())
|
||||
if isinstance(peer_data,dict) and peer_data.get('name')==proposed_name:
|
||||
raise RuntimeError('A different plugin with this name is already installed')
|
||||
except (OSError,ValueError,UnicodeError): continue
|
||||
(unpacked/marker).write_text(json.dumps({'storeId':int(plugin_id),'sha256':digest,'version':version})+'\n')
|
||||
# Stop even a failed unit before removal to cancel any pending auto-restart.
|
||||
if running or action=='remove': service('stop','frame-control-decky.service')
|
||||
@@ -228,9 +241,12 @@ def change(ssh, action, plugin_id, report=lambda stage, percent=None: None, revi
|
||||
raise ValueError('Root plugins are not supported by the Frame user service')
|
||||
if plugin.get('compatibility', {}).get('blockInstall'):
|
||||
raise ValueError(plugin['compatibility']['reason'])
|
||||
existing_name = [p for p in state['plugins'] if p.get('name') == plugin['name']]
|
||||
if action == 'install' and existing_name:
|
||||
raise ValueError('A plugin with this name is already installed')
|
||||
# Decky routes backend calls by plugin name. A store update may rename
|
||||
# its plugin, so check names for updates as well as fresh installs.
|
||||
existing_name = [p for p in state['plugins'] if p.get('name') == plugin['name']
|
||||
and (action == 'install' or p.get('storeId') != plugin_id)]
|
||||
if existing_name:
|
||||
raise ValueError('A different plugin with this name is already installed')
|
||||
if action == 'update' and len(installed) != 1:
|
||||
raise ValueError('Only an installed Frame Control store plugin can be updated')
|
||||
data = None
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"checkedAtUtc": "2026-10-25T23:19:48.854504+00:00",
|
||||
"source": "https://plugins.deckbrew.xyz/plugins",
|
||||
"hostCommit": "779f9c5",
|
||||
"method": "Exact SHA-256 verified archives. All frontends attempted in opaque-iframe Chromium for 1.2 s with backend calls and network denied; ready means initializer only. No official backend code was executed. See docs/framey-compatibility.md.",
|
||||
"method": "Exact SHA-256 verified archives. All frontends attempted in opaque-iframe Chromium for 1.2 s with backend calls and network denied; ready means initializer only. Backend calls were denied in the baseline browser audit. Scoped live backend follow-ups are recorded separately in docs/framey-compatibility.md.",
|
||||
"plugins": [
|
||||
{
|
||||
"id": 1,
|
||||
@@ -579,8 +579,8 @@
|
||||
"https://github.com/wynn1212/SDH-PauseGames",
|
||||
"https://github.com/wynn1212/SDH-PauseGames.git"
|
||||
],
|
||||
"status": "broken",
|
||||
"reason": "Frontend audit failed: SteamClient.System.UI.RegisterForSystemKeyEvents is not a function.",
|
||||
"status": "partial",
|
||||
"reason": "Settings render locally with inert system-key subscriptions and empty detached Steam context. Live-game discovery, process pause/resume and suspend integration are unsupported/unverified; no backend actions ran.",
|
||||
"blockInstall": false,
|
||||
"verification": [
|
||||
"static archive",
|
||||
@@ -590,8 +590,8 @@
|
||||
"browser": {
|
||||
"id": 13,
|
||||
"name": "Pause Games",
|
||||
"ready": false,
|
||||
"error": "SteamClient.System.UI.RegisterForSystemKeyEvents is not a function",
|
||||
"ready": true,
|
||||
"error": null,
|
||||
"logs": [],
|
||||
"methods": [
|
||||
"load_settings"
|
||||
@@ -3155,8 +3155,8 @@
|
||||
"https://github.com/saumya-banthia/tailscale-control.git",
|
||||
"https://github.com/tailscale-dev/deck-tailscale"
|
||||
],
|
||||
"status": "broken",
|
||||
"reason": "Initializer succeeds but React rejects a missing component (#130); no usable control claim.",
|
||||
"status": "partial",
|
||||
"reason": "Settings render locally after the SuspensefulImage fallback. Tailscale daemon/state, connection and exit-node changes unverified; bundled localhost QR asset remains unavailable. No network settings were changed.",
|
||||
"blockInstall": false,
|
||||
"verification": [
|
||||
"static archive",
|
||||
@@ -3168,13 +3168,10 @@
|
||||
"name": "Tailscale Control",
|
||||
"ready": true,
|
||||
"error": null,
|
||||
"logs": [
|
||||
{
|
||||
"kind": "error",
|
||||
"message": "Error: Minified React error #130; visit https://reactjs.org/docs/error-decoder.html?invariant=130&args[]=undefined&args[]= for the full message or use the non-minified dev environment for full errors and additional helpful warnings."
|
||||
}
|
||||
],
|
||||
"methods": []
|
||||
"logs": [],
|
||||
"methods": [
|
||||
"get_tailscale_state"
|
||||
]
|
||||
},
|
||||
"archiveValidation": {
|
||||
"id": 43,
|
||||
@@ -3355,7 +3352,7 @@
|
||||
"https://github.com/geeksville/steamback.git"
|
||||
],
|
||||
"status": "broken",
|
||||
"reason": "Unsafe plugin archive member. Frontend audit failed: App is not defined.",
|
||||
"reason": "Frontend audit failed: Cannot read properties of undefined (reading 'm_CurrentUser'). Steam account/app context is unavailable.",
|
||||
"blockInstall": true,
|
||||
"verification": [
|
||||
"static archive",
|
||||
@@ -3366,7 +3363,7 @@
|
||||
"id": 46,
|
||||
"name": "Steamback",
|
||||
"ready": false,
|
||||
"error": "App is not defined",
|
||||
"error": "Cannot read properties of undefined (reading 'm_CurrentUser')",
|
||||
"logs": [],
|
||||
"methods": []
|
||||
},
|
||||
@@ -4926,7 +4923,7 @@
|
||||
"https://github.com/your-username/PlayTime"
|
||||
],
|
||||
"status": "broken",
|
||||
"reason": "Frontend audit failed: Cannot read properties of undefined (reading 'GetJSON').",
|
||||
"reason": "Private SteamClient JSON cache now works; initialization still fails on missing Steam appStore.m_mapApps. Live app data and MobX game observations are unavailable; backend actions were denied.",
|
||||
"blockInstall": false,
|
||||
"verification": [
|
||||
"static archive",
|
||||
@@ -4937,9 +4934,21 @@
|
||||
"id": 66,
|
||||
"name": "PlayTime",
|
||||
"ready": false,
|
||||
"error": "Cannot read properties of undefined (reading 'GetJSON')",
|
||||
"logs": [],
|
||||
"methods": []
|
||||
"error": "Cannot read properties of undefined (reading 'm_mapApps')",
|
||||
"logs": [
|
||||
{
|
||||
"kind": "error",
|
||||
"message": "%c PlayTime %c ERROR background: #16a085; color: black; background: #FF000040; Impossible to get \"RunningApps\" observe. Events will not be attached."
|
||||
},
|
||||
{
|
||||
"kind": "error",
|
||||
"message": "%c PlayTime %c ERROR background: #16a085; color: black; background: #FF000040; Unable to get settings, saving defaults Error: Not found"
|
||||
}
|
||||
],
|
||||
"methods": [
|
||||
"fetch_playtime_information",
|
||||
"statistics_for_last_two_weeks"
|
||||
]
|
||||
},
|
||||
"archiveValidation": {
|
||||
"id": 66,
|
||||
@@ -10375,7 +10384,7 @@
|
||||
"https://github.com/kayon/memscan"
|
||||
],
|
||||
"status": "broken",
|
||||
"reason": "Plugin contains a non-ARM64 Linux binary: Reroll/bin/memscan.so. Frontend audit failed: window.SteamClient.Apps.RegisterForGameActionUserRequest is not a function.",
|
||||
"reason": "Frontend now initializes with inert game-action subscriptions, but the archive contains x86_64 memscan.so and requests root. No process-memory access was run.",
|
||||
"blockInstall": true,
|
||||
"verification": [
|
||||
"static archive",
|
||||
@@ -10385,11 +10394,12 @@
|
||||
"browser": {
|
||||
"id": 120,
|
||||
"name": "Reroll",
|
||||
"ready": false,
|
||||
"error": "window.SteamClient.Apps.RegisterForGameActionUserRequest is not a function",
|
||||
"ready": true,
|
||||
"error": null,
|
||||
"logs": [],
|
||||
"methods": [
|
||||
"get_setting"
|
||||
"get_setting",
|
||||
"version"
|
||||
]
|
||||
},
|
||||
"archiveValidation": {
|
||||
@@ -10399,7 +10409,7 @@
|
||||
},
|
||||
"backend": "Python + native payload",
|
||||
"hardware": "No specific hardware requirement detected statically; runtime requirements unverified.",
|
||||
"nativePort": "memscan.so needs ARM64 process-memory support and ABI testing; root requirement and missing Steam hook remain.",
|
||||
"nativePort": "memscan.so needs ARM64 process-memory support and ABI testing; root requirement remains. Frontend initializes but no memory access was tested.",
|
||||
"uiSurfaces": [
|
||||
"QAM/global tabs",
|
||||
"Steam stores"
|
||||
|
||||
+22
-3
@@ -11,6 +11,11 @@
|
||||
: R.createElement(type, elementProps);
|
||||
};
|
||||
window.SP_JSX = {jsx, jsxs:jsx, Fragment:R.Fragment};
|
||||
// Let plugins use optional chaining for Steam-only globals without inventing
|
||||
// user identity or suspend state in this isolated document.
|
||||
for (const name of ['App','appStore','appInfoStore','collectionStore']) window[name] = undefined;
|
||||
window.SuspendResumeStore = Object.freeze({});
|
||||
window.SteamUIStore = Object.freeze({RunningApps:Object.freeze([]), MainRunningApp:undefined});
|
||||
// Steam normally fills these token maps. Official plugins can safely use
|
||||
// their own fallback strings when the isolated panel has no Steam tokens.
|
||||
window.LocalizationManager = {m_rgLocalesToUse:[navigator.language || 'en'],
|
||||
@@ -284,6 +289,7 @@
|
||||
DialogButton: passthrough('button'), DialogButtonPrimary: passthrough('button'), ConfirmModal,
|
||||
DialogButtonSecondary: passthrough('button'), Button: ButtonItem, GamepadButton: passthrough('button'),
|
||||
Toggle: ToggleField, Spinner, SteamSpinner:Spinner, Tabs, SidebarNavigation,
|
||||
SuspensefulImage:({alt='', ...props})=>R.createElement('img', {...props, alt}),
|
||||
Marquee:passthrough('span'), DialogBody:passthrough('div'), DialogBodyText:passthrough('p'),
|
||||
DialogHeader:passthrough('h2'), DialogSubHeader:passthrough('h3'), DialogFooter:passthrough('footer'),
|
||||
DialogLabel:passthrough('label'), DialogControlsSection:passthrough('section'),
|
||||
@@ -326,17 +332,30 @@
|
||||
Settings:{GetCurrentLanguage:async()=>steamLanguage(),
|
||||
...register(['RegisterForSettingsChanges'])},
|
||||
Apps:register(['RegisterForAppDetails','RegisterForGameActionTaskChange',
|
||||
'RegisterForGameActionStart','RegisterForGameActionEnd']),
|
||||
'RegisterForGameActionStart','RegisterForGameActionEnd','RegisterForGameActionUserRequest']),
|
||||
GameSessions:register(['RegisterForAppLifetimeNotifications','RegisterForScreenshotNotification']),
|
||||
System:{...register(['RegisterForOnResumeFromSuspend','RegisterForOnSuspendRequest']),
|
||||
UI:register(['RegisterForFocusChangeEvents'])},
|
||||
UI:register(['RegisterForFocusChangeEvents','RegisterForSystemKeyEvents'])},
|
||||
Input:register(['RegisterForControllerInputMessages','RegisterForControllerStateChanges']),
|
||||
User:register(['RegisterForLoginStateChange','RegisterForPrepareForSystemSuspendProgress',
|
||||
'RegisterForResumeSuspendedGamesProgress','RegisterForCurrentUserChanges']),
|
||||
Overlay:register(['RegisterForOverlayActivated']),
|
||||
UI:register(['RegisterForUIModeChanged']),
|
||||
// This is private plugin cache, not Steam's account/global JSON store.
|
||||
Storage:{
|
||||
GetJSON:async key=>{
|
||||
const value=storage.getItem('steam-client:'+String(key));
|
||||
if(value===null)throw Error('Not found');
|
||||
return value;
|
||||
},
|
||||
SetObject:async(key,value)=>{
|
||||
const encoded=JSON.stringify(value);
|
||||
if(encoded===undefined)throw Error('SteamClient.Storage requires a JSON value');
|
||||
storage.setItem('steam-client:'+String(key),encoded);
|
||||
},
|
||||
},
|
||||
};
|
||||
DFL.Router = {...DFL.Navigation, MainRunningApp:undefined};
|
||||
DFL.Router = {...DFL.Navigation, ...window.SteamUIStore};
|
||||
DFL.DialogButton.render = () => ({type: DFL.DialogButton});
|
||||
const toaster = {toast: ({title, body, subtext, icon, logo, duration, showToast=true, onClick}={}) => {
|
||||
const data = {title, body, subtext, icon, logo};
|
||||
|
||||
Reference in new issue
Block a user