Skip to content

perf: draw the card grids from thumbnails - #406

Open
nGervasyuk wants to merge 2 commits into
Visual-Regression-Tracker:masterfrom
nGervasyuk:perf/use-thumbnails
Open

nGervasyuk wants to merge 2 commits into
Visual-Regression-Tracker:masterfrom
nGervasyuk:perf/use-thumbnails

Conversation

@nGervasyuk

Copy link
Copy Markdown
Collaborator

Needs backend#377, which stores the thumbnails. Without it every run simply has none and the grids fall back to what they draw today, so the two can ship in either order — but this one does nothing until the backend is out.

Why

Opening the variations dialog spent ~1.5 s and ~7 MB across 68 requests pulling a full-size diff for each of 34 cards, only for CSS to shrink each one to roughly a hundred pixels wide. Two requests per card — the API redirect, then the S3 GET — at ~200 kB apiece. The card grid in the run list did the same, and with Show diff off it pulled 1.4 MB screenshots instead.

What

A thumbnailFor helper beside the existing imageFor: it asks for the small copy the API stored at ingest, and falls back to the full-size picture when there is none. Both grids use it.

The lightbox behind a thumbnail deliberately keeps imageFor — the full-size image is the thing the reviewer opened it to look at.

Falling back

Runs ingested before thumbnails existed, and runs with no saved diff, have no small copy and render exactly as they do today. There is no probing and no broken image: the run either carries a name or it does not.

Tests

Four, watched failing first: the diff's small copy, the screenshot's small copy when Show diff is off, the fallback when nothing is stored, and the fallback when the run has no diff at all — that last one matters because a run can carry a stale diffThumbnailName while diffName is null, and the grid must not ask for a picture of a diff it is not showing.

jest 40/40, Playwright 93/93, tsc unchanged at its 30 pre-existing errors.

Opening the variations dialog spent ~1.5s and ~7 MB on 68 requests pulling a
full-size diff for each of 34 cards, only for CSS to shrink each one to about
a hundred pixels wide. The card grid in the run list did the same.

The API now stores a small copy of the checkpoint and of the diff beside each
run, so the grids ask for those instead. Runs from before those existed, and
runs with no saved diff, fall back to the full-size picture and look exactly
as they do today.

The lightbox behind a thumbnail keeps the full-size image: it is the picture
the reviewer opened it to look at.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

It introduces/extends a nullability mismatch around diffName (evidenced by null as never in tests) that should be resolved or consistently modeled to avoid masking real runtime contract issues.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR improves UI performance by rendering test-run card grids (run list and matching variations) from backend-provided thumbnail images instead of full-size screenshots/diffs, while preserving full-size images for the lightbox.

Changes:

  • Extend TestRun with optional imageThumbnailName / diffThumbnailName fields.
  • Add thumbnailFor() helper to prefer thumbnails for grids with a fallback to imageFor().
  • Update both grid renderers to use thumbnailFor() and add Jest coverage for thumbnail selection/fallback behavior.
File summaries
File Description
src/types/testRun.ts Adds thumbnail name fields to the TestRun model.
src/components/TestRunList/TestRunGrid.tsx Switches grid card images to use thumbnailFor() for thumbnail loading.
src/components/TestDetailsDialog/MatchingVariationsDialog.tsx Switches variation thumbnails to use thumbnailFor() while keeping imageFor() for the lightbox.
src/_helpers/testRunImage.helper.ts Introduces thumbnailFor() helper with fallback to full-size images.
src/_helpers/testRunImage.helper.test.ts Adds tests validating thumbnail selection and fallback behavior.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/types/testRun.ts Outdated
Comment on lines +7 to +11
diffName: string;
// Small copies the API makes at ingest for the card grids to draw. Absent on
// runs ingested before they existed, and on runs with no saved diff.
imageThumbnailName?: string;
diffThumbnailName?: string;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed, and done in 1c12cbd — this was the better half of the pair, so I took it rather than the empty-string workaround.

diffName has always arrived as null from the API: a run has no diff until it is compared, and none at all if it matched its baseline. Prisma has it as String? and the DTO narrowed it on the way out. The type was simply wrong, and being wrong is what pushed my test into a cast.

I measured the blast radius before committing, since a widened type usually is one: exactly three call sites objected, all of them places that already guarded — staticService.getImage opens with if (!name) return noImage, ImageDetails renders Loading... for a falsy name, DrawArea falls back to a placeholder. So it is four signature widenings and no behaviour change; tsc finishes on the same error count as before, the only difference being a pre-existing error whose line number moved by one.

I did not go further and tighten the call sites that assume diffName is present, as you suggested — those are pre-existing and unrelated to thumbnails, and this PR is already paired with a backend migration.


it("falls back when the run has no diff at all", () => {
const testRun = run({
diffName: null as never,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

You are right that the cast was the problem, though I went the other way on the remedy.

null as never is a type-escape standing exactly where the interesting behaviour is, and an empty string would have swapped it for a case that never actually occurs — the API sends null here, not "". That would leave the real path untested while looking tidy.

Fixed in 1c12cbd by making the type honest instead: TestRun.diffName is now string | null, and the test passes a plain null. See the reply on the sibling thread for why that turned out to cost nothing.

`TestRun.diffName` was typed as a required string while the API has always
sent null for it — a run has no diff until it has been compared, and none at
all if it matched its baseline. The type simply disagreed with the payload,
and my test for the no-diff case papered over that with `null as never`,
which is exactly the kind of cast that hides the thing it is standing on.

The type now admits null, and the cast is gone. Nothing needed a new guard:
every place this touches already checked for a falsy name and rendered a
placeholder or "Loading..." — `getImage`, `ImageDetails`, `DrawArea`. Only
their signatures said otherwise, so this is four type widenings and no
behaviour change. The two new thumbnail fields are typed the same way, for
the same reason.

Found by Copilot on Visual-Regression-Tracker#406.
@sonarqubecloud

sonarqubecloud Bot commented Sep 7, 2026

Copy link
Copy Markdown

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