Skip to content

feat(cli): align severity badges in finding tables - #122

Open
mldangelo wants to merge 1 commit into
openai:mainfrom
mldangelo:fix/severity-badge-alignment
Open

feat(cli): align severity badges in finding tables#122
mldangelo wants to merge 1 commit into
openai:mainfrom
mldangelo:fix/severity-badge-alignment

Conversation

@mldangelo

@mldangelo mldangelo commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

src/scan-history-renderer.ts pads the severity badge with severity.padEnd(8). That is a no-op for INFORMATIONAL, which is thirteen characters, so informational findings push their title five columns right of every other severity:

    HIGH      Short severity row
    INFORMATIONAL  Long severity row

INFORMATIONAL is a normal value, not an edge case — it is the longest key in SEVERITY_COLORS and it appears in DISPLAY_SEVERITIES in src/cli.ts. Any scan that reports an informational finding renders a broken table in scans compare and scans show.

The underlying problem is that the badge width and the two indents that must line up beneath it were three independent literals — padEnd(8), wrap(title, 14, ...), and a hardcoded 14-space path indent — plus two more at +2/+4 for the nested LINKED FINDINGS block. They agreed only for as long as every label happened to fit in eight columns.

What this changes

All five are now derived from one place:

const SEVERITY_LABELS: Record<string, string> = { INFORMATIONAL: "INFO" };
const SEVERITY_BADGE_WIDTH = Math.max(
  ...Object.keys(SEVERITY_COLORS).map((s) => (SEVERITY_LABELS[s] ?? s).length),
);
const FINDING_INDENT = 4 + SEVERITY_BADGE_WIDTH + 2;

Both values are unchanged: width 8, indent 14. So the only difference in rendered output is the label itself, INFORMATIONALINFO. Adding a longer severity in future automatically widens the column and moves every dependent indent with it, rather than silently breaking alignment again.

The one judgment call — please confirm

I chose to abbreviate rather than widen the column to 13. Widening is the alternative and it keeps the full word, but it costs every finding title five characters: at the 48-column minimum, titles drop from 32 usable characters to 27, and finding titles are routinely 40–60 characters, so they would wrap considerably more. INFO is unambiguous and the badge is a compact status column, so the trade seemed clearly worth it — but it is a user-visible label change, so say the word and I will switch to widening instead.

This affects only the TTY history view. SARIF/CSV/JSON exports go through a different path and are untouched.

Known remaining limitation: an unrecognized severity longer than eight characters still misaligns. I deliberately did not truncate arbitrary values, since mangling a future legitimate severity name seemed worse than misaligning an anomalous one. (A related ordering bug for unknown severities — they sorted above CRITICAL because indexOf returns -1 — is fixed in the companion PR below.)

Testing / QA instructions

Baseline before this branch: 470 pass / 6 skip / 0 fail. After: 472 pass / 6 skip / 0 fail (two added tests).

cd sdk/typescript          # run pnpm from here, not the repo root
CI=true pnpm install --frozen-lockfile
CI=true pnpm run types
CI=true pnpm run test
CI=true pnpm run format
CI=true pnpm run build

Confirm the new tests actually catch the bug

The two added tests must fail against main's renderer. Verify by reverting only the source and keeping the tests:

cd sdk/typescript
cp src/scan-history-renderer.ts /tmp/fixed.ts
git checkout main -- src/scan-history-renderer.ts
CI=true bun test --timeout 30000 ./tests-ts/scan-history-renderer.test.ts   # expect 6 pass / 2 fail
cp /tmp/fixed.ts src/scan-history-renderer.ts
CI=true bun test --timeout 30000 ./tests-ts/scan-history-renderer.test.ts   # expect 8 pass / 0 fail

Note that plain git stash does not work for this check — the tests live in the same file, so stashing removes them too and you get a misleading 6 pass / 0 fail.

New coverage — severity badge column

  1. starts every finding title at the same column — renders all five severities and asserts every title begins at the same offset, across color: false/true and widths 48/96/120. Also asserts the path line beneath each badge lines up with the title above it, which is the indent coupling.
  2. keeps the badge column as wide as its widest label — asserts the exact string INFO Informational finding and that INFORMATIONAL no longer appears.

Existing tests are unmodified

All six pre-existing tests in this file pass untouched. They assert relative ordering via text.indexOf(...) rather than badge text, which is why the label change does not churn them. Their exact-text assertions on other rows are the guard that indent 14 really did stay 14.

Manual check

cd sdk/typescript && bun -e '
import { renderScanHistory } from "./src/scan-history-renderer.js";
console.log(renderScanHistory({ beforeScanId:"b", afterScanId:"a",
  coverage:{afterCompleteness:"complete"}, summary:{new:2}, findings:[
    {severity:"high",title:"Short severity row",status:"new",locations:[{path:"a.ts",startLine:1}]},
    {severity:"informational",title:"Long severity row",status:"new",locations:[{path:"b.ts",startLine:2}]},
  ]}, "compare", {color:false}));'

Note for maintainers

This file is also touched by PR "fix: keep scan history rendering total over workbench payloads". Different regions (badge padding and indents here; field accessors there), but whichever lands second may need a trivial rebase.

`severity.padEnd(8)` is a no-op for INFORMATIONAL, which is thirteen
characters, so informational findings pushed their title five columns to
the right of every other severity and broke the table:

      HIGH      Short severity row
      INFORMATIONAL  Long severity row

The badge width and the two indents that line up beneath it were three
independent literals (8, 14, 14), so they only agreed while every label
happened to fit in eight columns.

Derive all of them from the label set and abbreviate INFORMATIONAL to
INFO. Both values are unchanged at 8 and 14, so only the informational
label differs; spelling the word out instead would cost every finding
title five characters, which matters most at the 48-column minimum.
@mldangelo
mldangelo force-pushed the fix/severity-badge-alignment branch from e493e05 to dc31734 Compare July 30, 2026 13:43
@mldangelo-oai mldangelo-oai added the enhancement New feature or request label Aug 3, 2026
@mldangelo-oai mldangelo-oai changed the title fix: align the severity badge column in finding tables feat(cli): align severity badges in finding tables Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants