The design said hi-res is offered in stream-sink mode and declined in monitor mode "unless the monitored node's own rate can be read from the registry", and that lookup was never written — so `PUNKTFUNK_STREAM_SINK=0` could never have the feature at all, by omission rather than by decision. It exists now. Reading our own capture stream's rate would be worthless: in monitor mode we tap someone else's sink THROUGH PipeWire's resampler, which reports a clean rate whatever is upstream — the same blindness as WASAPI's autoconvert, which is the trap this whole feature is built around. So the lookup binds the graph's elected default sink and reads that NODE's format: one bounded round-trip with a 3 s timer, so a sick graph costs a fallback rather than a stalled handshake. It reads `default.audio.sink`, the elected default, NOT the neighbouring `default.configured.audio.sink` that this host's own stream-sink code writes. That one is a preference: unset on a box nobody configured, and able to name a node that no longer exists. Unknown declines. No graph default, no format, no answer inside the timer — each resolves to Opus rather than guessing, because the whole point is that a session must never claim a rate its content does not have. That asymmetry is stated at the lookup, at the call site and in the module doc, since "unknown means fine" is the opposite convention to the one the wiring plan uses a few files away. One nuance the design does not address: a monitor tap emits the GRAPH-side rate, while `Format` on an adapter node forwards to the device side. On an ordinary box those are the same number, because PipeWire opens the device at the graph rate whenever it can. They diverge only for a device that cannot run the graph's rate, where reading the device side declines something that would have worked — the safe direction. The exactly right source is the sink's monitor PORT's own format, one further registry hop, named at the call site as a follow-up rather than left implied. Also documents the one variable name both ends read: the host treats `PUNKTFUNK_AUDIO_HIRES` as a boolean and the desktop client accepts a rate or a rate/depth pair, so a box that is both sees two grammars. `1` is the spelling that means "on" to both.
punktfunk-docs
The Punktfunk documentation site: Fumadocs on TanStack Start (Vite + Nitro/bun preset).
Content lives in content/docs/ as .md/.mdx. This site is the source of truth
for the user-facing guides; design rationale lives in the internal punktfunk-planning repo.
API reference
/api renders the host's management REST API as an interactive
Scalar reference (linked from the top nav, the docs
sidebar, and the landing page). It reads public/openapi.json — a
snapshot of the repo's generated spec. Refresh it after a management-API change:
# from the repo root — regenerate the spec, then copy the snapshot in:
cargo run -p punktfunk-host -- openapi > api/openapi.json
cp api/openapi.json docs-site/public/openapi.json
Nothing in CI diffs the two, so the snapshot goes stale silently — that manual cp is the only
thing keeping them in sync. Before publishing docs, check that they match:
diff <(jq -S . api/openapi.json) <(jq -S . docs-site/public/openapi.json)
That should print nothing. Right now it doesn't: the committed snapshot predates the
/api/v1/update/check, /api/v1/update/apply and /api/v1/update/status endpoints, so the
published /api reference is missing the host self-update surface — re-copy it.
Develop
bun install
bun run dev # http://localhost:3001 (docs at /docs)
CI gates every change on bun run build followed by bun run lint (the TypeScript typecheck), in
that order — the build emits the .source typegen the typecheck imports. Run both before you push.
Build & serve
bun run build
bun run start # serves .output/ via Bun
Layout
source.config.ts Fumadocs MDX collection (content/docs)
content/docs/ the docs content (.md/.mdx) + meta.json nav
src/
routes/
__root.tsx RootProvider + html shell
index.tsx landing page
docs/$.tsx catch-all docs renderer (Fumadocs DocsLayout)
api/index.tsx Scalar API reference (reads public/openapi.json)
api/search.ts Orama search endpoint
lib/source.ts Fumadocs loader over the generated collection
lib/layout.shared.tsx shared nav chrome
components/mdx.tsx MDX component map
styles/app.css Tailwind 4 + Fumadocs preset