421e8112252ac6ef83e7274d483f22c839ecaee1
62 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
833f3348a0 |
feat(store): console plugin store, index repo, and the fixes on-glass found
apple / swift (push) Successful in 1m22s
release / apple (push) Successful in 9m59s
windows-host / package (push) Successful in 9m52s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m20s
apple / screenshots (push) Successful in 6m28s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m21s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m39s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m45s
audit / cargo-audit (push) Successful in 2m58s
audit / bun-audit (push) Successful in 13s
ci / web (push) Successful in 52s
ci / docs-site (push) Successful in 59s
android / android (push) Has been cancelled
arch / build-publish (push) Has been cancelled
ci / bench (push) Has been cancelled
ci / rust (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 23s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 35s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 49s
flatpak / build-publish (push) Successful in 7m53s
docker / deploy-docs (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
deb / build-publish (push) Successful in 11m41s
deb / build-publish-host (push) Successful in 13m32s
Console: a Plugins section (Browse / Installed / Sources) on a static nav entry, with install friction proportional to trust — a plain confirm for a verified entry, a warning naming the curator for an external one, and a danger dialog that makes you retype the spec for a raw package. Tier badges are permanent and follow the plugin onto its own UI page. Index: unom/punktfunk-plugin-index published and served from Gitea's raw endpoint (real HTTPS, byte-exact, no vhost to stand up) — merge to main is publish, which resolves the design's open hosting question. Four things only running it could find: - runner discovery matched @punktfunk/plugin-* only, so a third-party scoped plugin (which D8 requires) would install and never run - ...and that convention also matches @punktfunk/plugin-kit, a plugin's own framework: it listed as installed and would have been imported as a unit. Both now key off the plugins dir's top-level dependencies, with an emptied dependency list meaning 'nothing installed' rather than falling back to the naming convention - the store must not pass new flags to the runner: the scripting package ships separately and an older one reads an unknown flag's value as a package name. The host writes the bunfig scope mapping itself - ureq reports only >= 400 as Err, so a conditional request's 304 arrives as Ok with an empty body — handled as an error it made every refresh after the first verify a signature over zero bytes and sit stale Also: the console's first Tabs use exposed an @unom/ui theme gap that rendered inactive tabs invisible (caught in a browser pass, not by types). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
84e1e5aeb4 |
fix(web): restore the Dashboard status tiles' top padding
apple / swift (push) Successful in 1m30s
apple / screenshots (push) Successful in 6m28s
windows-host / package (push) Successful in 9m27s
ci / web (push) Successful in 1m10s
ci / docs-site (push) Successful in 1m11s
android / android (push) Successful in 13m1s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
decky / build-publish (push) Successful in 20s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 13s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 37s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 11s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 22s
ci / bench (push) Successful in 6m9s
arch / build-publish (push) Successful in 15m32s
deb / build-publish (push) Successful in 9m10s
deb / build-publish-host (push) Successful in 10m45s
ci / rust (push) Successful in 24m42s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 17m6s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 17m24s
docker / deploy-docs (push) Successful in 12s
CardContent is shadcn's header-adjacent variant (`p-4 pt-0 sm:p-6 sm:pt-0`), but the four Live-status tiles use it with no CardHeader. tailwind-merge lets the call site's unprefixed `p-4` cancel the base `pt-0`, yet nothing cancels `sm:pt-0` — so the tiles lost their top padding at >=640px only, and the content sat against the top edge. Restores the top inset at the sm breakpoint and lets the content fill the stretched grid cell so it stays centred when a sibling tile is taller. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4094f6208d |
fix(web): load the inlang plugin from node_modules, not a CDN
audit / bun-audit (push) Successful in 13s
ci / docs-site (push) Successful in 54s
ci / web (push) Successful in 56s
apple / swift (push) Successful in 1m19s
audit / cargo-audit (push) Successful in 3m6s
decky / build-publish (push) Successful in 19s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 55s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 11s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
apple / screenshots (push) Successful in 6m41s
deb / build-publish (push) Successful in 10m14s
ci / bench (push) Successful in 10m59s
windows-host / package (push) Successful in 11m11s
deb / build-publish-host (push) Successful in 13m54s
android / android (push) Successful in 17m22s
arch / build-publish (push) Successful in 17m37s
ci / rust (push) Successful in 22m53s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 20m22s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 18m48s
docker / deploy-docs (push) Successful in 29s
`web/project.inlang/settings.json` pointed `modules` at https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js — inlang's stock `paraglide-js init` scaffolding, which nobody had a reason to keep. It made `bun run codegen` need the network at build time, so in a sandbox that has none (the Nix build; any air-gapped CI) the plugin import failed and paraglide compiled ZERO messages. That failure is silent by design: the inlang SDK logs a PluginImportError as a WARNING, paraglide still prints "Successfully compiled" and exits 0, vite bundles the empty message set, and the console only dies at request time inside renderToReadableStream because every `m.foo()` is undefined. Reported from a NixOS host running punktfunk-web-server. The plugin is a normal npm package, so depend on it like one: add @inlang/plugin-message-format as a devDependency and reference it by relative path. `loadProjectFromDirectory` (which both `paraglide-js compile` and the vite plugin use) imports non-`http` modules straight off disk, and the shipped dist/index.js is a self-contained bundle with no imports of its own, so it loads from the data: URL exactly as the CDN copy did. Compiles 268 messages offline, with no project.inlang/cache round-trip. Add tools/check-i18n.mjs, run at the end of `codegen`, so this can never ship silently again: it fails the build on a remote `modules` entry, a module that does not resolve, or a compiled message count below what messages/en.json defines. Verified it catches all three (remote URL, missing plugin, corrupt plugin that compiles to 0 messages) and passes a healthy build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c2bba13405 |
feat(host/web): per-scanner library toggles in the console
apple / swift (push) Successful in 1m16s
apple / screenshots (push) Successful in 6m29s
ci / web (push) Successful in 1m1s
ci / docs-site (push) Failing after 24s
ci / bench (push) Successful in 5m31s
deb / build-publish (push) Successful in 9m21s
decky / build-publish (push) Successful in 24s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 13s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 43s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
android / android (push) Successful in 19m41s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 9s
deb / build-publish-host (push) Successful in 9m44s
arch / build-publish (push) Successful in 18m17s
ci / rust (push) Successful in 19m0s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9m14s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 25m20s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 25m10s
windows-host / package (push) Successful in 17m47s
docker / deploy-docs (push) Successful in 10s
Every installed-store scanner (Steam; Lutris+Heroic on Linux; Epic/GOG/
Xbox on Windows) was hardwired on. New library-scanners.json persists the
operator's disabled set (default all on; absent/malformed = all on);
all_games() gates each provider, so disabling one hides its titles from
every surface (console grid, native clients, GameStream app list, launch
resolve). GET /library/scanners lists this platform's scanners + state;
PUT /library/scanners/{id} toggles and emits library.changed — admin lane
only (the cert allowlist's exact-path /library match keeps both off the
LAN surface). The console's Library page grows a "Game sources" card with
one chip per scanner (platform-shaped by the API), EN+DE strings, story.
The scanners are slated to become plugins; the stable per-scanner ids are
the migration seam.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
770994b7aa |
fix(web): tighten mobile padding so content isn't too narrow
apple / swift (push) Successful in 1m15s
apple / screenshots (push) Successful in 6m8s
ci / web (push) Successful in 54s
ci / docs-site (push) Successful in 1m3s
ci / bench (push) Successful in 5m28s
arch / build-publish (push) Successful in 11m41s
decky / build-publish (push) Successful in 23s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 36s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 39s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m8s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 14m33s
android / android (push) Successful in 16m39s
windows-host / package (push) Successful in 16m15s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 16s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 14s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 14s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
docker / deploy-docs (push) Successful in 24s
deb / build-publish (push) Successful in 8m59s
ci / rust (push) Successful in 19m26s
deb / build-publish-host (push) Successful in 10m5s
The main content wrapper used p-6 (24px) on mobile while the mobile top bar uses px-4 (16px) — an inconsistent, oversized side gutter that ate into the usable width on phones. Drop it to a 16px side gutter (px-4) on mobile, matching the top bar; sm+ padding is unchanged. Also make the shared Card padding responsive (p-4 on mobile → p-6 from sm up) so card content isn't double-inset on small screens. Desktop layout is identical. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ec84b30eae |
feat(plugins): console-hosted plugin UI surface (host registry + SDK servePluginUi + console proxy/nav)
Implements planning/design/plugin-ui-surface.md (U1-U3):
- host: in-memory lease-based plugin registry (mgmt/plugins.rs) — PUT/GET/DELETE
/api/v1/plugins + GET /plugins/{id}/ui-credential; bearer+loopback only (not on
the mTLS read-only allowlist); plugins.changed event; port-only registration
(proxy always dials 127.0.0.1); secret never in the listing.
- sdk: servePluginUi — loopback ephemeral bind + per-boot secret + constant-time
check + /__health + static/SPA-fallback + register/renew(30s)/deregister via
pf.request (skew-proof, D7). Example + tests.
- console: /plugin-ui/{id}/** reverse proxy (server-side secret injection, cookie
strip, SSE streaming, stale-secret 401-retry) + credential cache; BFF denylist
for the credential endpoint; dynamic Plugins nav (desktop + mobile) fed by a
polled list; iframe-in-shell page with health probe, offline card, open-in-tab,
deep-link sync. Dev-mode /plugin-ui middleware in vite.config.ts.
OpenAPI regen for the new endpoints follows in the next commit (built on Linux).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
75b3c94f60 |
fix(web): console sweep — pairing, displays, stats, logs, auth, i18n
ci / rust (push) Failing after 45s
ci / web (push) Successful in 52s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
ci / docs-site (push) Successful in 1m6s
decky / build-publish (push) Successful in 33s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 30s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
ci / bench (push) Successful in 6m1s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8m33s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9m40s
docker / deploy-docs (push) Successful in 26s
windows-host / package (push) Successful in 15m34s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m24s
arch / build-publish (push) Successful in 20m32s
android / android (push) Successful in 20m50s
deb / build-publish (push) Successful in 20m33s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 21m41s
apple / swift (push) Has been cancelled
apple / screenshots (push) Has been cancelled
Pairing: - Refresh the paired-devices list after a native PIN pairing (the happy path never invalidated it, so a newly paired device stayed hidden until remount). - Moonlight PIN: a 204 means "PIN delivered to the waiting handshake", NOT paired, so it now reads "PIN sent" instead of a false "Paired successfully". - Hide the Moonlight pairing card on native-only hosts (HostInfo.gamestream) — it could never receive a PIN there. - Per-row pending on unpair/approve/deny; PIN input maxLength 16 (was 8). Displays / Library: - "Arrange displays" save refreshes the settings card (it rewrites the policy), without clobbering unsaved Custom edits (re-seed only when the draft still matches the server). - Live-display list wrapped in QueryState so errors don't read as "no displays". - "Forever" keep-alive option in the custom editor; edit-game form round-trips the logo artwork (was dropped on save); per-card delete pending. Stats: - Distinct colour for the native "queue" latency stage (it collided with "capture"). - "Not measured on this path" note on the GameStream health chart; configured-bitrate target line on throughput; host-authoritative elapsed timer; LiveCard surfaces non-404 errors. Shell / auth / i18n: - SSR-stable locale: first client render matches the base-locale SSR (no hydration mismatch), then adopts the persisted/browser locale post-hydration. - BFF proxy maps an upstream (mgmt-token) 401 to 502 so a logged-in user isn't bounced into a post-login redirect loop. - Logout checks the POST result before navigating; logs dedup by seq (StrictMode); login "next" keeps query/hash; Dashboard shows the active-session count. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a85be8e467 |
feat(displays): clearer virtual-display preset names + descriptions
ci / web (push) Successful in 1m0s
ci / docs-site (push) Successful in 1m15s
decky / build-publish (push) Successful in 18s
apple / swift (push) Successful in 4m55s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 31s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m2s
ci / rust (push) Failing after 7m8s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 6m21s
docker / deploy-docs (push) Successful in 24s
windows-host / package (push) Failing after 8m23s
ci / bench (push) Successful in 8m44s
arch / build-publish (push) Successful in 12m26s
android / android (push) Successful in 13m37s
deb / build-publish (push) Successful in 14m23s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 12m34s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m17s
release / apple (push) Successful in 22m53s
apple / screenshots (push) Successful in 20m47s
"Gaming rig" actually meant a dedicated/headless box you only ever stream from, which confused users — rename it to "Headless box" and rewrite all five preset summaries to be scenario-first and shorter (the console cards already show the mechanics as badges). Updated across the host API summaries (mgmt.rs), the web console labels (en/de), and the docs table + prose. The internal preset id `gaming-rig` is unchanged (stable API / stored-policy / test contract). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a011aebef5 |
feat(host,web): experimental PnP monitor-devnode disable for Exclusive sessions
ci / web (push) Successful in 1m0s
ci / docs-site (push) Successful in 1m6s
apple / swift (push) Successful in 1m9s
decky / build-publish (push) Successful in 22s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 47s
windows-host / package (push) Failing after 3m40s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m54s
android / android (push) Has been cancelled
apple / screenshots (push) Has been cancelled
arch / build-publish (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / bench (push) Has been cancelled
deb / build-publish (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m21s
flatpak / build-publish (push) Failing after 2m26s
windows / build (aarch64-pc-windows-msvc) (push) Failing after 1m2s
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
release / apple (push) Failing after 19m24s
Second experiment against the connected-but-dark-head stutter (field-proven on the reporter's box: unplugging his standby HDMI TV removes a metronomic ~4 s double-jolt; DDC/CI is a dead end for TVs — measured on the lab LG, VCP 0xD6 gets no I2C ACK). An Exclusive isolate only removes physical monitors from the CCD topology; their PnP devnodes stay live, so every standby wake (auto input scan, Instant-On HPD cycling) still triggers the full Windows reaction: PnP arrival/removal, CCD re-evaluation, DWM invalidation — the suspected hiccup mechanism (Apollo #368's Device-Manager-refresh signature). - New `pnp_disable_monitors` display-policy axis (default off): orthogonal to presets like game_session/ddc_power_off, surfaced in GET/PUT /display/settings + the enforced list, carried through the layout transform. - windows/monitor_devnode.rs: after the isolate takes, disable exactly the deactivated monitors' devnodes — CCD target → monitor device path (DISPLAYCONFIG_TARGET_DEVICE_NAME) → PnP instance id → CM_Disable_DevNode with CM_DISABLE_PERSIST, so a hot-plug RE-ARRIVAL stays disabled (that persistence is the whole point). Teardown re-enables BEFORE the CCD restore (+300 ms re-arrival settle) so restored paths have their monitors back. Precise selection — co-installed third-party virtual displays are never touched. - Crash safety: instance ids journal to <config>/pnp-disabled-monitors.json before disabling; serve startup re-enables leftovers from a crashed host. Worst case is documented in the console help (Device Manager re-enable). - Web console: second Experimental-badged toggle (the DDC block refactored into a shared ExperimentalToggle), EN/DE strings, preset-switch carry. Verified: Linux 263 tests + clippy + fmt clean; Windows (RTX box) 220/220 + clippy clean; web tsc + production build clean; openapi.json regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cca5008805 |
feat(host,web): experimental DDC/CI monitor power-off for Exclusive sessions
ci / web (push) Successful in 47s
ci / docs-site (push) Successful in 1m7s
decky / build-publish (push) Successful in 19s
apple / swift (push) Successful in 1m10s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 15s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 12s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 30s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 11s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 9s
apple / screenshots (push) Successful in 5m28s
ci / bench (push) Successful in 6m40s
docker / deploy-docs (push) Successful in 20s
windows-host / package (push) Successful in 8m45s
android / android (push) Successful in 12m43s
deb / build-publish (push) Successful in 13m1s
arch / build-publish (push) Successful in 14m36s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m5s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m20s
ci / rust (push) Successful in 22m12s
The sole-virtual-display stutter investigation's active experiment: when the Exclusive isolate deactivates a physical monitor, the dark-but-connected head keeps getting serviced (monitor standby auto-input-scan / DP link churn) at a seconds-scale cadence — the leading suspect for the periodic double-jolt. A panel commanded off over DDC/CI (the VESA monitor-control channel in the video cable) believes it has an owner and, on cooperating firmware, stops probing. - New `ddc_power_off` display-policy axis (default off): orthogonal to presets like game_session, stored in display-settings.json, surfaced in GET/PUT /display/settings + the enforced list, carried through the layout transform. - windows/ddc.rs: VCP 0xD6 power-mode control via the dxva2 Physical Monitor API. Deliberately DPMS-off (0x04, DDC stays responsive, signal return wakes) and never power-button-off (0x05, bricks-until-button on many monitors). Probe-before-write; every failure is skip-and-log — monitors without DDC/CI, OSD-disabled, or behind docks/KVMs degrade to a logged no-op. - Manager wiring: panels commanded off immediately BEFORE the Exclusive CCD isolate (an HMONITOR — and with it the DDC channel — only exists while the display is active); teardown wakes them right after the CCD restore, where returning signal alone already wakes most firmware. - Web console: an Experimental-badged on/off control on the display card, applied immediately like the game-session axis and preserved across preset switches; EN/DE strings incl. the wake-failure escape hatch (press the monitor's power button once, turn the option off). Diagnostic value on top of the fix: if this kills a reporter's stutter, the churn is monitor-firmware-initiated; if only topology=primary/extend does, the driver services dark heads regardless — the two remaining root-cause classes. Verified: Linux 258 tests + clippy + fmt clean; Windows (RTX box) 220/220 + clippy clean; web tsc + production build clean; openapi.json regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e707a962b6 |
fix(security): anti-replay, 0600 client key, open redirect, supply-chain
Address findings from a repo security review:
- core: add a sliding-window anti-replay filter over the AEAD-authenticated
sequence in Session (poll_input/poll_frame), closing the input-replay gap the
data plane previously left to the LAN/VPN trust assumption. 4096-deep window,
unit-tested; the encrypted loopback suite confirms no false drops.
- clients: write the mTLS client private key 0600 and lock the config dir 0700
on Unix (it was world-readable at the umask default), re-locking existing
stores on load. pf-client-core::trust plus the probe's own identity writer.
Windows keeps the %APPDATA% ACL; Android/Apple already wrap the key.
- web: fix a post-login open redirect — resolve `next` via URL and require it to
stay same-origin, rejecting `/\evil.com` and tab/encoding variants the old
`!startsWith("//")` guard missed. Also fixes the dead safeNextPath helper.
- ci: SHA-256-pin the BtbN FFmpeg DLLs bundled into the signed Windows installer
(were fetched from the rolling `latest` tag unverified); fails closed on a
re-roll, matching the VB-CABLE gate.
- ci: fail-open fork-guard on the Windows/Apple host-mode PR build jobs that
share runner labels with the signing jobs. Definitive fix stays server-side
(Gitea outside-collaborator approval / isolated PR runners) — see the notes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
8743f63406 |
polish(web): favicon, Punktfunk casing, display-save toast, idle stop button
- Favicon: the brand-mark lens SVG at /favicon.svg, linked from the root head. - Branding: user-facing "punktfunk" → "Punktfunk" (tab title, app_name, pairing prose, the logo aria-labels/SVG titles) — protocol identifiers (punktfunk/1, _punktfunk._udp, binary/module names) stay lowercase. - The display policy auto-saves on every preset pick / field edit with no signal; mount @unom/ui's sonner Toaster in the root and fire a success toast from the save funnel's onSuccess (new i18n key, en + de). First toast usage in the console — errors stay inline as before. - The stop-session button rendered destructive-red even with nothing to stop: variant switches to the muted `secondary` while no session is active. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
52856d4b6b |
chore(web): refresh bun.lock
android / android (push) Successful in 4m34s
arch / build-publish (push) Successful in 5m39s
audit / bun-audit (push) Successful in 12s
audit / cargo-audit (push) Successful in 1m21s
ci / web (push) Successful in 49s
apple / screenshots (push) Successful in 5m49s
apple / swift (push) Successful in 1m12s
windows-host / package (push) Successful in 7m39s
ci / docs-site (push) Successful in 1m14s
ci / rust (push) Successful in 3m31s
ci / bench (push) Successful in 4m54s
decky / build-publish (push) Successful in 14s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
deb / build-publish (push) Successful in 4m0s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 34s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 11m17s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 11m12s
docker / deploy-docs (push) Successful in 18s
Dependency bumps within the existing package.json semver ranges; no manifest changes. |
||
|
|
f30a65d507 |
fix(web): override vulnerable transitive deps, clear bun audit
Force patched undici/dompurify/postcss/esbuild/js-yaml pulled in transitively via @unom/ui's payload/vite/storybook chains, and bump vite 7.3.5 -> 7.3.6 so esbuild resolves consistently to 0.28.1 instead of splitting across 0.27.7/0.28.0. Closes all 26 bun audit findings (3 high, 17 moderate, 6 low). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
79c4f4cbc1 |
feat(host): user-defined custom display presets
Save named bundles of the display-management policy (the six behavior axes
plus the game-session axis) as custom presets, alongside the built-ins. A
custom preset is data — stored in <config>/display-presets.json — not a Preset
enum variant, so DisplayPolicy::effective() stays pure and the built-in set is
untouched; applying one writes a Custom policy via the existing PUT
/display/settings.
- policy.rs: CustomPreset/CustomPresetInput + load/add/update/delete store
- mgmt.rs: GET/POST /display/presets + PUT/DELETE /display/presets/{id},
surfaced on GET /display/settings
- web console: custom-preset cards with save-as / edit / delete + i18n
- regenerated api/openapi.json; docs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
1992eb1c52 |
feat(host): game-mode integration + dedicated game sessions
Implements design/gamemode-and-dedicated-sessions.md (Parts A1-A5 + B0-B2):
reconciles the merged display-management registry with session-mobile
Bazzite/SteamOS hosts and adds a per-launch dedicated gamescope mode.
- A1 DisplayOwnership {Owned,External,SessionManaged} + poolable_now(): the
registry pools only what it owns, so gamescope managed/attach outputs are no
longer double-owned by the registry AND the gamescope restore worker (fixes
the game-mode-reconnect stale-node wedge).
- A2 validated reuse: (backend,mode,launch,epoch) reuse key + kept_display_alive
liveness probe + reused_gen/mark_failed on a reused-display first-frame failure.
- A3 policy-driven managed restore (keep_alive replaces the hardcoded 5s debounce;
forever = held = gaming-rig truthful) + crash-restore persist + SIGKILL teardown
(kill_unit, applied to our transient unit AND the autologin stop -- validated
live on .181 to avoid the F44 GPU-context leak).
- A4 session epoch: observe_session_instance bumps the epoch + invalidate_backend
on a desktop-compositor instance change; gamescope spawns are exempt.
- A5 per-spawn log + PID-scoped gamescope node discovery.
- B0 game_session {auto,dedicated} policy (top-level, preset-orthogonal) +
pick_gamescope_mode dedicated_launch + steam -silent command shaping.
- B1 free the autologin Steam before a dedicated Steam spawn (single-instance).
- B2 game-exit -> APP_EXITED_CLOSE_CODE (0x52) clean session end.
Adversarially reviewed (11 findings fixed). Validated on glass (.181 Bazzite F44,
RTX 4090): dedicated spawn streams a real game smoothly; keep-alive reuse; the
SIGKILL fix avoids the F44 vkCreateDevice leak. Workspace green
(build / test --workspace / clippy -D warnings / fmt), OpenAPI + C header
regenerated, web console tsc + vite build green. clients/probe: bump the
no-video timeout 8s->45s for gamescope cold starts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
f87a43f42d |
feat(web): harden login gate — throttle, scoped TLS, token-derived seal key
Remediates the two web-console residuals from the 2026-07-05 posture audit: - Brute-force throttle (loginThrottle.ts): per-IP exponential backoff after 5 free attempts, plus a global floor for spread-out floods, keyed on the socket peer IP (not spoofable X-Forwarded-For) with a size-capped map. The constant-time compare already stopped the timing leak; this bounds guess *volume* against a by-design LAN-exposed console. - Session seal key now derives from the high-entropy mgmt token instead of the low-entropy login password, so a captured cookie is no longer an offline password oracle. Falls back to the password only when no token is configured (dev/local). Rotating the token now invalidates sessions. - Replace the process-wide NODE_TLS_REJECT_UNAUTHORIZED=0 with per-request Bun TLS scoped to the loopback proxy hop; a non-loopback mgmt URL now verifies normally. Dropped the env var from the systemd unit, Steam Deck installer, Windows run scripts, env examples, and web README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b28ddfc2d4 |
fix(web): clearer topology/identity copy, capped description width, mobile More-nav, preset spacing
Console polish on the Virtual displays card + shell: - Topology help now leads with the streamed display's role (Extend/Primary/Exclusive) instead of the confusing physical-monitor-only framing; notes the headless case. Identity help spells out the actual behavior (stable per-client identity → the desktop reapplies that client's scaling/resolution on reconnect) + what Shared / Per-client / Per-client+resolution each do. - Cap description/help width at max-w-prose so long help text isn't a full-viewport line on large screens. - Mobile bottom nav: 8 flat tabs were too cramped → 4 pinned tabs + a "More" tab whose sheet holds the rest (Performance/Logs/Pairing/Settings), "More" highlighted when the active route is in the overflow. - More breathing room under the "Preset" heading. web tsc + biome + vite build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ac030bbda0 |
feat(vdisplay): ship keep_alive=forever (gaming-rig) — Windows MgrState::Pinned
Completes the last §6A-era preset. The Linux registry already resolved forever→Pinned (pure
lifecycle machine); the blockers were the Windows manager, the mgmt reject, and the console tag:
- Windows manager: new `MgrState::Pinned { mon }` — the last-released monitor under keep_alive=forever
is kept indefinitely (like Lingering but the linger timer never fires). A reconnect preempts +
recreates it (same as Lingering — a reused IddCx swap-chain is dead), snapshot reports "pinned",
and `force_release` (POST /display/release, the §8 escape hatch) frees a pinned monitor. release()
branches on the new `keep_alive_forever()`; all MgrState matches made exhaustive over Pinned.
- mgmt PUT /display/settings: stop rejecting keep_alive=forever (now honored on both platforms with a
release path). OpenAPI regenerated.
- web: un-disable the gaming-rig preset (DISABLED_PRESETS now empty) — one-click applies.
Linux paths + web/tsc/openapi green; 47 vdisplay tests pass. The Windows manager.rs is #[cfg(windows)]
(not compilable on the Linux dev box) — build-verified + on-glass validation on .173 to follow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
956420d947 |
fix(web): preset cards use the design-system animated Card (motion + material)
The preset options were raw <button>s — flat, no motion/material — unlike the rest of the console. They now render as the `interactive` AnimatedCard (motion hover + specular material, consistent with every other card), keyboard-accessible (role=button + Enter/ Space), with a 2px primary ring for the active one and a proper disabled state for gaming-rig. web tsc + vite build + biome-lint green; deployed on .21. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
42ea12ad8f |
fix(web): unify display-field spacing (shared Field) + clearer layout help
- Every option in the custom form now renders through one `Field` wrapper (label → control → help at a consistent `space-y-3`), so the label→input gap is roomier and identical across keep-alive, the button groups, and max-displays — the first field no longer spaces differently from the rest. - Reworded the multi-monitor layout help: it now says Auto is side-by-side and Manual gives a per-display X/Y editor "in the Live displays section below once two or more are streaming" — instead of pointing at an "arrangement table" that isn't visible until clients connect. web tsc + vite build + biome-lint green; deployed on .21. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5b71a74da5 |
fix(web): explicit keep-alive Off/Keep toggle + roomier custom display form
Two UX fixes on the Virtual displays Configuration card: - Keep-alive is no longer implicitly "on" by typing in the seconds field. It's an explicit two-button toggle — **Off** (tear down at disconnect) vs. **Keep for** [N] seconds — and the seconds input only appears when "Keep for" is selected. The duration is remembered across toggles, and the help text explains both modes. - Opened up the cramped custom form: the fields container is `space-y-6` with more padding (`p-5`, rounded-lg), each option group is `space-y-2.5`, and the Save button sits below a divider — so it reads as sections with room instead of a pressed stack. web tsc + vite build + biome-lint green; deployed on .21. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
167b3ae251 |
feat(web): move Virtual displays to its own nav section; roomier preset grid
The Host page was crowded (identity, codecs, ports, GPU, displays, compositors) and the virtual-display config surface is large enough to warrant its own home. - New **Virtual displays** nav section: `/displays` route + `sections/Displays` (moved DisplayCard out of `sections/Host`), a `MonitorPlay` sidebar entry after Host, and `nav_displays` i18n. Removed the displays card from the Host page/view. - On its own page the card splits into two: **Configuration** (presets + custom axes) and **Live displays** (the live list + arrangement table) — room to breathe. - Presets now render in a max-2-column grid (`sm:grid-cols-2`) with larger padding, a bigger section heading + preset titles (text-base semibold), roomier spacing, and bottom-aligned "what it sets" badges so the cards line up. web tsc + vite build + biome-lint green; deployed + verified on the Mutter box (.21). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
458790e8f4 |
feat(web): full virtual-display config surface — one-click presets + every axis editable
The Virtual displays card previously only exposed keep_alive/topology/max_displays as editable custom fields; conflict/identity/layout (enforced since Stages 3-5) had no controls, and the presets weren't surfaced as one-click options. Rework the card so the whole policy is configurable WITHOUT any client connected: - Presets front-and-center: each of the five (default/shared-desktop/hotdesk/workstation/ gaming-rig) is a one-click row showing its story AND what it sets (keep-alive · topology · conflict · identity badges), highlighting the active one. A click applies it immediately. gaming-rig stays disabled + "coming soon" (keep_alive: forever isn't cross-platform yet). - Custom mode reveals EVERY axis editably — keep-alive, topology, conflict, identity, layout, max-displays — seeded from the current effective behavior, with a Save button. A reusable `Choice` button-group + a tolerant `tr()` label lookup keep it tidy. - The live-display list + multi-monitor arrangement table stay below (they need a live session); the settings above work standalone. - en+de i18n for the new controls; refreshed the effective-preview row to show all axes. web tsc + vite build + biome-lint green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4c8b960048 |
feat(web): display arrangement table — the Stage 5 console x/y editor
Completes Stage 5's web piece (design/display-management.md §6.2): a `DisplayArrangement` editor in the Virtual displays card. For a ≥2-display group, it renders an x/y table over the live displays that carry a stable identity slot (the manual-layout key), seeded from the current computed positions; Save writes `PUT /display/layout` (via the generated `useSetDisplayLayout`), which switches the host to a manual layout applied from the next connect. Shared/anonymous displays (no identity slot) are omitted (they can't be pinned). Also refreshes the now-stale `display_pending_note` copy (conflict/identity/layout ARE enforced as of Stages 3-5) in en + de. web tsc + vite build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b988053a31 |
feat(vdisplay): lifecycle state machine + display state/release API (Stage 1)
Stage 1 of design/display-management.md — the lifecycle core + the display
management surface:
- vdisplay/lifecycle.rs: pure per-slot state machine (Idle/Active{refs}/
Lingering{until}/Pinned) with acquire/release/expiry/force-release
transitions. No I/O, no OS types — the platform-neutral distillation of the
Windows manager's model. Unit + a 200k-iteration seeded property walk
(no leaks / double-frees / refcount underflow across arbitrary interleavings).
- vdisplay/registry.rs: neutral snapshot/release facade over the per-OS
lifecycle owners. Windows reads/controls the VirtualDisplayManager; Linux
keep-alive (a per-session pool) lands in a following increment (needs GPU-box
validation).
- windows/manager.rs: additive snapshot() + force_release() (no behavior change
to the on-glass-validated path).
- mgmt: GET /api/v1/display/state (live/kept displays) + POST /api/v1/display/release
(tear down lingering/pinned now; refuses active). OpenAPI regenerated.
- web console: Virtual displays card gains a live-display list (polled) with
per-row + release-all buttons and a linger countdown.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
21f3c1326f |
feat(vdisplay): display-management policy surface (Stage 0)
A user-configurable policy layer above the per-compositor VirtualDisplay backends: keep-alive, topology, conflict, identity, layout, max-displays — persisted to display-settings.json, editable from the web console, applied per connect. Design: design/display-management.md. Stage 0 stands up the surface and wires the two behaviors the existing code can already express — the Windows monitor linger duration and the "make the streamed output the sole desktop" topology — through it; every other option is stored + echoed but not yet enforced (later stages). An unconfigured host (no display-settings.json) keeps today's exact behavior. - vdisplay/policy.rs: pure DisplayPolicy + 5 presets + JSON store (gpu-settings pattern) + EffectivePolicy; 9 unit tests. - vdisplay.rs: resolve_topology(Auto); apply_session_env drives *_VIRTUAL_PRIMARY from the policy only when a settings file exists. - windows/manager.rs: linger_ms() + should_isolate() read the policy when configured. - mgmt: GET/PUT /api/v1/display/settings (bearer-only); PUT rejects keep_alive forever until the lifecycle stage. OpenAPI regenerated. - web console: Host → Virtual displays card (preset picker + custom fields); en+de. - docs-site: virtual-displays.md + configuration.md cross-links. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
160a0ad015 |
fix(web): pin the sidebar at viewport height
Sticky h-dvh sidebar: long pages scroll the content, not the nav — the flex stretch was pushing the language switcher below the fold; overflow-y-auto keeps the nav usable on short viewports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
af74c9ca67 |
feat(web,host/windows): move the web console off :3000 to :47992
windows-host / package (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
web-screenshots / screenshots (push) Has been cancelled
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
release / apple (push) Has been cancelled
linux-client-screenshots / screenshots (push) Has been cancelled
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Has been cancelled
decky / build-publish (push) Has been cancelled
deb / build-publish (push) Has been cancelled
android / android (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
android-screenshots / screenshots (push) Has been cancelled
Port 3000 collides with half the dev-server ecosystem; 47992 sits next to the mgmt API (47990) in the punktfunk port family. Updates the run scripts, systemd/scheduled-task units, Dockerfile, Windows firewall rule + installer, packaging, and every doc that referenced :3000. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d86f1a6bdd |
feat(host,web): host log ring + GET /api/v1/logs + console Logs page
Remote debugging without shell access: a tracing layer tees every event at DEBUG-and-up — independent of the RUST_LOG filter gating stderr/host.log, so console-side debugging never needs a restart — into a bounded in-memory ring (log_capture.rs, 4096 newest entries, OnceLock singleton like config()), installed at both init sites (stderr path in main, the Windows service file path). The mgmt API serves it cursor-paged at GET /api/v1/logs?after=&limit= — bearer-only and deliberately NOT on the mTLS cert allowlist (log lines can name client identities and host paths). The web console grows a Logs page (follow/pause · min-level filter · text search · eviction-gap badge); polling self-paces: a non-empty page advances the after-cursor (new query key → immediate refetch, drains backlogs), an empty page idles at the 2s interval. OpenAPI regenerated; ring pagination/eviction, layer wiring, and the authed route are unit-tested; Storybook story included. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
dacc3b5209 |
feat(host,web): multi-GPU selection — GPU inventory + preference API, web-console GPU card
- new crate::gpu (compiled on all platforms so the OpenAPI doc stays platform-independent): DXGI / sysfs GPU inventory with reboot-stable ids (PCI vendor:device + occurrence — LUIDs are per-boot), persisted auto/manual preference (<config>/gpu-settings.json, atomic temp+rename with in-memory rollback), one selection with precedence console preference > PUNKTFUNK_RENDER_ADAPTER > max VRAM and graceful fallback when the preferred GPU is absent, plus a live "in use" record (RAII session guard wrapped around every encoder open_video returns) - fix: windows_gpu_vendor derived the encoder backend from DXGI adapter 0 instead of the selected render adapter — on a hybrid box (e.g. Intel iGPU at index 0 + NVIDIA dGPU) the backend could disagree with the GPU the capture ring / IddCx render pin sit on. The NVENC 4:4:4 probe now also runs on the selected adapter (was: OS default), the codec/4:4:4 probe caches are keyed per selected GPU (were process-lifetime OnceLocks), and an explicit PUNKTFUNK_ENCODER conflicting with the selected GPU's vendor warns up front - mgmt API: GET /api/v1/gpus (inventory + mode + preferred + next-session selection with reason + in-use GPU/backend/session-count) and PUT /api/v1/gpus/preference (validates mode/gpu_id before writing); openapi.json regenerated; the vdisplay render pin now also engages for a console preference (not just the env pin) - web console: GPU card on the Host page — list with vendor + VRAM, Automatic / Prefer controls, Preferred / Next session / "In use · backend" badges, missing-preferred-GPU warning and env-pin note; en + de messages - Linux: a matched manual preference picks the VAAPI render node and the NVENC-vs-VAAPI auto choice; auto mode is exactly the previous behavior Validated live on the hybrid laptop (RTX 3500 Ada + Intel Arc Pro, which enumerates twice — the occurrence ids disambiguate): enumerate, prefer, bad-id 400, restart persistence, auto-restore keeping the stored pick. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ae51276a03 |
feat(web): consolidate paired devices, self-contained sections, docs + lint
Web console - Pairing/Library/Stats refactored into self-contained subsections that each own their own queries + mutations; a shared slot-based layout (view.tsx) is filled by the live page (containers) and Storybook (pure cards + fixtures) so the layout can't drift. - All paired devices in one list on Pairing with a protocol column (punktfunk/1 + Moonlight), routing each unpair to the right endpoint; the redundant Clients page is removed. - Library: overview grid split from the add/edit form into separate files. - Login screen links out to the docs. Docs - "Console login password" section on every host page (apt/RPM/Bazzite/SteamOS/Windows) plus a new "Forgot your Password?" troubleshooting page, linked from the login screen. - Console served as HTTP/1.1 over TLS (drop the unusable HTTP/3 advertising) across the Bun entry, launchers, systemd units, and packaging. Tooling - Biome now respects .gitignore (stops linting generated code), config migrated to 2.5.1; all lint issues fixed cleanly. Also includes this branch's in-progress host, Apple client, packaging, and CI changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
3fb9d081d1 | fix build | ||
|
|
3628ea7547 |
feat(web): CI screenshot capture for the mgmt console
Marketing/store screenshots of the console, captured from the built Storybook with headless Chromium (web/tools/screenshots.mjs) — every Pages/* + Shell/* story rendered at 1440x900@2x. The page stories render from fixtures, so no live mgmt API, login, or GPU is needed (the web analogue of apple.yml's screenshots job). Gated to stable release tags in a standalone best-effort workflow; PNGs upload as a 30-day artifact, not committed. - Add Stats + Pairing stories (the two pages that lacked them) with stats/pairing fixtures typed against the generated models. - Extract a pure PairingView (index.tsx -> view.tsx), matching the Dashboard/Clients/Stats split, so the page renders host-free from mock state instead of racing its polling queries. Container wiring is behaviour-identical. - Playwright driver + a chromium-capable tag-gated job. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b2acebdf58 |
chore(windows): clean up build/packaging - drop vendored driver binaries + the LLVM-21 pin
Now that the drivers build from source in CI, remove the dead checked-in binaries and
the toolchain cruft they left behind:
- Delete packaging/windows/{pf-vdisplay,gamepad-drivers}/ (the prebuilt .dll/.inf/.cat/.cer).
pack-host-installer.ps1 builds + signs all three drivers from the drivers/ workspace and
nothing reads the vendored dirs anymore; stage-pf-vdisplay.ps1's -VendorDir is now a
mandatory build-output path, not a vendored default.
- Drop the LLVM-21 pin. The vendored bindgen 0.71->0.72 bump (the shipping pack already
builds green on the runner-default clang 22) retired the bindgen-0.71 layout-test overflow
that needed LLVM 21.1.2, so windows-drivers.yml + provision-windows-wdk.ps1 no longer
install/point at C:\llvm-21 (~898 MB off a fresh provision) - both driver builds now use one
toolchain (clang 22 + bindgen 0.72).
- pack -SkipBuild on the gamepad build (build-pf-vdisplay.ps1 already builds the whole
workspace), build-web.ps1 reaps a stale node too, deploy-dev.ps1 nefconc path + comments.
- Reword the vendored-driver references (build scripts, .iss, READMEs, the vite web-bundle
comment) to the build-from-source reality.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
da3f37317b |
feat(host): web-console performance capture — record stream stats, graph them
Arm streaming-perf-stats capture from the web console, play, stop, and review the run as graphs; finished captures are saved to disk as browsable/exportable recordings. Covers both the native punktfunk/1 path and GameStream. - stats_recorder.rs: one shared Arc<StatsRecorder> ring (created in gamestream::serve, shared with the mgmt API + both streaming loops, mirroring NativePairing). The hot-path gate is a runtime AtomicBool that replaces the startup-only PUNKTFUNK_PERF for *recording* (PERF stdout logging unchanged); bounded ring (~3 h); atomic temp+rename writes to ~/.config/punktfunk/captures/*.json; path-traversal-safe ids; poison-resilient locks. - native (punktfunk1.rs) + GameStream (stream.rs) emit a StatsSample at their existing ~2 s / ~1 s aggregation boundary — per-stage latency p50/p99, fps new/repeat, goodput, loss/FEC deltas — with no new per-frame work beyond the cheap atomic check. FrameMsg.was_measured keeps pre-arm in-flight frames out of the first window's percentiles (without zeroing the Windows-relay path's fps/encode). - mgmt.rs: 7 bearer-only /api/v1/stats/* endpoints (capture start/stop/status/live; recordings list/get/delete); api/openapi.json regenerated, in sync. - web: new "Performance" page (recharts, rendered SSR-safe) — capture control, live graphs while armed, recordings table (view / download-JSON / delete), and a detail view with the latency stacked-area bottleneck breakdown (p50/p99 toggle) + throughput + health. Charts adapt to either path's stage set. Design: design/stats-capture-plan.md. Built and adversarially reviewed via a multi-agent workflow; workspace build/clippy(-D warnings)/fmt/tests green, OpenAPI no-drift. Not yet on-glass validated against a live session. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0925f1aaa1 |
fix: complete the docs/→design/ and openapi→api/ rename references
The file moves (docs/ → design/, docs/api/openapi.json → api/openapi.json) landed
in
|
||
|
|
4591bc3b7e |
fix(web): library badge shows the actual store, not always "Steam"
The GameCard badge hard-coded steam-vs-custom, so any non-Steam non-custom store rendered with the "Steam" label. Add storeLabel(store): steam/custom keep their localized strings, every other store is shown as a capitalized proper noun — so the new Lutris/Heroic providers (and future ones) surface correctly with no per-store translation. tsc --noEmit clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
187738ed86 | improve web ui | ||
|
|
6bd2e3341f |
feat(web): Storybook for offline UI design + light theme + brand spinner
Stand up Storybook so the management console can be designed without a running host, plus the design-system work that surfaced along the way. Storybook (@storybook/react-vite): - Slim Start/Nitro-free vite config; the preview imports the app's real src/styles.css directly so the design tokens stay single-sourced (no mirror). - Stories for the @unom/ui primitives (Button/Card/Inputs/Badge), brand marks, the AppShell (throwaway in-memory TanStack router), and every data-driven page (Dashboard/Host/Clients/Library/Settings) rendered offline via a window.fetch stub + typed fixtures. The route page components are exported so stories can render them. Light theme: - styles.css now carries a light :root (lavender, from the docs palette) with the existing violet chrome moved to .dark; the live console still pins html.dark by default, so this only adds the option (Storybook's toolbar toggles it). - Fixes a stray `*/` inside a comment that prematurely closed it and silently broke Tailwind's @theme processing. Spinner: - The punktfunk lens recreated with motion/react: two circles surge through one another in depth (JS perspective scale + z-index — robust where mix-blend-mode flattens CSS preserve-3d) with a screen-blend lens highlight. Replaces the skeleton loading state in QueryState; removes ui/skeleton.tsx. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a659dcc88b |
fix(web): bundle deps into the server (noExternals) — kill the 47k-file install
The Windows installer ballooned to 154 MB and installed forever because the node-server
bundle externalized the WHOLE @unom/ui dependency tree (payload, lexical, date-fns,
prismjs…) to .output/server/node_modules — 47,567 files / 730 MB copied into Program
Files. Set Nitro `noExternals: true` so every dependency is bundled + tree-shaken into the
server output: .output drops to ~75 files / 10 MB, and the bare external imports
(srvx, seroval…) bun couldn't resolve at runtime are gone — so the console runs on bun
(no node, no node_modules), which is the issue we previously worked around with node.
Windows installer now ships bun.exe + the ~75-file .output (was node.exe + a node_modules
forest) and runs `bun .output\server\index.mjs`:
- windows-host.yml: fetch a pinned portable bun (build tool AND shipped runtime); drop the
node fetch + the .output/server install; smoke-boot under the bundled bun.
- pack-host-installer.ps1 / punktfunk-host.iss: -NodeExe -> -BunExe; stage {app}\bun\bun.exe.
- web-run.cmd / build-web.ps1: run/restart on bun; docs updated.
Net win everywhere: the Linux .deb shrinks (node still runs the self-contained output), and
the docker web image — which already ran `bun run .output/server/index.mjs` with only
.output copied — is fixed (the externals had no node_modules to resolve at runtime).
Validated locally: noExternals build = 75 files / 10 MB; node AND bun both serve /login
(200) + static assets (200) + gate /api (401).
(A true single binary via `bun build --compile` is blocked for now: Nitro serves public
assets from an import.meta-relative path `--compile` doesn't embed (/$bunfs/public); the
75-file payload is the clean result.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
7bc5deee7d |
fix(web): session-card button overflow + bottom-nav icon alignment
- Dashboard session card: the header stacks the title above the action buttons on narrow screens (flex-col -> sm:flex-row) and the button group wraps (flex-wrap), so "Request IDR" / "Stop session" no longer overflow the card. - Mobile bottom nav: give each label a fixed two-line-tall centered box so a 1- or 2-line label (labels vary by locale) keeps every tab icon at the same height. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
700b965c31 |
feat(web): drop material gloss, full punktfunk theme for Scalar, center mobile tabs
- console: remove @unom/ui's specular "material" gloss (drop UnomProviders + the material.css import) so components render flat like the marketing site; the violet brand + Geist stay. - mobile bottom tab bar: center the labels (w-full text-center, leading-tight) and even out the per-tab layout. - docs /api: roll the punktfunk dark-violet palette across the whole Scalar reference (surfaces/text/sidebar/links/buttons/method colours via the full --scalar-* token set), locked to dark (hideDarkModeToggle). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
56f724815e |
feat(web): unify console + docs on @unom/ui; host OpenAPI via Scalar
Move the management console (web/) off shadcn/ui to the shared @unom/ui design system the marketing site + docs are built on, on the punktfunk violet brand over dark chrome: - Add @unom/ui/@unom/style/motion/radix-ui/zod + Geist; web/.npmrc maps the @unom scope (packages are public-read, so CI needs no npm auth). - styles.css: one dark-violet palette (#141019/#1c1530, brand #6c5bf3 -> #a79ff8) exposed under BOTH the shadcn token names the routes use and @unom/ui's contract, so routes + components both resolve; pulls in @unom/ui's material gloss + easings. - components/ui/* now back onto @unom/ui (AnimatedButton/InputText/Label/ AnimatedCard); brand-mark/wordmark/logo replace the generic Radio icon in the shell + login. - MaterialProvider (specular gloss) at the root. No UI sounds, like the site. docs-site: new /api route renders the host management REST API as an interactive Scalar reference (reads public/openapi.json, a snapshot of docs/api/openapi.json), branded violet and linked from the top nav, the docs sidebar, the landing page, and host-cli.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0de648f106 |
feat(host): GameStream/Moonlight compat is now opt-in (--gamestream) — secure native-only by default
Follows the security audit (#5/#9): the GameStream-compat plane carries inherent on-path weaknesses that can't be fixed on the wire without breaking stock Moonlight — its pairing runs over plain HTTP (#9, MITM-able during the pairing window) and its legacy control encryption can reuse GCM nonces (#5, a passive eavesdropper can recover/forge input). The native punktfunk/1 plane (SPAKE2 PIN pairing + per-direction AEAD nonces) has neither. So flip the default to secure-by-default: - `serve` → native punktfunk/1 plane + management API ONLY (no GameStream surface). - `serve --gamestream` → ALSO the GameStream/Moonlight-compat planes (nvhttp pairing, RTSP, ENet control, _nvstream mDNS). Opt-in, logged with a trusted-LAN caveat. `--moonlight` is an alias. - The native plane is now ALWAYS on in `serve` (`--native` is a kept-for-compat no-op); the unified GameStream+native host is `serve --gamestream`. `gamestream::serve` gates the GameStream spawns (nvhttp/rtsp/control/mdns) on the flag; the native plane + mgmt + native-pairing handle always run. To avoid silently regressing validated Moonlight deployments, the explicit deployment configs PRESERVE Moonlight via `--gamestream` (each documents dropping it for a secure native-only host): the Linux systemd unit, the Steam Deck installer, and the Windows service default (DEFAULT_HOST_CMD). The bare `serve` default (new/manual use) is secure. Docs swept to match (host-cli, moonlight, quickstart, install, packaging READMEs, CLAUDE.md, README, …): Moonlight setup now instructs `--gamestream`; native/console refs use bare `serve`. OpenAPI regenerated (a stale "run `serve --native`" string). fmt + clippy clean; 94 host tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4a7cf9af7b |
feat(packaging/web): bundle the web console into the apt install (punktfunk-web)
Every user needs the console for pairing, so ship it via apt, auto-wired to the host — no manual bun/env setup. New punktfunk-web .deb (Architecture: all, Depends: nodejs >= 20 — runs the node-server build under apt-native node, no bundled bun): - packaging/debian/build-web-deb.sh: stages web/.output (server + public) + a /usr/bin/punktfunk-web-server wrapper (node) + the systemd --user units + the web.env template + docs. Refuses a bun bundle (Bun.serve) as a wrong-preset guard. - scripts/punktfunk-web.service: --user unit on :3000, EnvironmentFile sources the host's ~/.config/punktfunk/mgmt-token (the shared bearer) + the generated web-password; sets PUNKTFUNK_MGMT_URL=https://127.0.0.1:47990 + NODE_TLS_REJECT_UNAUTHORIZED=0 (loopback self-signed cert). Restart=on-failure rides out the host-writes-token-first ordering. - scripts/punktfunk-web-init.service + web-init.sh: --user one-shot that generates the login password (a .deb postinst runs as root → wrong $HOME) and surfaces it to the journal. - build-deb.sh: punktfunk-host now Recommends punktfunk-web (apt pulls it by default; headless boxes opt out with --no-install-recommends). - deb.yml: build the web console + smoke-boot it under node (gate the .deb on a real /login 200) + build-web-deb.sh; the publish loop globs it automatically. - web/{.env.example,web.env.example}: document the auto-wiring vs a manual deploy. End state: `apt install punktfunk-host` pulls punktfunk-web; enable both --user services; the console logs in (password from the journal) and proxies the host's HTTPS mgmt API with the shared token — zero hand-edited env. Local .deb build + node smoke-boot verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c9f15bc800 |
build(web): node-server preset (apt-native runtime, no bun dependency)
Switch the Nitro build preset from `bun` to `node-server` so the built .output/server is a standalone HTTP server runnable by apt-native `node` (validated: `node .output/server/index.mjs` → Listening, /login 200 on node v25.9.0). This lets the upcoming punktfunk-web .deb depend on `nodejs (>= 20)` instead of vendoring the bun binary. CI still BUILDS with bun; only the runtime target changes, and bun still runs a node-server build, so existing `bun run .output/server/index.mjs` deployments keep working. `vite dev` is unaffected. Prereq for bundling the web console into the apt install. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
944fb9a5e0 |
feat(host/mgmt): HTTPS + token auth by default (no loopback no-auth fallback)
The mgmt API already always serves HTTPS (the host identity cert), but on a loopback bind with no token it ran unauthenticated — any local process could drive it. Make auth required ALWAYS: - new mgmt_token::load_or_generate(): token precedence is --mgmt-token > env PUNKTFUNK_MGMT_TOKEN > persisted ~/.config/punktfunk/mgmt-token > freshly generated 32-byte hex, persisted 0600 in KEY=VALUE form (so the bundled web console can source it directly as a systemd EnvironmentFile — one source of truth). config_dir() made pub(crate). - parse_serve() resolves the token via load_or_generate() when unset, so a bare `serve` Just Works with auth on and no operator step. - mgmt::run() drops the loopback no-token exemption and requires a token; require_auth()'s unauthenticated fallback now returns 401. The paired-cert (mTLS) branch is unchanged — Apple client + library auth unaffected. - web /api proxy: 503 (legible) instead of forwarding an empty bearer. - tests: test_app/test_app_native default a token, send() auto-attaches the bearer; blank-token test asserts the new "no token" refusal. 80 pass. - docs: mgmt module doc + host.env.example reflect always-on auth + auto-gen. Compiles, clippy/fmt clean, openapi no drift. Part B (bundle the web console into apt, auto-wired to this token) follows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f4e37ac3b3 |
fix(web): default mgmt proxy to the HTTPS self-signed mgmt API
The mgmt API serves HTTPS with the host's self-signed identity cert and requires mTLS-or-bearer auth (the mTLS work), but the web console's proxy still defaulted to `http://127.0.0.1:47990` — so a deployment copying .env.example got a plain-HTTP request to an HTTPS port (→ 502 Bad Gateway, observed live on the Bazzite box). - .env.example + server/util/auth.ts + vite.config.ts: default PUNKTFUNK_MGMT_URL to https://127.0.0.1:47990. - vite dev proxy: `secure: false` (the host cert is self-signed). - Document that the deployment needs PUNKTFUNK_MGMT_TOKEN (matching the host's) and NODE_TLS_REJECT_UNAUTHORIZED=0 — the web server's only outbound TLS is the loopback hop to the host's own self-signed cert, so disabling verify there is scoped + safe. The running Bazzite box is already fixed live (web.env → https + token + cert-skip, verified: login 200, /api/v1/status 200). This makes fresh deployments correct. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5e7521fa5b |
feat(web/library): game library page — grid + custom-entry CRUD
Consumes the new library API (
|