Release 0.8.4 — reconnect reliability and streaming polish over 0.8.3.
The headline fix: a reconnect to a saved host that had stopped advertising on
mDNS (but still had a learned Wake-on-LAN MAC) was routed into a wait-for-mDNS
wake flow instead of being dialed — so a host reached over a routed network
(Tailscale/VPN/another subnet) could never be reconnected, reading as "Offline"
with no connection attempt in the host log. All four full clients now dial
first and only fall into the visible "Waking…" wait when the dial actually
fails. On the host side, the same investigation surfaced Windows-only gaps: a
deliberate quit (⌘D) now tears the pf-vdisplay virtual monitor down immediately
(no 10 s linger churning the reconnect's REMOVE→ADD), while keep_alive=forever
(the gaming-rig preset) correctly outranks the quit on both platforms so a
pinned screen stays alive. Silent deaths are no longer invisible: a panic hook
tees panics into the web console's Logs, a Windows SEH filter names the faulting
module on a native GPU-driver crash, and a watchdog flags a wedged monitor
teardown.
Also in this release: the presenter switches to MAILBOX present, pushes HDR10
mastering metadata, and tags HDR honestly; DualSense rumble accepts the v2
vibration valid-flag and bounds a stale buzz; the core caps the QUIC datagram
send buffer so audio/rumble go latest-wins; the host folds a Steam Deck to a
DualSense on Windows (split-pad touch + pad clicks) and applies input motion on
arrival rather than after the 4 ms tick; Android gains a tiered stats HUD with
windowed lost/skipped/FEC counters; and Windows surfaces a Help/shortcuts
screen and exits fullscreen when the stream stops.
The [workspace.package] version (inherited by every crate via version.workspace)
is the release being cut; the 14 workspace entries in Cargo.lock and the
api/openapi.json info.version are refreshed to match (CI builds --locked).
Canary derives from the tag as minor+1 of the latest stable
(scripts/ci/pf-version.sh), so cutting 0.8.4 keeps canary at 0.9.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Release 0.8.3: the native Linux client is re-architected — the GTK4 shell + GL
presenter give way to a two-binary design (phases 0–6). A relm4/libadwaita
desktop shell (host discovery, pairing, library) now hands desktop connects to a
dedicated Vulkan session binary, punktfunk-session: an SDL3 window + ash
presenter carrying a Skia "console" UI — stats OSD, capture HUD, and a
gamepad-navigable coverflow library with --browse. The split lands the streaming
wins the GL path couldn't: VAAPI dmabuf → Vulkan zero-copy import + CSC (no GL
round-trip), Vulkan Video decode on the presenter's own device for NVIDIA
hardware decode (via a new pf-ffvk bindgen shim over FFmpeg's Vulkan hwcontext),
and HDR10/P010 end to end. The console background is now an animated mesh gradient
matching the Apple client's MeshGradient. Along the way: a resize crash fixed
(never vkDeviceWaitIdle while the pump decodes), keyframe recovery when the
decoder produces no output, decode-stage stats that measure GPU completion, an
Alt+Enter fullscreen alias, pointer-lock + per-iteration motion-flush input
fixes, and the host-card hover highlight made concentric.
Packaging + Decky: the flatpak now builds and ships BOTH client binaries (shell +
session) so the Deck's stream/browse paths survive the split — the session
binary's Skia is fetched offline from a pinned, sha256-verified archive; the
mgmt/library port 47990 is opened on the LAN firewall profiles; and the plugin's
install-from-URL uses the unom.io/pf-decky short link.
Other clients + core: Windows gains the two missing stream shortcuts
(Ctrl+Alt+Shift+S stats, F11 fullscreen) plus an in-stream shortcut reference on
both clients; Android promotes the low-latency pipeline to default and attaches
its APK to CI; the host coalesces keyframe-request storms into one IDR per
cooldown; the core jumps to live on a standing receive backlog instead of
ratcheting latency; and two security bumps clear the audits (crossbeam-epoch
RUSTSEC-2026-0204, vulnerable web transitive deps).
The [workspace.package] version (inherited by every crate via version.workspace)
is the release being cut; refresh the 14 workspace entries in Cargo.lock to match
(CI builds --locked). Canary derives from the tag as minor+1 of the latest stable
(scripts/ci/pf-version.sh), so with 0.8.3 the newest stable it stays at 0.9.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ffmpeg-sys-next binds a curated header list that omits every
hwcontext_*.h, so AVVulkanDeviceContext/AVVulkanFramesContext/AVVkFrame
— the surface that lets FFmpeg's Vulkan Video decoder run on the
presenter's own VkDevice and hand the decoded VkImages back — had no
Rust bindings at all. pf-ffvk generates them at build time from the
SYSTEM headers, which is the only ABI-safe option: the struct layout
depends on FF_API_* deprecation gates resolved in libavutil/version.h
(confirmed: FFmpeg 8.1 still carries the FIXED_QUEUES fields). Ships
ash<->vulkan.h handle conversions; opaque AVHW*/AVFrame types keep
ffmpeg-sys-next the owner of the core surface (pointer casts across).
Needs vulkan-headers (Arch) / libvulkan-dev (CI, added);
PF_FFVK_VULKAN_INCLUDE overrides for cross builds. Verified: bindgen
layout tests pass, av_vk_frame_alloc links and zero-initializes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The §6.1 presenter↔console-UI contract lands: pf-presenter exposes its
device (SharedDevice) and composites at most one premultiplied-alpha
quad per frame (new overlay.frag + LOAD render pass over the swapchain;
zero cost while the overlay returns None). pf-console-ui implements it
with skia-safe on the shared VkDevice: DirectContext via the ash
dispatch chain, a ring of two offscreen render targets (one-frame-in-
flight safe), damage-driven redraws — the OSD re-renders at 1 Hz, the
hint on capture toggles, nothing per-frame. Skia never touches the
swapchain. The session binary carries it behind the default ui feature:
4.9 MB stripped without, 10 MB with (measured).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
punktfunk-session streams one --connect session in an SDL3 window: ash
swapchain with a transfer-only letterboxed blit of the software-decode
path (no graphics pipeline until the phase-2 dmabuf/CSC pass), the
ui_stream input-capture state machine on SDL events (scancode→VK table
cross-checked against the evdev one), gamepads via a new caller-pumped
GamepadService mode (SDL video owns the main thread here), and the
shell↔session stdout contract: {"ready":true}, per-window stats:
lines, JSON error + exit codes 0/2/3/4. Strict trust — no pin, no
connect. Design: punktfunk-planning linux-client-rearchitecture.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Session pump, FFmpeg decode, PipeWire audio, SDL3 gamepads, keymap, trust
store, mDNS discovery, library client and Wake-on-LAN move verbatim from
clients/linux into crates/pf-client-core, shared with the upcoming Vulkan
session binary (punktfunk-planning: linux-client-rearchitecture.md).
The GTK client re-exports them at the crate root so every existing
crate::-path keeps resolving; its manifest drops the moved-only deps.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Release 0.8.2: native AMF encoder reconnect reliability on Windows AMD — a
second connection no longer comes up black (the encoder now Flushes before
Terminate so a reconnect's overlapping teardown can't strand AMD's VCN encode
session, self-heals with a full context rebuild when a reconnect still wedges,
and logs a per-context bring-up number + first-AU line so a silent wedge is
visible); a native data-plane hardening pass that keeps the stream alive across
real Wi-Fi links; Android streaming wake/Wi-Fi locks that actually engage plus a
console-UI polish pass (per-controller glyphs, scrollable dialogs, animated
forms); and gamescope-takeover survival on Bazzite's SDDM session supervisor.
Also a Linux-client crash fix — the FlowBox activation cycle that stack-
overflowed on every host-card click.
The [workspace.package] version (inherited by every crate via version.workspace)
is the release being cut; refresh the 9 workspace entries in Cargo.lock to match
(CI builds --locked). Canary derives from the tag (scripts/ci/pf-version.sh), so
cutting v0.8.2 auto-advances canary to 0.9.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Release 0.8.1: game-mode + dedicated game sessions, zero-copy GPU import
process-isolation (and zero-copy on by default on all backends), user-defined
custom display presets, and a physical-monitor refresh-preservation fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The [workspace.package] version (inherited by every crate via version.workspace)
is the release being cut; refresh the 9 workspace entries in Cargo.lock to match
(CI builds --locked). Canary derives from the tag (scripts/ci/pf-version.sh), so
cutting v0.8.0 auto-advances canary to 0.9.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ship the flatpak PipeWire-socket audio fix (d1af630) to the stable channel —
a tag is required (main pushes only publish the canary flatpak branch), and
0.7.1 stable users on the Deck have no client audio until this lands. Bump
[workspace.package] + the 9 Cargo.lock workspace entries (CI builds --locked).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cut 0.7.1 to test the GTK + Decky polish batch (7eea983) on-device: the
host-click / disconnect-chord / gray-screen-recovery / leak fixes on the
Linux client, the Deck launcher perf profile, and the Decky pin/pairing
fixes. The [workspace.package] version (inherited by every crate via
version.workspace) is the release being cut; refresh the 9 workspace
entries in Cargo.lock to match (CI builds --locked). Canary derives from
the tag (scripts/ci/pf-version.sh), so cutting v0.7.1 auto-advances canary.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The [workspace.package] version (inherited by every crate via
version.workspace) is the release being cut. Refresh the 9 workspace entries
in Cargo.lock to match (CI builds --locked). Canary derives from the tag now
(scripts/ci/pf-version.sh), so no canary-base edit is needed — cutting v0.7.0
auto-advances canary to 0.8.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New crates/punktfunk-tray — a small per-user companion showing the host service
state at a glance (running / stopped / starting / degraded / failed + the live
session in the tooltip) with one-click actions: open web console, approve a
pending pairing request, start/stop/restart, open logs. No more digging through
logs to learn whether the service came back after a reboot or an update.
Status is service-manager-FIRST (SCM / systemd user unit — a port squatter can
never fake Running), then the new loopback-only unauthenticated
GET /api/v1/local/summary (counts/booleans only; the mgmt token and cert.pem
are SYSTEM/Admins-DACL'd on Windows, so a non-elevated tray cannot bearer-auth).
Windows: windows_subsystem binary (a console exe in the Run key would flash a
terminal at sign-in), Shell_NotifyIcon + hidden window, per-session single
instance, TaskbarCreated re-add, --quit for the uninstaller; service actions
elevate per click via ShellExecuteW "runas" onto the new
`punktfunk-host service restart` (stop → wait Stopped → start).
Linux: ksni/StatusNotifierItem over zbus, systemctl --user actions (no polkit),
/etc/xdg/autostart entry whose --autostart self-gates to actual host users.
Icons: scripts/gen-tray-icons.py (pure stdlib) renders the brand lens + status
dot into committed .ico/hicolor assets; deb/rpm/arch ship binary+autostart+icons.
Live-validated: Linux on the headless KDE session (SNI registration, state
transitions, menu-driven start, dbusmenu layout); Windows on the RTX box
(session-1 launch with no NIM_ADD failure, single instance, --quit, restart
round-trip, summary loopback-200/LAN-401).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The [workspace.package] version (inherited by every crate via version.workspace)
lagged at 0.4.1 — bump it to 0.4.2, the release being cut, and refresh the 8
workspace entries in Cargo.lock to match (CI builds --locked). This is a patch
release (Windows CI fixes + Apple gamepad UI); the canary base fallbacks stay
at 0.5.0, already one minor ahead of the 0.4.x stable line.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The [workspace.package] version (inherited by every crate via version.workspace)
lagged at 0.3.0 through the 0.4.0 release — bump it to 0.4.1, the release being
cut, and refresh the 8 workspace entries in Cargo.lock to match (CI builds
--locked).
Also advance the CI canary-base fallbacks (deb/rpm/flatpak/android/release
workflows + build-rpm.sh) from 0.3.0 to 0.5.0 so main/canary builds sort one
minor ahead of the latest stable line, per the documented channel convention.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Steam Deck pass-through (design/steam-deck-passthrough-plan.md), code-complete +
all CI checks green on Linux + adversarially reviewed; on-glass validation pending:
- usbip/`vhci_hcd` virtual Deck transport (inject/linux/steam_usbip.rs) for
non-SteamOS hosts (Bazzite/generic) — presents a real interface-2 USB Deck so
Steam Input promotes it. In-process vhci attach (loopback OP_REQ_IMPORT handshake
→ sysfs attach) with a bounded `usbip`-CLI fallback; detach on drop.
- Backed by a vendored, libusb-free trim of the `usbip` crate
(crates/punktfunk-host/vendor/usbip-sim, MIT + NOTICE; host/cdc/hid + rusb/nusb
removed; interrupt-IN paced by bInterval).
- Selection ladder raw_gadget (SteamOS fast-path) → usbip (universal) → UHID,
with PUNKTFUNK_STEAM_USBIP / PUNKTFUNK_USBIP_ATTACH knobs.
- Shared Deck descriptors + the 0x83/0xAE feature contract + a Steam-accepted
serial consolidated into steam_proto.rs; the raw_gadget backend reuses them.
- Linux client leave-shortcuts: Ctrl+Alt+Shift+D + holding the escape chord
(L1+R1+Start+Select) >=1.5s end the session (short press still exits
fullscreen); the chord state resets across sessions.
Also bundles in-progress work already staged in the tree:
- host(kwin): xdg-output logical-geometry mapping so the KWin fake_input backend
places absolute coordinates correctly under display scaling.
- docs: design/README index entries + design/controller-only-mode.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The shared host<->driver ABI crate already contains more than the virtual
display: the IDD-push frame ring + control plane AND the gamepad shared-memory
layouts (XusbShm / PadShm). "pf-vdisplay-proto" was a misnomer — the name now
represents all the drivers it serves.
Mechanical rename, no behavior change:
- git mv crates/pf-vdisplay-proto -> crates/pf-driver-proto (package name +
path-deps in the host crate and the driver workspace).
- pf_vdisplay_proto -> pf_driver_proto across host + driver Rust, both Cargo.lock
files, the workspace members, the CI path triggers (windows-drivers.yml), and
the docs/INF comments. The runtime Global\pfvd-* shared-object names are a
SEPARATE contract and are deliberately untouched (host<->driver name matching).
- The pf-vdisplay DRIVER crate + its INF service name (Root\pf_vdisplay,
UmdfService=pf_vdisplay, pf_vdisplay.dll) are unchanged — only the full
`pf_vdisplay_proto` token was replaced, never the `pf_vdisplay` driver name.
Linux-verified: cargo test -p pf-driver-proto (const size-asserts compile) +
cargo clippy -p punktfunk-host -D warnings clean; Cargo.lock regenerated. The
driver-workspace side (path-dep + imports + its Cargo.lock) is Windows-CI-gated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First foundation of the Windows-host rewrite (docs/windows-host-rewrite.md): a
self-contained, no_std + bytemuck crate that defines the host<->driver binary
contract ONCE — the control-plane IOCTLs (add/remove/set-render-adapter/ping/
get-info/clear-all) and the IDD-push frame transport (SharedHeader, the
(gen<<40|seq<<8|slot) FrameToken, the Global\pfvd-* name scheme, driver-status
codes). Previously these were hand-duplicated byte-for-byte across
idd_push.rs/frame_transport.rs and sudovda.rs/control.rs with only "must match"
comments; here const size-asserts + bytemuck round-trips make any drift a COMPILE
error.
Clean break from SudoVDA: a freshly-minted interface GUID (not e5bcc234), a
contiguous 0x900 op space (not the gappy 0x800/0x888/0x8FF), a u64 session id (not
the 16-byte GUID + pid-mangling), a single u32 protocol version. Self-contained
(no workspace inheritance, no Windows deps) so the out-of-workspace driver build
graph can path-dep it identically. 7 tests green on Linux; clippy + fmt clean.
Also lands the full rewrite plan in docs/windows-host-rewrite.md (decisions:
greenfield; IDD-push primary incl. secure desktop, WGC+DDA demoted to fallbacks;
unify drivers on windows-drivers-rs + solve /INTEGRITYCHECK; keep GameStream,
default secure).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two bodies of work in one commit (the rename moved files the fixes also touched).
Naming/structure cleanup (pre-launch):
- Host modules m3.rs->punktfunk1.rs, m0.rs->spike.rs; CLI m3-host->punktfunk1-host,
m0->spike; bare `punktfunk-host` now prints help. Types M3Options/M3Source->
Punktfunk1Options/Punktfunk1Source.
- Clients consolidated out of crates/ into clients/: punktfunk-client-rs->
clients/probe (crate punktfunk-probe), client-linux->clients/linux,
client-windows->clients/windows, punktfunk-android->clients/android/native
(crate punktfunk-client-android; kept [lib] name=punktfunk_android so the JNI
contract is unchanged). crates/ now holds only core + host.
- Milestone codes M0-M4 purged from code/CLI/CLAUDE.md/README/docs/docs-site,
kept only in docs/implementation-plan.md. docs/m2-plan.md->
docs/gamestream-host-plan.md. CI/gradle/flatpak paths updated.
Client loss-recovery (video froze and never recovered after a brief drop):
- Export punktfunk_connection_frames_dropped through the C ABI (the core already
tracked it for the client keyframe-recovery loop; it was never reachable from
the ABI clients). Regenerated punktfunk_core.h.
- Apple (StreamPump + Stage2Pipeline) and Android (decode.rs) now poll
frames_dropped and request a keyframe when it climbs -- the same loss-driven
recovery Linux/Windows already had. Under infinite GOP the decoder silently
conceals reference-missing frames, so the decode-error trigger rarely fires.
Apple rumble robustness (worked then went spotty -- DualSense + Xbox):
- Add CHHapticEngine stopped/reset handlers (rebuild on app background / audio
interruption / server reset) and drop the permanent `broken` latch on a
transient drive failure; latch only when the controller truly has no haptics.
- Surface swallowed SDL set_rumble errors on Linux/Windows + diagnostic logging.
Verified: cargo build/clippy/fmt --workspace, C-ABI harness, header drift.
Not runnable on this box (verify in CI): Gitea workflows, gradle/Android,
flatpak, Swift/decky.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "broken animations in HDR" was an encode-throughput cliff, not the ACCESS_LOST churn. Measured at
5120x1440@240 HEVC Main10 on the RTX 4090: forced 2-way split-encode = 7.6 ms/frame (~131 fps, well
over the 4.17 ms/240fps budget → choppy), while SINGLE engine = 2.8-3.9 ms/frame (~256-357 fps, fits
240). The split/merge overhead dominates for 10-bit; a single Ada NVENC engine already handles 5K@240
Main10 comfortably. So the split decision now forces DISABLE for Main10 (bit_depth >= 10), keeping the
existing forced-2 only for 8-bit above 1 Gpix/s. PUNKTFUNK_SPLIT_ENCODE still overrides. Added a
split-mode log line.
Validated live on the 4090: encode_us_p50 7.6 ms → 3.9 ms at 5K240 HDR with no env override.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rust-heavy client model (like punktfunk-client-linux): a new cdylib crate
crates/punktfunk-android links punktfunk-core and exposes the JNI seam;
Kotlin (clients/android) owns only the Android-framework surface. Kotlin can't
import the C header the way Swift can, so the bridge is written in Rust to reuse
the Linux client's orchestration rather than re-port it.
- crates/punktfunk-android: JNI bridge — abiVersion/coreVersion native-link
proof + session connect/close handle; plane pumps stubbed for M4 stage 1.
- clients/android: Gradle project — :app (Compose) + :kit (Android library with
a cargo-ndk Exec task -> jniLibs). AGP 9.2 / Gradle 9.4.1 / Kotlin 2.3.21 /
Compose BOM 2026.05.01 / compileSdk 37 / targetSdk 36 / minSdk 31, shipping
arm64-v8a + x86_64. Phone + TV (leanback) installable. README rewritten.
- .gitea/workflows/android.yml: CI mirroring apple.yml on a Linux runner.
- punktfunk-core: switch rcgen to the ring backend so the whole quic tree is
aws-lc-free (smaller client .so, cmake-free cross-compile; a win for all targets).
Validated on this box: :app:assembleDebug -> APK with both ABIs; emulator
first-light renders the bridge linked (core ABI v2) with logcat confirmation;
clippy -D warnings + cargo fmt clean; core tests green on the ring backend.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New crate crates/punktfunk-client-linux (binary punktfunk-client), the
native Linux client on the Option A architecture (2026-06-12 research):
- GTK4/libadwaita shell linking punktfunk-core directly (no C ABI):
mDNS host list, TOFU fingerprint prompt, SPAKE2 PIN pairing dialog,
preferences (mode/bitrate/gamepad/shortcut capture), stats overlay,
--connect host[:port] for scripting.
- Video: FFmpeg software HEVC decode (LOW_DELAY, slice threads) ->
RGBA -> GdkMemoryTexture inside GtkGraphicsOffload (the dmabuf
subsurface path lights up when VAAPI lands; black-background keeps
fullscreen scanout-eligible).
- Audio: Opus -> PipeWire playback stream, the host virtual-mic's
adaptive jitter ring inverted.
- Input: keyboard as the exact inverse of the host VK table (evdev
keycodes, layout-independent; unit-tested), absolute mouse through
the Contain-fit transform, WHEEL_DELTA(120) scroll, compositor
shortcut inhibition while streaming, Ctrl+Alt+Shift+Q release chord,
F11 fullscreen. SDL3 gamepad capture (single pad-0 model) + rumble
and DualSense lightbar feedback on the same thread.
- Session pump owns video+audio pulls; the gamepad thread owns
rumble+hidout — possible because NativeClient's plane receivers are
now mutexed, making it Sync (Arc-shared, compiler-verified per-plane
contract instead of the ABI's manual assertion).
- Linux-gated deps + a stub main keep cargo build --workspace green on
macOS.
Validated live against serve --native on this box: 1920x1080@60,
locked 60 fps, capture->decoded p50 ~6.4 ms (software decode, debug
build). Teardown keys off AdwNavigationPage::hidden — NavigationView
push fires a transient unmap/map cycle that must not end the session.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Full project rename, decided 2026-06-10:
- Crates/binaries: punktfunk-core / punktfunk-host / punktfunk-client-rs.
- C ABI: punktfunk_* symbols, Punktfunk* types, include/punktfunk_core.h,
PUNKTFUNK_FEATURE_QUIC guard (header regenerated; cbindgen renames updated, incl.
PUNKTFUNK_BTN_*/PUNKTFUNK_AXIS_* wire constants).
- Protocol: punktfunk/1 — control-plane magic LMN1 → PKF1, nonce salt lmn1 → pkf1.
WIRE BREAK: clients must be rebuilt from this revision.
- Env knobs: PUNKTFUNK_VIDEO_SOURCE / PUNKTFUNK_COMPOSITOR / PUNKTFUNK_ZEROCOPY / ….
- Host config dir: ~/.config/punktfunk (the box's dir was migrated in place — the
persistent identity is unchanged, pinned fingerprints stay valid).
- Swift package: PunktfunkKit + PunktfunkCore.xcframework + PunktfunkConnection
(Sources/PunktfunkClient app + tests renamed with it); build-xcframework.sh updated.
- scripts/: 60-punktfunk.rules, punktfunk-host.service; OpenAPI doc regenerated.
Also: scripts/headless/run-headless-kde.sh — full headless Plasma bringup. Root cause of
"desktop but no apps/settings" over the stream: plasmashell launched without
XDG_MENU_PREFIX=plasma-, so the launcher resolved a nonexistent applications.menu and
rendered an empty menu. The script sets the complete KDE session env (menu prefix,
KDE_FULL_SESSION, session version) and rebuilds ksycoca before starting plasmashell.
Gate: 97/97 tests, clippy -D warnings (both feature sets), fmt, C-ABI harness PASS,
zero lumen references left outside .git.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>