Skip to content

perf(desktop-gpui): bound capped screenshot scans like recordings - #2753

Open
celeroncoder wants to merge 2 commits into
CapSoftware:mainfrom
celeroncoder:perf/gpui-bounded-screenshot-and-recovery-scans
Open

celeroncoder wants to merge 2 commits into
CapSoftware:mainfrom
celeroncoder:perf/gpui-bounded-screenshot-and-recovery-scans

Conversation

@celeroncoder

@celeroncoder celeroncoder commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Every capped screenshot view scanned the whole screenshots library before truncating. list_screenshots_in parsed recording-meta.json for every .cap bundle, then read_dir'd the bundle to find its PNG, then stat'ed it, and only afterwards did the callers keep 9 (RECENT_MEDIA_LIMIT, Recents and the tray's Previous menu via recent_media_in) or 20 (LIBRARY_PANEL_LIMIT, the main window's Screenshots tab). Each screenshot capture triggers this three times (tray refresh, settings refresh, library refresh). #2532 fixed the same shape for recordings and left screenshots as is.

Change

screenshot_items_in mirrors recording_items_in from #2532:

  • Phase 1 is stat-only: for each .cap directory it stats original.png (the file screenshot.rs and import.rs write) and falls back to the old first-*.png read_dir search for legacy bundles. Standard bundles now cost one stat instead of read_dir plus stat.
  • Stable sort by the PNG's timestamp, newest first, so equal timestamps keep read_dir order exactly as before.
  • Phase 2 is lazy: recording-meta.json is parsed only for the items a caller consumes, and bundles whose meta fails to parse are skipped as before.

list_screenshots_in / list_screenshots are now collect() wrappers, so the settings page keeps its full listing. New list_screenshots_limited(limit); scan_screenshots takes RECENT_MEDIA_LIMIT and the Screenshots tab takes LIBRARY_PANEL_LIMIT.

-let mut items = library::list_screenshots();
-items.truncate(library::LIBRARY_PANEL_LIMIT);
+let items = library::list_screenshots_limited(library::LIBRARY_PANEL_LIMIT);

One new test builds a temp library (30 bundles 6 ms apart, a legacy shot.png bundle, a bundle without a PNG, a non-.cap directory, a loose.cap file, a newest bundle with corrupt meta, and two bundles whose PNGs are hard links so they share a timestamp) and asserts: the full listing equals a verbatim copy of the old code; the tied pair keeps read_dir order; take(K) equals the first K of the full listing for K in {0, 1, 9, 20, 100}; and after taking 9, deleting the remaining metas makes the same iterator yield nothing, which proves the parse is deferred.

Results

Real crate functions on a temp library, debug build, warm page cache, 2 vCPU aarch64 Linux box. Medians of 21 samples, p95 in brackets.

Bundles Limit main @ 2a61427 (scan + truncate) This PR (take(limit)) This PR, full listing
400 9 12.6 ms [15.5] 2.7 ms [2.9] 7.7 ms [7.9]
400 20 11.9 ms [12.3] 2.9 ms [3.6] 8.2 ms [10.6]
1,200 9 38.6 ms [41.4] 9.2 ms [9.5] 27.7 ms [29.6]
1,200 20 37.7 ms [48.8] 9.1 ms [9.6] 26.9 ms [31.7]

The capped scan is still O(N) stats plus a sort; only the meta parsing is bounded. Cold-cache and macOS numbers weren't measured.

Two small behaviour notes: a bundle with several top-level PNGs now prefers original.png instead of whichever read_dir returned first (the screenshot editor already assumes original.png), and the sort happens before the meta parse, the same window recording_items_in already has.

Overlap

How this was made

This PR is agent-generated.

  • The finding comes from an audit of main by OpenCode scouts and Gemini 3.8 Flash, re-checked against the source by the orchestrating Claude session; an earlier attempt at this change was rejected in review for a compile error and a test that read the real library, and was not reused.
  • Claude Opus 5.5 wrote this change and the test on current main and took the measurements above with the real crate functions.
  • Gemini 3.8 Flash (Antigravity) reviewed the diff independently: it checked the ordering and tie-break equivalence, the original.png fallback, the laziness test, that no test touches the real library, re-ran cargo check and the library:: and main_window:: tests, and approved.

🤖 Generated with Claude Code

The tray recents and the main window's screenshots panel parsed every
bundle's recording-meta.json before truncating to 9 / 20 items. Screenshot
listing now stats each bundle's PNG first (original.png, falling back to a
directory search for legacy bundles), sorts by that timestamp, and parses
meta lazily, so capped callers only parse the bundles they keep. The full
listing used by the settings page is unchanged.
@vercel

vercel Bot commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

@celeroncoder is attempting to deploy a commit to the Cap Software Inc Team on Vercel.

A member of the Team first needs to authorize it.

@celeroncoder

Copy link
Copy Markdown
Contributor Author

cc @richiemcilroy for review (GitHub doesn't let a fork request reviewers, so tagging instead).

Comment on lines +1589 to +1593
assert!(
full[1..]
.windows(2)
.all(|pair| pair[0].sort_time_millis > pair[1].sort_time_millis)
);

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.

P2 Timestamp ties fail the test

If the temporary filesystem has timestamp precision coarser than 6 ms, consecutive PNGs can share a timestamp despite the sleeps. The strict > assertion then fails even when the listing is correct. The assertions that the hard-linked pair occupies the first two positions have the same dependency.

Use controlled sort times, or allow other ties and locate the hard-linked pair by path.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop-gpui/src/library.rs
Line: 1589-1593

Comment:
**Timestamp ties fail the test**

If the temporary filesystem has timestamp precision coarser than 6 ms, consecutive PNGs can share a timestamp despite the sleeps. The strict `>` assertion then fails even when the listing is correct. The assertions that the hard-linked pair occupies the first two positions have the same dependency.

Use controlled sort times, or allow other ties and locate the hard-linked pair by path.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 34c4f1c. set_modified is not an option here because the sort key prefers created(), so the test now tolerates ties instead: the hard-linked pair is located by bundle path and must share a timestamp, and every adjacent pair in the listing must be strictly newer-to-older or, on equal timestamps, keep read_dir order (the stable-sort property the production code relies on). The assertions that assumed the sleeps produce distinct times (legacy last, the pair at positions 0-1, the strict > chain) are gone; the equivalence with the old listing, the take(K) prefix loop and the laziness check are unchanged. The pair is deliberately not asserted adjacent, since on a coarse filesystem a third bundle could share the timestamp and sit between them in directory order.

Fix written by Claude Opus 5.5, reviewed by DeepSeek V4.1 Flash and Gemini 3.8 Flash.

@richiemcilroy

Copy link
Copy Markdown
Member

Thanks @celeroncoder! Greptile hasn't scored this one yet, so I've asked it to review below. We only merge once Greptile reaches 5/5, so once its review is in, please address its findings (or reply to them if you think one is wrong) and comment @greptileai review to re-score.

@greptileai review

This branch has not been deployed

No deployments
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