Skip to content

fix(gui): make headless GUI path feature-independent#44

Merged
hyperpolymath merged 1 commit into
mainfrom
fix/gui-headless-feature-independent
May 26, 2026
Merged

fix(gui): make headless GUI path feature-independent#44
hyperpolymath merged 1 commit into
mainfrom
fix/gui-headless-feature-independent

Conversation

@hyperpolymath
Copy link
Copy Markdown
Owner

Summary

Root-cause fix for the CI failures that survived PR #39's merge. The readiness test `readiness_d_gui_headless_runs` and the `Readiness` workflow have been red on main since #39 hid `Commands::Gui` behind `#[cfg(feature = "gui")]` — the subcommand no longer exists in default builds, but the readiness test (which runs against default features) still invokes `panic-attack gui --headless`.

The headless renderer is pure text output — it touches `ReportFormatter` and the report data structures, never `eframe` or `egui`. Moving it into a feature-independent module fixes the underlying contract: the subcommand should always exist; only the windowed renderer needs the feature.

Changes

File Change
`src/report/gui_text.rs` (new) `run_headless` lifted verbatim from `gui::ReportGui::run_headless`. Always compiled.
`src/report/gui.rs` `run_headless` removed (now in `gui_text`); header docstring updated.
`src/report/mod.rs` `pub mod gui_text;` always; `pub mod gui;` stays `#[cfg(feature = "gui")]`
`src/main.rs:472` drop `#[cfg(feature = "gui")]` from `Commands::Gui` variant
`src/main.rs:1703` match arm: `--headless` always calls `gui_text::run_headless`; non-headless path is feature-gated with a `#[cfg(not(feature = "gui"))]` arm that does `anyhow::bail!("rebuild with --features gui")`

User-visible result

Default build (`cargo build`):

  • `panic-attack gui --headless report.json` ✅ works (prints text panels)
  • `panic-attack gui report.json` → clear error: "windowed GUI requires the `gui` feature; rebuild with `cargo build --features gui`, or pass --headless"

GUI build (`cargo build --features gui`):

  • Both modes work as before

Test plan

  • `cargo build` — default, OK
  • `cargo build --features gui` — opt-in, OK
  • `cargo clippy --all-targets --features signing,http -- -D warnings` — clean
  • `cargo clippy --all-targets --all-features -- -D warnings` — clean
  • `cargo test --test readiness` — 20 passed, 0 failed (including `readiness_d_gui_headless_runs`)
  • `cargo fmt --check` — clean
  • Signed commit

Why this is the principled fix

  • The readiness test stays meaningful on the MSRV-clean default build (it actually exercises the headless path).
  • The windowed GUI stays opt-in because eframe raises MSRV.
  • A user who tries the windowed mode without the feature gets a precise actionable error instead of clap's "unrecognized subcommand" stack trace.
  • No conditional skip, no `#[ignore]`, no workflow-level allow — the test runs every time and exercises real code.

🤖 Generated with Claude Code

Root-cause fix for the CI failures in PR #39 that survived its merge:
the readiness test `readiness_d_gui_headless_runs` invokes
`panic-attack gui --headless` and the `Test` job runs against the
default feature set. After #39 hid `Commands::Gui` behind
`#[cfg(feature = "gui")]`, the subcommand no longer existed in
default builds, so this test (and the `Readiness` workflow) fail
on every main-build until this lands.

The headless renderer is pure text output — it touches
`ReportFormatter` and the report data structures, never `eframe`
or `egui`. Move it into a new always-compiled module
`src/report/gui_text.rs` so the subcommand always exists and the
text path works regardless of the `gui` feature. The windowed
renderer stays gated.

Changes:
- `src/report/gui_text.rs` (new) — `run_headless` lifted verbatim
  from `gui::ReportGui::run_headless`. Always compiled.
- `src/report/gui.rs` — `run_headless` removed (now lives in
  `gui_text`); module header docstring updated to explain the
  feature gate.
- `src/report/mod.rs` — `pub mod gui_text;` always; `pub mod gui`
  stays `#[cfg(feature = "gui")]`.
- `src/main.rs` — drop `#[cfg(feature = "gui")]` from
  `Commands::Gui` (subcommand is always available). Match arm
  routes `--headless` to `gui_text::run_headless` always; the
  windowed path is `#[cfg(feature = "gui")]` with a
  `#[cfg(not(feature = "gui"))]` `anyhow::bail!` arm that gives a
  clear "rebuild with --features gui" error.

Verified locally:
* `cargo build`                        — default, OK
* `cargo build --features gui`         — opt-in, OK
* `cargo clippy --all-targets --features signing,http -- -D warnings` — clean
* `cargo clippy --all-targets --all-features -- -D warnings`         — clean
* `cargo test --test readiness`        — 20 passed, 0 failed (including
  `readiness_d_gui_headless_runs`)
* `cargo fmt --check`                  — clean

This is the principled fix: the readiness test stays meaningful on the
MSRV-clean default build, the windowed GUI stays opt-in, and a user
who tries non-headless mode without the feature gets a precise
actionable error instead of a clap "unrecognized subcommand".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 49 issues detected

Severity Count
🔴 Critical 4
🟠 High 16
🟡 Medium 29

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
    "type": "unpinned_action",
    "file": "governance.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Nickel file missing SPDX-License-Identifier header (1 occurrences, CWE-1104)",
    "type": "ncl_missing_spdx",
    "file": "/home/runner/work/panic-attack/panic-attack/reports/panic-attack-20260211180017.ncl",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "medium"
  },
  {
    "reason": "expect() in hot path (2 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/panic-attack/panic-attack/src/attestation/chain.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "medium"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (1 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/panic-attack/panic-attack/src/attestation/evidence.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "critical"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (1 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/panic-attack/panic-attack/src/ambush/mod.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "critical"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (3 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/panic-attack/panic-attack/src/kanren/strategy.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "critical"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (3 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/panic-attack/panic-attack/src/axial/mod.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "critical"
  },
  {
    "reason": "expect() in hot path (4 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/panic-attack/panic-attack/src/assail/analyzer.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "medium"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (4 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/panic-attack/panic-attack/benches/scan_bench.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "high"
  },
  {
    "reason": "expect() in hot path (2 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/panic-attack/panic-attack/benches/scan_bench.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "medium"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@hyperpolymath hyperpolymath enabled auto-merge (squash) May 26, 2026 09:23
@hyperpolymath hyperpolymath merged commit da58c21 into main May 26, 2026
28 checks passed
@hyperpolymath hyperpolymath deleted the fix/gui-headless-feature-independent branch May 26, 2026 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant