diff --git a/docs/evidence/framey-ui-fallbacks-2026-10-26.json b/docs/evidence/framey-ui-fallbacks-2026-10-26.json new file mode 100644 index 0000000..420249b --- /dev/null +++ b/docs/evidence/framey-ui-fallbacks-2026-10-26.json @@ -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" + ] +} diff --git a/docs/framey-compatibility.md b/docs/framey-compatibility.md index 0b08ac5..47952fb 100644 --- a/docs/framey-compatibility.md +++ b/docs/framey-compatibility.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. | diff --git a/docs/framey-loader.md b/docs/framey-loader.md index b4e17f5..2962871 100644 --- a/docs/framey-loader.md +++ b/docs/framey-loader.md @@ -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 diff --git a/scripts/framey-compatibility-doc.py b/scripts/framey-compatibility-doc.py index 4875d54..c768a26 100644 --- a/scripts/framey-compatibility-doc.py +++ b/scripts/framey-compatibility-doc.py @@ -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 ' diff --git a/tests/page/framey_actions.mjs b/tests/page/framey_actions.mjs index 8dd5fe0..74273d7 100644 --- a/tests/page/framey_actions.mjs +++ b/tests/page/framey_actions.mjs @@ -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,',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(); diff --git a/tests/page/framey_cache.mjs b/tests/page/framey_cache.mjs index c251969..838e2fa 100644 --- a/tests/page/framey_cache.mjs +++ b/tests/page/framey_cache.mjs @@ -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'),'{}'); diff --git a/ui/framey-compatibility.json b/ui/framey-compatibility.json index b687ca3..92f5b75 100644 --- a/ui/framey-compatibility.json +++ b/ui/framey-compatibility.json @@ -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" diff --git a/ui/framey-host.js b/ui/framey-host.js index 4c1d89d..15ebbfa 100644 --- a/ui/framey-host.js +++ b/ui/framey-host.js @@ -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};