Skip to content

output: braille summary visuals for scan Summary (TTY only) - #37

Open
buttonmonkey wants to merge 3 commits into
Corgea:mainfrom
buttonmonkey:feat/braille-summary-visuals
Open

output: braille summary visuals for scan Summary (TTY only)#37
buttonmonkey wants to merge 3 commits into
Corgea:mainfrom
buttonmonkey:feat/braille-summary-visuals

Conversation

@buttonmonkey

Copy link
Copy Markdown

What

Two small, dependency-free visual touches to the scan Summary, both TTY-only:

  1. Findings heat map — the "Most affected files" list gains a per-file braille bar: length = finding count (normalized to the busiest file), colour = worst severity in that file (reusing ui::severity_code).
  2. Severity mix bar — a single stacked braille bar under the ● N critical ● N high … line, segments sized by each severity's share and coloured by severity.

What it looks like

Colour doesn't render in GitHub code blocks — run it in a terminal to see it. Below, bar length = count and bar colour = severity (critical = bold red, high = red, medium = yellow, low = green).

A real multi-file scan — heat map (per file) + severity mix bar (overall blend):

Summary
  ● 2 critical   ● 7 high   ● 1 medium   ● 1 low
  ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿

     7  Command Injection
     2  Unsafe Deserialization
     1  Dynamic Import
     1  Open Redirect

Most affected files
  ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿     4  ./app/api/handlers.py
  ⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀     3  ./auth/session.py
  ⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀     2  ./db/query.py
  ⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀     1  ./utils/format.py
  ⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀     1  ./web/redirect.js

A balanced severity mix — the stacked bar reads the blend at a glance:

Summary
  ● 5 critical   ● 5 high   ● 5 medium   ● 5 low
  ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿

     5  Command Injection
     5  Dynamic Import
     5  Open Redirect
     5  Unsafe Deserialization

The mix bar is normalized to the total, so it shows the blend of severities rather than the volume.

Pipe-safety (the important guarantee)

Both visuals are gated behind ui::color_enabled() (TTY and NO_COLOR unset). When stdout is piped or NO_COLOR is set, the output falls back to the original plain list, byte-for-byte unchanged, and the JSON/CSV paths are untouched. Verified by diffing pre- vs post-change output:

  • sighthound sample | cat — identical
  • NO_COLOR=1 sighthound sample — identical
  • sighthound sample -o json / -o csv — identical

No new dependencies

Braille is packed by hand (U+2800 base), in the spirit of the dependency-free ui.rs. Cargo.toml / Cargo.lock are untouched. Bars are padded to a fixed 10 glyphs so the count column stays aligned; deterministic ordering (BTreeMap + existing sorts) is preserved.

Tests

Adds unit tests for the pure helpers in output.rs:

  • braille_bar — the char-count == width alignment invariant (across widths/fracs), fill math, out-of-range clamping, and partial-cell behaviour.
  • severity_rank — ordering including the unknown case.

Full suite green; no existing tests changed.

Try it

cargo build --release
./target/release/sighthound <a directory with a few findings>

Any tree with findings across several files / severities will show both visuals in a colour terminal; piping it (| cat) shows the unchanged plain output.

Scope: a single file, src/scanner/output.rs.

@asadeddin asadeddin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@buttonmonkey

Beautiful UI/UX contribution.

A couple of quick questions:
1- Any concern over accessibility for color blindness?
2- Most affected files, does it carry over the color scheme or will it always be red?

Image

@buttonmonkey

buttonmonkey commented Jul 10, 2026

Copy link
Copy Markdown
Author
  1. Potentially, the bars are mapped to your classification colors: critical, high, medium, low. Not sure of how to mitigate that as I don't know how to create a reliable pattern fill or some equivalent that would work in this setting. If I were to find that pattern, it would require changing your color values to match the pattern fills.
  2. The bars should equate to the classification types. Only the same color if they are all of the same type. (ex. all critical)
image

Comment thread src/scanner/output.rs Outdated
eatonte and others added 3 commits July 16, 2026 08:32
Add two dependency-free, TTY-only braille visuals to `print_summary`:

1. Findings heat map — the "Most affected files" list gains a per-file
   braille bar: length = finding count (normalised to the busiest file),
   colour = worst severity in that file (reusing ui::severity_code).
2. Severity mix bar — a single stacked braille bar under the
   `● N critical  ● N high  …` line, segments sized by count and coloured
   by severity.

Both are gated behind ui::color_enabled(): piped / NO_COLOR output falls
back to the original plain list, byte-for-byte unchanged, and JSON/CSV are
untouched. Braille is packed by hand (U+2800 base); no new dependencies.
Heat bars are padded to a fixed 10 glyphs so the count column stays aligned.

Adds unit tests for the pure helpers: the braille_bar width/alignment
invariant, fill math, clamping, partial-cell, and severity_rank ordering.
Per-segment rounding with .max(1.0) could push the stacked mix bar past
WIDTH (e.g. counts 997/1/1/1 rendered 27 cells). Allocate cells with the
largest-remainder method, reserving one cell per present severity: the bar
now never exceeds WIDTH and never drops a severity the tally above it lists,
so a lone critical among many highs stays visible.

Adds unit tests: total never exceeds WIDTH, fills exactly WIDTH when it fits,
every present severity keeps >=1 cell, absent severities get none.
…pers

Iterator form for the sub-column loop in braille_bar; rustfmt on the
mix-bar counts binding. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@buttonmonkey
buttonmonkey force-pushed the feat/braille-summary-visuals branch from bd26659 to 926adbe Compare July 16, 2026 12:34
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.

4 participants