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
| Check | Result |
|---|---|
cargo build --workspace | Pass |
cargo test --workspace | 169 passed, 3 graphical tests skipped |
| Graphical tests, each in a separate process | All 3 passed |
cargo fmt --check | Pass |
cargo clippy --workspace --all-targets | Pass, 2 existing warnings |
python3 scripts/bench-gui-refresh.py --check | Pass |
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.
| ID | Priority | Failure | Evidence |
|---|---|---|---|
| QA-01 | P1 | Focusing an approval clears it without answering | approval.json |
| QA-02 | P1 | Reconnection drops terminal output produced while disconnected | reconnect.json |
| QA-03 | P1 | An unresponsive server blocks the GTK main loop | freeze.json |
| QA-04 | P1 | A pane can spawn in another workspace's tab and survive closure of its declared workspace | server.json |
| QA-05 | P1 | Replacing a layout can orphan a running pane, which survives workspace closure | server.json |
| QA-06 | P2 | Layouts accept repeated pane IDs and ratios outside the divider limits | server.json |
| QA-07 | P2 | A failed spawn leaves a new empty tab behind | server.json |
| QA-08 | P2 | Relaunching the app creates another window | relaunch.json |
| QA-09 | P2 | After server restart, a restored terminal says Exited while its sidebar says Running | restart-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:
| 1 | cargo build --workspace |
| 2 | python3 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.spawnchecks 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_layoutvalidates only referenced panes. Omitting a sibling removes it fromworkspace.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_ratioenforces limits, but whole-layout updates do not. - Spawning a nonexistent executable returns
SPAWN_FAILEDafter creating the automaticagentstab. 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.
| Findings | Result and proof |
|---|---|
| QA-01 | Next Attention preserves the decision and gate; clicking Allow consumes it once and clears the gate. Answer bytes/stale IDs are integration-tested. |
| QA-02/03 | Outage 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–07 | Foreign tabs, omitted/duplicate panes are rejected; ratios clamp; failed spawn leaves no tab. Ownership cleanup and spawn/close races are integration-tested. |
| QA-08/09 | Relaunch 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.