forked from unom/punktfunk
bfed71192116652afebd06aa6ec649ae04d8c8d6
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3be7d1d4f8 |
feat(web): the console asks its own questions
Follow-up to
|
||
|
|
a85e845255 |
fix(web): the console stops falling out of its own design system
A pre-release sweep of the management console for two things that no type check
and no diff can catch: primitives that were never @unom/ui's, and animation
that a nested motion parent quietly cancelled.
THE PRESET TILES ALL LANDED ON THE SAME FRAME. @unom/ui's <Section> sets
`delayChildren: stagger(...)`, so a page whose cards are direct descendants of
it staggers for free — which is why every page but one looked right. An
<AnimatedCard> is ALSO a motion element and sets no `delayChildren`, and the
Virtual displays preset tiles are cards nested INSIDE that page's config card,
so that card became their timing group. Measured in a headless browser: the
opacity spread between the first and last tile was 0.00 across the whole
animation (six tiles in lockstep), and is 0.98 now — a ~100 ms cascade matching
the rest of the console. The four hand-rolled copies of the stagger container
collapse into one `<Stagger>` that carries the explanation.
FIVE FILES IMPORTED THE WRONG BUTTON. `@unom/ui/button` exports both a plain
`Button` and the `AnimatedButton` that this console's wrapper re-exports under
the same name — so `import { Button } from "@unom/ui/button"` compiles, renders,
and silently opts out of the mount animation and the hover/tap response.
Displays, SessionGame, GPU, Update and PendingDevices had dead buttons sitting
next to live ones.
THREE PRIMITIVES HAD NO WRAPPER, SO NOBODY REACHED FOR THEM. @unom/ui ships
form/select, form/textarea and form/checkbox; components/ui did not, and the
gap was filled with browser-chrome `<select>`, `<textarea>` and
`<input type="checkbox">` in the add-hook modal and both library forms. Select
needs the same token correction Tabs needed — upstream `text-secondary` is a
text colour, but here `--secondary` is a SURFACE, so the trigger's chevron and
placeholder rendered at near-zero contrast on the card behind them.
The hook timeout also stops accepting a value the host rejects: `min`/`max` on
a controlled `<input type="number">` are decoration (no form validation ever
runs), so 900 went into a field capped at 600 and failed later, at run time.
@unom/ui's InputNumber clamps on blur and lets the field be empty while you
retype instead of snapping to the fallback.
Storybook gains the page that had no story at all — the console's largest
config surface, and the reason this shipped unseen. Its <Card> wrapper is load
bearing: it reproduces the motion nesting that IS the bug.
|
||
|
|
f66de3eba4 |
fix(web): close the last two high-severity items from the closeout audit
**The streamed-screen pin could still be clobbered by three other write paths.** Deferring it to the server's value on Save fixed the reported sequence but not the general case: the draft is only re-seeded while it is CLEAN, so once there is an unsaved edit its `capture_monitor` is frozen at whatever it was before the operator used the picker — and `applyAxis` (which spreads the last saved policy), the built-in preset switch and the custom-preset apply all put that stale value back. Every write path reads `serverCaptureMonitor()` now; no path spreads the draft's copy. **The session⇄game grace input had no accessible name.** That card has its own `Field` and only DisplayCard's was fixed, so the number input was still announced as an unnamed spin button. Same treatment: `htmlFor`/`id` for the single control, `fieldset`/`legend` for the two button groups. Verified in a browser — zero inputs without an accessible name across the Displays page. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f2e1b9872c |
fix(web): four "fixes" from this branch that did not actually fix anything
A verification pass re-read every finding from the original sweep against the code on this branch rather than against the commit messages. It found that four of them were still broken, two because the edit I made was inert. Commit messages claim; code decides. - **The Storybook typecheck was never on.** `tsconfig.json` listed `.storybook` as a bare directory name, and tsc silently skips dot-prefixed directories in that form — so the entry typechecked nothing at all. Proved it by planting `export const __probe: string = 1` in `.storybook/preview.tsx` and watching `bun run lint` pass. `.storybook/**/*` is what actually pulls it in; the same probe now fails as it should. - **The Moonlight stale-PIN reset was a no-op.** `submit.reset()` sat at the top of `onSubmit`, immediately before `submit.mutate(...)` — which moves the status to pending in the same update, so it cleared a flag that was already changing. The green "PIN sent" note therefore still greeted the next pairing attempt over an empty PIN box. It now resets on the transition that actually matters: `pin_pending` going false → true. - **The session⇄game controls had the enforcement flag inverted**, and I never touched it. `enforced.length === 0 || …` reads an EMPTY list as "this build enforces everything", when the contract says the opposite in as many words: "Empty on a platform with no launch path (macOS), so the console can say so instead of offering a switch that does nothing". On exactly the platform the flag exists for, every control stayed live and reported success for an axis the host would never act on. Absent still means "assume it acts" — that is the compatible reading for an older host, and a different case from present-empty. - **Logout stopped revoking after a restart.** The epoch was a module-level counter starting at 1, so it revoked within one process run and then reset — and since the seal key derives from the stable mgmt token, a cookie captured before a restart unsealed fine and was accepted again for the rest of its 7-day TTL. One service restart undid the whole fix. It persists next to the host's config now. Verified: log out, restart the console, the captured cookie still 401s, a fresh login still works. Two more the pass rated as partial, both worth closing: - The plugin-UI response filter was a denylist of four header names, so `Clear-Site-Data` sailed through — a plugin error page could wipe `pf_session` and sign the operator out of the console, on our own origin, because the iframe is same-origin by design. It is an allowlist now; a plugin-supplied CSP, `X-Frame-Options` or CORS header no longer speaks for us either. - A half-configured TLS setup now refuses to start instead of logging a warning and serving anyway. Neither shape can work — one path missing puts the login password on the LAN in the clear, and PUNKTFUNK_UI_SECURE without TLS marks the cookie Secure so the browser drops it and login can never stick. Exiting with a reason beats a console that looks fine and is not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
35b6c835fd |
docs: on a gamescope session the display outranks the end-game setting
On-glass on .41: with `game_on_session_end: keep` — "leave it running, nothing
is ever closed" — a deliberate stop ended the game, and a drop ended it ~14 s
later. Both times the LEASE did the right thing (no `ending the launched game`
line); the game died because the virtual display was torn down, and a nested
launch runs inside that gamescope.
So on a dedicated gamescope session the game's lifetime IS the display's, and
keep-alive decides it — not this setting:
* a deliberate stop skips the linger by design, so the game goes at once,
* a drop lingers the keep-alive window, then takes the game with it,
* only keep-alive Forever actually keeps it — and `always` still ends it at
the reconnect window, which is the documented precedence and was verified
(a `forever`-pinned display released at grace expiry, 60.006 s).
That makes the console's promise untrue on the most common Linux setup, so the
card now says so for every option rather than only warning about `always`, and
the docs get the three cases as a table. Worded so a non-gamescope host reads it
and moves on — a desktop-session launch is an ordinary process and none of it
applies.
Not changing behavior here: making `keep` pin the display would pit it against
"a stop must not leave a ghost display", and which wins is a product call rather
than something to decide silently mid-test.
web: codegen + tsc + biome clean, en/de complete. docs-site: build + tsc clean.
|
||
|
|
6a2c127ce9 |
feat(mgmt/web): show the running game, and who decides when it ends
The host knows what it launched and, after a disconnect, that it is counting down to closing it. None of that was visible: the console showed a stream with no game, and a game on its way to being closed was something you found out about afterwards. The Dashboard gains a running-game card above the session card — box art matched against the catalog it already fetches, so no new endpoint and no request on the 2 s status poll. "End now" means the two different things the row's state implies: a live game ends by stopping its session (what then happens to the game follows the policy — stopping a session is not licence to close a game), while one already waiting out its reconnect window has no session left to stop and is ended directly. The settings card sits next to the display keep-alive policy because that is the same question one step out: keep-alive decides how long a *display* outlives a disconnect, this decides whether the *game* does. The copy says plainly what `always` costs, that a drop is not someone pressing Stop, and that a display kept forever is unaffected either way — the precedence rule that would otherwise surprise someone. Where a build enforces nothing (macOS, no launch path) the controls are shown disabled rather than hidden: "does nothing here" is information. Also fixes the story fixtures, which had gone stale against the `games[]` field Phase 1 added, and adds a story for the state the card exists for — a game whose client walked away. web: build + tsc clean, biome-formatted; en/de messages complete. |