Field report: launching the "Punktfunk Console" entry on Windows shows a terminal window before the couch UI appears.
Root cause
punktfunk-session.exe is deliberately console-subsystem — its stdout IS the shell↔session contract. Both couch entry points are GUI-subsystem processes with no console to lend it, so a spawn without CREATE_NO_WINDOW makes Windows mint a fresh one: a black terminal that then sits behind the couch UI for the whole session.
The shell's own stream spawns (clients/windows/src/spawn.rs) have carried the flag since the Windows port. The two --browse hand-offs never did:
punktfunk-console.exe — the "Punktfunk Console" MSIX tile (AppxManifest.xml) and the Inno installer's {userprograms}\Punktfunk Console shortcut
punktfunk-client --console — the Steam-shortcut / HTPC-startup-item path
Both are shipping today.
The second bug, in the same spawn
🛑CREATE_NO_WINDOW on its own turns the cosmetic bug into a crash. stdout was inherited. With a console that was merely invisible output; with the flag, the inherited handle is invalid — and match_window (clients/session/src/main.rs:501) println!s the settled window size regardless of --json-status, so a launched title would panic the child mid-stream on the first report. stdout is now an explicit Stdio::null(), not an inheritance.
The shape of the fix
The two hand-offs were copy-paste twins, which is precisely how they drifted apart on one flag — so they become one file, clients/windows/src/couch.rs, that both roots include: the standalone punktfunk-console.exe bin by #[path], the shell as a module. session_binary() folds in there too, so there is one resolver rather than two. Net −41 lines.
Verification
couch.rs type-checks and passes clippy -D warnings -W pedantic against x86_64-pc-windows-msvc, via a scratch crate that pulls the file in over a stubbed parent module — the crate itself cannot be cross-checked from macOS (SDL3, aws-lc-rs do not cross-build).
cargo check -p punktfunk-client-windows (the non-Windows stub build) is clean.
rustfmt --check clean on all four touched files.
⏳On-glass verification on Windows is owed — please confirm the Console tile comes up with no terminal behind it, and that launching a title from the couch library still survives the match_window report.
Two notes for the reviewer
The plain "Punktfunk" tile was reported as flashing a console too, and this PR does not explain that one. Audited: main() → AttachConsole (attaches to an existing parent console, never allocates; a no-op under MSIX) → identity → deeplink → windows_reactor::bootstrap() (a no-op under package identity) → SDL in-process → WinUI. No child process is spawned on that path at all. The console window's title bar names the process that owns it; that string is what the next round needs.
Same trap, still latent:crates/pf-paths/src/lib.rs spawns icacls.exe at four sites with no creation flags. punktfunk-tray is GUI-subsystem and depends on pf-paths — it currently only calls config_dir()/published_mgmt_port() so it dodges them, but any tray call to create_private_dir/write_secret_file would pop 2–4 console windows. Left out of this PR deliberately: not reachable today, and not the reported bug.
Field report: launching the **"Punktfunk Console"** entry on Windows shows a terminal window before the couch UI appears.
## Root cause
`punktfunk-session.exe` is deliberately **console-subsystem** — its stdout IS the shell↔session contract. Both couch entry points are GUI-subsystem processes with no console to lend it, so a spawn without `CREATE_NO_WINDOW` makes Windows mint a fresh one: a black terminal that then sits behind the couch UI for the whole session.
The shell's own stream spawns (`clients/windows/src/spawn.rs`) have carried the flag since the Windows port. The two `--browse` hand-offs never did:
- `punktfunk-console.exe` — the **"Punktfunk Console" MSIX tile** (`AppxManifest.xml`) *and* the Inno installer's `{userprograms}\Punktfunk Console` shortcut
- `punktfunk-client --console` — the Steam-shortcut / HTPC-startup-item path
Both are shipping today.
## The second bug, in the same spawn
🛑 **`CREATE_NO_WINDOW` on its own turns the cosmetic bug into a crash.** stdout was inherited. With a console that was merely invisible output; with the flag, the inherited handle is **invalid** — and `match_window` (`clients/session/src/main.rs:501`) `println!`s the settled window size **regardless of `--json-status`**, so a launched title would panic the child mid-stream on the first report. stdout is now an explicit `Stdio::null()`, not an inheritance.
## The shape of the fix
The two hand-offs were copy-paste twins, which is precisely how they drifted apart on one flag — so they become **one file**, `clients/windows/src/couch.rs`, that both roots include: the standalone `punktfunk-console.exe` bin by `#[path]`, the shell as a module. `session_binary()` folds in there too, so there is one resolver rather than two. Net −41 lines.
## Verification
- `couch.rs` type-checks **and** passes `clippy -D warnings -W pedantic` against `x86_64-pc-windows-msvc`, via a scratch crate that pulls the file in over a stubbed parent module — the crate itself cannot be cross-checked from macOS (SDL3, aws-lc-rs do not cross-build).
- `cargo check -p punktfunk-client-windows` (the non-Windows stub build) is clean.
- `rustfmt --check` clean on all four touched files.
⏳ **On-glass verification on Windows is owed** — please confirm the Console tile comes up with no terminal behind it, and that launching a title from the couch library still survives the `match_window` report.
## Two notes for the reviewer
1. The **plain "Punktfunk" tile was reported as flashing a console too, and this PR does not explain that one.** Audited: `main()` → `AttachConsole` (attaches to an existing parent console, never allocates; a no-op under MSIX) → identity → deeplink → `windows_reactor::bootstrap()` (a no-op under package identity) → SDL in-process → WinUI. No child process is spawned on that path at all. The console window's title bar names the process that owns it; that string is what the next round needs.
2. **Same trap, still latent:** `crates/pf-paths/src/lib.rs` spawns `icacls.exe` at four sites with no creation flags. `punktfunk-tray` is GUI-subsystem and depends on pf-paths — it currently only calls `config_dir()`/`published_mgmt_port()` so it dodges them, but any tray call to `create_private_dir`/`write_secret_file` would pop 2–4 console windows. Left out of this PR deliberately: not reachable today, and not the reported bug.
`punktfunk-session` keeps the CONSOLE subsystem on purpose — its stdout is the
shell/session contract. Both couch entry points are GUI processes with no console
to lend it, so a spawn without CREATE_NO_WINDOW makes Windows mint one: a black
terminal that then sits behind the couch UI for the whole session. The shell's
own stream spawns (spawn.rs) have carried the flag since the port; the two
`--browse` hand-offs never did — the "Punktfunk Console" MSIX tile and
`punktfunk-client --console` (Steam shortcut, HTPC startup item) hit it every launch.
The two hand-offs were copy-paste twins, which is how they drifted apart on one
flag, so they become one file (couch.rs) that both roots include — the standalone
`punktfunk-console.exe` bin by `#[path]`, the shell as a module. `session_binary()`
folds in there too, so there is one resolver rather than two.
Second, quieter bug in the same spawn: stdout was inherited. With a console that
was merely invisible output; with CREATE_NO_WINDOW the inherited handle is invalid,
and `match_window` reports the settled window size on stdout REGARDLESS of
`--json-status` — a `println!` onto a dead handle panics the child mid-stream. So
stdout is an explicit `Stdio::null()`, not an inheritance.
Verified: couch.rs type-checks and clippies clean against x86_64-pc-windows-msvc in
an isolated harness (the crate itself cannot be cross-checked from macOS — SDL3 and
aws-lc-rs do not cross-build). On-glass verification on Windows is owed.
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.
Field report: launching the "Punktfunk Console" entry on Windows shows a terminal window before the couch UI appears.
Root cause
punktfunk-session.exeis deliberately console-subsystem — its stdout IS the shell↔session contract. Both couch entry points are GUI-subsystem processes with no console to lend it, so a spawn withoutCREATE_NO_WINDOWmakes Windows mint a fresh one: a black terminal that then sits behind the couch UI for the whole session.The shell's own stream spawns (
clients/windows/src/spawn.rs) have carried the flag since the Windows port. The two--browsehand-offs never did:punktfunk-console.exe— the "Punktfunk Console" MSIX tile (AppxManifest.xml) and the Inno installer's{userprograms}\Punktfunk Consoleshortcutpunktfunk-client --console— the Steam-shortcut / HTPC-startup-item pathBoth are shipping today.
The second bug, in the same spawn
🛑
CREATE_NO_WINDOWon its own turns the cosmetic bug into a crash. stdout was inherited. With a console that was merely invisible output; with the flag, the inherited handle is invalid — andmatch_window(clients/session/src/main.rs:501)println!s the settled window size regardless of--json-status, so a launched title would panic the child mid-stream on the first report. stdout is now an explicitStdio::null(), not an inheritance.The shape of the fix
The two hand-offs were copy-paste twins, which is precisely how they drifted apart on one flag — so they become one file,
clients/windows/src/couch.rs, that both roots include: the standalonepunktfunk-console.exebin by#[path], the shell as a module.session_binary()folds in there too, so there is one resolver rather than two. Net −41 lines.Verification
couch.rstype-checks and passesclippy -D warnings -W pedanticagainstx86_64-pc-windows-msvc, via a scratch crate that pulls the file in over a stubbed parent module — the crate itself cannot be cross-checked from macOS (SDL3, aws-lc-rs do not cross-build).cargo check -p punktfunk-client-windows(the non-Windows stub build) is clean.rustfmt --checkclean on all four touched files.⏳ On-glass verification on Windows is owed — please confirm the Console tile comes up with no terminal behind it, and that launching a title from the couch library still survives the
match_windowreport.Two notes for the reviewer
main()→AttachConsole(attaches to an existing parent console, never allocates; a no-op under MSIX) → identity → deeplink →windows_reactor::bootstrap()(a no-op under package identity) → SDL in-process → WinUI. No child process is spawned on that path at all. The console window's title bar names the process that owns it; that string is what the next round needs.crates/pf-paths/src/lib.rsspawnsicacls.exeat four sites with no creation flags.punktfunk-trayis GUI-subsystem and depends on pf-paths — it currently only callsconfig_dir()/published_mgmt_port()so it dodges them, but any tray call tocreate_private_dir/write_secret_filewould pop 2–4 console windows. Left out of this PR deliberately: not reachable today, and not the reported bug.