Fix critical bugs: off-by-one crash, OCR data race, DB recovery, accessibility prompt#1362
Open
twitchyvr wants to merge 5 commits intop0deje:masterfrom
Open
Fix critical bugs: off-by-one crash, OCR data race, DB recovery, accessibility prompt#1362twitchyvr wants to merge 5 commits intop0deje:masterfrom
twitchyvr wants to merge 5 commits intop0deje:masterfrom
Conversation
The bounds check in Get, Select, and Delete intents used `>=` instead of `>`, causing an index-out-of-bounds crash when requesting the last item by number. For example, with 5 items, requesting item p0deje#5 would pass the guard (`5 >= 5` is true) but then crash on `items[5]`. Also adds a lower bound check (`index >= 0`) to prevent negative index access when `number` is 0 or negative. Co-Authored-By: Claude <noreply@anthropic.com>
The VNRecognizeTextRequest completion handler runs on an arbitrary background queue but writes directly to self.title, which is a property on a @model class (SwiftData). Since the model context uses the main actor, this is a data race that can corrupt SwiftData's change tracking. Dispatch the title update to the main queue to ensure thread safety. Co-Authored-By: Claude <noreply@anthropic.com>
If the SwiftData ModelContainer fails to load (e.g., due to a corrupt SQLite file from a forced quit or disk error), the app previously called fatalError(), permanently bricking the app until the user manually deleted ~/Library/Application Support/Maccy/Storage.sqlite. Now attempts to delete the corrupt database files (.sqlite, .shm, .wal) and recreate the container. The user loses their clipboard history but the app remains functional. Only calls fatalError if re-creation also fails. Co-Authored-By: Claude <noreply@anthropic.com>
shouldIgnore(_ item:) was creating new NSRegularExpression objects for every pattern in ignoreRegexp on every clipboard poll (default interval: 0.5s). Regex compilation is expensive relative to matching. Now caches the compiled regexes and only recompiles when the user's pattern list changes. Also moves the string extraction outside the loop since it only needs to happen once per check. Co-Authored-By: Claude <noreply@anthropic.com>
Accessibility.check() was a silent no-op — if the user hadn't granted accessibility permissions, paste() would proceed to simulate keyboard events that silently did nothing. Users had no indication why paste wasn't working. Now check() returns a Bool and prompts the macOS accessibility permissions dialog via AXIsProcessTrustedWithOptions when permissions are missing. Clipboard.paste() gates on the return value, preventing the futile keyboard event simulation. Co-Authored-By: Claude <noreply@anthropic.com>
8 tasks
Author
CI NoteThe Bitrise CI run shows The failure is an RTF title parsing issue on the CI VM: The RTF This PR does not modify:
The only |
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
Five targeted fixes for bugs found during code review — no new features, no model changes, no behavioral shifts.
1. Off-by-one crash in AppIntents (Get, Select, Delete)
Files:
Intents/Get.swift,Intents/Select.swift,Intents/Delete.swiftThe bounds check
items.count >= indexallowsitems[items.count], which is out of bounds. For example, with 5 items, requesting item #5 yieldsindex = 4, and5 >= 4passes the guard, butitems[4]is valid — however, requesting item #6 yieldsindex = 5, and5 >= 5passes the guard, thenitems[5]crashes. More critically, whenitems.count == index(boundary case), the access crashes.Fix: Changed to
index >= 0 && index < items.count— standard bounds checking that also prevents negative index access.2. Data race in Vision OCR callback
File:
Models/HistoryItem.swiftrecognizeTextHandlerruns on an arbitrary Vision framework queue and writes directly toself.title. SinceHistoryItemis a@Modelclass, its properties should only be mutated on the main actor (the model context's actor). This is a data race that can corrupt SwiftData's change tracking.Fix: Dispatch the title update to
DispatchQueue.main.async.3. Graceful database recovery instead of
fatalErrorFile:
Storage.swiftIf
ModelContainercreation fails (corrupt SQLite from forced quit, disk error, etc.), the app callsfatalError()and is permanently bricked until the user manually deletes~/Library/Application Support/Maccy/Storage.sqlite.Fix: Catch the error, delete the corrupt
.sqlite,.shm, and.walfiles, and retry container creation. The user loses history but the app remains functional. OnlyfatalErrors if re-creation also fails.4. Cache compiled regexes in clipboard ignore check
File:
Clipboard.swiftshouldIgnore(_ item:)creates newNSRegularExpressionobjects for every pattern on every clipboard poll (default: every 0.5s). Regex compilation is expensive.Fix: Cache compiled regexes and only recompile when the user's pattern list changes. Also restructured to extract the string once before the loop.
5. Prompt user for accessibility permissions
Files:
Accessibility.swift,Clipboard.swiftAccessibility.check()was a no-op when permissions were missing — it returned silently, andpaste()would proceed to simulate keyboard events that did nothing. Users had no indication why paste wasn't working.Fix:
check()now returns aBooland triggers the macOS accessibility permissions prompt viaAXIsProcessTrustedWithOptionswith the prompt option.paste()gates on the return value.Test plan
🤖 Generated with Claude Code