Two field-report gaps, one loop closed: a 0.31.4 user's gamepad UI never activates — even on Always, even with a pad — and the UIs they're left in had no way to send the log that would say why.
Android (1dbee2e7)
"Send logs to host" on the touch UI. The row existed only in the Skia console's host menu — the log-escape hatch sat behind the very shell that was failing. The upload moves out of SkiaConsole into a shared SendLogs object both shells call; the touch home's card menu grows the row on the console's own gate (host card, paired, online), with the desktop console's wording verbatim.
The switch stops being silently overruled.App gates the gamepad UI on SkiaConsole.wanted() && healthy on top of the user's setting, and those two terms are the only ones that can veto "Always" — mode, pad, TV and the dev flag are ORed. A device whose console never builds therefore ignores the switch with no trace, which is indistinguishable from the switch being broken — and is exactly what the field report describes. SkiaConsole.unavailable() names the reason and the Interface settings group prints it as its footer (null wherever the console works). The reason string itself (console: render thread ended: …) already reaches the ring via RingTee, so the new row uploads the log that names why the console is down.
GTK + WinUI (0d8b156d)
One ring layer, three bins.clients/session/src/ring_layer.rs moves into pf_client_core::logring — it was the session binary's private module, and the moment both desktop shells wanted the row, a copy per bin was the drift the crate exists to prevent. No cargo feature: tracing-subscriber (default-features off, ["std", "registry"] — the Layer trait is ungated) and tracing-log join the desktop target block, where every consumer already carries them; Android's target block is untouched. The session's manifest drops tracing-log and log.
Both shells install the layer beside their fmt layer with its own DEBUG filter — the env filter keeps scoping only what's printed, because the ring exists for the diagnostics nobody enabled before the bug happened. The row lands on each shell's saved-host card menu (paired + online), runs logring::send_to_host on a worker thread, and reports as a toast (GTK) / the status line (WinUI).
And the bundle is worth sending. The GTK shell inherited the session child's stderr, so its ring would have held everything except the stream the report is about. orchestrate::spawn_session now pipes child stderr through logring::forward_child_stderr — every line still lands on our stderr exactly as inherit interleaved it, and lands in the ring too. WinUI's logfile::forward_child_stderr notes each line on the same pass it already tees into client.log. GTK's OpenConsole (gio::Subprocess) is untouched — the console child has its own ring and its own row.
Not covered, deliberately: clients/cli (headless — its logs go to the terminal it's run from) and webOS (no client in tree).
Verified
Android: 98 app unit tests green, :app:assembleDebug clean, native builds for all three ABIs.
Linux (pf-gtkflow container): fmt + clippy --all-targets -D warnings + plain build + tests over pf-client-core, punktfunk-client-session, punktfunk-client-linux — GATE-GREEN, non-vacuity greps confirming each crate compiled. All three logring tests pass, including the bridge-normalization test that moved with the layer (now under a shared lock with the ring-bounds test — same process-global ring, one binary now).
Windows (.133 runner): cargo check --locked -p punktfunk-client-windows green against the final sources.
Two field-report gaps, one loop closed: a 0.31.4 user's gamepad UI never activates — even on Always, even with a pad — and the UIs they're left in had no way to send the log that would say why.
## Android (`1dbee2e7`)
**"Send logs to host" on the touch UI.** The row existed only in the Skia console's host menu — the log-escape hatch sat behind the very shell that was failing. The upload moves out of `SkiaConsole` into a shared `SendLogs` object both shells call; the touch home's card menu grows the row on the console's own gate (host card, paired, online), with the desktop console's wording verbatim.
**The switch stops being silently overruled.** `App` gates the gamepad UI on `SkiaConsole.wanted() && healthy` on top of the user's setting, and those two terms are the *only* ones that can veto "Always" — mode, pad, TV and the dev flag are ORed. A device whose console never builds therefore ignores the switch with no trace, which is indistinguishable from the switch being broken — and is exactly what the field report describes. `SkiaConsole.unavailable()` names the reason and the Interface settings group prints it as its footer (null wherever the console works). The reason string itself (`console: render thread ended: …`) already reaches the ring via `RingTee`, so the new row uploads the log that names why the console is down.
## GTK + WinUI (`0d8b156d`)
**One ring layer, three bins.** `clients/session/src/ring_layer.rs` moves into `pf_client_core::logring` — it was the session binary's private module, and the moment both desktop shells wanted the row, a copy per bin was the drift the crate exists to prevent. No cargo feature: `tracing-subscriber` (default-features off, `["std", "registry"]` — the `Layer` trait is ungated) and `tracing-log` join the desktop target block, where every consumer already carries them; Android's target block is untouched. The session's manifest drops `tracing-log` and `log`.
Both shells install the layer beside their fmt layer with its own DEBUG filter — the env filter keeps scoping only what's printed, because the ring exists for the diagnostics nobody enabled before the bug happened. The row lands on each shell's saved-host card menu (paired + online), runs `logring::send_to_host` on a worker thread, and reports as a toast (GTK) / the status line (WinUI).
**And the bundle is worth sending.** The GTK shell inherited the session child's stderr, so its ring would have held everything *except the stream the report is about*. `orchestrate::spawn_session` now pipes child stderr through `logring::forward_child_stderr` — every line still lands on our stderr exactly as inherit interleaved it, and lands in the ring too. WinUI's `logfile::forward_child_stderr` notes each line on the same pass it already tees into `client.log`. GTK's `OpenConsole` (gio::Subprocess) is untouched — the console child has its own ring and its own row.
Not covered, deliberately: `clients/cli` (headless — its logs go to the terminal it's run from) and webOS (no client in tree).
## Verified
- Android: 98 app unit tests green, `:app:assembleDebug` clean, native builds for all three ABIs.
- Linux (pf-gtkflow container): fmt + clippy `--all-targets -D warnings` + plain build + tests over `pf-client-core`, `punktfunk-client-session`, `punktfunk-client-linux` — GATE-GREEN, non-vacuity greps confirming each crate compiled. All three `logring` tests pass, including the bridge-normalization test that moved with the layer (now under a shared lock with the ring-bounds test — same process-global ring, one binary now).
- Windows (.133 runner): `cargo check --locked -p punktfunk-client-windows` green against the final sources.
Two halves of the same field report: a 0.31.4 user whose gamepad UI never
activates — on Always, with a pad attached — and who then has no way to send
anyone the log that would say why.
"Send logs to host" existed only in the Skia console's host menu. That put the
log-escape hatch behind the very shell that was failing: a phone whose console
does not come up has no route to its own logs at all. The upload moves out of
SkiaConsole into `SendLogs`, one implementation both shells call, and the touch
home's card menu grows the row on the console's own gate (paired + online, host
card only). The wording stays the desktop console's verbatim, so a quoted
message means the same thing on every client.
The activation half is a gap in what the app admits. `App` folds
`SkiaConsole.wanted() && healthy` into the gamepad-UI decision on top of the
user's setting, and those two terms are the ONLY ones that can veto "Always" —
the mode, the pad, the TV check and the dev flag are ORed, so a device whose
console never builds ignores every one of them. Until now that produced a switch
the app silently disobeyed, indistinguishable from the switch being broken.
`SkiaConsole.unavailable()` names the reason and the Interface settings group
prints it as its footer: null on every device where the console works, so it
costs nothing there.
The reason string itself was already in the ring the new row uploads —
`console: render thread ended: …` and `console: window surface: …` go through
`log::error!`, which `RingTee` tees into `pf_client_core::logring`. So the two
halves close the loop: the user stuck in the touch UI can now send the log that
names why they are stuck in it.
Verified: 98 app unit tests green, `:app:assembleDebug` clean, native builds for
all three ABIs.
The ring layer moves out of the session binary into pf_client_core::logring
(desktop-gated beside the ring it feeds) — it started as punktfunk-session's
private module, and the moment both desktop shells wanted the row, a copy per
bin was exactly the drift the crate exists to prevent. The session keeps the
same layer under its new name; tracing-log and log fall out of its manifest.
Both shells install it beside their visible layer with its own DEBUG filter —
the env filter keeps scoping only what's printed, because the ring exists for
the diagnostics nobody enabled before the bug happened.
The row itself lands on each shell's saved-host menu on the console's own gate
(paired + online), wired to logring::send_to_host on a worker thread, outcome
as a toast (GTK) / the status line (WinUI), in the console's wording verbatim.
And the bundle is worth sending: orchestrate now pipes the session child's
stderr through logring::forward_child_stderr — every line still lands on our
stderr exactly as inherit interleaved it, and lands in the ring too. Without
that, the one surface a GUI-only user can export held everything EXCEPT the
stream it was exported about — the same hole the WinUI logfile module was
built to close for the file; its child forwarder now feeds the ring on the
same pass.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Two field-report gaps, one loop closed: a 0.31.4 user's gamepad UI never activates — even on Always, even with a pad — and the UIs they're left in had no way to send the log that would say why.
Android (
1dbee2e7)"Send logs to host" on the touch UI. The row existed only in the Skia console's host menu — the log-escape hatch sat behind the very shell that was failing. The upload moves out of
SkiaConsoleinto a sharedSendLogsobject both shells call; the touch home's card menu grows the row on the console's own gate (host card, paired, online), with the desktop console's wording verbatim.The switch stops being silently overruled.
Appgates the gamepad UI onSkiaConsole.wanted() && healthyon top of the user's setting, and those two terms are the only ones that can veto "Always" — mode, pad, TV and the dev flag are ORed. A device whose console never builds therefore ignores the switch with no trace, which is indistinguishable from the switch being broken — and is exactly what the field report describes.SkiaConsole.unavailable()names the reason and the Interface settings group prints it as its footer (null wherever the console works). The reason string itself (console: render thread ended: …) already reaches the ring viaRingTee, so the new row uploads the log that names why the console is down.GTK + WinUI (
0d8b156d)One ring layer, three bins.
clients/session/src/ring_layer.rsmoves intopf_client_core::logring— it was the session binary's private module, and the moment both desktop shells wanted the row, a copy per bin was the drift the crate exists to prevent. No cargo feature:tracing-subscriber(default-features off,["std", "registry"]— theLayertrait is ungated) andtracing-logjoin the desktop target block, where every consumer already carries them; Android's target block is untouched. The session's manifest dropstracing-logandlog.Both shells install the layer beside their fmt layer with its own DEBUG filter — the env filter keeps scoping only what's printed, because the ring exists for the diagnostics nobody enabled before the bug happened. The row lands on each shell's saved-host card menu (paired + online), runs
logring::send_to_hoston a worker thread, and reports as a toast (GTK) / the status line (WinUI).And the bundle is worth sending. The GTK shell inherited the session child's stderr, so its ring would have held everything except the stream the report is about.
orchestrate::spawn_sessionnow pipes child stderr throughlogring::forward_child_stderr— every line still lands on our stderr exactly as inherit interleaved it, and lands in the ring too. WinUI'slogfile::forward_child_stderrnotes each line on the same pass it already tees intoclient.log. GTK'sOpenConsole(gio::Subprocess) is untouched — the console child has its own ring and its own row.Not covered, deliberately:
clients/cli(headless — its logs go to the terminal it's run from) and webOS (no client in tree).Verified
:app:assembleDebugclean, native builds for all three ABIs.--all-targets -D warnings+ plain build + tests overpf-client-core,punktfunk-client-session,punktfunk-client-linux— GATE-GREEN, non-vacuity greps confirming each crate compiled. All threelogringtests pass, including the bridge-normalization test that moved with the layer (now under a shared lock with the ring-bounds test — same process-global ring, one binary now).cargo check --locked -p punktfunk-client-windowsgreen against the final sources.