v0.22.0
2131
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b2cf4e908c |
chore(release): bump workspace version to 0.22.0
audit / bun-audit (sdk) (push) Successful in 17s
audit / bun-audit (web) (push) Successful in 17s
audit / docs-site-audit (push) Successful in 17s
audit / pnpm-audit (push) Successful in 11s
audit / bun-audit (plugin-kit) (push) Successful in 3m15s
audit / cargo-audit (push) Successful in 3m24s
android-screenshots / screenshots (push) Successful in 3m40s
apple / swift (push) Successful in 5m0s
audit / license-gate (push) Successful in 7m24s
decky / build-publish (push) Successful in 37s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m17s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 2m3s
arch / build-publish (push) Successful in 19m5s
linux-client-screenshots / screenshots (push) Successful in 10m39s
sbom / sbom (push) Successful in 1m16s
docker / deploy-docs (push) Successful in 11s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 24s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 24s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 8s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 5s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 19s
docker / builders-arm64cross (push) Successful in 5s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 23s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m23s
windows-host / package (push) Successful in 14m34s
windows-host / winget-source (push) Successful in 17s
android / android (push) Successful in 10m16s
web-screenshots / screenshots (push) Successful in 10m52s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m56s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m36s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m51s
deb / build-publish (push) Successful in 13m51s
deb / build-publish-host (push) Successful in 5m27s
deb / build-publish-client-arm64 (push) Successful in 4m9s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 27m37s
release / apple (push) Successful in 28m45s
apple / screenshots (push) Successful in 21m19s
ci / docs-site (push) Successful in 1m4s
ci / web (push) Successful in 2m46s
ci / rust-arm64 (push) Successful in 3m41s
ci / rust (push) Successful in 3m54s
flatpak / build-publish (push) Successful in 4m24s
256 commits since v0.21.0. Minor, not patch: the headline is settings profiles — named bundles of overrides bound per host, landing on all five clients at once (Linux, Windows, Apple, Android) with one settings surface editing either layer, marked-and-resettable override rows, per-profile colours, host bindings, one-off "Connect with", and pinned host+profile cards. With them: the punktfunk:// link grammar (one parser, one 44-case vector file run by the Rust, Swift and Kotlin suites), double-clickable shortcuts, and `punktfunk` — one headless front-end over the brain layer, which wakes a sleeping host the way a card click does. Also: opt-in HDR on the gamescope path plus the cursor-in-the-node patch that makes those sessions zero-copy; Vulkan Video 10-bit so AMD/Intel HDR keeps the good path; a zero-copy NVENC HDR leg; host OS detection with marks on every client and the console; PUNKTFUNK_HOST_NAME, PUNKTFUNK_MAX_FPS and PUNKTFUNK_VDISPLAY_HZ_MULT; monitor enumeration on Windows; and the release- integrity work (per-asset SHA256 sidecars, a signed sysext feed, one stable driver publisher identity, fail-closed signing guards on a v* tag). Wire protocol stays at 2, the embeddable C ABI at 13 and the Windows virtual- display driver protocol at 6 — 0.18-0.22 hosts and clients keep mixing freely. The Windows virtual-GAMEPAD channel protocol goes 2 -> 3 and fails closed both ways: it carries the fix for a LocalService principal being able to take over a pad's shared input section and forge HID input into the interactive desktop, so the host and its drivers must ship together. The installer ships both. Additive elsewhere: an advisory mDNS `os=` TXT key, HostInfo.os/os_name, MonitorsResponse.pin_supported, an eighth field on the Android JNI discovery record, and appended-last StoredHost fields per the frozen app-widget contract. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>v0.22.0 |
||
|
|
dee9ecf5c5 |
docs(release): v0.21.0's notes describe what v0.21.0 actually shipped
The file had accumulated the gamescope-HDR work as it landed — one "New: HDR on the gamescope path" section plus nine Under-the-hood bullets (the gamescope patches, Vulkan 10-bit, the per-codec probe, the NVENC HDR leg, the EFC BT.2020 model, the managed-spawn flag check, the CI wiring). All of it landed AFTER |
||
|
|
2b82ce6484 |
fix(ci/flatpak): host networking — ostree's resolver never worked through docker's embedded DNS
ci / rust-arm64 (push) Successful in 1m45s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 7s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 6s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 6s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 6s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 6s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 11s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 13s
ci / web (push) Successful in 4m2s
ci / docs-site (push) Successful in 4m7s
docker / builders-arm64cross (push) Successful in 15s
docker / deploy-docs (push) Successful in 35s
ci / rust (push) Successful in 7m10s
flatpak / build-publish (push) Successful in 7m23s
The flathub fetch has failed 10/10 retries for months, blamed on fleet load and DNS tuning. It is neither. Measured on home-runner-2, all inside ONE container: getent resolved dl.flathub.org, curl fetched the same URL with HTTP 200 (auto and -4), and flatpak still died '[6] Could not resolve hostname'. Rewriting resolv.conf to a real nameserver didn't help; the default bridge failed too; --network host works every time. So it is ostree's own resolver against Docker's embedded 127.0.0.11, and removing that resolver from the path is the fix. retry.sh stays as the backstop for genuine upstream blips. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
53ff313046 |
fix(packaging/gamescope): the shipped compositor starts on SteamOS — a rolling-distro libstdc++ never followed it there
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 14s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 11s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 10s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
ci / web (push) Successful in 52s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 1m6s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 1m8s
ci / docs-site (push) Successful in 2m13s
docker / builders-arm64cross (push) Successful in 10s
docker / deploy-docs (push) Successful in 40s
ci / rust (push) Canceled after 5m49s
ci / rust-arm64 (push) Canceled after 5m47s
arch / build-publish (push) Successful in 15m59s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 16m7s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 15m46s
`punktfunk-gamescope-3.16.25.pfhdr2-1` off the pacman repo cannot start on SteamOS 3.8.16: `/usr/lib/libstdc++.so.6: version 'GLIBCXX_3.4.35' not found`. The Arch container CI builds it in is on gcc 16.1.1; SteamOS ships libstdc++ 3.4.34 and moves when Valve says so. Nothing else about the binary was wrong — every other soname resolved on the box, and glibc was never close (it asks for 2.38 at most against SteamOS's 2.41) — so the one dynamic C++ runtime was the whole reason the gamescope backend's own most important platform got a package that dies at `--version`. The C++ runtime therefore goes static, for the same reason wlroots already does: this binary is built on a rolling distro and has to start on a frozen one. It is safe here because gamescope links no shared C++ library at all — its NEEDED list is all C, and glslang/SPIRV are build-time only — so no C++ ABI crosses a shared boundary. Cost is ~1 MB (5.9 → 7.1). The flags are appended to LDFLAGS rather than passed as `-Dcpp_link_args`, which would replace the value meson derives from the environment and silently drop makepkg's `-z relro`/`-z now`/`--as-needed`. A static runtime is invisible in a passing build and only surfaces as a binary that will not start somewhere else, so the build now asserts it: no `libstdc++` in NEEDED, which needs no version threshold to check and turns the regression back into a build failure. Verified by building in `archlinux:base-devel` — the environment arch.yml uses, gcc 16.1.1 and glibc 2.44, both far newer than the target — and running the result on SteamOS 3.8.16 with nothing supplied: banner `punktfunk-gamescope version 3.16.25-4-g6bbe157+pfhdr2`, no libstdc++ in NEEDED, max GLIBC_2.38, every soname resolving. The host's whole HDR gate chain then answers on SteamOS for the first time (780M / RADV PHOENIX): 10-bit PQ capture offered, cursor painted in-node, native-plane HDR and GameStream HDR capable both true once `PUNKTFUNK_GAMESCOPE_HDR` is on, false with the distro's gamescope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
31db452ca9 |
feat(client/apple): the host tile wears its OS mark where the initial was
apple / swift (push) Successful in 4m32s
ci / web (push) Successful in 1m5s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 7s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 6s
ci / docs-site (push) Successful in 1m18s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 8s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 7s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 18s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 21s
docker / builders-arm64cross (push) Successful in 13s
docker / deploy-docs (push) Successful in 40s
ci / rust (push) Failing after 6m10s
ci / rust-arm64 (push) Successful in 14m47s
release / apple (push) Successful in 25m17s
apple / screenshots (push) Canceled after 13m52s
Apple parity with the Android card (a94b1d3c's sibling): the OS mark moves out of the status row and into the tile, replacing the monogram. The initial says nothing the name beside it doesn't already say — twice over on a row of home-worker-N boxes — while the mark identifies the machine at a glance. Both host surfaces, because on tvOS the console home is the only one there is: the touch cards (saved and discovered) and GamepadHomeView's badge, which needed the chain carried on HomeTile to reach it. Sized to the monogram's own point size, so it lands at ~48% of the tile everywhere, and tinted through the same foregroundStyle the letter used — the assets are template imagesets, so they follow it like an SF Symbol. A host that advertises no OS chain, or one we ship no art for, keeps its letter: `osIconImage` already returns nil for both, so a mixed row still reads as one set. The mark carries the accessibility label now, since the status row it used to ride no longer names the OS. Untouched: the widget draws no monogram, and AboutView's is the app's own logo, not a host's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
af817bc03e |
fix(lock): cursor-probe's pf-frame dep reaches the lockfile
audit / bun-audit (web) (push) Successful in 14s
audit / docs-site-audit (push) Successful in 13s
audit / bun-audit (sdk) (push) Successful in 25s
audit / bun-audit (plugin-kit) (push) Successful in 27s
audit / pnpm-audit (push) Successful in 16s
ci / web (push) Successful in 54s
apple / swift (push) Canceled after 2m17s
apple / screenshots (push) Canceled after 0s
ci / rust (push) Canceled after 1m49s
ci / rust-arm64 (push) Canceled after 1m49s
ci / docs-site (push) Canceled after 47s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Canceled after 0s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 0s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
release / apple (push) Canceled after 0s
audit / cargo-audit (push) Successful in 2m46s
audit / license-gate (push) Successful in 6m35s
deb / build-publish-client-arm64 (push) Successful in 8m10s
flatpak / build-publish (push) Failing after 9m0s
deb / build-publish (push) Successful in 9m47s
android / android (push) Successful in 12m25s
windows-host / package (push) Successful in 16m43s
windows-host / winget-source (push) Skipped
arch / build-publish (push) Canceled after 52s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m44s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 17m26s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 5m24s
deb / build-publish-host (push) Successful in 23m59s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 21m51s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m22s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 3m50s
|
||
|
|
a94b1d3ccc |
fix(client/android): the stream keeps its aspect instead of stretching to the panel
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 7s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 8s
ci / web (push) Successful in 58s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 49s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 6s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 1m6s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 22s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
ci / docs-site (push) Successful in 1m37s
docker / builders-arm64cross (push) Successful in 14s
docker / deploy-docs (push) Successful in 31s
ci / rust-arm64 (push) Failing after 4m29s
android / android (push) Canceled after 4m35s
ci / rust (push) Canceled after 4m39s
MediaCodec scales whatever it decodes to fill the Surface it renders into, and the Surface filled the screen — so a stream whose resolution didn't match the panel's aspect came out stretched. Nothing downstream of the Surface can correct that; the Surface itself has to carry the aspect. Size the video to the negotiated mode's ratio, centred, with the remainder black. The mode is known from the handshake before the first frame arrives, via a new `nativeVideoSize` (the same `client.mode()` the HUD already reports as `w×h@hz`); an older native lib returning nothing falls back to filling, exactly as before. Input follows the picture. Direct-pointer touch, multi-touch passthrough and the pen lane all map positions against the size of the node they sit on, so the gesture layer moves onto the same rect as the video and all three stay correct by construction instead of each needing an offset threaded through it. The physical-mouse path can't work that way — its events arrive from the activity in WINDOW coordinates — so it now measures against the SurfaceView's rect on screen, subtracting the letterbox origin and clamping into the picture: a pointer out on a bar has no host position of its own, and the edge is the honest answer for it. One deliberate consequence: trackpad swipes that START inside a letterbox bar no longer register. Trackpad input is relative and could have kept the whole panel, but one rule — input lands on the picture — beats a mode- dependent input surface, and the pen lane rides inside trackpad mode too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
1f59498c5c |
fix(host/linux): a no-channel session composites the metadata cursor — Mutter never embeds on a virtual stream
ci / web (push) Successful in 3m26s
ci / docs-site (push) Successful in 3m32s
arch / build-publish (push) Failing after 4m29s
apple / swift (push) Successful in 4m38s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 5s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 5s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 4s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 5s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
ci / rust-arm64 (push) Failing after 4m50s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 12s
deb / build-publish (push) Failing after 5m6s
ci / rust (push) Failing after 5m47s
docker / builders-arm64cross (push) Successful in 6s
docker / deploy-docs (push) Successful in 34s
android / android (push) Canceled after 8m16s
apple / screenshots (push) Canceled after 3m46s
deb / build-publish-host (push) Canceled after 4m15s
deb / build-publish-client-arm64 (push) Canceled after 4m7s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 3m6s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 2m58s
windows-host / winget-source (push) Canceled after 0s
windows-host / package (push) Canceled after 8m21s
The capture-latched client (console.rs latched_mouse) never advertises CLIENT_CAP_CURSOR, so its session resolved cursor_blend=false and asked Mutter to EMBED the pointer. On a Mutter virtual stream that is a fiction: since Mutter 48 (7ff5334a, hw-cursor inhibition removed) the software cursor overlay is suppressed stage-globally whenever any physical head realizes a HW cursor — dmabuf-recorded frames blit the view without a pointer, and cursor-only motion schedules no re-record either (mutter#4939). Probed on-glass on Mutter 50.3: embedded + relative motion = frozen frame counter; SPA_META_Cursor positions kept flowing in the same setup. So the no-channel session now takes the path that was verified end to end: cursor-as-metadata + the host composites, permanently — the same arm a channel session lands in after its capture-model flip. Embedded remains only the can't-blend fallback (libav VAAPI/NVENC, software). - session_plan::cursor_blend_for grows the no-channel arm (codec + depth in, the same CUDA-payload prediction handshake makes); gamescope excluded so patch-2+ keeps its native-NV12 zero-copy shape - the encode loop's composite refresh + one-shot breadcrumbs now cover the no-channel session; the park schedule keeps retrying while its composite is starved (relative-only clients cannot park themselves) - the compositor retarget re-applies set_hw_cursor — the rebuilt display used to come up EMBEDDED even for channel sessions - the GameStream virtual source takes the same rule (it never has a channel); its stream_body blend flag mirrors the request - punktfunk-probe grows --cursor-nochannel (the latched-capture client, headless); cursor-probe grows --dump (PPM frames + a content-change counter, the pixel evidence the embedded A/B lacked) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9ed967cbaf |
feat(client/android): the host card wears its OS mark where the initial was
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 13s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 20s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 10s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 1m7s
android / android (push) Canceled after 2m56s
ci / rust (push) Canceled after 2m37s
ci / rust-arm64 (push) Canceled after 2m25s
ci / web (push) Canceled after 2m11s
ci / docs-site (push) Canceled after 2m11s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 18s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 1s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
The OS mark rode along at 12 dp in front of the address, competing with the text it prefixed. The avatar circle above it was showing the host's first letter — which says nothing the name underneath doesn't already say, twice over on a row of home-worker-N boxes. Put the mark in the circle instead, at 24 dp in the avatar's own onPrimaryContainer tint, and drop it from the address line. A host that advertises no OS chain — or one we ship no mark for — keeps the initial, so those cards look exactly as they did and a mixed row still reads as one set. On glass: the two Arch hosts wear the Arch mark; steamdeck and the Windows runner, both predating the `os=` TXT, keep their letters. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
be2fabcfba |
feat(ci/windows-host): the console and the drivers stop rebuilding what nobody changed
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 9s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 11s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 12s
android / android (push) Canceled after 1m24s
ci / rust (push) Canceled after 1m25s
ci / rust-arm64 (push) Canceled after 1m19s
ci / web (push) Canceled after 1m19s
ci / docs-site (push) Canceled after 1m18s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 44s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
arch / build-publish (push) Canceled after 4m30s
windows-host / package (push) Canceled after 4m34s
windows-host / winget-source (push) Canceled after 0s
The job's two cache-shaped tails, now actually cached (the runner just got wired to the central cache server — it had none): web/.output restores and skips the ~2.5 min bun build+smoke whenever web/ and sdk/ are untouched, and the UMDF drivers' in-tree target/ (which checkout's clean wiped every run because wdk-build can't relocate it) restores so cargo's fingerprints declare it fresh. Typical run: ~13.6 min -> ~10. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6a6be17ce7 |
fix(client/android): a session that receives no video at all now asks, and says so
The keyframe backstop added for the black LG TV only arms while AUs are actually going into the decoder (`fed > fed_at_output`) — deliberately, so an idle stream never asks for anything. That leaves its mirror image uncovered: a session that receives NOTHING. A decoder cannot be starved of output when it was handed no input, so no signal in either loop fires, and the session sits connected — audio, input and the control plane all alive — behind a black surface. That state is what a user just reported as "the stats are all basically 0": fps and Mb/s are counted at AU receipt (`note_received`), so all-zero stats with a drawn overlay means the decode thread started and received nothing. Same bug as the black screen, seen from the HUD. Both loops now watch for it: nothing received 1.5 s into a session ⇒ request a keyframe and log it, re-asking every 2 s while it lasts. Where it can help it does — the host encoding fine while every picture references an IDR this client never saw is precisely a keyframe request away. Where it can't, the log line is the point: "no video received N ms into the session" separates "the host never sent a picture" from "we received AUs and lost them", which no previous black-screen report could distinguish. Not a root cause. The remaining occurrences are still unattributed — this makes the next report diagnosable and recovers the case that is ours to recover. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
bb7baef20b |
fix(client/android): the menus keep their safe area after a stream
Returning from a stream left every menu laid out against the wrong safe area: content shoved right, the profile row sliding under the status bar, the tab labels crowding the gesture pill. Dumped on the reporter's phone, the window's real insets were bars=[0,162,0,72] cutout=[0,162,0,0] while the layout was using the landscape immersive set — cutout left=162 (Material3 lays out against systemBars.union(displayCutout)), bars all zero. No rotation and no IME animation could shake it loose. Compose attaches its OnApplyWindowInsets and WindowInsetsAnimation callbacks when the first composable reads an inset and removes them when the last reader goes away (WindowInsetsHolder.increment / decrementAccessors). StreamScreen reads no insets at all, so a stream drops that count to zero for its whole duration. Survivable on its own — but a session that ends while the app is BACKGROUNDED is the common case (leaving the app ends the session), and then the entire window restore runs on a stopped activity. The corrected insets arrive while Compose has no listener attached; when the menus recompose, incrementAccessors re-attaches and asks for a fresh pass, but a stopped window produces no dispatch and on resume nothing has changed any more, so none ever comes. Compose keeps serving the landscape, bars-hidden values for the rest of the process. Hold one inset reader at the root for the activity's whole life, so the listeners survive the stream and every dispatch lands. It subscribes to no inset VALUE, only the holder object, so it costs one DisposableEffect and no recomposition. Verified on the reporter's device by replaying the real teardown sequence (forced landscape + immersive, composition swapped to a screen that reads no insets, torn down while stopped) and reading the layout back through uiautomator: 3 of 4 runs wrong without this, 4 of 4 correct with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
91def82219 |
fix(client/android): the menus keep their safe area after a stream
Returning from a stream left every menu laid out against the wrong safe area: content shoved right by the landscape side inset, the profile row sliding under the status bar, the tab labels crowding the gesture pill. Compose attaches its OnApplyWindowInsets AND WindowInsetsAnimation callbacks when the first composable reads an inset, and tears both down when the last reader goes away (WindowInsetsHolder.increment / decrementAccessors). The immersive stream reads no insets at all — it is a bare full-screen surface — so entering one dropped the reader count to zero right in the middle of the hide(systemBars()) animation StreamScreen had just started. With the animation callback gone, that animation's onEnd never arrived, so the listener kept runningAnimation = true for the rest of the process, and from then on every onApplyWindowInsets was swallowed (it defers to an onProgress that can no longer come). The values froze at the last animation frame — landscape, bars hidden — and that is what the menus got when they came back. Hold one inset reader at the root for the activity's whole life: the listeners now survive the stream, the landscape lock and the bar animations, and every animation gets its onEnd. It subscribes to no inset VALUE, so it costs nothing per frame. As a backstop, request a fresh insets pass from onConfigurationChanged — the activity declares configChanges=orientation|screenSize, so a rotation re-lays out in place and a dropped dispatch would otherwise go unnoticed until the layout is already wrong on screen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
1a18ae1fae |
fix(packaging/bazzite): the feed publisher signed a redirect page, not the manifest
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 17s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 8s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 8s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 6s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 12s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
docker / builders-arm64cross (push) Successful in 4s
docker / deploy-docs (push) Successful in 25s
ci / web (push) Successful in 2m46s
ci / docs-site (push) Successful in 2m45s
ci / rust-arm64 (push) Successful in 11m54s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 17m4s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 17m12s
ci / rust (push) Canceled after 18m14s
Every Bazzite install on the stable channel has been refusing the feed: !! the feed's SHA256SUMS is NOT signed by packages@unom.io (AF245C506F4E4763). The client was right and the feed was wrong. The registry answers a file GET with a 303 See Other pointing at presigned object storage, and `curl -f` does not treat a 3xx as an error — so the publisher's two un-`-L`'d manifest reads "succeeded" holding the redirect's HTML body, `<a href="…">See Other</a>.`, and handed that to callers as the manifest. That broke both of them: * --seal signed the HTML page. Its presigned URL is regenerated per request and expires 300s later, so the published .asc covers bytes that exist nowhere and can never verify. Stable feeds only publish on a tag, so they are only ever sealed — f43 and f44 were re-broken at 19:54Z on 2026-07-29 by the seal step of a canary run, and every canary push re-broke them. f43-canary survived by accident: on the publish path the same polluted bytes get both signed and uploaded, so it is at least self-consistent. * the publish merge read took the page for the previous manifest, and `grep -v " $FNAME$"` kept it — so each publish prepended a stale redirect page and dropped every prior image line. f43 runs KEEP=0 (keep all) and holds exactly one line, with 0.20.0 and 0.19.2 still in the registry and no longer listed. This has been quietly eating feed history since long before signing existed; nobody noticed because the client's latest() only matches ^punktfunk-.*-x86-64\.raw$, so an HTML line is invisible to it. Both reads now go through one read_manifest that follows redirects AND keeps only well-formed "<sha256> <filename>" lines, so a manifest is never again whatever the transport happened to return. --seal re-publishes a manifest that normalizing changed, since the signature has to cover the bytes a client downloads — that is what repairs the live feeds — and refuses outright when a manifest lists no images, rather than sealing an empty feed that would read as "up to date" to `punktfunk-sysext update`. The publish path now logs its carry-over count; silence there is what let the history loss run for months. Verified end to end against a fake registry that 303s to a fresh URL per request, with a throwaway key and the real scripts: HEAD reproduces the injected page, the lost image line and the client's VERIFY-FAIL; the fix keeps both images and verifies; and the new --seal over the broken state normalizes the manifest and turns it back to VERIFY-OK. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2e6cd0e235 |
feat(capture): stall attribution phases A.2+A.3 — micro-probes and DxgKrnl ETW name the class
ci / web (push) Successful in 2m30s
ci / docs-site (push) Successful in 2m49s
apple / swift (push) Successful in 4m36s
ci / rust-arm64 (push) Successful in 10m10s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 6s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 5s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 4s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 5s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 43s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 9s
android / android (push) Successful in 12m44s
deb / build-publish (push) Successful in 11m34s
docker / builders-arm64cross (push) Successful in 23s
windows-drivers / probe-and-proto (push) Successful in 29s
docker / deploy-docs (push) Successful in 30s
arch / build-publish (push) Successful in 15m48s
ci / rust (push) Successful in 16m21s
windows-drivers / driver-build (push) Successful in 1m46s
deb / build-publish-client-arm64 (push) Successful in 9m22s
deb / build-publish-host (push) Successful in 19m7s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 12m34s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 11m40s
apple / screenshots (push) Successful in 20m22s
windows-host / package (push) Successful in 14m39s
windows-host / winget-source (push) Skipped
Phase A.2: a refcounted micro-probe engine (fence round-trip per adapter, DwmGetCompositionTimingInfo tick, watchdogged DwmFlush, Level-Zero D3DKMTGetScanLine, CPU jitter sentinel) samples continuously on detached sacrificial threads; each stall report reads the window back and the verdict matrix folds it with the driver telemetry into a named class: ours-worker / ours-delivery / CLASS-1 adapter freeze / CLASS-2 compositor blocked / frame-generation / unattributed. The metronomic WARN carries the per-class session tally. Phase A.3: an event-id-filtered real-time ETW session on Microsoft-Windows-DxgKrnl (QueryChildStatus 150/151, SetPowerState 154/155, IndicateChildStatus 272, SetTimingsFromVidPn 430, DisplayDetectControl 1096/1097) rides every stall line as a DDI bracket summary — naming the servicing call and its duration instead of 'below Windows'. Degrades to etw=unavailable without admin; probes degrade per-leg (absence is stated, never guessed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b2168dae6a |
feat(vdisplay): stall attribution v1 — the driver testifies which leg lost the frames
Every IDD-push capture stall now carries a VERDICT instead of a hypothesis list. The shared ring header grows a v2 telemetry tail (drain-loop heartbeat QPC, last-acquire QPC, full-width offered counter) the driver stamps on every drain pass; the host samples it between fresh frames and attributes each stall: worker-stalled (our thread starved) / compose-silence (DWM composed nothing — the disturbance is below capture) / delivery-leg (frames existed, our publish/ring/consume lost them). The metronomic WARN prints the running tally, so one pasted log line settles the Branch-1/Branch-2 fork of the vdisplay-disturbance-immunity program per session, per box. Both directions stay version-safe: the tail is gated on the HOST-stamped header version (a v2 driver never writes past a v1 host's 64-byte layout — it maps the whole section instead of a fixed 88 bytes), and a v2 host reads a zero heartbeat as pre-telemetry driver, no verdict. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
75e4f00e50 |
Merge branch 'chore/windows-rerender-semantics' into main
ci / docs-site (push) Failing after 31s
ci / web (push) Successful in 54s
apple / swift (push) Successful in 1m17s
ci / rust-arm64 (push) Failing after 7m25s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 58s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 16s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 8s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 7s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 49s
deb / build-publish-client-arm64 (push) Successful in 9m13s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 1m7s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m27s
deb / build-publish (push) Successful in 15m0s
android / android (push) Canceled after 15m44s
apple / screenshots (push) Canceled after 0s
arch / build-publish (push) Canceled after 15m46s
ci / rust (push) Canceled after 15m45s
deb / build-publish-host (push) Canceled after 15m12s
docker / builders-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 4m11s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 44s
windows-host / package (push) Canceled after 15m35s
windows-host / winget-source (push) Canceled after 0s
release / apple (push) Successful in 17m52s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m29s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m12s
flatpak / build-publish (push) Failing after 12m18s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 2m59s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 4m12s
Windows 11 tray theming + per-connect device-name announcement, and the pairing approve button no longer escapes the canvas on portrait phones. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a86b4c18ee |
feat(ci): arch gets its builder image too — the last per-run gigabyte, and sccache through makepkg
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 5s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 4s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 8s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 12s
ci / docs-site (push) Successful in 1m0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 1m3s
docker / deploy-docs (push) Successful in 30s
docker / builders-arm64cross (push) Successful in 17s
ci / web (push) Successful in 3m1s
ci / rust-arm64 (push) Successful in 10m7s
ci / rust (push) Successful in 13m56s
arch / build-publish (push) Successful in 12m30s
The arch job was the one still paying full freight every run: ~1 GB of pacman across its two install steps (never cached — container layers die with the job) and cold cargo builds (arch was skipped in the sccache rollout). punktfunk-arch-ci bakes base-devel + both makepkg legs' deps + bun + node + sccache; the in-job installs become --needed no-op guards for the one push where :latest lags. Rolling-release note in the Dockerfile: packages now build against the image's snapshot, the same staleness the gamescope cache already embraces, re-snapshotted on any ci/ edit. sccache reaches makepkg by crossing the sudo boundary explicitly — env_reset strips ambient env, so the wrapper env rides the existing `sudo -u builder env ...` list. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
43a9cf741b |
fix(client/android): the build resolves cargo from CARGO_HOME before guessing ~/.cargo
ci / web (push) Successful in 2m3s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 1m3s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 11s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
ci / docs-site (push) Successful in 3m27s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 24s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 21s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 31s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 34s
docker / builders-arm64cross (push) Successful in 15s
docker / deploy-docs (push) Successful in 49s
ci / rust-arm64 (push) Successful in 11m57s
android / android (push) Successful in 15m54s
ci / rust (push) Successful in 18m7s
The kts resolves cargo by ABSOLUTE path on purpose (a GUI Android Studio launch has no ~/.cargo/bin on PATH), but user.home is the wrong anchor in the CI image, where the shared toolchain lives at CARGO_HOME=/usr/local/cargo — gradle died starting /root/.cargo/bin/cargo. CARGO_HOME/bin is where rustup puts binaries whenever the variable is set, so it wins; the ~/.cargo fallback keeps GUI launches working. Also: android.yml's path filter learns ci/android-ci.Dockerfile — an image change must exercise its consumer instead of needing a manual rerun to prove itself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
12b0ce9b2d |
fix(ci): the android image learns node, and msix finally builds the CLI it packs
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 8s
ci / docs-site (push) Successful in 1m6s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 4s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 1m26s
ci / web (push) Successful in 1m48s
ci / rust (push) Canceled after 2m12s
ci / rust-arm64 (push) Canceled after 2m12s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m8s
docker / builders-arm64cross (push) Successful in 1m33s
docker / deploy-docs (push) Successful in 1m30s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m13s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m36s
Two first-run discoveries. actions/checkout (and every JS action) execs `node` inside the job container — the android-ci image didn't ship it, so the job died at checkout with exit 127 (flatpak.yml documents the same lesson for fedora:43); a trailing layer keeps the fat SDK/NDK layers cache-valid. And windows-msix has been red since |
||
|
|
a420855b5a |
feat(tray): the Windows tray dresses for Windows 11 and announces connects
The menu was a stock Win32 popup: light-mode on a dark taskbar,
DPI-virtualized (blurry on every scaled laptop), no icons. Now the
process opts into the system dark mode via the uxtheme ordinals
(135/136 - the same undocumented calls Explorer/PowerToys/Notepad++
make; menus never got a documented opt-in, and there is no WinUI tray
API to move to), a PerMonitorV2 manifest makes menu and icon crisp, the
multi-size .ico serves the DPI-correct frame, and items carry Segoe
Fluent glyph bitmaps - with the UAC shield on the elevated service
actions, per Explorer's convention. Rounded corners come free on
Windows 11. Everything degrades gracefully: missing ordinals mean the
classic light menu, a missing icon font means no glyphs.
New: a connect toast on the idle-to-streaming edge - title "<device>
connected" (from the summary's new client_name), body the mode
("Streaming 2560x1440 @ 120 fps") - via NIF_INFO, which Windows 11
renders as a native toast. The tray tags itself with the AUMID
unom.punktfunk.tray and the installer registers it under
Classes\AppUserModelId (DisplayName "Punktfunk" + the brand icon), so
the toast is attributed to Punktfunk with the logo. Unregistered dev
runs degrade to generic attribution, older hosts to a nameless title;
a tray started mid-session never fires a stale toast.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
9b2404a580 |
feat(core): every connect introduces the device by name
The wire always had Hello.name and the host always honored it - but the connect path hardcoded None (only the PIN-pairing ceremony sent a name), so every no-PIN "request access" knock surfaced as the fingerprint placeholder "device abcd1234", and approving one without retyping a name persisted that placeholder into the trust store forever. NativeClient::connect now takes the device name. The session workers and the probe connects pass trust::device_name() (the hostname), the C ABI defaults to the same without a signature change (an ex10 variant can make it explicit if an embedder wants a custom label), and Android threads Build.MODEL through nativeConnect - the same convention its pairing dialogs already use for nativePair. The host, in turn, resolves the streaming client's display name (trust store first, so an approval-time rename wins; else the sanitized Hello name) and exposes it as client_name in GET /api/v1/local/summary for the tray's connect toast - a deliberate, documented loosening of that route's "no device names" contract, in the local user's favor: it tells them who is on their machine. A paired-but-idle device's name still never appears, which the mgmt tests now pin explicitly. openapi.json, its docs-site copy, and the SDK bindings regenerate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bf9d101be8 |
fix(web): the approve button stops hiding off-canvas on portrait phones
The pending-devices table's four p-card cells overflow a 360-430 px viewport, and the actions cell - Approve - ended up past the right edge of an overflow-auto wrapper inside the page's overflow-x-hidden column: the button existed but could not be seen or reached (landscape is wide enough, which is why it "worked" there). The name cell now flexes and truncates, and the fingerprint/age columns collapse into a sub-line under the name on narrow screens instead of widening the row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
171f08184f |
fix(ci/android): cargo-ndk v4 refuses direct invocation — probe it as the subcommand it is
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 8s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 4s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 6s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 42s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m45s
docker / builders-arm64cross (push) Successful in 30s
docker / deploy-docs (push) Successful in 38s
ci / rust (push) Canceled after 3m42s
ci / web (push) Successful in 2m29s
ci / docs-site (push) Successful in 2m45s
apple / swift (push) Successful in 6m46s
android / android (push) Failing after 3m54s
ci / rust-arm64 (push) Canceled after 6m44s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 19m39s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 20m35s
release / apple (push) Successful in 35m40s
apple / screenshots (push) Successful in 20m41s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
395daa8e98 |
fix(packaging/rpm): the CLI's %files entry had wandered into %install
|
||
|
|
9cda05dffb |
feat(ci): android gets a baked builder image; apple gets sccache and a pinned DerivedData
Android was the last job still downloading its world every run: ~3 GB of SDK/NDK/CMake from Google, a from-source cargo-ndk build, and one monolithic cache key where a gradle edit invalidated the cargo registry and the Rust target/ with it. punktfunk-android-ci (content-keyed, LAN registry) bakes JDK 21 + SDK + NDK + cargo-ndk + sccache; the workflow shrinks to checkout → two caches → gradle. The cargo-home cache joins the fleet-wide namespace (same lockfile, same layout — it was the same bytes under a private key), gradle gets its own key shared with android-screenshots (which stored the identical content under a second name), and target/ leaves the cache — sccache covers the three ABI builds now. Apple: sccache (self-healing ~/.local/bin install + already provisioned on the mini) covers every cargo invocation build-xcframework.sh makes across the swift job, the screenshots job and release.yml — three jobs that each recompiled the same core. screenshots.sh learns PF_SHOT_DERIVED_DATA so CI pins one stable DerivedData root instead of cold-building into two throwaway mktemp trees per run (release.yml's disease, same cure), and the Simulators get shut down after capture (the 846-leaked-sims lesson). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fcdb4d147d |
feat(ci): sccache everywhere a Rust job runs — one warm compile cache for the whole fleet
audit / bun-audit (plugin-kit) (push) Successful in 25s
audit / bun-audit (sdk) (push) Successful in 25s
audit / bun-audit (web) (push) Successful in 37s
audit / docs-site-audit (push) Successful in 36s
audit / pnpm-audit (push) Successful in 29s
audit / cargo-audit (push) Successful in 2m57s
decky / build-publish (push) Successful in 43s
deb / build-publish-client-arm64 (push) Failing after 1m9s
deb / build-publish-host (push) Failing after 1m16s
ci / rust-arm64 (push) Failing after 1m35s
ci / web (push) Successful in 1m2s
ci / docs-site (push) Successful in 1m11s
apple / swift (push) Successful in 5m32s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 2m3s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 2m53s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 3m30s
audit / license-gate (push) Successful in 7m50s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 2m38s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9m43s
flatpak / build-publish (push) Successful in 10m55s
android / android (push) Successful in 21m25s
windows-host / package (push) Successful in 20m6s
windows-host / winget-source (push) Skipped
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 13m57s
docker / deploy-docs (push) Successful in 57s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 13m36s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 9m32s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Failing after 3m20s
deb / build-publish (push) Successful in 16m37s
docker / builders-arm64cross (push) Successful in 2m27s
apple / screenshots (push) Successful in 21m3s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Failing after 3m12s
ci / rust (push) Canceled after 10m44s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m42s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 7m29s
arch / build-publish (push) Successful in 15m41s
Backend = the existing RustFS (storage.unom.io, S3, region home-central; LAN-pinned to home-central's address by ci-core's unbound so cache traffic never hairpins the router). Repo secrets SCCACHE_ACCESS_KEY_ID/SECRET carry a keypair scoped to the unom-ci-sccache bucket; keys embed compiler hash + target + flags, so the Ubuntu, Fedora, cross-arm64 and MSVC universes share one bucket without ever colliding. Wired: ci (rust, rust-arm64), deb (all three), rpm, bench, linux-client-screenshots, windows, windows-msix, windows-host. CARGO_INCREMENTAL=0 alongside (sccache and incremental are mutually exclusive, and incremental artifacts are what bloated the persistent Windows target dirs anyway). The binary is baked into the builder images; a per-job ensure-step (same pattern as the GTK4 packages step) keeps jobs green while the running :latest predates the bake, and ensure-windows-toolchain.ps1 self-provisions sccache.exe on the Windows runner. windows-drivers stays unwrapped (wdk-build owns its build env), arch/android/apple are follow-ups. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fcdb90c39b |
feat(ci): builder images move to the LAN registry, content-keyed; fan-out scoped by paths
The five builder images now live on home-ci-core's LAN registry (192.168.1.58:5010) under content keys — a hash of the ci/ tree (+ rust-toolchain.toml for the cross image). docker.yml builds one only when its key has no manifest yet, so a push that doesn't touch ci/ costs a curl per image instead of seven WAN pushes and a set of per-SHA tags that no plain prune could ever reclaim. Releases pin builders by copying the key manifest to a vX.Y.Z tag via the registry API — no rebuild, no bytes moved. Around that: deb/rpm/arch/android/apple/decky get path filters so docs-only pushes stop lighting up the whole fleet (branch pushes only — tag runs match tags:, as flatpak/windows-msix releases have proven for months); the report-only bench job moves to bench.yml (nightly + dispatch) and stops occupying a fleet slot per push; flatpak caches its Flathub runtimes and builder state instead of re-downloading multi-GB every run; rpm's cargo registry cache gets its own key namespace instead of sharing the Ubuntu jobs'; audit caches cargo bin+registry rather than the whole toolchain dir; docker-prune.sh loses the local act-cache cap/burst-clear (the cache is central now — deleting it under disk pressure was how runner-2 ended up cold-building everything) and gains a leaked-network prune. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
90c84ef4bc |
fix(packaging/deb): build the CLI the client .deb installs
ci / web (push) Successful in 1m4s
ci / docs-site (push) Successful in 1m12s
apple / swift (push) Successful in 5m23s
arch / build-publish (push) Failing after 5m59s
ci / bench (push) Failing after 6m2s
decky / build-publish (push) Successful in 41s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 30s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 39s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 17s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 23s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 18s
ci / rust-arm64 (push) Successful in 10m24s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Failing after 12s
docker / build-push-arm64cross (push) Skipped
docker / deploy-docs (push) Skipped
deb / build-publish-client-arm64 (push) Failing after 4m53s
android / android (push) Failing after 11m21s
deb / build-publish (push) Successful in 10m27s
deb / build-publish-host (push) Successful in 12m45s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 11m29s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 13m19s
ci / rust (push) Successful in 22m37s
apple / screenshots (push) Successful in 21m5s
With the client crates compiling again, deb got as far as "Build .debs" and died on `install: No such file or directory` — a confusing way to report that punktfunk-cli was never built. Two halves of the same omission, from when the `punktfunk` CLI was added. deb.yml pre-builds `-p punktfunk-client-linux -p punktfunk-client-session` and stops there, while build-client-deb.sh installs THREE binaries. Its build-if-missing guard tested only the first two, so the pre-built pair satisfied it, the fallback build (which does list punktfunk-cli) was skipped, and the install of a binary nobody had built failed. deb.yml now builds all three, and the guard tests every binary it goes on to install rather than a subset — either change alone fixes it; both together mean the script is correct however it is called. That asymmetry is also why the arm64 leg passed throughout: it pre-builds nothing, so its guard always fired and built all three. arch was already right (`-p punktfunk-cli` in its PKGBUILD, installed at line 255) and the rpm spec ships no CLI, so this was deb-only. Verified: guard truth-tabled (skip when all three present, BUILD when the CLI is missing, BUILD when nothing is), shellcheck clean, and punktfunk-cli builds to target/release/punktfunk on a real Linux box. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ab6d7727d3 |
fix(ci/runner): the disk guard has to fire before the disk is tight, not after
ci / web (push) Successful in 57s
ci / docs-site (push) Successful in 2m23s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 21s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 14s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 15s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 19s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 57s
ci / bench (push) Successful in 6m45s
ci / rust-arm64 (push) Successful in 10m32s
ci / rust (push) Failing after 10m39s
decky / build-publish (push) Successful in 38s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 22s
docker / build-push-arm64cross (push) Successful in 23s
android / android (push) Successful in 12m39s
docker / deploy-docs (push) Successful in 1m18s
apple / swift (push) Successful in 4m56s
arch / build-publish (push) Successful in 20m44s
apple / screenshots (push) Successful in 22m0s
deb / build-publish-host (push) Successful in 10m47s
deb / build-publish-client-arm64 (push) Successful in 7m53s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 7m27s
deb / build-publish (push) Failing after 6m6s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 11m40s
Measured over six hours after the last change: zero burst clears fired, while deb still died of ENOSPC between polls. Both 30 min and 10 min lost the same race — three concurrent Rust builds fill the disk and drain it again inside the interval, so every poll landed on a healthy df and the guard concluded all was well. Two changes, both aimed at that gap rather than at the symptom. The interval goes to 2 min: the script is a few docker calls and no-ops in about a second, so sampling five times more often costs nothing worth counting. And MIN_FREE_GB goes 45 -> 60, because the clear only reclaims idle images (~18 G measured) while three jobs can eat the remainder inside one interval — a guard that waits for "tight" has already lost. It has to act while there is still room to act in. This narrows the window; it does not close it. With a 43 G image baseline on a 172 G disk, three concurrent heavy jobs are working inside ~129 G and can exceed it. Sub-interval spikes are a polling problem, and the honest fixes are fewer concurrent replicas or more disk. Deployed to home-runner-1; deployed md5 matches the repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
7fc2775fad |
chore: the third-party notices catch up with the rebase
audit / bun-audit (sdk) (push) Successful in 20s
audit / bun-audit (plugin-kit) (push) Successful in 20s
audit / bun-audit (web) (push) Successful in 21s
audit / pnpm-audit (push) Successful in 9s
audit / docs-site-audit (push) Successful in 1m16s
audit / cargo-audit (push) Successful in 3m51s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Failing after 2m54s
ci / web (push) Successful in 3m3s
ci / docs-site (push) Successful in 1m51s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Failing after 1m55s
audit / license-gate (push) Successful in 7m7s
arch / build-publish (push) Failing after 7m56s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 2m47s
apple / swift (push) Successful in 5m44s
deb / build-publish (push) Failing after 5m39s
ci / rust-arm64 (push) Successful in 10m45s
ci / bench (push) Successful in 6m56s
android / android (push) Failing after 11m37s
decky / build-publish (push) Successful in 27s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 13s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 18s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 17s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 45s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 25s
deb / build-publish-client-arm64 (push) Successful in 8m57s
deb / build-publish-host (push) Successful in 11m13s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7m55s
docker / deploy-docs (push) Successful in 59s
flatpak / build-publish (push) Failing after 9m41s
windows-host / package (push) Successful in 13m44s
windows-host / winget-source (push) Skipped
ci / rust (push) Successful in 22m43s
apple / screenshots (push) Canceled after 0s
docker / build-push-arm64cross (push) Canceled after 4m51s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 12m5s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 11m37s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m39s
release / apple (push) Successful in 27m44s
Regenerated after rebasing the Windows-client series onto main: the union of the bumped windows-rs family and main''s os-icons vendored marks, with the conflict the generated file picked up mid-rebase resolved by regeneration — the only honest edit for a file the generator owns. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6edd7c3377 |
feat(client/windows): the tile menu grows real submenus
The flyout CAN nest at the current reactor rev (the "no submenus" comment predated the bump), so the per-profile families move into MenuFlyoutSubItems: one "Connect with" (bare profile names + Default settings as leaves) and one "Pin tiles" (verb-prefixed leaves). The top level stays a fixed handful whatever the catalog grows to. The backend wires submenu clicks recursively and reports LEAF text, so the connect-with leaves are matched as the fall-through arm against the catalog, and the pin leaves keep their prefix — that prefix is what tells the two families apart in the shared callback. UIA-verified through the nesting: Pin tiles -> Pin tile: Work persists and the pinned tile appears in the same gesture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a9d3ca2fb1 |
feat(client/windows): the sheets learn to be dismissed, and the profile sheet says Save
Escape closes every overlay — the profile sheet, the host editor, the add-host modal — and so does a tap on the scrim. The scrim-tap needed one trick: WinUI bubbles `Tapped` out of the card into the scrim (reactor cannot mark it handled), so the card raises a shared flag first and the scrim's handler swallows exactly that tap; only a genuine outside tap dismisses. The profile sheet's dismiss button reads Save (with the Save glyph) instead of Close — every field commits as you type, so Save is the promise the button already kept, and a sheet full of edits wants a verb. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9614e1a92e |
fix(client/windows): the switcher becomes what it pretended to be — one native control
The fused combo-plus-pencil was three controls squeezed into a wrapper, and hover gave the trick away (each kept its own radius and hover fill; the pencil clipped). The honest verdict: WinUI can build a real input group — CornerRadius is per-corner on every control — but windows-reactor exposes only a uniform radius on Border, so the fake was never going to hold. The native answer was next to it all along: the switcher is now ONE DropDownButton — its label is the scope in play, its menu holds Default settings, the profiles, "New profile…" and "Edit …" — with one hover state and no seams, and the pencil folded into the menu. Retired with the ComboBox: the items/selected_index remount hazard (a button label is one plain prop) and the scope-sentinel id. UIA-verified: menu -> Work applies the scope (the section repaints, markers included), menu -> Edit opens the sheet, Close closes it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6703be377f |
feat(client/windows): pinning moves to the tile menu, and the chrome quiets down
Pin/unpin lives in the tile's "…" menu now (review decision, reversing the editor-owns-it rule): one flat prefix-matched entry per profile — "Pin tile: X" / "Unpin tile: X" — beside Copy link and Create shortcut, the other tile-shaped actions. The write is paired with a new `hosts_rev` bump (the hosts-page mirror of `settings_rev`), so the pinned tile appears — or vanishes — in the same gesture instead of on the next discovery tick. UIA-verified: menu -> Pin tile: Work -> the pin is in the store and the second tile is on the grid immediately. The rest is the review list: * Both sheets (host editor, profile) put their content in a scroll_view — a window shorter than the card scrolls it instead of clipping the bottom controls. * The home header keeps ONE labelled action: Add host, in accent. Console/Shortcuts/ Settings drop to icons with tooltips — four written-out buttons read as four competing calls to action, and icon-only removes the compact-width special case too. * The profile switcher becomes one combined element: combo + pencil (icon only, the label is gone) inside a shared control-look wrapper — stock 4-epx radius, CardStroke outline, the pencil a borderless segment. In the defaults scope the bare combo stands alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8783f11e90 |
fix(client/windows): pinning works where you can see it, and the rows learn one skeleton
"Can't pin hosts with a certain profile" — live diagnosis found the pin switches WORKED
but sat below the fold: the host editor rendered as an in-grid tile, its grid cell parked
mid-page, and on an ordinary window the pin section's visible rect was a 9-px sliver a
mouse can't hit (UIA's pattern-toggle, which bypasses hit-testing, persisted fine — that
is what isolated it). The editor is now a centred sheet — scrim + card, titled with the
host, Save/Cancel right-aligned — in a stable overlay slot beside the add modal and the
forget dialog. Verified end-to-end with a real mouse click: toggle flips, the pin lands in
client-known-hosts.json, and the pinned tile is on the grid after Save. Pin toggles also
log now — this hunt was blind until they did.
The settings rows learn one skeleton, label / (marker) / input / caption, per review:
* The Overridden capsule ("Overridden │ Reset", one tinted pill, whole-pill tap target)
sits BETWEEN the row's label and its input, left-aligned — not trailing controls of
wildly different widths, not mixed into the caption.
* Labels move out of the widgets (`.header` is gone from the row builders) into the row
itself — a widget-embedded header allows nothing between label and box, and uniform
6-epx row spacing needs the row to own all four lines.
* The scope bar's combo and Edit button share an explicit 36-epx height, and the bar shows
the active profile's colour as a 12-epx chip beside the combo (dropdown items are plain
strings in this toolkit, so the chip cannot ride inside the list).
* The sheet's colour swatches size the disc itself, not its child — they rendered as
squashed ovals under the sheet's layout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
ba7667ee2b |
fix(client/windows): dialogs stop panicking the shell, and the chrome stops shouting
Deleting a profile reliably killed the app with E_BOUNDS ("Daten außerhalb des gültigen
Bereichs"): a ContentDialog is a PHANTOM child in the reactor backend — tracked logically,
never attached to the panel — and the reconciler unmounts a child before removing it, so
by the time `remove_child` runs the dialog's handle is gone, the backend no longer
recognises it as phantom, and it RemoveAt()s a visual child that never existed. (The third
upstream windows-reactor bug this client documents.) Both confirmation dialogs — delete
profile, forget host — are now ALWAYS MOUNTED with `is_open` doing the arming, and both
live in STABLE overlay slots (settings: [nav, sheet slot, dialog]; hosts: [page, add-modal
slot, dialog], each closed slot a same-kind background-less Border) so no pass ever
removes or repositions a dialog. UIA-verified: create profile -> delete -> confirm, the
shell survives.
The rest is the review feedback:
* A group with no fields renders NOTHING — Decoding and Library showed a heading over an
empty card in profile scope (device facts, never per profile).
* The override marker leaves the caption: a small "Overridden" chip and the Reset button
sit beside the control, bottom-aligned; the description below stays a description,
identical in both states.
* Host tiles stop wearing three badges: Paired is the resting state and earns no chip
(a chip is for a decision — Trusted/PIN/Open), and the bound profile becomes a small
dot in its accent colour plus the name in caption text.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
7bbee0eeb6 |
feat(client/windows): the profile selector settles above the settings surface
Out of the NavigationView for good. The pane footer clipped in the compact rail, the clip-aware monogram variant read as broken, and reactor offers no pane-opened event to adapt on — so the switcher is now a slim bar riding an Auto grid row above the nav: an "Editing" label, the scope combo, and Edit profile… (with its glyph), each vertically centred against the combo and sharing the content column's 24/28 page margins. Visible from every section at every width, in no one's way, and the nav below keeps its Star row so the surface still fills the window. The sheet drops its embedded scope combo — the bar owns the choice now — and opens only with a profile in scope. UIA-verified live: scope switch from the bar repaints in place (marker 5 -> 6), the sheet opens from the bar and closes, Default scope restores. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
69743e3a09 |
feat(client/windows): the profile surfaces get their icons
Reset carries Undo, Edit profile carries Edit, Duplicate/Delete/Close carry Copy, Delete and Accept — the icon support the bumped reactor added (#4736), applied where a glyph disambiguates faster than a word. The rest of the shell already had its icons. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e8eb803a3f |
feat(client/windows): 4:4:4 reaches Windows, and the Overridden marker stops moving the page
The GTK client grew its "Full chroma (4:4:4)" switch in the same commit that fixed its override marker; Windows got neither until now. The row lands in the Quality group under HDR with the GTK wording verbatim (same setting, same constraints), a full overridable profile row — the core overlay and the session binary already spoke `enable_444`, so the shell was the only gap. OverrideFlags grows the matching flag, and a unit test now pins the whole overlay -> row-flag mapping (tri-state resolution included): a field that records without marking its row is the original Overridden-row bug wearing a new face. And the marker obeys the GTK rule learned the hard way there: nothing that appears on an edit may move what was edited. Both states of a profile-scope row reserve the same caption-line height, so the marker + Reset materialise in place instead of shoving every row below them down by a button's height. Scope-blind audit (the bitrate-box failure class): clean. Decoder and GPU are hidden in profile scope as device facts (GTK parity), the forwarded controller stays gated, and every visible profile row commits through `commit`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
09d1691af4 |
fix(client/windows): the switcher footer survives the pane clip instead of guessing at it
Binding the footer's form to the WINDOW width was wrong twice over: the pane's open state belongs to the user (the hamburger), so a wide window with a closed pane still clipped the combo, and a narrow window with the pane opened still showed the compact disc. Reactor exposes no pane-opened/closed event — so the footer now needs no pane state at all: its LEADING 48 epx is the scope's monogram disc (profile accent when set) and the combo + Edit button sit after it, so the compact rail's clip line falls exactly between them. Closed pane → a clean disc (which opens the profile sheet); open pane → disc + combo. The same trick NavigationViewItems use, at every width and in every mode — the display mode goes back to Auto, the width threshold and the root window-size read are gone. The sheet is now self-sufficient (title "Profiles"): it always carries the scope combo — whether the pane's own combo is visible depends on state this page cannot observe — plus the name/colour/duplicate/delete rows whenever a profile is in scope, so the disc is a complete profile entry point from the bare rail. UIA-verified: scope pick from the pane combo, Overridden marker 5 -> 6 in place, the sheet opens from Edit profile… and closes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4b5bca93a0 |
fix(client/windows): the scope switcher stays in the nav at every width
The width-adaptive fallback moved the switcher OUT of the pane on narrow windows, and in the pane it could still clip — both read as broken. Now the nav is the switcher's only home: `Left` mode (always-expanded 280 pane, combo sized to fit) while the content column gets a workable width, and below that a `LeftCompact` rail whose footer swaps to a fitting compact form — a 40-square monogram disc for the scope (profile accent when set) instead of a clipped ComboBox. The disc opens the profile sheet, which in compact mode carries the scope combo too (keyed per scope against the items/selected_index diff hazard), so switching, creating, and editing profiles all stay reachable from the rail. The inline content-column fallback is gone. UIA-verified on the expanded path: scope pick from the pane, Overridden marker 5 -> 6 in place, sheet opens and closes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2409ec9a4b |
docs(client/windows): the harness passes where the real backend fails, and the note says so
Case 3 of the characterization suite (a sync use_state write from a backend-fired handler re-renders) is true of the ENGINE and was taken as license to de-hoist event-driven page state — and the live app immediately disproved it: with settings componentized and its scope local, a real ComboBox pick changed nothing on screen (UIA-verified, no repaint). The real WinUI backend's handler wiring still never pumps the pass the harness pumps. The de-hoist is reverted one commit back; this one makes the record honest. The module discipline note now separates the three tiers of evidence — engine rules (harness-pinned), real-backend behaviour (live-verified, hoisting stays), and the tween-keyed corollary — and case 3's comment warns it must not be read as de-hoist permission without a live UIA check. The upstream gap worth reporting grows to two: the AsyncSetState HostId registry miss, and the backend event wiring that bypasses the render pump. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
27039e04ff |
Revert "refactor(client/windows): page-only state climbs back down from root, on the measured rules"
This reverts commit 427f99a06647fd99e5c72d18cb7a710d8a7ac885. |
||
|
|
69c13b5aae |
fix(client/windows): the settings pane stops auto-collapsing under the default window
WinUI Auto mode collapses the NavigationView pane below 1008 epx — narrower than the app's own 1000-wide default window, so the scope switcher kept getting booted out of the nav into its inline fallback. The page now forces the mode: Left (always expanded, at a tighter 250 pane length) down to 720 epx, LeftMinimal below that — the switcher lives in the pane wherever the content column still gets a workable width. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6ab2ee4819 |
refactor(client/windows): page-only state climbs back down from root, on the measured rules
The root component had absorbed every page's UI state because of re-render folklore; the characterization suite (tests/reactor_semantics.rs) replaced the folklore with measured rules at the current windows-reactor pin: a child's sync use_state re-renders its owner reliably, including from backend-wired event handlers (MenuFlyout clicks, pointer enter/exit, ComboBox selection) and under element-equal wrappers. Only a background thread's AsyncSetState write is still dropped. So user-event-driven page state moves into the pages that own it: * hosts_page: forget, rename and hover become local sync use_state; the HostsProps fields, their PartialEq arms and the root plumbing are gone. * settings_page: converted from a hook-free 14-parameter fn into a real component(...) with a SettingsProps (data-only PartialEq, the HostsProps pattern). Scope, the delete confirmation, the edit-modal flag and the repaint revision are its own sync use_state; commit() and the row helpers take the sync setter now. What deliberately stays rooted, each with its reason in place: show_add and the settings section drive root tween workers (a thread writing root async state can only be started by the owner of the trigger), the tween progress values are those workers' output, and everything thread-fed (discovery, HUD, probes, pads, deep links, speed, library) keeps the async-drop rule. window size stays a root read so resize re-renders the tree. One deliberate behaviour shift: page-local state now resets when its page unmounts, so re-entering Settings lands on the Default settings scope (the section still persists, being root state). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9907ce6229 |
test(client/windows): the re-render rules become measured, and the switcher survives a collapsed pane
Adopts windows-reactor's own headless harness (the `test` feature + the upstream repo's `test_reactor`/RecordingBackend crate, same pinned rev) and pins the three semantics this client's state placement depends on, in tests/reactor_semantics.rs: * A child's sync `use_state` re-renders it through element-equal wrappers — the pre-bump reconciler pruned at the wrapper; fixed upstream (`nested_state_rerender.rs`). * A sync write from a backend-fired event handler re-renders — the "MenuFlyout handlers bypass the flush" claim does not hold at the engine level on this rev. * A child-owned `AsyncSetState` write still does NOT re-render its owner — newly diagnosed: every component RenderCx draws a fresh HostId but rerender callbacks are registered only for the root, so the child's request is a registry miss, silently dropped; the value surfaces on the next unrelated pass. The test asserts the broken behaviour on purpose, so the bump that fixes it upstream turns the test red and the discipline can be relaxed. (Upstream's own async test installs the guard for the very cx it tests and cannot see this; worth reporting with this repro.) The stale discipline note in app/mod.rs — written against the June rev — is replaced by the measured rules, each citing its test. Net: per-screen EVENT-driven UI state may live in the screen's own component; THREAD-driven state stays hoisted at root, now for a proven reason. Riding along (field report from the live review): the scope switcher no longer clips when the NavigationView collapses. WinUI's Auto mode shrinks the pane below 1008 epx and CLIPS pane-footer content to the rail; reactor exposes no pane-opened event, so root now reads the window size and the switcher follows the same threshold — expanded pane → pane footer, collapsed → a slim row leading the content column (an Auto/Star grid row, not a vstack, so the scroll viewport survives). Test builds use their own CARGO_TARGET_DIR by convention: windows-reactor-setup stages resources.pri next to the test binary and a RUNNING dev client holds that file mapped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1c1b81133c |
fix(client/windows): the settings surface fills the window again
The redesign left the page rooted in a vstack, and a StackPanel hands its child the DESIRED height — the NavigationView was clipped in a short window and floated in a tall one. The nav, the Edit-profile scrim and the delete ContentDialog now share one grid, so every layer stretches with the window (the dialog is its own WinUI layer and only needs to be in the tree). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6b956a2bf4 |
fix(client/windows): the Overridden marker appears on edit, and profile chrome leaves the page
Committing a profile-scope edit wrote the catalog and nothing else — no reactor-observable
state changed, so no render pass ran, OverrideFlags was never recomputed, and the
"Overridden here" row only surfaced after some unrelated navigation. The mirror of the
Linux fix ("the override marker appears on touch"): `commit` now ends every save with a
`settings_rev` bump, threaded through `setting_combo`/`setting_toggle`, so the marker lands
in the same pass that recorded the override. The bitrate box goes through `commit` too —
it wrote the GLOBAL settings regardless of scope, which corrupted the defaults from inside
a profile and could never mark its row.
The profile chrome moves out of the content column while its wiring is open. The scope
switcher sits in the NavigationView's pane footer (`pane_footer`, new in the bumped
reactor) where it reads as navigation chrome — which layer am I editing — instead of a bar
of controls above every section. Rename, colour, duplicate and delete hide behind an
"Edit profile…" modal (the Add-host scrim-and-card pattern; ContentDialog still fits no
text box), and "New profile…" lands straight in that modal to be named. The footer combo
is keyed by scope + name list so a rename or delete remounts it past the documented
items/selected_index diff hazard; renames repaint the dropdown on modal Close, not per
keystroke.
The absorb-level semantics (record the touched field against the effective snapshot; a
value equal to the global is still a pin) were already covered by pf-client-core's
`absorb_records_the_touched_field_only`; render-level tests wait for the reactor test
harness. Verified live via UIA on the running shell: with the "Game" profile in scope,
changing Render scale grew the marker count 5 -> 6 in place with no navigation, the
override landed in client-profiles.json, the global settings file stayed byte-identical,
and the Edit modal opens and closes from the pane.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
35c61fee64 |
chore(client): the windows-rs pin moves a month forward, onto the SDK-metadata bindings
The July 2026 windows-rs brings a reconciler keyed-child-order fix (#4728), widget validation (#4727), a DPI collision fix (#4751), icon elements (#4736), multi-window support (#4730) and scroll virtualization (#4710) — the re-render fixes the Windows client has been working around at the architecture level. All three pinned deps (windows-reactor, windows, windows-reactor-setup) move together so windows-core stays unified across the swap-chain hand-off, and pf-client-core moves with them. The bulk of the diff is #4689: windows/windows-sys now generate straight from the Windows SDK, so the `Win32_*` namespace features became one feature per SDK header (winuser, dxgi, d3d11, …), the PascalCase namespace modules became header-named modules, struct-returning COM methods take explicit out-params and return HRESULT, Win32 functions return their raw BOOL/HANDLE instead of Result, and flag constants are plain integers. Both crates' Win32 code is rewritten to that shape; behaviour is unchanged on every path. Riding along, all already stale before the bump: the README and the three Windows workflows stop claiming windows-reactor's build.rs needs CARGO_WORKSPACE_DIR (that build.rs no longer exists — staging moved to windows-reactor-setup via OUT_DIR); the README layout section stops describing modules that moved into the session binary long ago and gains the manual smoke checklist; the notices generator learns the SPDX for crates that ship license files without a `license` field, which turns windows-reactor-setup's UNKNOWN into MIT OR Apache-2.0; and the crate records its real rust-version (1.96) instead of inheriting the workspace's 1.82. Verified: cargo check/clippy/fmt clean on punktfunk-client-windows, pf-client-core and punktfunk-client-session; both bins build; --discover finds the LAN hosts; the GUI shell comes up (WinAppSDK bootstrap intact under the new reactor-setup). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |