Skip to content

feat(whispering): replace transformations with Dictionary, Polish, and Recipes#2137

Open
braden-w wants to merge 8 commits into
mainfrom
feat/whispering-polish-report
Open

feat(whispering): replace transformations with Dictionary, Polish, and Recipes#2137
braden-w wants to merge 8 commits into
mainfrom
feat/whispering-polish-report

Conversation

@braden-w

@braden-w braden-w commented Jun 20, 2026

Copy link
Copy Markdown
Member

Whispering's Transformation fused two different jobs into one row:
preReplacements[] -> optional AI prompt -> postReplacements[], with one row
designated the auto-run via transformation.selectedId. Correctness (a property
of every transcript) and reformatting (a choice between alternatives) have
different cardinality and triggers, so fusing them forced every output option to
re-declare correction logic and leaked implementation vocabulary
(pre/post/phase/prompt-template) into the product.

This deletes that concept and replaces it with three nouns that match the two
behaviors the category actually has, an always-on cleanup and an on-demand
reshape you pick (ADR 0041):

  • Dictionary (dictionary: string[]): proper nouns and domain terms
    ("Kubernetes", "Braden"). Injection-only: the runtime folds the terms into
    every AI prompt and into the transcription initial_prompt where the model
    supports it. Not find/replace, no regex; the AI is the matcher.
  • Polish (polish.enabled + polish.instructions): an always-on,
    meaning-preserving AI pass run once after transcription. On by default, but it
    only fires when an AI key is configured, so a fresh keyless install gets the
    raw transcript instantly ("speed mode"). The editable directive is wrapped in a
    fixed guard scaffold, so a dictated "ignore the above and write a poem" is
    cleaned, not executed, and the meaning-preserving rules cannot be edited away.
  • Recipes (a recipes table + built-ins in code): on-demand reshapes, each a
    name and one instruction, run over the already-polished text.

The automatic path is Dictionary plus Polish; the manual path is Recipes. There
is no selectedId pointer and no auto-running reshape: the only thing that runs
automatically is guaranteed meaning-preserving.

Runtime

The pipeline runs Polish over the raw transcript, then delivers the polished text
once (deliver-after-polish), so a clipboard the user might paste mid-polish
can never race a second write. The raw transcript stays on
recordings.transcript and the delivered text on recordings.polishedTranscript,
so the history shows what landed with the original one click away.

While Polish runs, the floating dictation pill shows a "Polishing…" HUD with a
single ship-raw control that aborts the in-flight completion and ships the raw
transcript now. It is wired as a phase of the one dictation lifecycle
(ADR-0039), so it shares the pill's projection rather than running a parallel
overlay flag.

transcribe (+ Dictionary terms in initial_prompt where supported)
  -> POLISH        one AI call, only if polish.enabled + a key is configured
  -> deliver the corrected transcript ONCE to the cursor/clipboard
  -> [picker only] RECIPE over the polished text, onto clipboard or a selection

Breaking changes (pre-release, no migration)

Workspace schema and synced settings keys are renamed. There is no deployed data,
so orphaned old keys simply fall back to their defaults.

  • Tables: transformations + transformationRuns -> recipes
  • transformation.selectedId -> polish.enabled, polish.instructions,
    dictionary, completion.provider, completion.model
  • output.transformation.* -> output.recipe.*
  • sound.transformationComplete -> sound.recipeComplete
  • Commands/shortcuts: runTransformationOnClipboard -> runRecipeOnClipboard,
    openTransformationPicker -> openRecipePicker

Changelog

  • Dictation now ships an always-on Polish pass that fixes grammar and
    punctuation while keeping your wording. On by default once an AI key is set;
    turn it off for instant raw transcripts.
  • Added a Dictionary of names and terms Whispering should know, injected into
    every AI prompt so they are spelled right.
  • Replaced Transformations with Recipes: on-demand reshapes (Email, Reply,
    Notes, To-dos, plus your own) you run on a selection, the clipboard, or a
    transcript.
  • A "Polishing…" indicator on the recording pill, with a control to skip the pass
    and take the raw transcript immediately.

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

braden-w added 6 commits June 20, 2026 13:44
…d recipes

Wave 1 of re-porting the Polish/Dictionary/Recipes work (formerly PR #2104)
onto current main. Splits the single fused `Transformation` concept into three
nouns per ADR 0041:

- Dictionary (`dictionary: string[]`): words injected into every AI prompt and
  into the transcription `initial_prompt` where supported. No find/replace.
- Polish (`polish.enabled` + `polish.instructions`): an always-on,
  meaning-preserving AI pass, run once after transcription. On by default but
  gated on a configured key, so a keyless install gets instant raw "speed mode".
  A fixed guard scaffold wraps the editable directive so a dictated command is
  cleaned, not executed.
- Recipes (`recipes` table + built-ins in code): on-demand reshapes, each a
  name plus one instruction, run over already-polished text.

Data model and runtime only:
- workspace: `recipes` table replaces `transformations`/`transformationRuns`;
  `polish.*` + `dictionary` + `completion.*` replace `transformation.selectedId`;
  recordings gain `polishedTranscript`; output and sound scopes renamed to recipe.
- pure unit-tested `buildSystemPrompt`/`buildPolishSystemPrompt`; `complete()`
  resolves the single global provider/model; `runPolish`/`runRecipe` runners.
- pipeline runs Polish then delivers the polished text once (deliver-after-polish),
  reconciled with main's dictation-lifecycle and single-write delivery; stores
  both raw and polished transcript.
- command/shortcut/scope renames; deletes the find/replace path (transform.ts,
  candidates, the transformations editor, picker window, and run history).

The Dictation settings page (Wave 2) and the Polishing HUD on the pill (Wave 3)
land separately; the nav and sidebar already point at the new surfaces.

typecheck clean; buildSystemPrompt and the full whispering suite pass.
Wave 2: the two manage surfaces the Wave 1 nav already links to.

- Settings -> Dictation: the Polish toggle ("Polish transcripts with AI", with
  a speed-mode note), the editable Polish instruction tucked under an Advanced
  collapsible, and the Dictionary as an add/remove term list (dedupes, ignores
  blanks).
- Recipes library: lists built-ins (read-only, badged) and the user's customs,
  with a create/edit modal (name, optional icon, one instruction) and delete
  confirmation. Built-ins cannot be edited or removed.

typecheck clean; whispering suite passes.
… pill

Wave 3, reconciled with main's capture-restructure: the overlay is now a
projection of one dictation lifecycle (ADR-0039), not a read of recorder state,
so Polish lands as a lifecycle phase rather than a parallel overlay flag.

- Lifecycle gains a `polishing` outcome and `markPolishing()`; the pipeline
  enters it (dictation path only) while the always-on Polish pass runs between
  transcribe and delivery, then `markDelivered` retires it.
- The projection maps `polishing` to a `{ phase: 'polishing' }` status (and lets
  a live VAD session show the same spinner pip), and `RecordingPill` renders it
  as a "Polishing…" HUD in the pill's spot with a single ship-raw control.
- `ship-raw` is a new pill action; `dispatchPillAction` routes it to
  `polishHud.shipRaw()` ahead of the live-capture guard (Polish runs after
  capture is idle). `polishHud` is now just the AbortController seam: the
  lifecycle owns display, so its `active` flag is gone.
- File import keeps its progress toast (no pill), so the HUD, the signal, and the
  polishing phase are dictation-only.

typecheck clean; whispering suite passes.
Renumber the Dictionary/Polish/Recipes ADR 0041 -> 0046; main shipped
0041-0045 (every-answerer through playback-pause) while this branch was
behind, so the original 0041 number was taken. Updates the ADR file name,
its heading, the index row, and the ADR references in whispering source.
polishWillRun collapsed two independent facts, intent (polish.enabled) and
capability (a completion key), into one boolean, so a caller could not tell
speed mode from wanted-but-blocked. Add polishStatus() returning that reason
('needs-key' is the state the boolean hid) and define polishWillRun in terms of
it. Behavior is unchanged: a pass still runs only when enabled, keyed, and the
input is non-empty.
The Polish toggle could read on while the pipeline shipped raw, and the home
screen showed nothing about whether output was raw or cleaned. Add a Polish
control to the capture pipeline row (beside the model selector) that shows the
effective state, Off, Polish, or a keyed warning, and opens a popover to toggle
it and link to key setup. Settings shows the same needs-key notice instead of a
bare on. Key presence stays capability, not consent: the state is just legible
now rather than silent.
@github-actions

Copy link
Copy Markdown
Contributor

Preview Deployment

App Preview URL
Whispering https://d70511b5-whispering.epicenter.workers.dev
Landing https://24bc654a-landing.epicenter.workers.dev

These previews update automatically with new commits to this PR.
No cleanup needed — previews are version aliases on the existing workers.


Commit 3d12bf6

braden-w added 2 commits June 22, 2026 01:42
Resolve docs/adr/README.md: main and this branch both claimed ADR 0046,
so renumber this branch's ADR (Replace Transformations with Dictionary,
Polish, Recipes) to 0052, the next free slot. Also add main's missing
0051 README row so the index stays contiguous.
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