2 months ago
023fdc0* Harden clipboard pin ordering, load and pruning Three follow-ups from the review of #63: - A pinned entry could fall outside the FTS statement's LIMIT 200 and vanish from a filtered search. Every pinned row is resident in `items` by construction, so the pinned block is now matched in memory and the FTS result contributes only unpinned rows. - `load`'s `pinned = 1 OR rowid >= ?` could not be driven from an index while holding row order, so it scanned the whole table on every launch (~12ms at 200k rows, on the main actor). Split into two indexed branches over a partial index on the pin flag: ~1ms for the same data. - `prune`'s cheap guard tested the oldest row, which a retention-exempt pin at the tail made permanently true, re-scanning the window on every capture. It now tests the oldest unpinned row. * Order the Pinned section by pin time, Raycast-style Pins were a boolean, so the Pinned section stayed in recency order: pinning two entries interleaved them by when they were copied rather than keeping them in the order they were pinned, and unpinning dropped a row back into whatever date bucket it came from, jumping the list under the selection. `pinned` becomes `pinned_at`, a stamp: - The Pinned section is ordered newest pin first, so pinning a row never reshuffles the pins already there. - Unpinning rejoins the history as its newest entry (the same delete + re-insert `promote` uses), so the row stays where the eye already is. - `promote` skips pinned rows: a pasted pin holds its place instead of rewriting the row and its FTS entry for no visible change. The boolean column never shipped, so nothing migrates it. Adds `Tools/clipboard-test.swift` (23 checks: pin order, unpin recency, paste, retention exemption, the FTS limit, persistence, and migrating a shipped pre-pin database), which is why the store gained an injectable directory and dropped its unused AppKit import. * Order pins oldest first, so a new pin joins the end of the section
Parentec136f1