Dashboard patch state: kept until cleanup --all; frame-controls v6

The policy for the `state = true` files
(~/.local/state/steam-frame-nix/ui-patches/<name>.json), now stated in the
option, the injector and the README: they are user data, kept when a patch
is disabled or removed (the choices come back when it is enabled again),
and removed only by `steam-frame-nix-cleanup --all` / `install.sh
uninstall`; `--orphans` removes stale *.json.tmp only.

frame-controls v6 drops v5's one-time read of version 4's localStorage copy:
steamvr.service deletes ~/.cache/SteamVR (and with it that localStorage) on
every SteamVR start, so there is nothing left to migrate.
This commit is contained in:
Pierre Kisters committed 2026-09-29 00:08:43 +02:00
1 parent 69909f5b4c
commit 28c285815e
4 files changed
+20 -19

No files matched your search

+6 -5
View File
@@ -330,8 +330,10 @@ binding `window.__sfuiStoreSave`; the service writes the file atomically,
only on change, only for that page's `state` patches, at most 64 KiB. Used
by the [window control bar](#window-control-bar-dashboardframecontrols)
and the [Steam close button](#steam-close-button-dashboardsteamclosebuttonenable).
The file is user data, not generated by Nix; see
[Changes outside Nix](#changes-outside-nix).
The file is user data, not generated by Nix: it is kept when the patch is
disabled or removed (the choices come back when you enable it again) and
removed only by `steam-frame-nix-cleanup --all` or `install.sh uninstall`;
see [Changes outside Nix](#changes-outside-nix).
**DevTools on the LAN:** Steam's Developer Mode enables
`steam-web-debug-portforward` (`0.0.0.0:8081` → `8080`) and
@@ -764,8 +766,7 @@ controller), and theater windows have no "Float".
popup moves that control between bar and menu **for all windows**.
Placements survive SteamVR restarts and reboots (saved in
`~/.local/state/steam-frame-nix/ui-patches/frame-controls.json`, see
[persistent state](#ui-patches-uipatchespatches); version 4 kept them in
localStorage, which SteamVR wipes on every start, and is migrated once).
[persistent state](#ui-patches-uipatchespatches)).
- `inBar` / `inMenu` set where controls start; a popup choice wins until
that control's entry changes.
- `floatInTheater` gives theater windows the "Float" control.
@@ -930,7 +931,7 @@ the feature off:
| Path | What, why | Cleanup |
|---|---|---|
| `~/.local/state/steam-frame-nix/ui-patches/<name>.json` | Choices made in the dashboard: window control bar placements (`frame-controls.json`), "Steam hidden" (`steam-close-button.json`). Written by `steam-ui-patches` because SteamOS's `steamvr.service` deletes `~/.cache/SteamVR` (the dashboard's own browser storage) on every SteamVR start. Kept when a feature is disabled (your choices come back when you re-enable it). | `rm -rf ~/.local/state/steam-frame-nix/ui-patches` (also done by `install.sh uninstall`) |
| `~/.local/state/steam-frame-nix/ui-patches/<name>.json` | Choices made in the dashboard: window control bar placements (`frame-controls.json`), "Steam hidden" (`steam-close-button.json`). Written by `steam-ui-patches` because SteamOS's `steamvr.service` deletes `~/.cache/SteamVR` (the dashboard's own browser storage) on every SteamVR start. Kept when a feature is disabled (your choices come back when you re-enable it). | `steam-frame-nix-cleanup --all` (also run by `install.sh uninstall`) |
## Rollback
+4 -11
View File
@@ -30,8 +30,7 @@
// ~/.local/state/steam-frame-nix/ui-patches/frame-controls.json), which
// seeds a fresh page (SteamVR restart, reboot, reload). localStorage can't
// do that: steamvr.service deletes ~/.cache/SteamVR (vrwebhelper's profile)
// on every start. Version 4's localStorage copy is read once as a fallback
// (migration) and removed once the file has the state.
// on every start (which also took version 4's localStorage copy with it).
//
// Long press: the dashboard only gets primary-button laser input (no right
// click; thumbstick click arrives as nothing). Holding changes nothing; the
@@ -69,9 +68,8 @@
// setPlacement(name or "icon:N", 'bar' | 'menu' | null), reset().
((find, sigs, opts) => {
const NAME = 'frame-controls';
const VERSION = 5;
const VERSION = 6;
const T_SPACER = 1, T_ACTION = 2;
const LS_KEY = 'sfui.frameControls.v1'; // version <= 4 (migration only)
// Action icon enums (sigs.controls anchors them): names for the options.
const NAMES = { keyboard: 22, float: 26, dashboard: 27, theater: 28, dockLeft: 29, dockRight: 30, close: 31, curvature: 40 };
const ICON = { more: 38, float: 26, dashboard: 27 };
@@ -118,13 +116,9 @@
const store = window.__sfuiStore; // injector's persistent store (absent: in-page only)
let S = window.__sfuiFrameControlsState, restored = null;
if (S?.schema !== 1) {
let saved = store?.get(NAME), from = 'file';
if (saved?.schema !== 1) {
from = 'localStorage';
try { saved = JSON.parse(localStorage.getItem(LS_KEY) ?? 'null'); } catch { saved = null; }
}
const saved = store?.get(NAME);
const ok = saved?.schema === 1 && saved.placement && typeof saved.placement === 'object';
restored = ok ? from : 'nothing';
restored = ok ? 'file' : 'nothing';
S = window.__sfuiFrameControlsState = { schema: 1, placement: ok ? { ...saved.placement } : {}, nixSeen: ok ? { ...saved.nixSeen } : {}, log: [] };
}
const logBuf = S.log ??= [];
@@ -136,7 +130,6 @@
let unsaved = true; // last save not sent (no injector binding): retried by check()
const save = () => {
unsaved = !store?.set?.(NAME, { schema: 1, placement: { ...S.placement }, nixSeen: { ...S.nixSeen } });
if (!unsaved) { try { localStorage.removeItem(LS_KEY); } catch { /* none */ } }
};
for (const k of new Set([...Object.keys(optMap), ...Object.keys(S.nixSeen)])) {
if (S.nixSeen[k] !== optMap[k] && k in S.placement) {
+7 -2
View File
@@ -6,7 +6,9 @@
# It exists while `patches` is non-empty, restarts on every switch (the old
# instance reverts removed patches) and is stopped once the list is empty.
# Patches with `state = true` get a persistent JSON value (injector.mjs:
# "Persistent state"). Patch calling convention (mkPatch): lib/default.nix.
# "Persistent state"), kept when the patch goes and removed by
# steam-frame-nix-cleanup --all. Patch calling convention (mkPatch):
# lib/default.nix.
{ config, pkgs, lib, ... }:
let
cfg = config.steamFrame.uiPatches;
@@ -73,7 +75,10 @@ let
SteamVR restarts and reboots: the page reads it with
`window.__sfuiStore.get(name)` and writes it with
`window.__sfuiStore.set(name, value)` (see injector.mjs). The file
is user data and stays when the patch is removed.
is kept when the patch is disabled or removed (the choices come
back when it is enabled again) and removed by
`steam-frame-nix-cleanup --all` (also run by `install.sh
uninstall`).
'';
};
};
+3 -1
View File
@@ -34,7 +34,9 @@
// authoritative from then on. set() goes through the CDP binding
// window.__sfuiStoreSave('{"name","value"}'), which this process writes to the
// file (atomically, only on change, only for "state" patches of that target,
// at most 64 KiB). The file is user data: nothing deletes it.
// at most 64 KiB). The file is user data: kept when the patch is disabled or
// removed, deleted only by steam-frame-nix-cleanup --all (install.sh
// uninstall).
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';