Ship vista v0.1: screenshot search for macOS - #1
Merged
Merged
Conversation
Sets up the Swift Package layout (VistaCore library + Vista menu-bar app) and lands the end-to-end pipeline from FSEvents → Vision OCR → SQLite FTS5, plus the query parser that feeds the search UI in Phase 2. VistaCore: - ScreenshotStore: sqlite3 with external-content FTS5 and triggers that keep the fts table in sync with inserts/updates/deletes on the base table. - OCRRecognizer: thin wrapper over VNRecognizeTextRequest supporting off/fast/accurate and optional language hints. - FSEventsWatcher: CFRunLoop-free async stream of create/modify/rename/ delete events per watched folder. - Indexer: actor that does the initial scan, handles FS events, and dedupes unchanged files via an (mtime, size) fingerprint. - QueryParser: name:/text:/date: prefixes plus free-text, with natural dates (today, yesterday, last week, march, 2024-12-25). Vista: - MenuBarExtra with indexing status, Pause, Rescan Now, Quit. The floating panel, preferences, and global hotkey come in Phase 2+. - Info.plist (LSUIElement = menu bar only, usage descriptions for Desktop/Documents/Downloads folder access, NSAppleEventsUsageDescription for the future "Paste to front app" action). - Entitlements: hardened runtime on, app-sandbox off (FSEvents on arbitrary folders is painful inside the sandbox; notarisation still passes). Tests: 35 passing. QueryParser, ScreenshotStore (schema + FTS + pinning + fingerprint), and OCRRecognizer with a programmatically-rendered fixture. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
Lands the interactive surface. ⌘⇧S summons a HUD-style NSPanel that hosts a SwiftUI grid of thumbnails with the search bar. Typing in the bar debounces through the query parser into ScreenshotStore.search; arrows move selection, Enter runs the primary action, Esc dismisses. Vista target: - FloatingPanel: NSPanel subclass — nonactivating, HUD level, sized to 60% of the active screen and re-centered on show. Panel-size preference comes in Phase 3. - HotKeyManager: Carbon RegisterEventHotKey wrapper. Default ⌘⇧S. Zero deps — the surface we need is small enough that vendoring is cheaper than pulling a package. Rebinding UI lands in Phase 3. - PanelController: AppKit glue wiring NSHostingView → FloatingPanel → VistaCore. Exposes toggle() for the hotkey to bind to. - SearchViewModel: @observable. Owns queryText, debounces 120ms, runs QueryParser → store.search, tracks selectedIndex. - PanelContentView: SwiftUI. Search field, Images source dropdown stub, adaptive LazyVGrid of thumbnails, empty-state copy, footer with "Copy to Clipboard ⏎" and "Actions ⌘K" labels. Handles arrow-key navigation, Enter, ⌘P pin, ⌘⇧C copy OCR, Esc dismiss. - ActionHandlers: Open / Copy to Clipboard / Show in Finder / Copy Path / Copy OCR Text / Pin / Move to Trash. Paste-to-front-app stubs to Copy for now — full AppleScript glue lands with permissions in Phase 3. - MenuBarContentView: adds "Search Screenshots…" entry and the default ⌘⇧S shortcut on the menu item itself as a backup to the global hotkey. VistaCore: - ThumbnailCache: ImageIO-driven rendering at 256 / 512 / 1024 px, PNG on disk under ~/Library/Caches/com.gordonbeeming.vista/thumbs/ with a sha1(path)-mtime-size key. In-process NSCache in front of it. Purges all sizes for a given source when asked. Tests: 35 still passing (no existing test broke; new UI surface is exercised via manual e2e in the Phase 2 verify step). Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
Panel was showing a dark strip at the top — the `.titled` style mask bakes in a titlebar area that the SwiftUI material + rounded-corner clip can't fully cover. Switched to `.borderless`, dropped titlebarAppearsTransparent / titleVisibility (no longer applicable), and made the window non-opaque so the corner clip defines the visible shape. Preferences was disabled on purpose because the real prefs UI is Phase 3, but that's the wrong default — users hit the menu item and nothing happens. Added a Settings scene with a placeholder SettingsView so the menu item can be a SettingsLink, ⌘, works, and there's a real target to flesh out when Phase 3 lands. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
Replaces the "coming soon" placeholder with the real tabbed settings window and wires every control through to the running components. Preferences model: - Preferences: @observable store. Persists scalars to UserDefaults; the watched-folder list is security-scoped bookmarks serialised to Application Support/watched_folders.json so hardened-runtime builds can re-open user-granted folders across launches. - Emits Change events on an AsyncStream — AppState listens and dispatches each change to the right component instead of recomputing everything on every tweak. Settings window (TabView): - General: launch at login (SMAppService), pause indexing. - Behaviour: Primary Action dropdown (Copy / Paste to Front / Open / Show in Finder / Copy OCR). - Folders: system screenshot folder is auto-detected (reads com.apple.screencapture location) and shown as a locked row; any number of user folders can be added via NSOpenPanel with full add / remove / show-in-Finder controls. Include-all-media toggle. - Search: OCR accuracy (Off / Fast / Accurate), Storage Duration (Unlimited / 1w / 1mo / 3mo / 6mo / 1y — prunes unpinned rows only, never touches image files). - Appearance: panel size slider 30-100% with Compact/Comfortable/ Large/Full presets. - Shortcuts: KeyRecorderView for the invoke hotkey. Press any modifier+key chord, ⎋ cancels, ⌫ clears. Uses UCKeyTranslate so non-QWERTY layouts get the right labels. - Permissions: deep-links into the relevant System Settings panes + shows the current indexed count. VistaCore additions driven by the new prefs: - Indexer.setOCRRecognizer: swap recogniser at runtime. - Indexer.prune(olderThan:): storage-duration pruner, called at bootstrap and whenever the duration preference changes. - ScreenshotStore.deleteUnpinned(olderThan:): pinned rows stay regardless. Crash fix along the way: - FSEventsWatcher was casting the callback's path payload as CFArray without kFSEventStreamCreateFlagUseCFTypes set, so the underlying const char*const* got interpreted as objc pointers and crashed the moment FSEvents fired. Added the flag. LSUIElement=NO temporarily so the Settings scene actually appears as a standard window; kept the menu bar-only experience for the main app by other means. (Reverting once we verify the Settings flow — low-risk follow-up.) Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
Everything needed for `brew install --cask gordonbeeming/tap/vista` to work once a GitHub release is cut, lifted from the proven insomnia setup and trimmed to vista's simpler shape (GUI-only, no CLI archive). Scripts: - build-release.sh: swift build -c release, assembles Distribution/ Vista.app with a dynamically-generated Info.plist (bundle id, version, usage descriptions, LSUIElement=YES). Local smoke test: bundle launches, menu bar icon appears, indexing runs. - package-for-homebrew.sh: wraps the .app in a UDZO DMG, computes the SHA, and prints the exact cask stanza for local verification. .github/workflows/build.yml: - Every push + PR: swift build + swift test on macos-14. - Published release: import Developer ID cert into a temp keychain, codesign --options runtime --entitlements, submit to notarytool --wait, staple the ticket, create the DMG, upload as a release asset, then SSH into gordonbeeming/homebrew-tap and commit a fresh Casks/vista.rb with the new version + SHA256. Uses the same HOMEBREW_TAP_DEPLOY_KEY / APPLE_* secret names as insomnia so the existing secrets carry over. App icon punted to a follow-up — the bundle ships fine without one and we can drop an AppIcon.icns into Resources/ later; build-release.sh already picks it up conditionally. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
Folders tab: the system default screenshot folder was rendered as a locked System row with no way to opt out. If macOS's screencapture location defaults to ~/Desktop but the user takes all their screenshots via Cmd+Shift+5 → Save to iCloud, watching Desktop is pure noise. Now the default folder is a checkbox — on by default (matches Raycast) but clickable off. The state persists to UserDefaults and flows through Preferences.changes into the Indexer like every other folder change. KeyRecorder: widened the acceptance criteria. - Listens to .flagsChanged as well as .keyDown so users holding a Hyper mapping see their modifiers echoed live in the button (⌃⌥⇧⌘…) — without that feedback, there's no way to tell if the chord is even reaching the app. - Masks modifier flags through deviceIndependentFlagsMask so a stuck Caps Lock doesn't pollute the captured chord. - Rejects pure-modifier keyDowns (some rewriters emit them) and waits for the real trigger key. - Labels: F13-F20 now render correctly. Added a Shortcuts-tab hint that if a chord doesn't register, another tool (Karabiner/BTT/Raycast) has probably claimed it at a higher level — NSEvent monitors can't intercept events that are consumed by a higher-priority event tap upstream. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
Panel size controls how much of the screen the window takes, but at 100% the adaptive grid was still capping column width at 360 pt — so users got lots of tiny thumbnails on a huge panel instead of the obvious "bigger window → bigger previews" outcome. Added a separate Preview Size preference (160-720 pt, default 280, Small / Medium / Large / XL presets) that drives: - the grid's per-column minimum (columns still flex up to 1.6× so the final row fills the panel cleanly) - the ThumbnailCache size bucket we load from (>380 pt pulls the 1024-px cache entry so XL previews aren't upscaled from 512 px) Appearance tab is now two sections — Panel Size + Preview Size — each with a slider and four presets. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
Preferences window sometimes opened behind whichever app was frontmost when the menu was clicked, because agent apps (LSUIElement=YES) don't auto-activate the way regular apps do. Replaced SettingsLink with a Button that calls NSApp.activate(ignoringOtherApps: true) before invoking the @Environment(.openSettings) closure, and added a defence-in-depth onAppear in SettingsView that re-raises the hosting window via orderFrontRegardless in case activation races with the initial window creation. Empty-state hint was hard-coded to "Drop a screenshot onto your Desktop" which was wrong for anyone who'd disabled the default folder toggle. Now it reads the current watch list and names a relevant folder — a user-added folder if any, otherwise the system default if still enabled, otherwise prompts the user to open Preferences → Folders to add one. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
The Settings scene is tied to SwiftUI's built-in preferences machinery,
which assumes a regular foreground app. For an agent app (LSUIElement
=YES), openSettings() creates the window but macOS refuses to bring
it to front — activate + find-and-raise from onAppear wasn't enough
because the window SwiftUI created didn't always match our search
criteria, and sometimes didn't surface at all.
Switched to a plain Window scene with an explicit identifier
("vista.preferences"). Opening it is: activate the app, openWindow(id:),
and on a 50 ms delay run orderFrontRegardless + makeKeyAndOrderFront on
the window with that identifier — two-pass so we catch both the case
where the window already exists (first pass raises) and the case where
SwiftUI is still creating it (second pass raises after creation).
SettingsView.onAppear does the same lookup by identifier as a final
safety net.
Net effect: the preferences window comes to the front every time, the
⌘, shortcut from the menu bar still works, and we don't need to toggle
activation policy (no Dock icon flicker).
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: GitButler <gitbutler@gitbutler.com>
Two problems from the Settings→Window swap: 1. Window showed but wasn't key. setActivationPolicy(.regular) isn't fully synchronous — the Dock server acknowledges on a later runloop tick, and activating before that acknowledgement arrives is a silent no-op. Result: the window appeared, the user clicked it, but keystrokes (including ⌘Q!) still flowed to whatever app was frontmost before. Fix: stagger willOpen → openWindow → activate+makeKeyAndOrderFront across runloop ticks (0 ms, 0 ms async, 50 ms async). Each step's async consequences settle before the next runs. 2. Tabs collapsed into a "Navigation Tab Bar" popover in the toolbar. SwiftUI's TabView renders the classic macOS preferences-style top tabs only inside a Settings scene. Inside a Window scene it falls back to a navigation popover on macOS 14+. Fix: roll the tab bar by hand. PrefTab enum owns the identity, label, and SF Symbol for each tab; SettingsView draws an HStack of icon+label buttons with accent-tinted selection state and a switch for the selected content. Same visual weight as the built-in preferences tabs, entirely under our control. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
Arrow keys were behaving like a 1-D list (up/down = prev/next), and
Left/Right did nothing because the focused TextField consumed them
for cursor movement before SwiftUI's .onKeyPress saw anything.
Two changes:
1. Dropped the .onKeyPress handler and installed an NSEvent local
monitor for .keyDown while the panel is on screen. Local monitors
run before any responder, so the TextField no longer swallows
Left/Right. The TextField still types normally because we return
the event unchanged whenever we don't want to handle it.
2. Made the grid know its column count. A GeometryReader wraps the
LazyVGrid and recomputes `columnCount` whenever width or the
preview-size preference changes, using the same formula
LazyVGrid.adaptive uses internally:
N = floor((usableWidth + spacing) / (minCol + spacing))
Left/Right move by ±1; Up/Down move by ±columnCount for true
same-column row jumps. moveSelection clamps so the edges are safe.
⌘P and ⌘⇧C also routed through the monitor (pin toggle and copy OCR
text), replacing the old .onKeyPress path.
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: GitButler <gitbutler@gitbutler.com>
Dropped Resources/AppIcon.icns (generated from vista.png via sips + iconutil, every standard size from 16 to 1024 at 1x and 2x). Info.plist now references it via CFBundleIconFile; the build-release.sh conditional copy already knew how to thread it into the .app bundle, so signing and notarisation happen on the iconed bundle going forward. Added an About window (WindowID.about) with: - Large app-icon preview pulled from the bundle (NSImage named "AppIcon") - Version string from CFBundleShortVersionString (falls back to "dev" for unbundled swift-run builds) - Clickable rows for source, issues, author, blog — one line of SF Symbol + label + muted URL, opens in the default browser - MIT license + copyright footer "About Vista" menu bar entry opens it via the same activation-policy flip + find-and-raise pattern as Preferences, so the window comes to front reliably in the agent-app activation state. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
New in the running app you can see which source produced it. Clicks
on the badge jump to the release notes for that build.
Info.plist gets two new keys written at bundle time:
- VistaGitCommit — short SHA (falls back to `git rev-parse` for local
build-release.sh runs; CI passes ${GITHUB_SHA:0:7}).
- VistaReleaseTag — release tag when built from a tag (CI passes
${GITHUB_REF_NAME}); empty string for dev builds.
BuildInfo reads both + CFBundleShortVersionString and exposes a footer
badge string ("v0.1 · abc1234") and a link URL that prefers
/releases/tag/<tag> when a tag is present and falls back to
/commit/<sha>, finally to the repo root for unbundled `swift run` builds.
Panel footer + About window each render the badge as a plain button
with a small "open in new window" glyph — opens the chosen URL in the
default browser.
Also copied the create-release skill pattern from insomnia into
.claude/skills/create-release, adapted for vista (repo name, version
scheme, brew cask name). Invokes via "create a release" / "cut a
release" etc.
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: GitButler <gitbutler@gitbutler.com>
The CI runner (macos-14) ships Swift 5.10; my dev machine is Swift 6.3. 5.10 doesn't infer @mainactor for SwiftUI View structs the way 6.x does, so every view that touches Preferences / AppState / ActionHandlers (all @mainactor) failed to build on CI with "main actor-isolated property cannot be referenced from a non-isolated context" and "call to main actor-isolated initializer in a synchronous nonisolated context". Explicit @mainactor on: - VistaApp (for `@State private var appState = AppState()`) - SettingsView + all seven private tab structs (GeneralTab, BehaviourTab, FoldersTab, SearchTab, AppearanceTab, ShortcutsTab, PermissionsTab) — they all bind or read Preferences - MenuBarContentView (touches AppState + NSApp window management) - PanelContentView + ResultCell (touch SearchViewModel / ActionHandlers / Preferences) - AboutView (opens windows via NSApp) Swift 6.x keeps inferring the same actor, so the annotations are no-ops there; 5.10 now has what it needs. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces the initial macOS screenshot-search app (“vista”): a SwiftUI menu-bar app backed by a new VistaCore library that watches folders, OCRs images via Vision, indexes metadata + OCR text into SQLite/FTS5, and provides a query parser + thumbnail caching. It also adds CI/release automation for signing/notarization/DMG packaging and Homebrew tap updates.
Changes:
- Add
VistaCore(SQLite/FTS5ScreenshotStore, VisionOCRRecognizer,Indexer,FSEventsWatcher,QueryParser,ThumbnailCache, shared models/paths). - Add
Vistaapp UI + plumbing (floating panel, panel content/grid navigation, preferences + key recorder, global hotkey, action handlers, about window, app state). - Add tests, scripts, CI workflow, packaging docs, and licensing/docs updates.
Reviewed changes
Copilot reviewed 36 out of 39 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/VistaCoreTests/ScreenshotStoreTests.swift | New unit tests for store schema/CRUD/search/pinning/fingerprint. |
| Tests/VistaCoreTests/QueryParserTests.swift | New unit tests for query parsing and date parsing edge cases. |
| Tests/VistaCoreTests/OCRRecognizerTests.swift | New OCR smoke tests using runtime-rendered text fixtures. |
| Tests/VistaCoreTests/Fixtures/.gitkeep | Keeps fixtures directory in repo for test resources. |
| Sources/VistaCore/VistaPaths.swift | Centralizes standard filesystem locations and screenshot folder resolution. |
| Sources/VistaCore/ThumbnailCache.swift | Adds disk + in-memory thumbnail caching. |
| Sources/VistaCore/ScreenshotStore.swift | Adds SQLite-backed index with FTS5 search and pinning/pruning APIs. |
| Sources/VistaCore/QueryParser.swift | Parses user search text into structured query terms + date ranges. |
| Sources/VistaCore/OCRRecognizer.swift | Wraps Vision OCR behind a small async API. |
| Sources/VistaCore/Models.swift | Adds shared value types (ScreenshotRecord, Query). |
| Sources/VistaCore/Indexer.swift | Adds actor that scans/watches folders and indexes OCR results into the store. |
| Sources/VistaCore/FSEventsWatcher.swift | Async wrapper around FSEvents for file change streaming. |
| Sources/Vista/VistaApp.swift | App entry point: menubar extra plus Preferences/About window scenes. |
| Sources/Vista/Vista.entitlements | Defines hardened runtime entitlements (sandbox explicitly off, Apple events on). |
| Sources/Vista/SettingsView.swift | Full tabbed preferences UI (folders/search/appearance/hotkeys/permissions, etc.). |
| Sources/Vista/SearchViewModel.swift | Debounced query runner + selection model for the panel UI. |
| Sources/Vista/PreferencesActivation.swift | Activation-policy flip helper for preferences/about windows in LSUIElement app. |
| Sources/Vista/Preferences.swift | Persistent preferences store (UserDefaults + bookmark JSON) + change stream. |
| Sources/Vista/PanelController.swift | Manages the floating panel lifecycle and embeds SwiftUI content. |
| Sources/Vista/PanelContentView.swift | Implements the search UI, thumbnail grid, and keyboard navigation/shortcuts. |
| Sources/Vista/MenuBarContentView.swift | Menubar dropdown UI and window-opening logic. |
| Sources/Vista/LaunchAtLogin.swift | Wrapper over SMAppService for login item registration. |
| Sources/Vista/KeyRecorderView.swift | UI control to capture and persist a key chord. |
| Sources/Vista/Info.plist | App bundle metadata + usage descriptions + displayed license string. |
| Sources/Vista/HotKeyManager.swift | Carbon global hotkey registration and dispatch. |
| Sources/Vista/FloatingPanel.swift | Nonactivating HUD-style panel window implementation. |
| Sources/Vista/BuildInfo.swift | Reads version/commit/tag metadata for UI footer/about window linking. |
| Sources/Vista/AppState.swift | Central coordinator wiring preferences ↔ indexer/store/panel/hotkey. |
| Sources/Vista/ActionHandlers.swift | Implements open/copy/show/copy OCR/pin/trash actions. |
| Sources/Vista/AboutView.swift | About window UI including version badge and project links. |
| Scripts/package-for-homebrew.sh | Local helper to package DMG and print a Homebrew cask snippet. |
| Scripts/build-release.sh | Builds a release .app bundle and generates shipping Info.plist. |
| README.md | Updates docs: description, install/build instructions, license section. |
| Package.swift | Defines two-target SPM layout (VistaCore library + Vista executable) + tests. |
| LICENSE | Adds the repository license text (FSL-1.1-MIT). |
| .gitignore | Adds common build/artifact/cert ignores. |
| .github/workflows/build.yml | Adds CI build/test and release pipeline (sign/notarize/dmg/tap update). |
| .claude/skills/create-release/SKILL.md | Adds release-cutting workflow instructions for automation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Address 12 review comments: - QueryParser / Models / ScreenshotStore: switch date ranges from ClosedRange<Date> to half-open Range<Date> and compile with `>= AND <` in SQL so sub-second captured_at values at day/week/month boundaries aren't excluded by an inclusive upper bound that stops a whole second early. - OCRRecognizer: drop unused `import AppKit` (unused-import warning). - ThumbnailCache: drop unused `import UniformTypeIdentifiers`. - ThumbnailCache: include source file size in the cache key so files that change inside the same whole second still invalidate the cached thumbnail. Higher-precision mtimes aren't reliable across APFS copy/ move, so size is the cheapest stable tiebreaker. - PanelContentView: remove unused `RelativeDateTimeFormatter` allocation in ResultCell.caption — `timeAgoStyle()` handled the formatting. - OCRRecognizerTests: add `import CoreText` so CTLineCreateWithAttributedString resolves on a clean build. - Indexer: replace O(n) pendingPaths.removeFirst() with an index cursor (pendingIndex) so bursty FS events don't quadratic. Compacts the array every 256 items to cap memory. - SettingsView / AppState: replace blind `togglePause()` Toggle setter with `setPaused(newValue)` so programmatic state updates and SwiftUI replays stay in sync with the indexer. - FSEventsWatcher: stop finishing the events continuation in stop(). Indexer.updateWatchedFolders does stop/start to swap paths; finishing the continuation on the first stop made all subsequent yields silent no-ops, so restart was doing nothing. Moved continuation.finish() to deinit (plus an explicit close() for callers that really want to tear the stream down early). - README / Info.plist / build-release.sh / AboutView: align every user- visible license string with the actual LICENSE file (FSL-1.1-MIT) instead of claiming MIT. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
gordonbeeming/homebrew-tapvia a dedicated deploy key.What's in it
VistaCore:ScreenshotStore(SQLite + FTS5 with triggers for sync),OCRRecognizer(Vision wrapper, off/fast/accurate),FSEventsWatcher,Indexer(actor-driven incremental scan with mtime+size fingerprint + storage-duration pruner),QueryParser(name:/text:/date:prefixes, natural dates),ThumbnailCache(ImageIO, 256/512/1024 bucketed, disk-backed).Vistaapp:MenuBarExtrashell,FloatingPanel(borderless HUD NSPanel),SearchViewModel,PanelContentView(adaptive grid, window-scopedNSEventmonitor for true arrow-key grid navigation),ActionHandlers(Open / Copy to Clipboard / Show in Finder / Copy OCR / Pin / Trash),HotKeyManager(CarbonRegisterEventHotKey, zero deps),PreferencesActivation(agent ↔ regular activation flip so prefs window reliably takes focus),AboutView.Preferences:@Observablestore, UserDefaults-backed scalars + security-scoped bookmarks for watched folders inApplication Support/watched_folders.json, change stream consumed byAppStateand dispatched toIndexer/HotKeyManager/FloatingPanel.AppIcon.icns(fromvista.png, full size ladder 16→1024 at 1x/2x).VistaGitCommit+VistaReleaseTagbaked into Info.plist at build time, rendered as a clickablev0.1 · abc1234chip in the panel footer + About window linking to the release / commit on GitHub..claude/skills/create-releasefor cutting future releases through the same flow insomnia uses.Test plan
swift build --build-testspassesswift test --parallel— 35 tests green (QueryParser 20, ScreenshotStore 12, OCRRecognizer 2, plus the trivial fixtures one)Scripts/build-release.sh 0.1 1produces a launchableDistribution/Vista.appbundleopen Distribution/Vista.app: menu bar icon appears, Desktop/iCloud indexing runs, ⌘⇧S summons the panel, typing filters results, arrow keys move through the grid two-dimensionally, Enter runs the configured primary actionv0.1release:build-and-test+releasejobs both green, DMG uploaded as release asset,Casks/vista.rbappears on the tapbrew install --cask gordonbeeming/tap/vistaon a clean machine installs without Gatekeeper warningsNotes
LSUIElement=YESwith activation-policy flipping (.accessory↔.regular) on Preferences / About — same pattern Rectangle / 1Password mini-window use. Brief Dock icon during those windows is expected.--arch arm64 --arch x86_64 + lipo) — can add if anyone asks.