uttrflow / uttrflow/uttrflow-swift

[P2] Real decoded thumbnails count as zero bytes, bypassing cache eviction

Open
#810 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.