History: redo a transcription, and save the recording it came from - #20
Merged
Conversation
…aving Retry answers "the words never arrived". It cannot answer "the words arrived wrong", which is the more common complaint — a name misheard, a provider that was the wrong one for the accent — because that row is completed and `canRetry` is gated on the status. `canRedo` asks the only question that actually decides it: is the recording still on disk. For a completed dictation that means the keep-audio setting was on when it was made. `audioExportName` names the recording for when it was said. On disk it is the record's UUID, which is the right name for a file the store owns and a useless one in a downloads folder next to twenty others. Fixed format in all three languages, not a cultural one: a filename with a slash in it is not a filename. The re-transcription path now also drops the rewrite beside the transcript it just replaced. Keeping it would be worse than losing it — `deliveredText` prefers the styled version, so redoing a rewritten dictation would replace the words and still show the old ones, a button that appears to do nothing. Failed rows never carry a rewrite, so this changes nothing for Retry. Mirrored four ways as usual: Swift, C#, Kotlin, and the same three test suites. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jptSNBmsbgyPSf8FMpxpa
Two buttons on a history row, both keyed to `canRedo` — the recording still being on disk — rather than to the status. The circular arrow already meant "Retry" on a failed row; on a completed one it now means "Redo the transcription". Deliberately not the same ending as Retry: a retry is recovering words that never reached a cursor, so it types them, and nothing is owed a cursor when the user is reading their own history. The row updates and Copy is one button away. The down-arrow saves the recording itself. It is the evidence behind the row — what a wrong transcript should be judged against, and the only thing here that cannot be reconstructed. A copy, not a move: saving it does not cost the ability to redo it. Keep audio's tooltip now says what keeping it buys beyond Retry, because that setting is the whole reason a completed row has either button. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jptSNBmsbgyPSf8FMpxpa
…story The same two actions as macOS, in the idiom the Windows history already uses: the row's right-click menu, where Retry, Copy and Delete already live. Retry becomes one item renamed for the row it is opened on — "Retry this dictation" on a failed row, "Redo the transcription" on a completed one. The request is identical; what differs is what the user is asking for. Both are gated on CanRedo, so double-click reaches a completed row too and says which of the two it is doing while it runs. "Save the original audio…" writes the recording to a file the user picks, suggested as donottype-<when it was said>.wav. A copy, so saving it does not cost the ability to redo it. IO failures land in the summary line uncut: the path and the reason are what tells someone to pick a different folder. The hint under the list names both, since neither is visible until a right-click. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jptSNBmsbgyPSf8FMpxpa
…story A second line under the row, present only when the recording still is — which for a dictation that succeeded means the keep-audio setting was on when it was made, so on most rows this line does not exist rather than sitting there disabled. Four buttons beside the transcript would have left it a column too narrow to read, and reading it is what the row is for. "Redo transcription" appears only on completed rows: on a failed one, Retry above already transcribes the very same recording, and two names for one action is worse than one. "Save audio" writes the recording through the document picker, suggested as donottype-<when it was said>.wav. The picker holds the row's id across the trip rather than a megabyte of audio, and the store is re-read when the answer comes back — which is also what makes a row deleted in the meantime say so instead of writing a file from a stale copy. Keep audio's own description in Settings now says what keeping it buys beyond Retry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jptSNBmsbgyPSf8FMpxpa
The same two row buttons as macOS, keyed to the recording still being on disk rather than to the status. The circular arrow already meant "Retry" on a failed row; on a completed one it now means "Redo the transcription", and deliberately does not end the way Retry does. A retry is recovering words that never reached the keyboard, so it delivers them; nothing is waiting on a redo, and putting its text on the clipboard would quietly replace whatever is there. The down-arrow saves the recording through Files rather than a share sheet: it is evidence somebody wants to keep, and Save to Files is the ending that leaves it somewhere they can find again. The bytes are read into the export document so the picker can offer donottype-<when it was said>.wav — the store's own copy is named for a UUID, which is an implementation detail to put in front of a user. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jptSNBmsbgyPSf8FMpxpa
…gelog Two new rows under Other capabilities, plus the one difference worth writing down: Android puts the redo on a line of its own because a fourth control beside the transcript leaves the transcript too narrow to read, while the other three give redo and Retry one control between them — a row can only ever want one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jptSNBmsbgyPSf8FMpxpa
A completed row is not broken, so a double-click on one is as likely to be exploratory as intended — and every one of these spends a request. The redo now asks for itself from the row menu, which is where it was discoverable anyway. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jptSNBmsbgyPSf8FMpxpa
Merged
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.
Retry only ever answered the words never arrived. The more common complaint is that
they arrived wrong — a name misheard, a provider that was the wrong one for the
accent — and that row is completed, which
canRetryexcludes by definition. The onlyrecovery was to say the whole thing again.
Two offers per history row, on all four clients:
Both are keyed to
canRedo— the recording still being on disk — rather than to thestatus. For a dictation that succeeded that means Keep audio was on when it was made,
so on most rows neither offer exists at all rather than sitting there disabled. Keep
audio's own description now says what keeping it buys beyond Retry, on all four.
Three decisions worth reviewing
A redo is not Retry renamed. Retry recovers words that never reached a cursor, so it
types them. A redo is read in the history: typing into whatever sits behind the settings
window, or replacing the clipboard on a phone, would be a surprise. It updates the row
and leaves Copy one button away. The shared request is the same call either way — only
the caller knows whether the text is owed to a cursor somewhere.
A redo drops the rewrite beside the transcript it replaced.
deliveredTextprefersthe styled version, so without this a redo of a rewritten dictation would replace the
words and still show the old ones — a button that appears to do nothing. Failed rows
never carry a rewrite, so Retry is unaffected. Covered by a test in the Swift suite.
Windows double-click stays retry-only. A completed row is not broken, a double-click
on one is as likely to be exploratory as intended, and every one of these spends a
request. The redo asks for itself from the row menu.
Layout
macOS, Windows and iOS give redo and Retry one control between them, since a row can
only ever want one of the two. Android puts the redo on a line of its own beneath the
row: a fourth control beside the transcript leaves the transcript too narrow to read,
which is the thing the row is for.
Mirrored four ways
canRedoandaudioExportNameare hand-ported to Swift, C# and Kotlin with the sametest in each of the three suites, per the usual rule for shared core logic. Saved files
are named
donottype-20260828-143205.wav; on disk they are named for a UUID, which isuseless in a downloads folder. The format is fixed rather than cultural in all three — a
filename with a slash in it is not a filename.
Verification
dotnet format --verify-no-changescleangradlew lintcleanNew tests in all three core suites cover
canRedoon kept versus discarded audio, theexport name, and the stale-rewrite drop.
Not done: the CLI is untouched —
dnt history retrystill only reaches failed rows.The ask was the history UI on desktop and mobile, and the CLI is a separate surface. I
also did not drive the installed macOS app: killing the running copy would cost its
Accessibility grant on the next ad-hoc rebuild.
🤖 Generated with Claude Code
https://claude.ai/code/session_016jptSNBmsbgyPSf8FMpxpa