uttrflow / uttrflow/uttrflow-swift
[P2] Real decoded thumbnails count as zero bytes, bypassing cache eviction
- Dominant language
- Swift
- Stars
- 4
- Forks
- 17
- Avg merge
- 3h 32m
- Merged PRs (30d)
- 277
Description
The thumbnail cache's byte limit does not apply to the images its production decoder returns. On the reviewed Mac, `NSImage(cgImage:size:)` wraps the thumbnail in `NSCGImageSnapshotRep`, while the cache counts only `NSBitmapImageRep`. Every decoded thumbnail therefore costs zero according to the cache, and the eviction condition never becomes true.
### Production path
Reviewed main `5c310667cd35c44abd8bbd26c19b0905471287b4`:
- `Sources/Uttrflow/Panel/PanelThumbnailSource+System.swift`: ImageIO creates a small CGImage and returns `NSImage(cgImage: image, size: ...)`.
- `Sources/Uttrflow/Panel/PanelThumbnails.swift`: `bytes(of:)` skips every representation that cannot cast to NSBitmapImageRep. `thumbnail(for:)` adds that cost to `held`; eviction runs only while `held > budget`.
- `Sources/Uttrflow/Panel/QuickPanelView.swift:498` uses `PanelThumbnails.shared`, so real panel rows reach this path and the cache survives window closures.
As distinct image files are displayed over an app session, their thumbnails remain retained despite the advertised byte budget. The images are still downsampled; this is a cache accounting/eviction defect, not a claim that full-resolution images are retained.
### Reproduction and evidence
An isolated Swift test compiled the unmodified cache and production ImageIO source. It wrote a synthetic 128-by-128 PNG in a temporary directory, then loaded it through `PanelThumbnailSource.system.load(file, 68)`:
```
Representations: ["NSCGImageSnapshotRep"], bytes: 0
```
A second check used the same real source behind a load counter, configured a 1-byte cache budget, and loaded eight distinct PNG paths. Revisiting the first path produced:
```
bytesHeld=0, decodes before/after revisiting oldest=8/8
```
The oldest entry should have been evicted under that tiny budget (the cache intentionally allows one newest image to exceed the limit). Both the nonzero-cost assertion and the oldest-entry eviction assertion fail.
The ten existing thumbnail tests pass: their capacity fixtures explicitly add NSBitmapImageRep, which bypasses this mismatch. Ten existing application-icon tests also pass. No RSS growth amount or native panel interaction is claimed; the reproduction exercises the real decoder and cache directly.
### Expected fix
Account for the representation produced by the actual decoder, or make that decoder return an image with reliably measurable decoded storage. Keep the existing recency behavior and document any approximation. Add a regression using an actual PNG through the production source, proving nonzero accounting and eviction after exceeding a small budget.
Contributor guide
Research direction
Start with Sources/Uttrflow/Panel/PanelThumbnails.swift and Sources/Uttrflow/Panel/PanelThumbnailSource+System.swift, then inspect the existing thumbnail tests. Reproduce the real PNG path and verify that decoded storage is counted and a tiny budget evicts the oldest entry while preserving recency behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- macos, swift
- Domain
- desktop, performance, testing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 72/100