Skip to content

History: redo a transcription, and save the recording it came from - #20

Merged
bojieli merged 7 commits into
mainfrom
history-redo-and-save-audio
Aug 28, 2026
Merged

History: redo a transcription, and save the recording it came from#20
bojieli merged 7 commits into
mainfrom
history-redo-and-save-audio

Conversation

@bojieli

@bojieli bojieli commented Aug 28, 2026

Copy link
Copy Markdown
Owner

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 canRetry excludes by definition. The only
recovery was to say the whole thing again.

Two offers per history row, on all four clients:

macOS Windows Android iOS
Redo the transcription circular arrow, replacing Retry on completed rows row menu, renamed per row own button under the row circular arrow
Save the original audio ⬇ → save panel row menu → save dialog "Save audio" → document picker ⬇ → Files

Both are keyed to canRedo — the recording still being on disk — rather than to the
status. 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. deliveredText prefers
the 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

canRedo and audioExportName are hand-ported to Swift, C# and Kotlin with the same
test 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 is
useless 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

Swift core 524 tests pass
C# core 471 tests pass, dotnet format --verify-no-changes clean
Android JVM unit suite passes, gradlew lint clean
iOS shared unit tests + both UI suites pass on the simulator
Builds all four clients, including the WinForms app

New tests in all three core suites cover canRedo on kept versus discarded audio, the
export name, and the stale-rewrite drop.

Not done: the CLI is untouched — dnt history retry still 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

bojieli and others added 7 commits August 28, 2026 23:33
…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
@bojieli
bojieli merged commit 9a795fc into main Aug 28, 2026
13 checks passed
@bojieli
bojieli deleted the history-redo-and-save-audio branch August 28, 2026 16:24
@bojieli bojieli mentioned this pull request Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant