Commit Graph
5 Commits
Author SHA1 Message Date
enricobuehlerandClaude Opus 5 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>
2026-08-01 00:20:10 +02:00
enricobuehlerandClaude Opus 5 55e01c1460 fix(web): stop shipping 7 MB of sound the console cannot play
`@unom/ui/button` reaches `sound/defaults.js`, which resolves two game-UI sprite
sheets with `new URL(…, import.meta.url)` at module scope — a 4.8 MB .wav and a
2.2 MB .mp3. Vite emitted both into the build, so they rode into the Windows
installer and the .deb. The console never mounts UnomProviders, so no player is
bundled and not one byte of it could ever be played. A build-time rewrite of
those two expressions takes the asset payload from 8.2 MB to 1.5 MB; the login
page and the button chunk are unchanged. Deleting the plugin is the whole revert
if the console ever wants click sounds.

Also:

- `bun run dev` forwards the management bearer, so developing against a real
  host stops 401ing into a /login bounce that dev has no gate to satisfy.
- `check-i18n` runs after `build`, not only inside `codegen`. It exists to stop a
  zero-message console shipping, and the CI job and the installer build both
  install with `--ignore-scripts`, so it had never once run where it mattered.
- Bun's idle timeout goes from its 10 s default to 120 s. The host sends SSE
  keep-alives every 15 s, so anything long-lived proxied through the console was
  cut by us first — which the event stream is about to depend on.
- The typecheck covers the Storybook config and preview.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 00:20:10 +02:00
enricobuehler 187738ed86 improve web ui 2026-06-26 05:43:34 +00:00
enricobuehlerandClaude Opus 4.8 d356d1534f feat(web): login-gated BFF auth — sealed session cookie + server-side token injection
Single-user, LAN-reachable-but-gated. The web server is a backend-for-frontend:

- Login: POST /_auth/login {password} checks PUNKTFUNK_UI_PASSWORD (constant-time) and
  sets a SEALED session cookie (h3 useSession / AES-GCM). server/middleware/auth.ts gates
  every request — pages 302 → /login, /api → 401 — and FAILS CLOSED (503) when
  PUNKTFUNK_UI_PASSWORD is unset, so a misconfigured LAN-exposed server admits no one.
- The management API stays loopback-only + token (never LAN-exposed). The proxy
  (server/routes/api/[...].ts) injects PUNKTFUNK_MGMT_TOKEN server-side and drops the
  browser's cookie before forwarding — the token never reaches the browser, which only
  holds the session cookie.

Nitro doesn't auto-scan a server/ dir, so the Nitro plugin gets an explicit scanDirs to
pick up middleware + routes. Client: removed the localStorage token (server injects it);
the fetcher bounces to /login on 401; new /login page (bare, no shell); Settings drops the
token field and gains a Sign-out button; en/de strings.

Validated live end to end: unauth /→302, /api→401; wrong pw→401; right pw→200+cookie;
authed /api/v1/status→200 (proxied, mgmt token injected — the host required it); logout→
session cleared→401. tsc + build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 18:43:14 +00:00
enricobuehlerandClaude Opus 4.8 7d64bb5a43 feat(web): management console — TanStack Start + orval + shadcn + Paraglide
Browser UI for the host's management REST API (mgmt.rs / docs/api/openapi.json).

Stack, exactly as specified:
- TanStack Start (Vite, SPA mode) — file-based routes, SSR shell + client hydration.
- React Query via orval codegen from the checked-in OpenAPI spec: a custom fetch mutator
  (src/api/fetcher.ts) centralizes the base URL, the bearer token (Settings → localStorage),
  JSON, and a throwing ApiError; the query client skips retries on 4xx. orval returns the
  response body directly (includeHttpResponseReturnType:false) so a query's `.data` is the
  typed payload; GET→useQuery, POST/DELETE→useMutation by method.
- shadcn/ui on Tailwind v4 (CSS-first tokens, dark-first) — button/card/badge/input/label/
  table/skeleton primitives hand-authored from the canonical source.
- Paraglide i18n (en + de) with a reactive useLocale() hook and a language switcher.

Pages: dashboard (live status — video/audio/session/stream, stop-session + request-IDR,
2s polling), host (identity/codecs/ports), clients (paired list + unpair), pairing (PIN
submit, polls pin_pending), settings (API token + language).

Dev server proxies /api → 127.0.0.1:47990 (same-origin, no CORS; PUNKTFUNK_MGMT_URL to
override). Generated code (orval client, paraglide runtime, routeTree) is gitignored and
reproduced by `pnpm codegen` (prepare/pre* scripts). Validated live against `serve`: API
shapes match, dev proxy works, SSR shell renders the localized nav, build + tsc green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:00:12 +00:00