An audit for v0.1.0 turned up the same pattern in four places: a
computed property doing real work (filtering/sorting/scoring a
collection), read multiple times per render including from
unrelated state changes (selection, hover, scroll), so the work
reran far more often than the underlying data actually changed.
Converted each to a @State cache recomputed only via onChange of its
real inputs:
- DocumentSearchSheet: matchingLineIndices re-scanned the whole
document per access, read once per visible row plus twice more in
the header/step logic - O(n^2) case-insensitive scan per frame on
a large document. Also split into an ordered array (for
currentMatchIndex/stepping) plus a parallel Set for the per-row
highlight check, which was an O(k) linear .contains before.
- CollectionDocumentsOutline: tree rebuilt the whole dictionary-
grouped, recursively-sorted document tree on every body
evaluation, not just when documents/sortOption actually changed.
- CommandPaletteView: results re-scored and re-sorted the entire
index (up to the whole local workspace cache in Full Workspace
mode) on every render, including ones from selectedIndex moving as
arrow keys are pressed.
- CollectionOverviewView: sortedDocuments re-sorted on every render;
same pattern, smaller blast radius (capped at 100 docs).
Also:
- HomeViewModel.fetchPinnedThrowing fetched each pinned document
serially in a for loop (one round trip at a time) - switched to a
TaskGroup so latency doesn't scale with pin count, results
reordered back to pins.list's own order since task completion
order isn't submission order.
- AvatarCropperView.renderFinalImage ran ImageRenderer + JPEG
compression synchronously on the main actor from the "Use Photo"
button tap. ImageRenderer itself has to stay on the main actor (it
captures live SwiftUI state), but JPEG compression on the already-
rendered bitmap has no SwiftUI dependency left - hopped that part
to a detached Task via tiffRepresentation (plain Data, unlike
NSImage itself isn't Sendable) so it doesn't hitch the UI.
No crash risks or retain cycles found in the same audit (no try!/
as!, force-unwraps essentially absent outside a hardcoded URL
literal, weak self already used where it matters) - this is purely
the perf half of the findings.
Not compiler-verified - Outpost app target has no CLI build path.