Skip to content

GLOOK-60: hold the vulnerabilities page still when filters change - #76

Merged
maescomua merged 1 commit into
mainfrom
GLOOK-60-vuln-layout-stability
Sep 29, 2026
Merged

maescomua merged 1 commit into
mainfrom
GLOOK-60-vuln-layout-stability

Conversation

@maescomua

@maescomua maescomua commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes GLOOK-60. The Vulnerabilities page jumped whenever a filter changed. Fixing that took two steps:

  1. Scroll to top. useUrlState called router.push/router.replace without scroll: false, so every filter change scrolled the page to the top.
  2. Layout jumps. With the scroll fixed, the page still jumped. Almost every panel sized itself to its current data, so picking a team, a repo, a baseline or an alert chip resized a panel and moved everything below it. Near the bottom of the page, the browser then pulled the scroll position up to the new, shorter end of the page.

The fix follows one rule: a filter change may change what a slot shows, never the slot's size or where a control sits. Growth the user asks for, such as "Show 20 more" or expanding a coverage list, is exempt.

What changed

  • No scroll to top. UrlSchema gains an opt-in scroll?: false. The five URL-state fields on this page set it. Other pages keep their current behaviour.
  • The "By team" table keeps every team. It reads its own summary request without the team param, and highlights the selected row. The KPI tiles, trend, alerts and coverage stay scoped to the team. With no team selected, the two SWR keys are identical and dedupe to one request.
  • Baseline disclosure. Every available delta shows a vs <date> caption naming the snapshot it compares against. A fallback to an older snapshot is now visible.
  • Alerts panel:
    • Reserved height: it always reserves room for a full page of rows, 20 by default. "Show 20 more" now persists across filter changes, and the reservation follows it.
    • Fixed layout: the table is table-fixed, with fixed column widths that fit without horizontal scroll from 1024px up. Package is the one flexible column.
    • One-line cells: cells don't wrap. Long text ends with an ellipsis, and hovering shows the full value.
    • Always-present count line: "Showing N of M" always renders.
    • Reopened badge: it moves into the State cell, so a long CVE id is never cut off to make room for it.
  • Stable slots:
    • The team line always renders: "Showing all teams" or "Filtered to team X · clear".
    • The KPI captions reserve their lines.
    • The pivot's Δ caption reserves its line, and its column has a minimum width.
    • The trend legend reserves two lines.
    • The trend's empty state keeps the legend and note space, with the text hidden.
    • The Repo select has a fixed width.

Visible copy changes

  • New line: "Showing all teams" when no team is selected.
  • The truncated-list footnote goes from "Showing 200 of 340 alerts." to "200 of 340 alerts loaded.". A separate "Showing N of M" count line now always renders above it.
  • KPI tile 2 with no baseline measurement now shows "—", with "no measurement for this view before " as its caption. "N repos not in baseline" moves to its own line.

Not covered

  • Switching codebase still moves the page. Different codebases have different teams, so the pivot's row count really changes.
  • Coverage lists can still change size when the team changes. You open those lists yourself, and they sit at the bottom of the page.

Testing

  • Suite: npm test: 197 suites and 1969 tests pass. npx tsc --noEmit and npm run build are clean.

  • New tests:

    • page-level tests that each control writes the URL with scroll: false;
    • the unfiltered pivot;
    • the team line;
    • the reserved alerts height after "Show 20 more", including the clamp to loaded rows;
    • the column-to-header width mapping;
    • the trend skeleton matching between empty and populated states;
    • the KPI tile 2 states.

    jsdom has no layout engine, so these tests guard structure: slots, inline sizes and node identity. The real layout check was in a browser.

  • Mutation check: each fix was deliberately broken to confirm its tests fail. Every mutation jsdom can observe is caught.

  • Browser check: headless Chrome at 1440px and 1024px, against the mock server and a synthetic fixture. The fixture has 12 teams, a 45-alert team, a zero-alert team, long names, and deltas both available and unavailable. It also runs with responses delayed 800ms. Across 137–178 actions per run, it checked:

    • page height, including mid-load;
    • scroll position;
    • control positions and widths;
    • every table header's position and size;
    • row heights;
    • the trend panel's height with and without data;
    • a layout-shift score of at most 0.001.

    All six runs pass. The same gates fail on the code before this change.

🤖 Generated with Claude Code

Filter changes on /vulnerabilities scrolled the page to the top, and with
that fixed the page still jumped: almost every panel sized itself to its
current data, so a team, repo, baseline or chip change resized it and moved
everything below. Near the bottom of the page the browser then clamped the
scroll position.

- useUrlState gains an opt-in `scroll: false`; the vulnerabilities page's
  URL state uses it. Other pages are unchanged.
- The "By team" pivot reads its own unfiltered summary, so selecting a team
  highlights a row instead of collapsing the table.
- Deltas show a "vs <date>" caption naming the baseline snapshot, so a
  fallback to an older snapshot is visible.
- Every filter-driven slot keeps a fixed size: the alerts table reserves a
  page of rows (or the expanded page, which now persists across filters)
  with fixed column widths and one-line cells; the team line, KPI captions,
  pivot delta caption and trend legend/note always render; the Repo select
  has a fixed width.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@maescomua
maescomua merged commit c50d5a6 into main Sep 29, 2026
1 check passed
@maescomua
maescomua deleted the GLOOK-60-vuln-layout-stability branch September 29, 2026 17:23
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