Field report (CachyOS/Arch, KDE Wayland, punktfunk-client 0.29.0-1): the host library loads fine, artwork and all, and then clicking any game aborts the whole client.
thread 'main' (5698) has overflowed its stack
fatal runtime error: stack overflow, aborting
fish: Process 5698, 'punktfunk-client' ... terminated by signal SIGABRT (Abort)
The reporter's own guess was right, and it names the mechanism precisely: the library card path uses the same child-activated → child.activate() pattern that caused the host-card stack overflow.
The cycle
A click on a FlowBoxChild emits child-activated on the FlowBox, never the child's own activate. A grid whose per-card handler hangs off child.connect_activate() therefore has to bridge the two — and the naive bridge is a cycle, because FlowBoxChild's default activate handler re-emits child-activated on its parent, which re-enters the bridge, which activates the child again.
Why the earlier fix didn't cover it
Commit
Date
7eea9836
07-04
added the unguarded bridge to both the hosts page and the library page
53c8eefa
07-06
fixed the reported host-card crash — only in ui_hosts.rs
883c3178
08-06
added a third unguarded bridge, for the launcher shelf
So the guard lived in one of three places. The regression test that shipped with 53c8eefa was written against a hand-copied replica of the wiring rather than the shared code, so it only ever covered the page that had already been fixed.
By ancestry, the library-grid crash is in every tagged release from v0.7.1 through v0.29.0; the launcher-shelf variant since v0.25.0.
This change
The guarded bridge moves into a new clients/linux/src/ui_flow.rs and all three call sites go through it — hosts (saved + discovered), library games, library launchers. Patching the two broken sites in place would have left the footgun loaded for the next grid, which is exactly how it got here.
The regression test moves with it, so it now exercises the code both pages actually run.
ui_settings.rs::swatch_row is the only other FlowBox in the shell; it uses Button/connect_clicked and was never affected.
Verification
Run in a Linux container (punktfunk-client-linux is cfg(target_os = "linux")-gated, so it compiles to nothing on a Mac):
cargo build --locked -p punktfunk-client-linux — 0 (no --all-targets, to catch dead-outside-test)
cargo test --locked -p punktfunk-client-linux — 3 passed, 0 failed
the recursion test executed for real against Xvfb rather than skipped — passed
Two checks worth calling out, since a green gate on its own proves less than it looks:
The test is non-vacuous. Backing the guard out and re-running reproduces the reported crash exactly — stack overflow, aborting, signal: 6, SIGABRT.
The gate compiled these bytes. SHA-256 of all four files matches between the container tree and the commit.
Follow-up, not in this PR
The test is still #[ignore = "needs a Wayland/X display"], so CI never runs it — which is the reason a fix applied to one of three sites went unnoticed for six weeks. It runs fine headless under Xvfb (verified here), so giving the client CI leg a display would make this class of bug self-catching. Left out as out of scope.
Field report (CachyOS/Arch, KDE Wayland, `punktfunk-client` 0.29.0-1): the host library loads fine, artwork and all, and then clicking **any** game aborts the whole client.
```
thread 'main' (5698) has overflowed its stack
fatal runtime error: stack overflow, aborting
fish: Process 5698, 'punktfunk-client' ... terminated by signal SIGABRT (Abort)
```
The reporter's own guess was right, and it names the mechanism precisely: the library card path uses the same `child-activated → child.activate()` pattern that caused the host-card stack overflow.
## The cycle
A click on a `FlowBoxChild` emits `child-activated` on the **FlowBox**, never the child's own `activate`. A grid whose per-card handler hangs off `child.connect_activate()` therefore has to bridge the two — and the naive bridge is a cycle, because `FlowBoxChild`'s default `activate` handler re-emits `child-activated` on its parent, which re-enters the bridge, which activates the child again.
## Why the earlier fix didn't cover it
| Commit | Date | |
|---|---|---|
| `7eea9836` | 07-04 | added the unguarded bridge to **both** the hosts page and the library page |
| `53c8eefa` | 07-06 | fixed the reported host-card crash — **only in `ui_hosts.rs`** |
| `883c3178` | 08-06 | added a **third** unguarded bridge, for the launcher shelf |
So the guard lived in one of three places. The regression test that shipped with `53c8eefa` was written against a hand-copied replica of the wiring rather than the shared code, so it only ever covered the page that had already been fixed.
By ancestry, the library-grid crash is in every tagged release from **v0.7.1** through **v0.29.0**; the launcher-shelf variant since **v0.25.0**.
## This change
The guarded bridge moves into a new `clients/linux/src/ui_flow.rs` and all three call sites go through it — hosts (saved + discovered), library games, library launchers. Patching the two broken sites in place would have left the footgun loaded for the next grid, which is exactly how it got here.
The regression test moves with it, so it now exercises the code both pages actually run.
`ui_settings.rs::swatch_row` is the only other `FlowBox` in the shell; it uses `Button`/`connect_clicked` and was never affected.
## Verification
Run in a Linux container (`punktfunk-client-linux` is `cfg(target_os = "linux")`-gated, so it compiles to nothing on a Mac):
- `cargo fmt --all --check` — 0
- `cargo clippy --all-targets --locked -p punktfunk-client-linux -- -D warnings` — 0
- `cargo build --locked -p punktfunk-client-linux` — 0 (no `--all-targets`, to catch dead-outside-test)
- `cargo test --locked -p punktfunk-client-linux` — 3 passed, 0 failed
- the recursion test **executed for real** against Xvfb rather than skipped — passed
Two checks worth calling out, since a green gate on its own proves less than it looks:
**The test is non-vacuous.** Backing the guard out and re-running reproduces the reported crash exactly — `stack overflow, aborting`, `signal: 6, SIGABRT`.
**The gate compiled these bytes.** SHA-256 of all four files matches between the container tree and the commit.
## Follow-up, not in this PR
The test is still `#[ignore = "needs a Wayland/X display"]`, so CI never runs it — which is the reason a fix applied to one of three sites went unnoticed for six weeks. It runs fine headless under Xvfb (verified here), so giving the client CI leg a display would make this class of bug self-catching. Left out as out of scope.
Clicking any game in the host library aborted punktfunk-client outright:
thread 'main' has overflowed its stack
fatal runtime error: stack overflow, aborting
A click on a FlowBoxChild emits `child-activated` on the *FlowBox*, never the
child's own `activate`, so a grid whose per-card handler hangs off
`child.connect_activate()` has to bridge the two. The naive bridge is a cycle:
FlowBoxChild's default `activate` handler re-emits `child-activated` on its
parent, which re-enters the bridge, which activates the child again.
53c8eefa fixed exactly this for the host cards — but only in ui_hosts.rs. The
library page had carried the same unguarded bridge since 7eea9836, and 883c3178
then added a third one for the launcher shelf. So the guard existed in one of
three places, and both library grids still aborted on the first click.
Rather than patch the two sites and leave the footgun loaded, the guarded bridge
moves into ui_flow.rs and all three call sites go through it. The regression test
moves with it, so it now covers the code both pages actually run instead of a
hand-copied replica of it — which is why it never caught this.
Verified in a Linux container (fmt / clippy -D warnings / build / test all green)
with the display test executed for real against Xvfb rather than skipped. Removing
the guard again reproduces the reported abort exactly (SIGABRT, stack overflow),
so the test is known to catch the bug rather than merely 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.
Field report (CachyOS/Arch, KDE Wayland,
punktfunk-client0.29.0-1): the host library loads fine, artwork and all, and then clicking any game aborts the whole client.The reporter's own guess was right, and it names the mechanism precisely: the library card path uses the same
child-activated → child.activate()pattern that caused the host-card stack overflow.The cycle
A click on a
FlowBoxChildemitschild-activatedon the FlowBox, never the child's ownactivate. A grid whose per-card handler hangs offchild.connect_activate()therefore has to bridge the two — and the naive bridge is a cycle, becauseFlowBoxChild's defaultactivatehandler re-emitschild-activatedon its parent, which re-enters the bridge, which activates the child again.Why the earlier fix didn't cover it
7eea983653c8eefaui_hosts.rs883c3178So the guard lived in one of three places. The regression test that shipped with
53c8eefawas written against a hand-copied replica of the wiring rather than the shared code, so it only ever covered the page that had already been fixed.By ancestry, the library-grid crash is in every tagged release from v0.7.1 through v0.29.0; the launcher-shelf variant since v0.25.0.
This change
The guarded bridge moves into a new
clients/linux/src/ui_flow.rsand all three call sites go through it — hosts (saved + discovered), library games, library launchers. Patching the two broken sites in place would have left the footgun loaded for the next grid, which is exactly how it got here.The regression test moves with it, so it now exercises the code both pages actually run.
ui_settings.rs::swatch_rowis the only otherFlowBoxin the shell; it usesButton/connect_clickedand was never affected.Verification
Run in a Linux container (
punktfunk-client-linuxiscfg(target_os = "linux")-gated, so it compiles to nothing on a Mac):cargo fmt --all --check— 0cargo clippy --all-targets --locked -p punktfunk-client-linux -- -D warnings— 0cargo build --locked -p punktfunk-client-linux— 0 (no--all-targets, to catch dead-outside-test)cargo test --locked -p punktfunk-client-linux— 3 passed, 0 failedTwo checks worth calling out, since a green gate on its own proves less than it looks:
The test is non-vacuous. Backing the guard out and re-running reproduces the reported crash exactly —
stack overflow, aborting,signal: 6, SIGABRT.The gate compiled these bytes. SHA-256 of all four files matches between the container tree and the commit.
Follow-up, not in this PR
The test is still
#[ignore = "needs a Wayland/X display"], so CI never runs it — which is the reason a fix applied to one of three sites went unnoticed for six weeks. It runs fine headless under Xvfb (verified here), so giving the client CI leg a display would make this class of bug self-catching. Left out as out of scope.Clicking any game in the host library aborted punktfunk-client outright: thread 'main' has overflowed its stack fatal runtime error: stack overflow, aborting A click on a FlowBoxChild emits `child-activated` on the *FlowBox*, never the child's own `activate`, so a grid whose per-card handler hangs off `child.connect_activate()` has to bridge the two. The naive bridge is a cycle: FlowBoxChild's default `activate` handler re-emits `child-activated` on its parent, which re-enters the bridge, which activates the child again.53c8eefafixed exactly this for the host cards — but only in ui_hosts.rs. The library page had carried the same unguarded bridge since7eea9836, and883c3178then added a third one for the launcher shelf. So the guard existed in one of three places, and both library grids still aborted on the first click. Rather than patch the two sites and leave the footgun loaded, the guarded bridge moves into ui_flow.rs and all three call sites go through it. The regression test moves with it, so it now covers the code both pages actually run instead of a hand-copied replica of it — which is why it never caught this. Verified in a Linux container (fmt / clippy -D warnings / build / test all green) with the display test executed for real against Xvfb rather than skipped. Removing the guard again reproduces the reported abort exactly (SIGABRT, stack overflow), so the test is known to catch the bug rather than merely pass.