Files
saphid--frame-control/examples/framey-settings

Framey settings sample

This is a small Decky API v2 plugin for authors. Its panel uses the official @decky/api, @decky/ui, and @decky/rollup packages. A toggle calls a Python backend method, stores one boolean under decky.DECKY_PLUGIN_SETTINGS_DIR, and reads it back when the panel opens. The backend rejects other keys and values. The frontend also shows a local toast with a React body after a successful save. The toast stays inside the Framey panel; it is not a Steam system notification. A Framey settings tab exposes the same toggle, and a read-only quick action fetches the saved value without changing it. These two fields are Framey additions; Decky's own menu still uses the main panel content. The quick action calls the backend, so it reads the persisted setting rather than a browser cache. A missing, damaged, or old non-boolean value reads as the caller's default; the next save atomically replaces the sample's settings file. The panel also guards its own read and write calls synchronously. If two controller events arrive before React redraws a disabled control, the second event is ignored; after the request settles, a new change can be saved.

The About this setting button opens a local detail page at /framey-sample/details/saved-setting. It demonstrates routerHook.addRoute, Navigation.Navigate, useParams, NavigateBack, and removal of the route in onDismount. The :topic? segment is optional. Framey keeps this navigation inside the plugin panel; it does not open a Steam library page. Put optional segments at the end of the path and do not use query strings. This detail page has not been checked with headset controllers.

Build

From this directory, run:

pnpm install --frozen-lockfile
pnpm build
pnpm exec tsc --noEmit

The committed dist/index.js is the built frontend. For a Decky release ZIP, put plugin.json, main.py, and dist/index.js inside one top-level plugin folder. Publish it through the official Decky store if you want Frame Control's managed installer to offer it. Framey deliberately opens only plugins that the official store installed and marked as owned; copying this example into the plugin directory does not enable its VR panel.

Test and adapt

Run python3 -m unittest discover -s tests -p test_framey_sample.py from the repository root. It checks backend persistence and evaluates the built bundle against Framey's actual host script, including the quick action's scoped backend call and local route registration/navigation. The fake Frame store flow is exercised by the repository's e2e suite. No worn-headset visibility or controller-input test has passed for this sample yet.

The plugin needs no root access or system changes. Its settings remain after the managed plugin folder is removed, following Decky's settings behavior. For a production plugin, add explicit migrations for every supported schema, explain how users can reset settings, and test every UI path in a worn headset.