Files
enricobuehler ec500b0943 feat(host): monitor mode declined hi-res because nobody had written the lookup it was waiting on
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.
2026-08-16 11:27:17 +02:00
..

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