docs/qa/2026-09-29.md

App verification, 2026-09-29

Baseline: commit 649d8eb on Linux, KDE Plasma, with real PTYs and the GTK client. The original findings below describe that baseline. All nine findings were fixed and reverified in source commit 3789618; see the resolution evidence at the end.

Existing checks pass

CheckResult
cargo build --workspacePass
cargo test --workspace169 passed, 3 graphical tests skipped
Graphical tests, each in a separate processAll 3 passed
cargo fmt --checkPass
cargo clippy --workspace --all-targetsPass, 2 existing warnings
python3 scripts/bench-gui-refresh.py --checkPass

The graphical tests covered workspace-close confirmation, batched event refresh, and validation in the workspace dialog. Each ran with dbus-run-session -- cargo test -p signaltty-gui <filter> -- --ignored --test-threads=1. Filters were close_workspace_confirms, event_batches, and dialog_confirm.

The refresh benchmark sent 400 hook events across 8 workspaces. The message burst delivered all 200 notification events, with 33 workspace reads and no full workspace-list refresh.

Clippy warnings are at server/src/audit.rs:111 and server/src/router.rs:300.

Confirmed failures

P1 means a blocked workflow, lost displayed output, or processes left running after their workspace closes. P2 means incorrect state or behavior without that immediate impact. Priorities are triage judgments.

IDPriorityFailureEvidence
QA-01P1Focusing an approval clears it without answeringapproval.json
QA-02P1Reconnection drops terminal output produced while disconnectedreconnect.json
QA-03P1An unresponsive server blocks the GTK main loopfreeze.json
QA-04P1A pane can spawn in another workspace's tab and survive closure of its declared workspaceserver.json
QA-05P1Replacing a layout can orphan a running pane, which survives workspace closureserver.json
QA-06P2Layouts accept repeated pane IDs and ratios outside the divider limitsserver.json
QA-07P2A failed spawn leaves a new empty tab behindserver.json
QA-08P2Relaunching the app creates another windowrelaunch.json
QA-09P2After server restart, a restored terminal says Exited while its sidebar says Runningrestart-gui.txt

QA-01: an approval disappears when opened

Create an inactive pane with agent_hint: "codex" and a harmless shell command. Send hook-event with agent: "codex", event: "PermissionRequest", and a structured decision. Invoke the GUI's Next Attention action.

Before navigation, the pane has attention: "permission_required" and an answerable decision with Allow and Deny options. After navigation, attention is none and pending_decision is absent. No answer was sent.

The focus handler in gui/src/terminal.rs:255 calls pane.mark_seen. That method also clears the pending decision. Opening the request therefore removes the buttons the user needs to answer it.

QA-02: output does not recover after reconnection

Connect the GUI through a Unix-socket relay to the real server. Close relay connections temporarily, and send echo QARECONNECTB to the pane through the server's actual socket. Restore the relay and wait for GUI reconnection. Send echo QAONLINE after reconnection.

pane.read contains both markers. The VTE accessibility text contains QAONLINE but does not contain QARECONNECTB. Live streaming resumes, but the missing output never appears. Restarting the GUI restores that output.

gui/src/actor.rs:242 sends reattach requests without registering their snapshot replies. A workspace refresh preserves existing terminal widgets and therefore does not repair their contents.

QA-03: a server stall freezes the interface

Pause only the isolated server with SIGSTOP, then invoke New Tab through the GUI's exported org.gtk.Actions.Activate action. Request org.gtk.Actions.List from the same window with a one-second deadline. The request times out. Resume the server with SIGCONT; the request path works again and the new tab opens.

gui/src/actor.rs:79 waits with blocking_recv on the GTK thread. The IPC response reader has no deadline. A request to a connected but stalled server can block window interaction indefinitely.

org.freedesktop.DBus.Peer.Ping is not a valid probe for this bug. It still responds while the GTK main loop is blocked.

QA-04 through QA-07: server state integrity

Reproduce against a fresh isolated server:

sh
1cargo build --workspace
2python3 scripts/qa-server-edge-cases.py

At the baseline the script exited 1 and reported six failed assertions. It starts its own server with temporary socket, state, plugin, and agent directories, then shuts down that server and its PTYs. It never uses the default socket.

  • pane.spawn checks whether an explicit tab exists but does not check its workspace. A pane declared in workspace A can live in workspace B's tab. Closing A leaves that pane live.
  • tab.set_layout validates only referenced panes. Omitting a sibling removes it from workspace.get, but not from the PTY manager or pane store. Closing the workspace leaves that hidden sibling live.
  • The same method accepts a split referencing one pane twice and accepts ratio: 3.0. tab.set_ratio enforces limits, but whole-layout updates do not.
  • Spawning a nonexistent executable returns SPAWN_FAILED after creating the automatic agents tab. The failed operation changes workspace state.

QA-08 and QA-09: window and restart behavior

Launching the GUI a second time with the same socket increased the accessible window count from one to two. gui/src/main.rs constructs another App on every activation instead of presenting the existing window.

After shutting down and restarting the isolated server, all 7 panes restored without processes, as expected. The selected terminal header and contents said Exited, but its workspace row still said Running. Sidebar summaries use saved lifecycle values without accounting for the restored pane's live state.

Other observed behavior

The real GUI accepted keyboard input and displayed QAINPUT from a shell. New Tab, Ctrl+PgUp, Split Right, Split Down, and Close Pane worked in the tested sequence. Closing the GUI left all 7 server processes running. Server restart restored 2 workspaces and 7 panes with zero live processes.

Light and dark rendering were inspected in light.png and dark.png. The screenshots show different points in the test session, not a comparison of identical state.

Limits of this pass

This pass exercised the real backend, real GTK actions, terminal keyboard input, synthetic agent hooks, and restart behavior. It did not run paid model requests or certify native Codex, Claude, OpenCode, and Cursor sessions. Clipboard, IME, hyperlink opening, native folder-picker behavior, actual divider dragging, and notification clicks still need dedicated coverage. Some GUI interaction used X11 while keyboard automation was established; the reconnection, approval, and light-theme checks used the default Wayland backend. This is a broad audit, not proof that every app path works.

Fix verification

Source commit 3789618 resolves QA-01 through QA-09. New regression tests first reproduced approval dismissal, delayed GTK actions, incorrect pane ownership, layout omission/duplication, failed spawn side effects and restored status. The final gates passed: 188 workspace tests plus 6 display tests, workspace build, fmt and clippy with only the original two warnings. The refresh benchmark still delivered all 200 message events with 34 targeted reads and no full list.

FindingsResult and proof
QA-01Next Attention preserves the decision and gate; clicking Allow consumes it once and clears the gate. Answer bytes/stale IDs are integration-tested.
QA-02/03Outage and online markers appear exactly twice in the echoing cat fixture; GTK Actions.List responds in under 1 ms while the owned server is stopped. Offset overlap, deadlines and response-ID isolation are fake-socket tested.
QA-04–07Foreign tabs, omitted/duplicate panes are rejected; ratios clamp; failed spawn leaves no tab. Ownership cleanup and spawn/close races are integration-tested.
QA-08/09Relaunch remains at one window; restored working metadata renders Exited consistently with stopped live state.

The server probe and native GUI probe are rerunnable and use only temporary servers/state. Native results passed three consecutive final runs: light, dark, and repeat. Other proof: server, checks, refresh. Light/dark restored-state screenshots were inspected: light, dark.

Review also caught delayed completions replacing newer workspace navigation, viewer cleanup skipped after failed attach replies, notification suppression checked before an await, and resumed output lost to removed viewer registrations. Those are fixed, with delayed-navigation, cleanup and resume regressions.

One intermittent observation failure came from stale AT-SPI text: accessible/server comparison omitted QAONLINE in accessibility while the actual window already displayed it. The harness now drains its GLib context and clears the accessibility cache before reading. This was distinguished from stream loss before accepting the final results.

The project verification skill records launch, isolation, evidence, cleanup and three feature maps. Its server instructions were executed and proof survived cleanup. Keep it current with /maintain-verification-skill as user-visible paths change. The coverage limits listed above still apply; these fixes do not certify paid provider sessions or all desktop integrations.

Publication also exposed a pre-existing CI dependency failure: the Ubuntu 24.04 runner installed GTK 4.14.5, below the GUI crate's required GTK 4.18. The workflow now selects ubuntu-26.04, an available GitHub runner, whose GTK package meets that minimum. The local source checks above remain unchanged.

Once dependency installation passed, CI exposed two integration tests requiring an installed Codex CLI. They now use a temporary executable named codex and a manifest resume override, while builtin-hook tests still check the official resume argv. Both tests passed locally with PATH=/usr/bin:/bin, without any provider CLI. The resume test now observes the fixture's session marker instead of relying on a fixed sleep and a real interactive agent.

Remote CI run 36632158290 passed fmt, workspace clippy and all workspace tests on source commit d89d53a.