Summary
Several renderer-side caches grow without an upper bound across a session. Individually small, together they leak steadily and pin DOM/Blob references.
Evidence
apps/desktop/src/renderer/src/lib/url-metadata.ts:18 — module-level cache: Map<string, Promise<UrlMetadata>> for link previews. No eviction. Grows with every distinct URL rendered.
apps/desktop/src/renderer/src/hooks/use-wiki-link-hover.ts:36 — per-hook-instance Map capped at 50 entries, but multiple instances exist concurrently across mounted editors. Each preview holds a snippet of note content.
apps/desktop/src/renderer/src/components/quick-capture.tsx:160,199,205 — pasted clipboard Blob retained in component state plus its ObjectURL. URL is revoked on unmount, but the Blob itself can stay pinned across multiple captures in the same quick-capture window session.
Proposed Work
- URL preview cache: convert to LRU, cap 200 entries, expose a dev-tooling clear method.
- Wiki hover cache: hoist into a single shared module-level LRU keyed by note id, cap 100, drop per-hook caches.
- Quick capture: revoke
ObjectURL and null the Blob reference immediately after the capture is committed or discarded. Don't carry the prior blob across paste events.
Acceptance Criteria
- Each cache documents its cap and eviction policy in a code comment.
- Unit test: after 1000 distinct URL previews are rendered, the cache has at most 200 entries.
- Unit test: after committing 20 large screenshot pastes in quick capture, no Blob references remain in component state.
- No regressions in link preview rendering or wiki hover UX.
Benchmark (worktree vs main)
Depends on the memory snapshot harness landing first.
pnpm memory:snapshot --scenario paste-50-large-screenshots on main.
- Repeat on worktree.
- Expected delta: renderer
usedJSHeapSize at T2 reduced by ≥ 10 MB after the screenshot scenario.
Attach both JSON files plus the compare output to the PR.
Tags: desktop, renderer, caches, blob, memory-footprint
Summary
Several renderer-side caches grow without an upper bound across a session. Individually small, together they leak steadily and pin DOM/Blob references.
Evidence
apps/desktop/src/renderer/src/lib/url-metadata.ts:18— module-levelcache: Map<string, Promise<UrlMetadata>>for link previews. No eviction. Grows with every distinct URL rendered.apps/desktop/src/renderer/src/hooks/use-wiki-link-hover.ts:36— per-hook-instance Map capped at 50 entries, but multiple instances exist concurrently across mounted editors. Each preview holds a snippet of note content.apps/desktop/src/renderer/src/components/quick-capture.tsx:160,199,205— pasted clipboardBlobretained in component state plus itsObjectURL. URL is revoked on unmount, but the Blob itself can stay pinned across multiple captures in the same quick-capture window session.Proposed Work
ObjectURLand null the Blob reference immediately after the capture is committed or discarded. Don't carry the prior blob across paste events.Acceptance Criteria
Benchmark (worktree vs main)
Depends on the memory snapshot harness landing first.
pnpm memory:snapshot --scenario paste-50-large-screenshotsonmain.usedJSHeapSizeat T2 reduced by ≥ 10 MB after the screenshot scenario.Attach both JSON files plus the compare output to the PR.
Tags: desktop, renderer, caches, blob, memory-footprint