feat: hover-to-reveal actions in app file list#40
feat: hover-to-reveal actions in app file list#40momenbasel merged 2 commits intomomenbasel:mainfrom
Conversation
- File rows show Reveal in Finder and Remove buttons on hover - Single-file removal with confirmation dialog - Subtle background highlight on hover with smooth animation - Extracted FileRow component for cleaner code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix app size showing 1 byte: replace kMDItemFSSize (returns directory entry size) with totalFileAllocatedSize for accurate bundle sizes - Increase file enumeration limit from 5000 to 10000 for large apps - Use Finder via AppleScript for file removal — triggers system auth prompt and works with macOS permission model - Show error alert with "Open System Settings" button when removal fails due to missing Full Disk Access - Verify files are actually gone from disk before removing from UI Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Note: When building locally without code signing (ad-hoc CODE_SIGN_IDENTITY="-"), PureMac won't appear in System Settings → Privacy & Security → Full Disk Access. This is a macOS limitation, only properly code-signed apps register in the TCC/FDA pane. File removal will fail with permission errors in unsigned builds. This works correctly when built with a valid Developer ID signing identity. |
There was a problem hiding this comment.
Pull request overview
Adds hover-revealed per-file actions to the Installed Apps “discovered files” list, enabling quick Reveal-in-Finder and single-file removal flows while improving row presentation and app size calculation.
Changes:
- Extracts a
FileRowcomponent with hover-to-reveal action buttons and a per-file confirmation dialog. - Updates removal flow to use Finder (AppleScript) to move selected files to Trash and surfaces removal failures via a new
removalError. - Revises app size calculation to prefer
totalFileAllocatedSizeand improves the file enumeration fallback.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| PureMac/Views/Apps/AppFilesView.swift | Introduces FileRow, hover actions, single-file remove confirmation, and a removal-failure alert. |
| PureMac/ViewModels/AppState.swift | Adds removalError and changes file removal to Finder/AppleScript “move to Trash” behavior. |
| PureMac/Logic/Scanning/AppInfoFetcher.swift | Updates app size computation to use allocated-size resource values and adjusts enumeration behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let posixPaths = urls.map { "\"\($0.path)\"" }.joined(separator: ", ") | ||
| let script = """ | ||
| tell application "Finder" | ||
| set theFiles to {} | ||
| repeat with p in {\(posixPaths)} | ||
| set end of theFiles to (POSIX file p as alias) | ||
| end repeat | ||
| delete theFiles | ||
| end tell | ||
| """ |
| let success = errorInfo == nil | ||
| if let errorInfo { | ||
| Logger.shared.log("Finder trash error: \(errorInfo)", level: .error) | ||
| } | ||
| completion(success) |
| if failed > 0 { | ||
| self.removalError = "\(failed) file\(failed == 1 ? "" : "s") could not be removed. Grant Full Disk Access in System Settings → Privacy & Security to allow PureMac to manage all files." | ||
| Logger.shared.log("Failed to remove \(failed) files — likely missing FDA", level: .error) |
| guard let enumerator = fileManager.enumerator( | ||
| at: url, | ||
| includingPropertiesForKeys: [.fileSizeKey, .isRegularFileKey], | ||
| includingPropertiesForKeys: [.totalFileAllocatedSizeKey, .isRegularFileKey], |
| self.discoveredFiles.removeAll { removed.contains($0) } | ||
| self.selectedFiles.subtract(removed) |
| .alert("Remove \(fileURL.lastPathComponent)?", isPresented: $showConfirmation) { | ||
| Button("Cancel", role: .cancel) {} | ||
| Button("Remove", role: .destructive) { onRemove() } | ||
| } message: { | ||
| Text("This will permanently delete this file. This action cannot be undone.") |
| if isHovering { | ||
| Button { | ||
| NSWorkspace.shared.selectFile(fileURL.path, inFileViewerRootedAtPath: "") | ||
| } label: { | ||
| Image(systemName: "folder") | ||
| .font(.caption) | ||
| .foregroundStyle(.secondary) | ||
| } | ||
| .buttonStyle(.plain) | ||
| .help("Reveal in Finder") | ||
| .transition(.opacity) | ||
|
|
||
| Button { | ||
| showConfirmation = true | ||
| } label: { | ||
| Image(systemName: "trash") | ||
| .font(.caption) | ||
| .foregroundStyle(.red) | ||
| } | ||
| .buttonStyle(.plain) | ||
| .help("Remove this file") | ||
| .transition(.opacity) | ||
| } |
| private func removeSingleFile(_ url: URL) { | ||
| appState.selectedFiles = [url] | ||
| appState.removeSelectedFiles() |
Summary
FileRowcomponent for cleaner separationAs suggested in the closed PR discussions — bringing the hover-to-reveal uninstall UX to v2's AppFilesView.
Test plan
🤖 Generated with Claude Code