Compare commits

..
Author SHA1 Message Date
Alex Southwell b961fc4cef Merge pull request #319 from saphid/fix/fakeframe-pacman-retry
Retry truncated fake Frame package downloads
2026-10-26 10:00:55 +00:00
SAPHID 9af06c7845 Retry transient fake Frame package downloads 2026-10-26 09:52:47 +00:00
10 changed files with 45 additions and 594 deletions

No files matched your search

@@ -0,0 +1,22 @@
# Fake Frame package download retry, 26 October 2026
The ARM64 fake Frame container sometimes fails before project tests start.
Valve's `holo-packages.steamos.cloud` returned package responses with **164
bytes missing** during the `pacman -Syu` layer in
`tests/fakeframe/Containerfile`:
| Run | Package and outcome |
| --- | --- |
| [v0.4.44 candidate checks](https://github.com/saphid/frame-control/actions/runs/40296431563) | `iptables` download truncated; a failed-only rerun passed on the unchanged PR head. |
| [PR #316 checks](https://github.com/saphid/frame-control/actions/runs/40298316496) | `c-ares` download truncated before e2e tests. |
| [PR #318 checks](https://github.com/saphid/frame-control/actions/runs/40301478955) | First attempt truncated `iptables`; failed-only rerun on the unchanged documentation head truncated `rsync`. The six other jobs passed. |
The fake Frame image now retries that package transaction at most five times,
with a two-second delay after each failed attempt except the last. A fifth
failure still fails the build. The end-to-end tests and their assertions are
unchanged. This retry affects only the fake Frame test image; it does not
change a real headset or the released application.
Exact-head hosted CI must pass before this change is merged. A successful
container build would show that at least one package attempt completed; it
would not establish that Valve's package service is permanently healthy.
@@ -1,305 +0,0 @@
{
"checkedAtUtc": "2026-10-26T09:28:48.295443+00:00",
"plugin": {
"id": 124,
"name": "AlarMe",
"version": "1.6.3",
"sha256": "17d4166a9e98979df5fdb07c7eae42317acd54a453d3b82757508f971193c0fe"
},
"status": "partial",
"sharedControls": {
"ConfirmModalOfficialImporters": 43,
"FocusableOfficialImporters": 65,
"claim": "Import counts show shared surface, not successful behavior in every plugin. Only AlarMe received a real-backend follow-up."
},
"realFrame": {
"os": "Frame OS 0.5.6",
"arch": "aarch64",
"decky": {
"version": "v3.3.0",
"sha256": "8ef5dcf36cbbbb6e97201a6baed5d79c4cc44ec27789fc9af1c5b6ae8f0b25b6"
},
"transport": "trusted SSH through coordinator host; receipt-bound real CEF Decky RPC/events",
"isolation": "Exact official archive installed by production APPLY into a random private path. Removable unprivileged user service, not enabled, RuntimeMaxSec=900 and TimeoutStopSec=5.",
"settings": {
"timer_subtle_mode": true,
"timer_volume": 0,
"timer_auto_suspend": false,
"timer_prevent_sleep": false,
"pomodoro_subtle_mode": true,
"pomodoro_volume": 0,
"pomodoro_prevent_sleep": false,
"overlay_enabled": false
},
"safeRpcResults": [
{
"method": "create_timer",
"args": [
300,
"Framey silent private timer",
true,
false,
false
],
"result": "91c1f87d"
},
{
"method": "pause_timer",
"args": [
"91c1f87d"
],
"result": true
},
{
"method": "save_preset_from_timer",
"args": [
"91c1f87d"
],
"result": {
"id": "47c046cd",
"seconds": 300,
"label": "Framey silent private timer",
"subtle_mode": true,
"auto_suspend": false,
"prevent_sleep": false
}
},
{
"method": "remove_preset",
"args": [
"47c046cd"
],
"result": true
},
{
"method": "cancel_timer",
"args": [
"91c1f87d"
],
"result": true
},
{
"method": "stopwatch_start",
"args": [],
"result": {
"status": "running",
"start_time": 1793006404.791917,
"elapsed_ms": 0,
"laps": [],
"prevent_sleep": false,
"current_elapsed_ms": 1.0344982147216797
}
},
{
"method": "stopwatch_lap",
"args": [],
"result": {
"status": "running",
"start_time": 1793006404.791917,
"elapsed_ms": 0,
"laps": [
{
"label": "Lap 1",
"absolute_ms": 31747.3783493042,
"split_ms": 31747.3783493042
}
],
"prevent_sleep": false,
"current_elapsed_ms": 31750.77533721924,
"total_lap_count": 1,
"lap_limit_reached": false
}
},
{
"method": "stopwatch_pause",
"args": [],
"result": {
"status": "paused",
"start_time": null,
"elapsed_ms": 34339.426040649414,
"laps": [
{
"label": "Lap 1",
"absolute_ms": 31747.3783493042,
"split_ms": 31747.3783493042
}
],
"prevent_sleep": false,
"current_elapsed_ms": 34339.426040649414,
"total_lap_count": 1,
"lap_limit_reached": false
}
},
{
"method": "stopwatch_reset",
"args": [],
"result": {
"status": "idle",
"start_time": null,
"elapsed_ms": 0,
"laps": [],
"current_elapsed_ms": 0
}
}
],
"eventCounts": {
"alarme_timers_updated": 3,
"alarme_sleep_inhibitor_updated": 6,
"alarme_timer_tick": 124,
"alarme_presets_updated": 2,
"alarme_stopwatch_updated": 3
},
"finalReadback": {
"timers": [],
"stopwatch": {
"status": "idle",
"start_time": null,
"elapsed_ms": 0,
"laps": [],
"current_elapsed_ms": 0
},
"sleep": {
"active": false,
"items": []
},
"presets": [
{
"id": "preset-5",
"seconds": 300,
"label": "5 minutes",
"subtle_mode": false,
"auto_suspend": false
},
{
"id": "preset-10",
"seconds": 600,
"label": "10 minutes",
"subtle_mode": false,
"auto_suspend": false
},
{
"id": "preset-15",
"seconds": 900,
"label": "15 minutes",
"subtle_mode": false,
"auto_suspend": false
},
{
"id": "preset-30",
"seconds": 1800,
"label": "30 minutes",
"subtle_mode": false,
"auto_suspend": false
}
]
},
"before": {
"decky": "failed",
"panel": "inactive",
"battery": [
97
]
},
"after": {
"decky": "failed",
"panel": "inactive",
"battery": [
96
]
},
"cleanup": {
"baseAbsent": true,
"unitAbsent": true
},
"lockReleased": true
},
"browser": {
"host": "Repository headset.html and opaque allow-scripts iframe with bundled React 18.3.1; loopback RPC/event relay to the real ARM64 backend",
"verified": [
"labeled silent timer creation and live tick display",
"timer pause through onActivate and backend readback",
"save paused timer as preset through onActivate",
"delete confirmation shows preset name and description",
"Cancel closes dialog without dispatching remove_preset; backend readback retains preset",
"Delete invokes remove_preset once and readback removes the private preset",
"timer cancellation",
"Watch tab activation; stopwatch start/lap/pause",
"fresh panel reopen retains paused 34.339-second stopwatch and one lap",
"stopwatch reset; no active timers or sleep inhibitors in final backend readback"
],
"domFixture": {
"type": "fixture:result",
"checks": [
"description visible",
"underlying panel inert",
"dialog fixed",
"Tab wraps in top dialog",
"Cancel callback once",
"parent draft preserved",
"disabled actions",
"old Close retains child",
"programmatic Close skips callback",
"OK does not close newly opened dialog",
"Update changes body",
"Escape cancels top dialog",
"panel reactivated",
"pointer Enter Space activate once each"
]
},
"limits": "Local Chromium with real backend, not the deployed headset HTTP host or worn VR. Synthetic DOM checks use programmatic DOM events, not physical controllers."
},
"fixtureCorrection": {
"issue": "Initial seed used settings/<display-name>; actual Decky uses settings/<folder>. Safety assertion failed on readback before any timer action.",
"resolution": "First private service/data removed and lock released. Correct folder seed read back before second run exposed actions."
},
"cleanupAudit": {
"servicesAudited": 2,
"allBaseUnitArchiveAbsent": true,
"allLoadStateNotFound": true,
"allActiveStateInactive": true,
"allMainPidZero": true,
"sharedLock": "Fresh atomic lock acquired and released for cleanup audit"
},
"unverified": [
"audio playback and sound imports",
"alarms and reminders",
"auto-suspend and /dev/uinput sleep inhibition",
"in-game/global Steam overlay: no injection target in isolated panel",
"background notifications after panel closes",
"worn VR and physical controller/keyboard input"
],
"validation": {
"fullPython": {
"ran": 1628,
"skipped": 16,
"result": "pass"
},
"nodeContracts": [
"tests/page/framey_actions.mjs",
"tests/page/framey_running.mjs"
],
"siteTestFiles": 2,
"currentStoreRefresh": {
"plugins": 116,
"exactIdsVersionsAndDigestsUnchanged": true
},
"fakeFrame": {
"blocked": 56,
"installed-removed": 60
},
"noContextBrowser": {
"plugins": 116,
"ready": 101,
"failed": 15,
"noRegressionsAgainstCatalogBaseline": true,
"scope": "Initialization only, all backend/network denied; library-dependent plugins lack Steam context"
},
"contextReplayBrowser": {
"plugins": 116,
"ready": 104,
"failed": 12,
"initializationRegressionsAgainstPriorContextReplay": [],
"scope": "Synthetic library/running replay; backend and network denied, initialization only"
}
}
}
@@ -1,100 +0,0 @@
# Framey activation and dialog follow-up — 2026-10-26
AlarMe 1.6.3 now has usable timer, preset and stopwatch controls in the local
Framey host with its official backend running on the ARM64 Frame. It remains
**partial**. This check does not establish audio, alarm/reminder, sleep-control,
background-notification or worn-VR compatibility.
The exact archive is store ID 124, SHA-256
`17d4166a9e98979df5fdb07c7eae42317acd54a453d3b82757508f971193c0fe`.
See the [structured results](framey-dialog-controls-2026-10-26.json).
## Reproduced blockers and repairs
AlarMe uses DFL `Focusable.onActivate` for timer pause, save-as-preset, cancel,
preset deletion, tabs and stopwatch controls. Framey's previous `div`
passthrough ignored that callback: clicks on the save/pause controls did not
dispatch backend calls. Focusable now maps pointer activation and Enter/Space
to plugin callbacks, exposes a keyboard focus target and respects disabled
controls. A click does not invoke both `onClick` and `onActivate` when a plugin
supplies both; nested activation stops bubbling to the parent action.
The previous ConfirmModal omitted `strDescription`, Cancel, middle actions and
disabled flags. showModal did not inject a `closeModal` callback into custom
dialog components, and every Close handle cleared the same root. A confirmation
opened by another dialog could therefore disappear when the earlier action
closed. Dialogs also appeared below the panel's long page content.
Dialogs now occupy a scrollable overlay inside the opaque iframe. They preserve
the underlying dialog's mounted state, receive scoped close callbacks, support
Close/ClosedPromise/Update, show confirmation descriptions and cancel/middle
actions, and respect disabled flags. The underlying plugin surface becomes
inert while a dialog is open; Tab stays in the top dialog, Escape cancels it and
closing restores focus. Steam popouts and controller navigation are not emulated.
43 official archives import ConfirmModal and 65 import Focusable in this
snapshot. These are shared API surfaces, **not** verification or status
promotions for every importing plugin.
## Real backend and local browser verification
Held the normal atomic shared headset lock. Installed the exact official archive
through the production archive validation/extraction path into a random private
directory. Used published Decky 3.3.0 ARM64, an unprivileged user service that was
never enabled, with a 900-second runtime bound and five-second stop timeout.
RPCs and events used Framey's production receipt-bound bridge through real Steam
CEF. The Chromium host served repository headset.html, React and framey-host.js;
its status endpoints were fixture-shaped, while AlarMe RPCs/events were live.
The initial private settings seed used the plugin display name instead of the
archive folder. A safety assertion failed on readback of the defaults before any
timer action. That service/data was removed and the lock released. The corrected
run read back subtle mode, zero volume, no auto-suspend, no sleep inhibition and
no overlay before exposing actions. Its relay allowed only the documented safe
methods; sound, suspend, sleep-inhibitor and arbitrary plugin actions were denied.
Verified with actual browser clicks and real backend readback:
1. Add Label opened a custom timer dialog. Created a silent five-minute timer
named `Framey silent private timer`; live backend ticks changed its display.
2. Pause and save-as-preset activated after the Focusable repair. Backend
readback reported a paused timer and its saved preset.
3. Delete Preset showed the preset's name in its confirmation description.
Cancel closed the dialog without dispatching `remove_preset`; readback kept
the preset. Reopening and choosing Delete dispatched it once and removed it.
4. Cancel removed the private timer. Watch opened the stopwatch; Start, Lap and
Pause produced real backend state and events.
5. Closing and reopening the panel retained the paused stopwatch at 34.339
seconds and one lap. Reset cleared it. Final readback showed no active timers,
no laps and no active sleep inhibitor.
A separate local Chromium DOM fixture passed 14 checks for nested-dialog state,
scoped handles, descriptions, disabled buttons, Cancel, Escape, Tab wrapping,
Close/ClosedPromise/Update and pointer/Enter/Space activation. These programmatic
DOM checks do not verify physical input devices.
## Cleanup and remaining limits
Removed both private service units, bases and uploaded archives; reloaded the
user service manager and released the matching shared lock. A fresh locked audit
confirmed both units were not found, inactive and PID zero, with all test paths
absent. Managed Decky remained failed and the headset panel inactive, matching
the initial state. Battery was 97% before and 96% after. No power settings changed.
AlarMe stays partial. Audio, sound imports, alarms/reminders, auto-suspend and
`/dev/uinput` sleep inhibition were not exercised. Steam's global/in-game overlay
has no injection target inside Framey's isolated panel. Notifications require
the plugin panel to remain open. The deployed headset HTTP host, worn VR and
physical controller/keyboard input were not tested in this pass.
The store and documentation use the same exact-digest catalog reason. Aggregate
counts remain **4 works / 49 partial / 52 broken / 11 N/A**.
Validation: all 116 official IDs/versions/digests unchanged on store refresh;
fake-Frame 60 installed/removed and 56 blocked; full Python suite 1,628 tests,
16 skipped; Framey action/running Node contracts and both site test files pass.
The no-Steam-context Chromium pass attempted all 116 frontends (101 initialized,
15 failed); backend and network access were denied throughout that pass.
The repeat with synthetic library/running context initialized 104 of 116, with no
initialization regressions against the prior context-replay pass. Neither
initialization pass is a useful-functionality check.
+3 -3
View File
@@ -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. A [local audio follow-up](evidence/framey-audio-backends-2026-10-26.md) exercised Volume Mixer, Volume Boost and Mixer through real Decky 3.3.0 amd64 backends on private silent virtual audio targets. Sliders, numeric volume entry, independent server readbacks and Mixer filter persistence across a runtime restart passed. An [ARM64 audio follow-up](evidence/framey-audio-arm64-2026-10-26.md) verified the unmodified official backends in actual Decky 3.3.0 on the Frame user audio session. Volume Mixer and Mixer now qualify as works for per-app volume: Chromium controls changed only a named silent stream, with independent Pulse readbacks. That run left Volume Boost partial because sink writes were denied. A [Volume Boost follow-up](evidence/framey-volume-boost-arm64-2026-10-26.md) now verifies linked/separate channel controls, re-link and reopen through the actual ARM64 backend. Independent Pulse reads confirmed 125%/150% gain writes while the default sink stayed muted; exact original volume/mute was restored. Volume Boost qualifies as works for sink controls. Audible output, Bluetooth playback and worn VR remain unverified. A [running-context follow-up](evidence/framey-running-context-2026-10-26.md) supplies detached running-app/main-app reads and relays actual Steam lifetime callbacks while a panel is open. Real Frame checks verified an empty list, registration/cleanup and the exact Discord Status ARM64 backend reporting no IPC connection. Nonempty games and transitions were synthetic fixtures; real game events, Discord activity and worn VR remain unverified. Discord Status moves to partial; no new works claim is made. A [library context follow-up](evidence/framey-library-context-2026-10-26.md) read 1,106 app overviews, 895 game IDs and the current public Steam ID from real Frame CEF under the shared lock. Local Chromium replayed this detached snapshot to render Deck Settings game selection, Steamback's empty save panel and PlayTime statistics/settings. Steamback and PlayTime used actual Decky 3.3.0 amd64 backends in an isolated container; their ARM64 backends and automatic game hooks remain unverified. Shotty's frontend now starts, but its published backend fails on missing `click`; it stays broken. A [chunked frontend follow-up](evidence/framey-deck-shelves-browser-2026-10-26.md) loaded Deck Shelves 2.0.0 through a managed JavaScript module route and rendered its settings in an opaque local Chromium panel. Its backend was denied; Steam Home shelves and worn VR remain unverified. A [local module lookup follow-up](evidence/framey-module-lookup-2026-10-26.md) supplies a finite registry of Framey-local controls and CSS. TabMaster QAM/docs now render; Steam library-tab editing remains unavailable. That pass exposed Junk-Store executable-script extraction failures; the follow-up below repairs them. SteamGridDB still requires Steam context-menu internals and DeckSP still fails its published MobX fallback. That pass tested TabMaster/Junk-Store in actual isolated amd64 Decky, without ARM64 or worn-VR coverage. A [script entrypoint follow-up](evidence/framey-script-entrypoints-2026-10-26.md) grants owner execute to shebang scripts and ZIP-declared executables without copying archive privilege/write bits. Junk-Store now passes static menu/backend calls in actual Decky 3.3.0 ARM64 and local Chromium Games/Epic navigation, back and reopen. It moves to partial: About still crashes on missing installed-app metadata, Legendary is absent, and login/game operations remain unverified. Existing installations require update/reinstall to repair permissions. A [read-only installed-metadata follow-up](evidence/framey-installed-context-2026-10-26.md) now repairs the About crash. The library snapshot copies only Steam-supplied installed booleans into detached metadata, preserving unknown values as undefined. Junk-Store About/Dependencies, Back and reopen render in local Chromium with actual Frame library data and the official ARM64 backend. It remains partial: dependency/login/game operations, RSS, the optional Line truncated
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. A [local audio follow-up](evidence/framey-audio-backends-2026-10-26.md) exercised Volume Mixer, Volume Boost and Mixer through real Decky 3.3.0 amd64 backends on private silent virtual audio targets. Sliders, numeric volume entry, independent server readbacks and Mixer filter persistence across a runtime restart passed. An [ARM64 audio follow-up](evidence/framey-audio-arm64-2026-10-26.md) verified the unmodified official backends in actual Decky 3.3.0 on the Frame user audio session. Volume Mixer and Mixer now qualify as works for per-app volume: Chromium controls changed only a named silent stream, with independent Pulse readbacks. That run left Volume Boost partial because sink writes were denied. A [Volume Boost follow-up](evidence/framey-volume-boost-arm64-2026-10-26.md) now verifies linked/separate channel controls, re-link and reopen through the actual ARM64 backend. Independent Pulse reads confirmed 125%/150% gain writes while the default sink stayed muted; exact original volume/mute was restored. Volume Boost qualifies as works for sink controls. Audible output, Bluetooth playback and worn VR remain unverified. A [running-context follow-up](evidence/framey-running-context-2026-10-26.md) supplies detached running-app/main-app reads and relays actual Steam lifetime callbacks while a panel is open. Real Frame checks verified an empty list, registration/cleanup and the exact Discord Status ARM64 backend reporting no IPC connection. Nonempty games and transitions were synthetic fixtures; real game events, Discord activity and worn VR remain unverified. Discord Status moves to partial; no new works claim is made. A [library context follow-up](evidence/framey-library-context-2026-10-26.md) read 1,106 app overviews, 895 game IDs and the current public Steam ID from real Frame CEF under the shared lock. Local Chromium replayed this detached snapshot to render Deck Settings game selection, Steamback's empty save panel and PlayTime statistics/settings. Steamback and PlayTime used actual Decky 3.3.0 amd64 backends in an isolated container; their ARM64 backends and automatic game hooks remain unverified. Shotty's frontend now starts, but its published backend fails on missing `click`; it stays broken. A [chunked frontend follow-up](evidence/framey-deck-shelves-browser-2026-10-26.md) loaded Deck Shelves 2.0.0 through a managed JavaScript module route and rendered its settings in an opaque local Chromium panel. Its backend was denied; Steam Home shelves and worn VR remain unverified. A [local module lookup follow-up](evidence/framey-module-lookup-2026-10-26.md) supplies a finite registry of Framey-local controls and CSS. TabMaster QAM/docs now render; Steam library-tab editing remains unavailable. That pass exposed Junk-Store executable-script extraction failures; the follow-up below repairs them. SteamGridDB still requires Steam context-menu internals and DeckSP still fails its published MobX fallback. That pass tested TabMaster/Junk-Store in actual isolated amd64 Decky, without ARM64 or worn-VR coverage. A [script entrypoint follow-up](evidence/framey-script-entrypoints-2026-10-26.md) grants owner execute to shebang scripts and ZIP-declared executables without copying archive privilege/write bits. Junk-Store now passes static menu/backend calls in actual Decky 3.3.0 ARM64 and local Chromium Games/Epic navigation, back and reopen. It moves to partial: About still crashes on missing installed-app metadata, Legendary is absent, and login/game operations remain unverified. Existing installations require update/reinstall to repair permissions. A [read-only installed-metadata follow-up](evidence/framey-installed-context-2026-10-26.md) now repairs the About crash. The library snapshot copies only Steam-supplied installed booleans into detached metadata, preserving unknown values as undefined. Junk-Store About/Dependencies, Back and reopen render in local Chromium with actual Frame library data and the official ARM64 backend. It remains partial: dependency/login/game operations, RSS, the optional Line truncated
## Every current plugin
@@ -129,7 +129,7 @@ The API column shows manifest / actual bundled frontend API. Many manifests say
| 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 | Silent labeled timer create/ticks/pause/cancel, preset save/cancel-confirm/delete and stopwatch start/lap/pause/reset/reopen pass in local Chromium with the official Decky 3.3.0 ARM64 backend. Audio, alarms/reminders, suspend inhibition, Steam overlays and worn VR remain unverified; notifications require the panel to stay open. | 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 |
| 125: Decky Localsend 0.4.5 | 1 / 2 | broken | Plugin contains a non-ARM64 Linux binary: decky-localsend/bin/localsend-core. | static archive, local browser, fake-Frame |
| 126: Launch Options 1.9.2 | 1 / 2 | broken | Plugin contains a non-ARM64 Linux binary: decky-launch-options/bin/hello. Frontend audit failed: Cannot convert undefined or null to object. | static archive, local browser, fake-Frame |
| 127: Decky Translator 0.9.0 | 1 / 2 | partial | Initializer succeeds; x86_64 download and hidraw/controller assumptions need an ARM64 audit. OCR/translation not exercised. | static archive, local browser, fake-Frame |
@@ -252,7 +252,7 @@ DFL includes exports imported through decky-frontend-lib / @decky/ui. Steam stor
| Reroll | Apps.RegisterForGameActionUserRequest, GameSessions.RegisterForAppLifetimeNotifications, Settings.GetCurrentLanguage | ButtonItem, DialogButton, Field, Focusable, Marquee, ModalRoot, PanelSection, PanelSectionRow, SliderField, libraryAssetImageClasses, mainMenuAppRunningClasses, quickAccessMenuClasses, showModal | SteamUIStore, QAM/global tabs, Steam stores | Python + native payload; x86_64 | No specific hardware requirement detected statically; runtime requirements unverified. |
| DeckPass | Apps.AddShortcut, Apps.RunGame, Apps.SetCustomArtworkForApp, Apps.SetShortcutExe, Apps.SetShortcutLaunchOptions, Apps.SetShortcutName, Input.ControllerKeyboardSendText | Button, ButtonItem, DialogButton, Field, Focusable, ModalRoot, PanelSection, PanelSectionRow, Router, TextField, findSP, showModal, sleep, staticClasses | Router, appDetailsStore, appStore, toaster, window.SP_REACT, toasts, Steam stores | Python | flatpak |
| Steam Status | none detected | ButtonItem, ConfirmModal, DialogButton, Field, Focusable, Navigation, PanelSection, PanelSectionRow, SliderField, ToggleField, definePlugin, showModal, staticClasses | toaster, toasts | Python | No specific hardware requirement detected statically; runtime requirements unverified. |
| AlarMe | GameSessions.RegisterForAppLifetimeNotifications | ButtonItem, ConfirmModal, Dropdown, Focusable, Navigation, PanelSection, PanelSectionRow, ProgressBarWithInfo, SidebarNavigation, SliderField, TextField, ToggleField, findModuleChild, showModal, staticClasses | routerHook, toaster, window.SP_REACT, local/Steam routes, toasts, QAM/global tabs, Steam page patches/module lookup | Python | Timers/stopwatch use Python. Optional sleep inhibition opens /dev/uinput; sound and auto-suspend are unverified. Steam game overlays have no target in the isolated panel. |
| AlarMe | GameSessions.RegisterForAppLifetimeNotifications | ButtonItem, ConfirmModal, Dropdown, Focusable, Navigation, PanelSection, PanelSectionRow, ProgressBarWithInfo, SidebarNavigation, SliderField, TextField, ToggleField, findModuleChild, showModal, staticClasses | routerHook, toaster, window.SP_REACT, local/Steam routes, toasts, QAM/global tabs, Steam page patches/module lookup | Python | No specific hardware requirement detected statically; runtime requirements unverified. |
| Decky Localsend | none detected | ButtonItem, DialogBody, DialogButton, DialogButtonPrimary, DialogFooter, DialogHeader, Field, Focusable, ModalRoot, Navigation, PanelSection, PanelSectionRow, Router, SidebarNavigation, TextField, ToggleField, showModal, staticClasses | Router, routerHook, toaster, local/Steam routes, toasts, Steam stores | Python + native payload; x86_64 | No specific hardware requirement detected statically; runtime requirements unverified. |
| Launch Options | Apps.RegisterForAppDetails, Apps.SetAppLaunchOptions | ButtonItem, ConfirmModal, DialogBody, DialogButton, DialogButtonPrimary, DialogControlsSection, DialogControlsSectionHeader, DialogFooter, DialogHeader, Dropdown, Field, Focusable, MenuItem, ModalRoot, Navigation, PanelSection, PanelSectionRow, ScrollPanel, SidebarNavigation, SteamSpinner, Tabs, TextField, Toggle, ToggleField, afterPatch, createReactTreePatcher, fakeRenderComponent, findInReactTree, findInTree, findModule, findModuleByExport, showModal, staticClasses, useParams | SteamUIStore, routerHook, local/Steam routes, Steam page patches/module lookup, Steam stores | Python + native payload; x86_64 | flatpak; gamescope |
| Decky Translator | User.RegisterForPrepareForSystemSuspendProgress | ButtonItem, DialogButton, Dropdown, DropdownItem, Field, Focusable, ModalRoot, Navigation, PanelSection, PanelSectionRow, Router, SliderField, Tabs, TextField, ToggleField, findModuleChild, showModal, staticClasses | Router, routerHook, toaster, window.SP_REACT, local/Steam routes, toasts, QAM/global tabs, Steam page patches/module lookup | Python | /dev/hidraw; /sys/class/hidraw/hidraw{i}; /sys/class/hidraw/hidraw{i}/device/uevent; x86_64 |
-3
View File
@@ -73,9 +73,6 @@ def render(data):
'install folders and system information, plus IsThereAnyDeal\'s country dropdown in local Chromium. '
'The installed-plugin status was fixture-shaped in that relay; plugin backends stayed denied and the deployed headset HTTP endpoint remains unverified. '
'A [Web Browser frontend follow-up](evidence/framey-web-browser-2026-10-26.md) verified its exact archive now initializes with an inert null Steam React-root lookup. Its GamepadUI CDP/browser backend path remains unavailable, so it stays broken. '
'A [dialog and activation follow-up](evidence/framey-dialog-controls-2026-10-26.md) fixes DFL Focusable pointer/keyboard activation and confirmation descriptions, cancel/disabled actions, scoped close callbacks and nested dialogs. '
'AlarMe silent timer ticks/pause/cancel, private preset save/cancel-confirm/delete and stopwatch start/lap/pause/reset/reopen pass in local Chromium with its official Decky 3.3.0 ARM64 backend. '
'AlarMe stays partial: audio, alarms/reminders, sleep inhibition, Steam overlays and worn VR are unverified; notifications require the panel to remain open. '
'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 '
+7 -2
View File
@@ -10,8 +10,13 @@ ARG BASE=archlinux:base
FROM ${BASE}
RUN (pacman-key --init && pacman-key --populate) >/dev/null 2>&1; \
pacman -Syu --noconfirm --needed openssh rsync python nodejs curl iproute2 procps-ng util-linux \
&& pacman -Scc --noconfirm
attempts=0; \
until pacman -Syu --noconfirm --needed openssh rsync python nodejs curl iproute2 procps-ng util-linux; do \
attempts=$((attempts + 1)); \
[ "$attempts" -lt 5 ] || exit 1; \
sleep 2; \
done; \
pacman -Scc --noconfirm
# The Frame's user is steamos (docs/ssh.md). Its password stands in for the
# Developer Mode password.
+1 -65
View File
@@ -7,7 +7,6 @@ const testSource=source.replace(/\n start\(\);\n\}\)\(\);\s*$/, '\n window.__s
assert.notEqual(testSource,source);
const states=[];
const React={Fragment:'fragment',Component:class {},createElement:(type,props,...children)=>({type,props:props||{},children}),
cloneElement:(element,props)=>({...element,props:{...element.props,...props}}),
useState:value=>[value,next=>states.push(next)], useRef:value=>({current:value}),
useEffect(){},isValidElement:value=>!!value?.type};
class Element {constructor(){this.children=[];this.textContent='';this.attributes={};}
@@ -358,70 +357,7 @@ await assert.rejects(window.SteamClient.Storage.SetObject('undefined',undefined)
assert.equal(window.indexedDB,undefined); // localForage must use bounded plugin storage.
const unpatch=window.DFL.beforePatch({},'method',()=>{});
assert.equal(unpatch.unpatch,unpatch);unpatch.unpatch();
// Official AlarMe uses Focusable onActivate for pause, save and delete, without onClick.
let activated=0, stopped=0;
const activatable=window.DFL.Focusable({onActivate:()=>activated++});
assert.equal(activatable.props.role,'button');assert.equal(activatable.props.tabIndex,0);
activatable.props.onClick({stopPropagation:()=>stopped++});
const ownTarget={};
for(const key of ['Enter',' '])activatable.props.onKeyDown({key,target:ownTarget,currentTarget:ownTarget,
preventDefault(){},stopPropagation:()=>stopped++});
activatable.props.onKeyDown({key:'Enter',target:{},currentTarget:ownTarget,preventDefault(){}});
assert.equal(activated,3);assert.equal(stopped,3);
const inertFocusable=window.DFL.Focusable({onActivate:()=>activated++,disabled:true});
inertFocusable.props.onClick({});
inertFocusable.props.onKeyDown({key:'Enter',target:ownTarget,currentTarget:ownTarget});
assert.equal(activated,3);assert.equal(inertFocusable.props['aria-disabled'],true);
let pointerCalls=0;
window.DFL.Focusable({onClick:()=>pointerCalls++,onActivate:()=>activated++}).props.onClick({stopPropagation(){}});
assert.equal(pointerCalls,1);assert.equal(activated,3);
// Dialog actions must preserve descriptions, cancellation and the dialog they belong to.
const modalRoot=roots.get(elements.get('framey-modal'));
let confirmed=0, cancelled=0, dismissed=0;
const modalHandle=window.DFL.showModal(React.createElement(window.DFL.ConfirmModal,{
strTitle:'Remove preset?',strDescription:'Only this private preset will be removed.',
strOKButtonText:'Remove',strCancelButtonText:'Keep',
onOK:()=>confirmed++,onCancel:()=>cancelled++,closeModal:()=>dismissed++}));
let modal=modalRoot.value.at(-1).children.at(-1);
let confirm=modal.type(modal.props);
assert.equal(nodes(confirm,'div')[0].children[0],'Only this private preset will be removed.');
assert.deepEqual(nodes(confirm,'button').map(button=>button.children[0]),['Remove','Keep']);
nodes(confirm,'button')[1].props.onClick();
await modalHandle.ClosedPromise;
assert.equal(confirmed,0);assert.equal(cancelled,1);assert.equal(dismissed,1);
assert.equal(modalRoot.value,null);
modalHandle.Close();assert.equal(dismissed,1);
// Opening a confirmation from another dialog preserves its state; stale Close handles
// cannot dismiss the newer dialog. Programmatic Close does not call plugin callbacks.
const parentModal=window.DFL.showModal(React.createElement('p',{closeModal:()=>dismissed++}));
const childModal=window.DFL.showModal(React.createElement(window.DFL.ConfirmModal,{
strTitle:'Confirm',bOKDisabled:true,bCancelDisabled:true,bMiddleDisabled:true,
onOK:()=>confirmed++,onCancel:()=>cancelled++,onMiddleButton:()=>confirmed++}));
assert.equal(modalRoot.value.length,2);assert.equal(modalRoot.value[0].props.hidden,true);
modal=modalRoot.value.at(-1).children.at(-1);confirm=modal.type(modal.props);
for(const button of nodes(confirm,'button')){assert.equal(button.props.disabled,true);button.props.onClick();}
assert.equal(confirmed,0);assert.equal(cancelled,1);
let prevented=0;
listeners.find(listener=>listener.type==='keydown').handler({key:'Escape',preventDefault:()=>prevented++});
assert.equal(prevented,1);assert.equal(modalRoot.value.length,2);
parentModal.Close();parentModal.Close();await parentModal.ClosedPromise;
assert.equal(modalRoot.value.length,1);assert.equal(dismissed,1);
childModal.Close();await childModal.ClosedPromise;assert.equal(modalRoot.value,null);
let replacement;
const first=window.DFL.showModal(React.createElement(window.DFL.ConfirmModal,{
onOK:()=>{replacement=window.DFL.showModal(React.createElement('p',null,'Next dialog'));}}));
modal=modalRoot.value.at(-1).children.at(-1);
nodes(modal.type(modal.props),'button')[0].props.onClick();
await first.ClosedPromise;assert.equal(modalRoot.value.length,1);
replacement.Update(React.createElement('p',null,'Updated'));
assert.equal(modalRoot.value[0].children.at(-1).children[0],'Updated');
replacement.Close();await replacement.ClosedPromise;
const alert=window.DFL.ConfirmModal({bAlertDialog:true,onCancel:()=>cancelled++});
assert.equal(nodes(alert,'button').length,1);
const escapeModal=window.DFL.showModal(React.createElement('p',{onCancel:()=>cancelled++}));
listeners.find(listener=>listener.type==='keydown').handler({key:'Escape',preventDefault(){}});
await escapeModal.ClosedPromise;assert.equal(cancelled,2);assert.equal(modalRoot.value,null);
assert.equal(typeof window.DFL.showModal({type:'p'}).Close,'function');
let selectedMenuItem=0;
window.DFL.MenuItem({onSelected:()=>selectedMenuItem++}).props.onClick({});
window.DFL.MenuItem({disabled:true,onSelected:()=>selectedMenuItem++}).props.onClick({});
+3 -10
View File
@@ -10876,13 +10876,12 @@
"https://github.com/mpcallahan/DeckyHourNotifications"
],
"status": "partial",
"reason": "Silent labeled timer create/ticks/pause/cancel, preset save/cancel-confirm/delete and stopwatch start/lap/pause/reset/reopen pass in local Chromium with the official Decky 3.3.0 ARM64 backend. Audio, alarms/reminders, suspend inhibition, Steam overlays and worn VR remain unverified; notifications require the panel to stay open.",
"reason": "Initializer succeeds; timers, reminders, suspend inhibition and audio are unverified.",
"blockInstall": false,
"verification": [
"static archive",
"local browser",
"fake-Frame",
"real Frame SSH"
"fake-Frame"
],
"browser": {
"id": 124,
@@ -10942,7 +10941,7 @@
"folder": "alarme-decky-plugin"
},
"backend": "Python",
"hardware": "Timers/stopwatch use Python. Optional sleep inhibition opens /dev/uinput; sound and auto-suspend are unverified. Steam game overlays have no target in the isolated panel.",
"hardware": "No specific hardware requirement detected statically; runtime requirements unverified.",
"uiSurfaces": [
"local/Steam routes",
"toasts",
@@ -10959,12 +10958,6 @@
"evidence": "docs/evidence/framey-running-context-2026-10-26.json",
"mode": "synthetic nonempty running/library replay; backend and network denied",
"frontendReady": true
},
"controlsFollowUp": {
"evidence": "docs/evidence/framey-dialog-controls-2026-10-26.json",
"mode": "local opaque-iframe Chromium with receipt-bound official ARM64 backend RPC/events; private silent data only",
"frontendReady": true,
"status": "partial"
}
},
{
+8 -103
View File
@@ -197,26 +197,6 @@
}, {passive:true});
const passthrough = tag => ({children, ...props}) => R.createElement(tag, props, children);
const Focusable = ({children, onActivate, onClick, onKeyDown, disabled, tabIndex, role, ...props}) => {
const actionable=typeof onActivate==='function' || typeof onClick==='function';
const activate = event => {
if (disabled || event.defaultPrevented) return;
event.stopPropagation();
(onClick || onActivate)?.(event);
};
return R.createElement('div', {...props, role:role || (actionable ? 'button' : undefined),
tabIndex:tabIndex ?? (actionable ? (disabled ? -1 : 0) : undefined),
'aria-disabled':actionable && disabled ? true : undefined,
onClick:actionable ? activate : undefined, onKeyDown:event=>{
onKeyDown?.(event);
if (actionable && !disabled && !event.defaultPrevented && event.target===event.currentTarget &&
(event.key==='Enter' || event.key===' ')) {
event.preventDefault();
event.stopPropagation();
(onActivate || onClick)?.(event);
}
}}, children);
};
const Field = ({label, description, children}) => R.createElement('label', null,
R.createElement('strong', null, label), description && R.createElement('small', null, description), children);
const ToggleField = ({label, description, checked, disabled, onChange}) => R.createElement(Field, {label, description},
@@ -283,61 +263,11 @@
const MenuItem = ({children, disabled, bDisabled, onClick, onSelected}) =>
R.createElement('button', {type:'button',disabled:!!(disabled || bDisabled),
onClick:event=>{if(!disabled && !bDisabled)(onClick || onSelected)?.(event);}}, children);
const ConfirmModal = ({strTitle, strDescription, strOKButtonText, strCancelButtonText,
strMiddleButtonText, onOK, onCancel, onMiddleButton, closeModal,
bOKDisabled, bCancelDisabled, bMiddleDisabled, bAlertDialog, children}) => {
const action = (callback, disabled) => () => {
if (disabled) return;
callback?.();
closeModal?.();
};
return R.createElement('section', null,
R.createElement('h2', null, strTitle),
strDescription && R.createElement('div', null, strDescription), children,
R.createElement('button', {type:'button', disabled:!!bOKDisabled,
onClick:action(onOK, bOKDisabled)}, strOKButtonText || 'OK'),
onMiddleButton && R.createElement('button', {type:'button', disabled:!!bMiddleDisabled,
onClick:action(onMiddleButton, bMiddleDisabled)}, strMiddleButtonText || 'More'),
!bAlertDialog && R.createElement('button', {type:'button', disabled:!!bCancelDisabled,
onClick:action(onCancel, bCancelDisabled)}, strCancelButtonText || 'Cancel'));
};
const ConfirmModal = ({strTitle, strOKButtonText, onOK, children}) => R.createElement('section', null,
R.createElement('h2', null, strTitle), children,
R.createElement('button', {type:'button', onClick:()=>{onOK?.(); closeModal();}}, strOKButtonText || 'Close'));
const modalRoot = window.ReactDOM.createRoot(document.getElementById('framey-modal'));
const modals = [];
let modalId = 0;
const renderModals = () => {
document.getElementById('framey-root').inert = modals.length > 0;
modalRoot.render(modals.length ? modals.map((modal,index) =>
R.createElement('section', {key:modal.id, className:'framey-dialog', tabIndex:-1,
ref:node=>{modal.node=node;}, hidden:index!==modals.length-1,
role:'dialog', 'aria-modal':index===modals.length-1 ? true : undefined,
'aria-label':typeof modal.element?.props?.strTitle==='string' ? modal.element.props.strTitle : 'Plugin dialog'},
!modal.element?.props?.bHideCloseIcon && R.createElement('button', {
type:'button', disabled:!!modal.element?.props?.bCancelDisabled,
onClick:()=>dismissModal(modal)}, 'Close'), modal.element)) : null);
};
function dismissModal(modal) {
if (modal.element?.props?.bCancelDisabled) return;
modal.element?.props?.onCancel?.();
modal.dismiss();
}
addEventListener('keydown', event => {
const modal=modals.at(-1);
if (!modal) return;
if (event.key === 'Tab') {
const controls = [...(modal.node?.querySelectorAll('button,input,select,textarea,a[href],[tabindex]') || [])]
.filter(node=>!node.disabled && node.tabIndex>=0 && node.getClientRects().length);
const index=controls.indexOf(document.activeElement);
if (index < 0 || (!event.shiftKey && index===controls.length-1) || (event.shiftKey && index===0)) {
event.preventDefault();
(event.shiftKey ? controls.at(-1) : controls[0])?.focus();
}
return;
}
if (event.key !== 'Escape') return;
event.preventDefault();
if (modal.element?.props?.onEscKeypress) modal.element.props.onEscKeypress();
else dismissModal(modal);
});
const closeModal = () => modalRoot.render(null);
const localPath = path => typeof path === 'string' && path.length <= 120 &&
/^\/[A-Za-z0-9/_-]+$/.test(path) && !path.includes('//');
const routePattern = path => typeof path === 'string' && path.length <= 120 &&
@@ -391,34 +321,9 @@
};
const noPatch = () => {const undo=()=>{};undo.unpatch=undo;return undo;};
const showModal = element => {
let resolveClosed;
const ClosedPromise = new Promise(resolve=>{resolveClosed=resolve;});
const modal = {id:++modalId, previousFocus:document.activeElement};
const Close = () => {
const index=modals.indexOf(modal);
if (index < 0) return;
const wasTop=index===modals.length-1;
modals.splice(index,1);
renderModals();
resolveClosed();
if (wasTop) setTimeout(()=>{
if (modals.length) modals.at(-1).node?.focus();
else if (modal.previousFocus?.isConnected) modal.previousFocus.focus();
},0);
};
modal.dismiss = () => {
if (!modals.includes(modal)) return;
element?.props?.closeModal?.(); Close();
};
modal.element = R.isValidElement(element) ? R.cloneElement(element, {closeModal:modal.dismiss}) : element;
modals.push(modal);
renderModals();
setTimeout(()=>{if(modals.at(-1)===modal) modal.node?.focus();},0);
return {Close, ClosedPromise, Update:next=>{
if (!modals.includes(modal)) return;
// Decky's Update replaces the element without injecting new callbacks.
modal.element=next; renderModals();
}};
modalRoot.render(R.createElement('section', null,
R.createElement('button', {type:'button',onClick:closeModal}, 'Close'), element));
return {Close:closeModal};
};
// These are Framey's own styles and components, never Steam's webpack cache.
// Keep the registry finite: an unknown Steam implementation stays unavailable.
@@ -468,7 +373,7 @@
definePlugin: init => init,
PanelSection, PanelSectionRow: passthrough('div'), Field, ToggleField, DropdownItem,
Dropdown: DropdownItem, ButtonItem, DialogCheckbox, TextField, SliderField,
Focusable, ScrollPanel: passthrough('div'), ScrollPanelGroup: passthrough('div'),
Focusable: passthrough('div'), ScrollPanel: passthrough('div'), ScrollPanelGroup: passthrough('div'),
DialogButton: passthrough('button'), DialogButtonPrimary: passthrough('button'), ConfirmModal,
DialogButtonSecondary: passthrough('button'), Button: ButtonItem, GamepadButton: passthrough('button'),
Toggle: ToggleField, Spinner, SteamSpinner:Spinner, Tabs, SidebarNavigation,
+1 -3
View File
@@ -119,9 +119,7 @@ label{display:grid;gap:6px;margin:10px 0}small{color:#b9c9d6}
.framey-sections,.framey-library{display:flex;flex-direction:column;gap:12px}.framey-actions{display:flex;flex-wrap:wrap;gap:8px}
.framey-action{min-height:52px}.framey-button-child{display:inline-flex;align-items:center;gap:8px}.framey-green{background:#246b42}
.framey-long-title{overflow-wrap:anywhere}.framey-muted{background:#1b3042}.framey-image{max-width:100%;object-fit:contain}
.framey-focus:focus-visible{outline:4px solid #f8d370}
.framey-dialog{position:fixed;inset:12px;margin:0;z-index:100;overflow:auto;box-shadow:0 0 0 24px #0d1925e6}
.framey-dialog[hidden]{display:none}</style>
.framey-focus:focus-visible{outline:4px solid #f8d370}</style>
<p id="framey-error" role="status"></p><div id="framey-toasts" aria-live="polite"></div><div id="framey-root"></div><div id="framey-modal"></div>
<script id="framey-bundle" type="application/json"''' + module + '''>''' + encoded + '''</script>
<script src="/framey-runtime/react.js"></script><script src="/framey-runtime/react-dom.js"></script>