Invariants: The desktop surface¶
The desktop client's surface in the dev build: its restoration ledgers and its selectors. The desktop Play button's
stranded-panel gate and the per-ROM detail state it reads are entries of the panel; the Steam internals and
@decky/ui names the surface reaches are entries of the start-up check. How an entry reads and binds, and
the other areas: the register's index.
- Every change the desktop surface makes to Steam's desktop window goes through a restoration ledger and is given
back: the navigation watcher's on leaving the page whenever the ledger holds anything — a pass that threw partway
included — and each launch question's when the question settles (dev build only) — test + prompt-only — the watcher
runs a pass on every change Steam makes to a RomM game page, so a change that bypasses the ledger, or a ledger that
records the wrong original, is not an occasional leak but the page left changed after every visit: a second pass that
recorded
display: noneas the original would leave Steam's Play button, badges and sections hidden once the page is left, and a style written straight onto the play bar would stay on it. Neither shows in any one adaptation's test, because each of those checks its own element, so the rule is held by one test over the whole page:frontend/src/desktop/navigationWatcher.test.ts, "every change it makes is given back", records thestyleattribute of every element before the watcher starts, runs passes from Steam's mutations and from a change to the set of RomM shortcuts, has a scroll and a resize re-pin the play bar — each asserted to have reached its listener — leaves the page, and requires every attribute to match — once with both views handed and once with none. Leaving restores whenever the ledger holds anything rather than once a page was entered, which is what gives back a pass that threw after the sticky play bar was set up; "gives back what a pass changed before it threw" holds that. What the test cannot see is a write to anything butstyle— a class, another attribute, a node moved — and a change to an element its page does not hold; those are prompt-only. The launch prompt host keeps a ledger of its own for each question and gives it back when the question settles (docs/architecture/desktop-dom-architecture.md, "Dialogs for a start the launch watcher catches"); a container left behind would stay drawn over Steam's page after its start was settled.frontend/src/desktop/launchPromptHost.test.tsxholds it: the container is gone from the window's body however the question settles, a second window's included, and once it is answered the window'spagehideandunloadlisteners are removed, and with the root's unmount the dialog's ownkeydownlistener, which a component's effect cleanup gives back outside the ledger. Beside a launch question, any change but its container and its window's listeners is prompt-only. The rule's home is.claude/rules/desktop-dom.md, and the mechanismdocs/architecture/desktop-dom-architecture.md, "The restoration ledger". Prompt-only: beside the navigation watcher, a write to anything butstyle— a class, another attribute, a node moved — and a change to an element its test's page does not hold; beside a launch question, any change but its container and its window's listeners - No code under
frontend/src/desktop/names one of Steam's minified class hashes (dev build only) — check — a class hash is webpack's, and a Steam update may change it, so a selector that names one finds nothing after that update and the surface goes inert without an error. The finders resolve through the selector ladder instead (docs/architecture/desktop-dom-architecture.md, "The selector ladder"), whose first rung takes the exact class@decky/uiresolved from Steam's own modules at run time — the one place a hash may appear, because it was never written down.frontend/eslint.config.js(no-restricted-syntax,NO_CLASS_HASHES) fails a string literal, regex literal or template part underfrontend/src/desktop/that holds a token of twelve or more letters, digits,_and-with a digit, a capital and a small letter in it; test files are left out, because a test builds Steam's page out of such classes.frontend/src/eslintNoClassHashes.test.tskeeps the rule reporting, and holds that the block, which replaces the package-wideno-restricted-syntaxoptions for those files, still carries the rule that no name calls Tender a plugin. Unseen by it: a hash with no digit, no capital or no small letter in it, one shorter than twelve characters, and one built at run time. This list is the one home of what the check misses; the config and the rules file point here