uttrflow / uttrflow/uttrflow-swift

Deleting, flagging or restoring from the main window fails silently when the store refuses, and deletes there have no undo

Open
#679 0 comments 0 reactions 0 assignees View on GitHub
area:general bug P2
Dominant language
Swift
Stars
4
Forks
17
Avg merge
3h 32m
Merged PRs (30d)
277

Description

## What happens

Most changes made from the main window go through one helper:

```swift
// Sources/Uttrflow/AppDelegate.swift:1647-1657 (abridged to the helper)
/// Runs a store change and redraws from what the store then holds, never from what it returned.
private func act(_ change: @escaping () async throws -> Void) {
intentWork = Task { [weak self] in
do {
try await change()
} catch {
Self.log.error("store change failed: \(error.localizedDescription, privacy: .public)")
}
self?.refreshMainWindow()
}
}
```

It is used for deleting a dictation (`:1561-1570`), deleting a dictionary word (`:1578-1579`), restoring a retired word (`:1580-1581`), deleting a snippet (`:1602-1603`) and flagging a dictation (`:1625-1627`). When the store throws, the error goes to the log and the page redraws unchanged, so the button appears to do nothing. `undoCorrection` swallows its failure the same way with `try?` (`:1614-1623`).

Two editors in the same file do report a refusal: saving a word sets `wordRefusal` and saving a snippet sets `snippetRefusal`, which the editors draw (`:1668-1680`, `:1692-1704`). The store errors already carry user-facing text for this (`SnippetStoreError.couldNotWrite`: "Your snippets could not be updated on this Mac.", `Sources/UttrflowCore/Errors/SnippetStoreError.swift:15`).

The successful case has a gap too: these deletes are immediate, with no confirmation and no undo. The clipboard panel keeps the deleted clip and offers Undo (`AppDelegate.swift:890-898`); the main window's Delete on a dictation, a dictionary word (losing its use and undo counts) or a snippet (losing its text) cannot be taken back.

## Why it matters

A full disk, a permissions problem on Application Support, or a file another process holds makes Delete, Flag and Restore silently do nothing, and the user retries or assumes the app is broken. For a privacy-motivated delete of a dictation, silence is the worst outcome: the user believes it is gone. A full disk is most likely on the smallest Macs.

## How to reproduce

Make the dev bundle's Application Support folder unwritable (for example `chmod a-w` on the folder, since the stores write atomically), then delete a dictation or a snippet from the main window. The row stays, nothing is shown, and Console has "store change failed".

## Acceptance criteria

- A failed main-window store change shows a short, specific message near what the user pressed (reuse the store error's `userMessage`), and VoiceOver hears it.
- `undoCorrection` reports a failed undo the same way.
- Decide on the issue whether main-window deletes get an Undo like the clipboard panel's (at least for snippets and hand-added words, whose content cannot be recreated from anything else).
- A test covers the failure path. `AppDelegate` is hard to test today (#145); a small presenter or intent-result type that `act` reports into is enough.

Contributor guide

Open the contributing guide

Research direction

Start in Sources/Uttrflow/AppDelegate.swift at act, undoCorrection, the main-window actions, and the clipboard panel's undo handling. Read SnippetStoreError.swift for userMessage and inspect the testing limitation in #145. Done means failed changes produce a specific VoiceOver-visible message, a failure-path test exists, and the delete-undo scope is resolved.

Written by the indexing model from the issue text.

Assessment

Tech stack
macos, swift
Domain
accessibility, desktop
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.