v0.2: live permission state + Paste to Front App + index diagnostics - #2
Merged
Merged
Conversation
Permissions tab rebuilt: - Removed the misleading "Accessibility required" row — Carbon's RegisterEventHotKey doesn't need Accessibility. Vista's hotkey works without it. - Reframed "Full Disk Access" as optional with accurate copy: folders added via NSOpenPanel already work via security-scoped bookmarks; FDA is only useful for protected locations the bookmarks can't reach. - "Paste to Front App" row now shows live state via AEDeterminePermissionToAutomateTarget — granted / denied / not yet requested, plus a Grant button that triggers the macOS prompt (askUserIfNeeded: true). First grant also registers vista in System Settings → Privacy → Automation so you can toggle it there. Paste to Front App wired up for real: - PanelController captures NSWorkspace.frontmostApplication before it steals focus so we know which app to hand the paste back to. - ActionHandlers.pasteToFrontImpl is a closure the controller sets: copies the image, orders the panel out, reactivates the previous app on a short delay, then sends Cmd+V via AppleScript. First run triggers the Automation prompt. Folders tab: - Rescan Now button moved here too (menu bar still has it). Users expect to be able to retrigger a scan after adding a folder without hunting through the menu bar. - Live "Indexed screenshots: N" caption under the folder list so the scan result is immediately visible without switching to Permissions. Indexer diagnostics (NSLog; shows up in Console.app under vista): - Logs each watched folder with existence/isDirectory probe result — surfaces permission-denied or stale-path failures instead of silently yielding zero candidates. - Wraps the FileManager enumerator with an errorHandler that logs per-entry errors (e.g. iCloud placeholders) but keeps walking. - Reports seen-vs-matched counts per folder + overall discovered count. 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 improves v0.2’s UX and diagnosability by wiring up “Paste to Front App”, showing live Automation permission state in Settings, adding manual rescan + live indexed-count UI, and expanding indexer diagnostics/logging.
Changes:
- Added preflight checks + errorHandler logging to initial indexing scans, including per-folder enumerated/matched counts.
- Updated Settings UI: Folders tab gets “Rescan Now” + live indexed count; Permissions tab now probes/requests Automation permission and reframes Full Disk Access as optional.
- Implemented “Paste to Front App” by capturing the pre-panel frontmost app, reactivating it after dismissal, and issuing a Cmd+V via AppleScript; introduced a PermissionProbe helper.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| Sources/VistaCore/Indexer.swift | Adds scan diagnostics (folder probing, enumerator error logging, seen/matched counts). |
| Sources/Vista/SettingsView.swift | Wires rescan + live indexed count; replaces static permission rows with live Automation permission UI and optional Full Disk Access row. |
| Sources/Vista/PermissionProbe.swift | Adds Apple Events-based probe/request helpers for Automation (System Events). |
| Sources/Vista/PanelController.swift | Captures previous frontmost app and implements “Paste to Front App” flow (reactivate + scripted Cmd+V). |
| Sources/Vista/ActionHandlers.swift | Adds an injectable implementation hook for Paste-to-Front-App and invokes it after copying. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Gordon reported "Grant does nothing" for Paste to Front App and "my prod other already has full disk access" but both were showing as Unknown / Optional regardless of real state. Two fixes: 1. Automation grant path now uses NSAppleScript instead of AEDeterminePermissionToAutomateTarget(askUserIfNeeded: true). Running a minimal `tell application "System Events"` query reliably triggers the macOS Automation prompt on current macOS and registers vista in Privacy → Automation. The raw AE API with askUserIfNeeded has been flaky for us on macOS 26. 2. FDA now has a live probe: try to open /Library/Application Support/com.apple.TCC/TCC.db. That file is FDA-gated — readable means granted, EPERM means denied. Probed on tab appear; row shows "Granted · Optional" when on, plain "Optional" otherwise. Also added NSLog around the automation read-only probe so unexpected OSStatus values surface in Console.app — we'll know if Apple introduces a new code instead of silently reporting Unknown. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
`swift run Vista` runs the bare executable, which skips Info.plist so LSUIElement, bundle id, icon, and usage descriptions all go unread and dev-build TCC grants end up keyed off the wrong identity. Scripts/dev-run.sh does a fast debug build, wraps the binary in a Distribution/Vista Dev.app with a `com.gordonbeeming.vista.dev` bundle id, and `open`s it. Separate bundle id means dev Automation / Full Disk Access grants and UserDefaults are isolated from any brew-installed production copy — you can iterate locally without stepping on production prefs, and revoke dev-only Automation without affecting the shipping build. pkills any running VistaDev first so `open` launches the freshly-built binary rather than foregrounding the stale instance. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
Apps only show up in System Settings → Privacy & Security → Full Disk Access after they've attempted to read a TCC-gated file at least once. Vista had no such attempt at launch, so users who wanted to grant FDA had to "+" browse to Vista.app manually instead of finding it in the standard list. New TCCBootstrap.registerWithTCC() fires two cheap read attempts at launch — /Library/Application Support/com.apple.TCC/TCC.db (canonical FDA-gated file) and ~/Library/Safari/Bookmarks.plist (second path for reliability across macOS versions). Both reads are expected to fail until the user grants FDA; the attempt itself is what TCC records. Called from AppState.init before the async bootstrap Task starts so the probe runs even if the ScreenshotStore or Indexer fail to open. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
Gordon added his iCloud screenshots folder (4,750 images) via the Folders tab and the indexer found zero — but the directory is readable from the shell with no special setup. Root cause: ad-hoc-signed dev builds get a fresh code signature each rebuild, which invalidates every security-scoped bookmark the previous build stored. Preferences. resolveFolders() returned nil for the iCloud bookmark, so the folder never reached the indexer. Since vista runs unsandboxed, the security scope is theatre — the app has full filesystem access from its plain path anyway. Added a fallback: if `folder.resolve(startAccess: true)` returns nil, use URL(fileURLWithPath: displayPath) instead. Verified against the iCloud folder — bookmark fails, fallback kicks in, enumerator finds all 4750. Also added VistaLog — a tiny tee that calls NSLog AND appends a timestamped line to ~/Library/Logs/Vista/vista.log, so `tail -f` is all you need to see indexer diagnostics. macOS's unified-log filtering has been unreliable for third-party process output, which was making the original bookmark failure invisible. Routed the Indexer initialScan logs and the new bookmark-fallback log through VistaLog so the diagnostic trail actually lands on disk. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
…gning Two complaints to address: 1. "Each dev install needs to re-do permissions" — prior dev-run.sh used a separate `.dev` bundle id on purpose, which meant dev and brew-installed were distinct TCC principals: Automation, Full Disk Access, and folder bookmarks all had to be granted separately. 2. "Should auto-tail the log after launch" — previously had to run `tail -f` in a second terminal every time. Changes: - Bundle id now matches prod (com.gordonbeeming.vista). TCC grants and UserDefaults preferences are shared with any brew-installed copy; grant once in either build and it carries across. - Script pkills any running `Vista` process before launching so dev and prod never fight over the same bundle id at runtime. `open -n` bypasses Launch Services caching of the /Applications path. - Ad-hoc sign with `codesign -s - --identifier com.gordonbeeming.vista` so the signing identifier is stable across rebuilds, which keeps TCC grants from being invalidated on every `swift build`. - Automatic `tail -f ~/Library/Logs/Vista/vista.log` after launch. `--no-tail` skips the tail for scripted use. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
The prior dev-run.sh produced Distribution/Vista Dev.app with an executable named `VistaDev` and bundle id com.gordonbeeming.vista.dev. The current script uses `Vista` + com.gordonbeeming.vista — but the rename left users with both a running VistaDev process and a stale Vista Dev.app on disk. Kill both `Vista` and `VistaDev` at startup (exact-name pkill, silent if absent) and rm the old Vista Dev.app bundle so Launch Services stops advertising a version that will never run again. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
- SettingsView: capitalise "Vista" in the Full Disk Access hint so the product name reads consistently with the rest of the UI. - PanelController.pasteToPreviousFrontmost: guard on a non-nil previousFrontmostApp. Without the guard, reactivating target + sending Cmd+V would fire even when we never captured a target, landing the keystroke in whichever app happened to be frontmost after the panel hid — often vista itself or Finder. The clipboard copy from ActionHandlers is still useful on its own; the keystroke is what we skip. - PermissionProbe: add explicit `import CoreServices` for the AE-family types and constants. Not strictly required on current macOS (AppKit re-exports them transitively) but makes the dependency explicit and keeps the file resolving on older SDKs. - Indexer: fix the log message mismatch — the guard checks existence + isDir, not readability, so "skipping — path does not exist or is not a directory" is the honest description. - SettingsView.PermissionsTab: re-probe Automation + Full Disk Access state on NSApplication.didBecomeActiveNotification, not just on tab appear. Covers the common flow of clicking "Open Settings", toggling the permission in System Settings, and returning — previously the chip stayed stale until the user switched tabs. Reverted Copilot's suggestion to call `activate(options:)`: the no-arg `activate()` is macOS 14+ which matches our deployment target, and the `activateIgnoringOtherApps` option is deprecated on 14+ (it's a no-op there anyway). Kept the no-arg form to avoid the deprecation warning. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
`swift build -c release` alone produces a bare executable with no Info.plist, which is exactly the "dev feels different from prod" friction we fixed with Scripts/dev-run.sh. The README still pointed people at `swift build -c release` as the primary local-dev path, which is misleading. Reorganised so dev-run.sh is the headline instruction with a short explanation of what it does (shared bundle id, stable ad-hoc identifier, auto-tail). `build-release.sh` mentioned for the signed + notarised release flow CI uses. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
Two cosmetic issues: 1. The badge button was the only focusable control on the About window, so SwiftUI drew its default accent-coloured focus ring around it whenever the window was key — making it look like a text field. `.focusEffectDisabled()` on the button drops the ring without affecting tap behaviour. 2. BuildInfo.footerBadge prepended "v" to the version string unconditionally, which produced "vdev · 41f7640" on dev builds where CFBundleShortVersionString is the literal "dev". Now we only prepend the "v" when the version actually starts with a digit (i.e. looks like semver), so dev builds show "dev · 41f7640" and tagged builds still show "v0.2 · abc1234". 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
RegisterEventHotKeydoesn't need it.PanelControllercaptures the previously-frontmost app before the panel steals focus, then the action hides the panel, reactivates that app, and sends ⌘V via AppleScript. First invocation triggers the macOS Automation prompt and registers vista in the system list.Test plan