Skip to content

feat(tui): compliance panel pq_mode gate + export menu (refs #248)#259

Merged
peaktwilight merged 3 commits intomainfrom
feat/248-compliance-gate-and-export
Apr 24, 2026
Merged

feat(tui): compliance panel pq_mode gate + export menu (refs #248)#259
peaktwilight merged 3 commits intomainfrom
feat/248-compliance-gate-and-export

Conversation

@Darkroom4364
Copy link
Copy Markdown
Collaborator

@Darkroom4364 Darkroom4364 commented Apr 22, 2026

Summary

  • Bug fix: compliance panel (Shift+N) was visible in all scan modes — now gated to Pqc mode only via self.request.pq_mode
  • Feature: e key opens a CBOM / JSON / SARIF export picker, writes findings to findings.{cbom,sarif}.json or findings.json
  • Made build_cbom and build_sarif public so the TUI can reuse them without stdout

Test plan

  • cargo test --lib — 378 tests pass
  • cargo clippy — clean
  • Manual: Pqc mode → Shift+N shows panel; Scan mode → Shift+N does not
  • Manual: run with findings → e → pick CBOM → verify file written

Summary by CodeRabbit

  • New Features

    • Export findings via a new export menu (press e) to JSON, SARIF, or CBOM; supports Enter to confirm, arrow/vim j/k navigation.
  • Behavior Changes

    • Export blocked when no results; deterministic filenames for exports; runtime notices for success, failure, and empty CBOM outputs.
  • Bug Fixes

    • CNSA 2.0 compliance panel now only shows when appropriate (visibility gated by PQC mode).
  • Tests

    • Added unit tests for export menu flows and compliance panel visibility.

…248)

- compliance panel now requires pq_mode, hidden in Scan/Diff/Secrets
- new 'e' key opens CBOM/JSON/SARIF export picker
- make build_cbom/build_sarif public for TUI reuse
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Refactors SARIF and CBOM generation into public builder functions and adds a TUI export modal to write CBOM/JSON/SARIF files; export is blocked when no results, emits success/failure notices, and CBOM export warns on empty cryptographic findings. Also gates CNSA 2.0 compliance panel on PQ-mode and adds unit tests.

Changes

Cohort / File(s) Summary
Report builders
src/report/sarif.rs, src/report/cbom.rs
Extracted SARIF construction into pub fn build_sarif(findings) -> serde_json::Value; made build_cbom public (pub fn build_cbom(...) -> (serde_json::Value, bool)). print_sarif now delegates to build_sarif.
TUI export feature & UI
src/tui.rs
Added export_menu: Option<ExportMenu>, e keybind, modal rendering and navigation, export handling that blocks when no results, writes deterministic filenames for CBOM/JSON/SARIF, emits runtime notices on success/failure, and warns when CBOM is empty. Adjusted CNSA 2.0 compliance panel visibility to require request.pq_mode.
Tests
tests/*, src/tui.rs (tests)
Added/updated unit tests covering export menu open/no-op behavior, CBOM file writing and empty-CBOM warnings, and compliance panel visibility gating on PQ-mode.

Sequence Diagram(s)

sequenceDiagram
  participant User as "User"
  participant TUI as "TuiApp"
  participant Builder as "Report Builder(s)"
  participant FS as "FileSystem"
  participant Notice as "RuntimeNotice"

  User->>TUI: press "e"
  TUI->>TUI: show Export Modal (CBOM / JSON / SARIF)
  User->>TUI: select format + confirm
  TUI->>Builder: request payload (build_cbom / build_sarif / serialize)
  Builder-->>TUI: JSON payload (or (payload, empty_flag))
  TUI->>FS: write pretty JSON to deterministic file path
  FS-->>TUI: success / error
  alt success
    TUI->>Notice: emit success notice with path/count
    alt CBOM empty_flag == true
      TUI->>Notice: emit "CBOM empty (no crypto findings)" warning
    end
  else failure
    TUI->>Notice: emit "export failed: ..." notice
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I nudged the modal, keys in a spin,
Builders hummed, the bytes tumbled in.
CBOM, SARIF, JSON — files take flight,
A rabbit’s small hop made export just right. 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly reflects the two main changes in the PR: a compliance panel pq_mode gate and an export menu feature. Both changes are clearly present and primary in the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/248-compliance-gate-and-export

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 22, 2026

Greptile Summary

This PR gates the CNSA 2.0 compliance panel (Shift+N) to PQC scan mode only and introduces a new e-key export picker that writes findings as CBOM (CycloneDX 1.6), plain JSON, or SARIF. The report-layer changes are minimal: build_cbom is made pub and build_sarif is extracted as a pure pub function so the TUI can reuse both without touching stdout.

Key changes:

  • show_compliance draw gate now requires self.request.pq_mode in addition to result.is_some(), fixing the panel being visible in non-PQC modes
  • New ExportMenu / ExportFormat types + open_export_menu, handle_export_menu_key, export_findings, and draw_export_menu wired into the existing modal dispatch chain
  • build_sarif extracted from print_sarif (no logic change); build_cbom visibility lifted to pub
  • 4 new unit tests cover menu open/close, no-result guard, and CBOM file write

Issues found:

  • export_writes_cbom_file test calls std::env::set_current_dir — a process-global mutation — without coordinating with the parallel Rust test runner; can cause intermittent failures in other tests that use relative paths (P1)
  • draw_export_menu uses centered_rect(40, 20, …) which provides only ~4–6 inner rows on typical 24–40 row terminals, fewer than the 7 rows the layout requires; the footer hint will be silently clipped (P2)
  • export_findings uses std::fs::write unconditionally, silently overwriting any existing export files without user confirmation (P2)

Confidence Score: 3/5

Safe to merge after fixing the thread-unsafe set_current_dir test; the runtime feature itself is sound but the test can cause flaky CI failures in parallel runs.

The production logic (pq_mode gate and export flow) is correct and well-tested conceptually. However the export_writes_cbom_file test mutates process-global state (set_current_dir) without coordination, which is a real reliability risk in the default parallel test runner — not merely theoretical. The two P2 items (popup sizing and silent overwrite) are non-blocking but worth addressing before shipping.

src/tui.rs — specifically the export_writes_cbom_file test and draw_export_menu popup sizing

Important Files Changed

Filename Overview
src/tui.rs Adds export menu (Cbom/Json/Sarif via e key) and gates compliance panel on pq_mode; the export test uses set_current_dir which is not thread-safe in parallel test execution, the popup is sized too small for typical terminals (20% height), and existing export files are silently overwritten.
src/report/sarif.rs Extracts build_sarif as a public pure function returning serde_json::Value; print_sarif now delegates to it — clean refactor with no logic changes.
src/report/cbom.rs Promotes build_cbom from private to pub — single-character visibility change, no logic altered.

Sequence Diagram

sequenceDiagram
    participant User
    participant TuiApp
    participant ExportMenu
    participant report_cbom as report::cbom
    participant report_sarif as report::sarif
    participant FS as std::fs

    User->>TuiApp: press 'e'
    TuiApp->>TuiApp: open_export_menu()
    alt result is None
        TuiApp-->>User: push_runtime_notice("no results to export")
    else result exists
        TuiApp->>ExportMenu: create (Cbom | Json | Sarif, selected=0)
        TuiApp-->>User: draw_export_menu()
    end

    User->>TuiApp: navigate with j/k or arrow keys
    TuiApp->>ExportMenu: update selected index

    User->>TuiApp: press Enter
    TuiApp->>TuiApp: export_findings(format)
    alt ExportFormat::Cbom
        TuiApp->>report_cbom: build_cbom(findings)
        report_cbom-->>TuiApp: (Value, has_pqc_risk)
    else ExportFormat::Sarif
        TuiApp->>report_sarif: build_sarif(findings)
        report_sarif-->>TuiApp: Value
    else ExportFormat::Json
        TuiApp->>TuiApp: serde_json::to_string_pretty(findings)
    end
    TuiApp->>FS: write(filename, content)
    FS-->>TuiApp: Ok(()) or Err
    TuiApp-->>User: push_runtime_notice("exported N findings to …")

    User->>TuiApp: press Shift+N
    TuiApp->>TuiApp: toggle show_compliance_panel
    Note over TuiApp: draw gate: show_compliance_panel && result.is_some() && request.pq_mode
Loading

Reviews (1): Last reviewed commit: "feat(tui): gate compliance panel to pqc ..." | Re-trigger Greptile

Comment thread src/tui.rs Outdated
Comment on lines +4414 to +4416
std::env::set_current_dir(dir.path()).expect("set_current_dir");
app.export_findings(ExportFormat::Cbom);
std::env::set_current_dir(&original).expect("restore cwd");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Thread-unsafe set_current_dir in parallel test

std::env::set_current_dir is a process-wide operation, and Rust runs tests within the same module in parallel by default. Between set_current_dir(dir.path()) and set_current_dir(&original) on line 4416, any other concurrently-running test that opens a relative path (e.g., the path: ".".to_string() in tui_app_with_findings, or apply_action_lower_severity_writes_override_and_replaces and apply_action_disable_rule_globally_appends_and_detects_duplicate) will observe the temp directory as cwd — potentially causing spurious failures or incorrect file writes.

The correct fix is to refactor export_findings to accept an explicit output path, or test at a lower level by calling std::fs::write(dir.path().join("findings.cbom.json"), ...) directly — avoiding mutation of shared global state entirely.

Comment thread src/tui.rs Outdated
Comment on lines +1799 to +1825
let area = centered_rect(40, 20, frame.area());
let items = menu
.formats
.iter()
.map(|fmt| ListItem::new(Line::from(Span::styled(fmt.label(), Style::default()))))
.collect::<Vec<_>>();
let list = List::new(items)
.block(panel_block(None, PANEL_BG))
.highlight_style(
Style::default()
.fg(Color::White)
.bg(DETAIL_BG)
.add_modifier(Modifier::BOLD),
)
.highlight_symbol("> ");
let inner = area.inner(Margin {
vertical: 1,
horizontal: 1,
});
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(1),
Constraint::Length(menu.formats.len() as u16 + 2),
Constraint::Length(1),
])
.split(inner);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Export menu too small for typical terminal heights

centered_rect(40, 20, frame.area()) allocates only 20% of the terminal height for the popup. The inner area (after Margin { vertical: 1, horizontal: 1 }) is area_height − 2. The layout requires 1 + (formats.len() as u16 + 2) + 1 = 7 rows (1 header + 5 for the bordered list + 1 footer).

The popup must be at least 9 rows tall (7 inner + 2 for the outer border), so the terminal must be at least ⌈9 / 0.20⌉ = 45 rows. A typical 80×24 terminal gives only ~4 rows — ratatui will silently clip the list and the footer hint ("Enter export Esc cancel") will never render.

Consider raising the percentage (e.g. centered_rect(40, 40, …)) similar to how draw_severity_picker uses centered_rect(44, 34, …).

Comment thread src/tui.rs
Comment on lines +522 to +536
fn export_findings(&mut self, format: ExportFormat) {
let findings = match self.result.as_ref() {
Some(r) => &r.findings,
None => return,
};

let filename = format.filename();
let content = match format {
ExportFormat::Cbom => {
let (cbom, _) = crate::report::cbom::build_cbom(findings);
serde_json::to_string_pretty(&cbom).expect("Failed to serialize CBOM")
}
ExportFormat::Json => {
serde_json::to_string_pretty(findings).expect("Failed to serialize findings")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Silent overwrite of existing export files

std::fs::write(filename, &content) unconditionally replaces any pre-existing file. A user who has manually edited or version-controlled their findings.cbom.json / findings.json / findings.sarif.json will lose that data without any warning.

Consider checking for existence first and either (a) surfacing a confirmation notice, or (b) appending a timestamp to make each export unique (e.g. findings.cbom.1714000000.json).

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/tui.rs`:
- Around line 4409-4416: The test must not call std::env::set_current_dir
(process-global); change the test to pass the tempdir path into the export
routine instead: update export_findings (or add a new method like
export_findings_to_dir) to accept a destination Path/PathBuf (or
Option<PathBuf>) and use that in the ExportFormat::Cbom branch, then in the test
call app.export_findings_to_dir(ExportFormat::Cbom, dir.path()) (using
tempfile::tempdir() as before) and remove the std::env::set_current_dir/restore
calls; reference the existing symbols app.export_findings, ExportFormat::Cbom,
tui_app_with_findings and tempfile to locate where to add the parameter/overload
and change the test.
- Around line 530-533: In the ExportFormat::Cbom branch, capture the second
return value from crate::report::cbom::build_cbom (the
empty_but_findings_present flag) instead of discarding it; if
empty_but_findings_present is true, surface the condition (e.g., log a warning
or return an error/result that indicates CBOM cannot be produced) rather than
serializing an empty CBOM, otherwise proceed to
serde_json::to_string_pretty(&cbom); reference the ExportFormat::Cbom match arm,
the build_cbom function and the empty_but_findings_present flag when making the
change so the TUI mirrors the warning behavior in the cbom report code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 52fda49f-975d-438e-a88a-e37b303bb3b2

📥 Commits

Reviewing files that changed from the base of the PR and between 322883d and 067843b.

📒 Files selected for processing (3)
  • src/report/cbom.rs
  • src/report/sarif.rs
  • src/tui.rs

Comment thread src/tui.rs
Comment on lines +530 to +533
ExportFormat::Cbom => {
let (cbom, _) = crate::report::cbom::build_cbom(findings);
serde_json::to_string_pretty(&cbom).expect("Failed to serialize CBOM")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Surface the empty-CBOM case instead of silently exporting it

build_cbom returns the empty_but_findings_present flag for exactly the "findings exist, but none are cryptographic" case. Dropping it here means Scan/Secrets/Diff exports can write an empty CBOM and still report success, which looks like a broken export flow. src/report/cbom.rs:294-300 already turns this same signal into a warning; the TUI path should do the same or hide the CBOM option when it cannot produce meaningful output.

Possible fix
     fn export_findings(&mut self, format: ExportFormat) {
         let findings = match self.result.as_ref() {
             Some(r) => &r.findings,
             None => return,
         };
 
         let filename = format.filename();
-        let content = match format {
+        let finding_count = findings.len();
+        let mut export_notice = None;
+        let content = match format {
             ExportFormat::Cbom => {
-                let (cbom, _) = crate::report::cbom::build_cbom(findings);
+                let (cbom, empty_but_findings_present) = crate::report::cbom::build_cbom(findings);
+                if empty_but_findings_present {
+                    export_notice = Some(
+                        "CBOM export is empty: no cryptographic findings detected".to_string(),
+                    );
+                }
                 serde_json::to_string_pretty(&cbom).expect("Failed to serialize CBOM")
             }
             ExportFormat::Json => {
                 serde_json::to_string_pretty(findings).expect("Failed to serialize findings")
             }
             ExportFormat::Sarif => {
                 let sarif = crate::report::sarif::build_sarif(findings);
                 serde_json::to_string_pretty(&sarif).expect("Failed to serialize SARIF")
             }
         };
+
+        if let Some(notice) = export_notice {
+            self.push_runtime_notice(notice);
+        }
 
         match std::fs::write(filename, &content) {
             Ok(()) => {
                 self.push_runtime_notice(format!(
                     "exported {} findings to {}",
-                    findings.len(),
+                    finding_count,
                     filename
                 ));
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ExportFormat::Cbom => {
let (cbom, _) = crate::report::cbom::build_cbom(findings);
serde_json::to_string_pretty(&cbom).expect("Failed to serialize CBOM")
}
fn export_findings(&mut self, format: ExportFormat) {
let findings = match self.result.as_ref() {
Some(r) => &r.findings,
None => return,
};
let filename = format.filename();
let finding_count = findings.len();
let mut export_notice = None;
let content = match format {
ExportFormat::Cbom => {
let (cbom, empty_but_findings_present) = crate::report::cbom::build_cbom(findings);
if empty_but_findings_present {
export_notice = Some(
"CBOM export is empty: no cryptographic findings detected".to_string(),
);
}
serde_json::to_string_pretty(&cbom).expect("Failed to serialize CBOM")
}
ExportFormat::Json => {
serde_json::to_string_pretty(findings).expect("Failed to serialize findings")
}
ExportFormat::Sarif => {
let sarif = crate::report::sarif::build_sarif(findings);
serde_json::to_string_pretty(&sarif).expect("Failed to serialize SARIF")
}
};
if let Some(notice) = export_notice {
self.push_runtime_notice(notice);
}
match std::fs::write(filename, &content) {
Ok(()) => {
self.push_runtime_notice(format!(
"exported {} findings to {}",
finding_count,
filename
));
}
Err(e) => {
self.push_runtime_error(format!("Failed to export findings: {}", e));
}
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tui.rs` around lines 530 - 533, In the ExportFormat::Cbom branch, capture
the second return value from crate::report::cbom::build_cbom (the
empty_but_findings_present flag) instead of discarding it; if
empty_but_findings_present is true, surface the condition (e.g., log a warning
or return an error/result that indicates CBOM cannot be produced) rather than
serializing an empty CBOM, otherwise proceed to
serde_json::to_string_pretty(&cbom); reference the ExportFormat::Cbom match arm,
the build_cbom function and the empty_but_findings_present flag when making the
change so the TUI mirrors the warning behavior in the cbom report code.

Comment thread src/tui.rs Outdated
- Accept explicit output path in export_findings_to, removing
  thread-unsafe set_current_dir from test
- Surface empty-CBOM notice when no cryptographic findings detected
- Bump export popup height from 20% to 40% for small terminals
- Fix cargo fmt
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/tui.rs (1)

1677-1692: ⚠️ Potential issue | 🟡 Minor

Mark Shift+N as Pqc-only in the help copy.

The panel is now gated by request.pq_mode, but the help still advertises Shift+N as a general toggle. In Scan/Diff/Secrets modes that makes the shortcut look broken.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tui.rs` around lines 1677 - 1692, Update the help text entry that
currently reads Line::from("Shift+N        toggle CNSA 2.0 compliance panel") to
indicate it's PQC-only (for example "Shift+N (PQC only)        toggle CNSA 2.0
compliance panel") so users in Scan/Diff/Secrets modes don't assume the shortcut
is available; keep the existing request.pq_mode gating intact (the display logic
that uses request.pq_mode should not be changed).
🧹 Nitpick comments (1)
src/tui.rs (1)

4257-4272: This test mirrors the implementation instead of exercising it.

compliance_panel_hidden_outside_pqc_mode re-evaluates the same predicate used in draw_body, so it stays green even if rendering stops honoring that gate. Please assert through a shared helper or a rendered buffer so the test covers real behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tui.rs` around lines 4257 - 4272, The test
compliance_panel_hidden_outside_pqc_mode currently re-checks the same predicate
used in draw_body instead of exercising rendering; update the test to toggle
fields (app.show_launch/app.show_compliance_panel and ensure app.request.pq_mode
is false), then call the actual render routine used by the app (e.g., the draw
or render_to_buffer method that invokes draw_body) and assert the compliance
panel's UI is not present in the rendered buffer (e.g., by checking for the
panel title or specific text), rather than re-evaluating the predicate;
reference draw_body, show_compliance_panel, and request.pq_mode when locating
the code to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/tui.rs`:
- Around line 1811-1837: The popup currently uses centered_rect(40, 40,
frame.area()) which can shrink below the required content height; instead
calculate the dialog's required rows from content (e.g. let required_rows = 1
/*header*/ + menu.formats.len() as u16 + 2 /*footer/margins*/ + 2 /*outer
margins*/), clamp it to the terminal height (let term_h = frame.area().height;
let height_rows = required_rows.min(term_h.saturating_sub(0)) ), convert that to
a height percentage (let height_pct = (height_rows.saturating_mul(100) /
term_h).max(1)), and call centered_rect(width_pct, height_pct, frame.area());
keep the inner/layout code using Constraint::Length(menu.formats.len() as u16 +
2) so the list gets its full required length when terminal is large enough and
otherwise is clamped by the computed height_pct.

---

Outside diff comments:
In `@src/tui.rs`:
- Around line 1677-1692: Update the help text entry that currently reads
Line::from("Shift+N        toggle CNSA 2.0 compliance panel") to indicate it's
PQC-only (for example "Shift+N (PQC only)        toggle CNSA 2.0 compliance
panel") so users in Scan/Diff/Secrets modes don't assume the shortcut is
available; keep the existing request.pq_mode gating intact (the display logic
that uses request.pq_mode should not be changed).

---

Nitpick comments:
In `@src/tui.rs`:
- Around line 4257-4272: The test compliance_panel_hidden_outside_pqc_mode
currently re-checks the same predicate used in draw_body instead of exercising
rendering; update the test to toggle fields
(app.show_launch/app.show_compliance_panel and ensure app.request.pq_mode is
false), then call the actual render routine used by the app (e.g., the draw or
render_to_buffer method that invokes draw_body) and assert the compliance
panel's UI is not present in the rendered buffer (e.g., by checking for the
panel title or specific text), rather than re-evaluating the predicate;
reference draw_body, show_compliance_panel, and request.pq_mode when locating
the code to change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3b442538-f8ef-4c0b-b78e-5106a126b937

📥 Commits

Reviewing files that changed from the base of the PR and between 067843b and 24ca505.

📒 Files selected for processing (1)
  • src/tui.rs

Comment thread src/tui.rs
Comment on lines +1811 to +1837
let area = centered_rect(40, 40, frame.area());
let items = menu
.formats
.iter()
.map(|fmt| ListItem::new(Line::from(Span::styled(fmt.label(), Style::default()))))
.collect::<Vec<_>>();
let list = List::new(items)
.block(panel_block(None, PANEL_BG))
.highlight_style(
Style::default()
.fg(Color::White)
.bg(DETAIL_BG)
.add_modifier(Modifier::BOLD),
)
.highlight_symbol("> ");
let inner = area.inner(Margin {
vertical: 1,
horizontal: 1,
});
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(1),
Constraint::Length(menu.formats.len() as u16 + 2),
Constraint::Length(1),
])
.split(inner);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

The export modal still clips on small terminals.

Line 1811 still sizes the popup purely by percentage. This layout needs at least 9 rows end-to-end, but a 20-line terminal only gives the modal 8 rows at 40%, so the list/footer gets truncated. Please size the dialog from its content height and then clamp to the terminal instead of relying on a fixed percentage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tui.rs` around lines 1811 - 1837, The popup currently uses
centered_rect(40, 40, frame.area()) which can shrink below the required content
height; instead calculate the dialog's required rows from content (e.g. let
required_rows = 1 /*header*/ + menu.formats.len() as u16 + 2 /*footer/margins*/
+ 2 /*outer margins*/), clamp it to the terminal height (let term_h =
frame.area().height; let height_rows =
required_rows.min(term_h.saturating_sub(0)) ), convert that to a height
percentage (let height_pct = (height_rows.saturating_mul(100) / term_h).max(1)),
and call centered_rect(width_pct, height_pct, frame.area()); keep the
inner/layout code using Constraint::Length(menu.formats.len() as u16 + 2) so the
list gets its full required length when terminal is large enough and otherwise
is clamped by the computed height_pct.

@Darkroom4364 Darkroom4364 self-assigned this Apr 23, 2026
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.

2 participants