Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b4bc481
docs(whispering): spec the Cleanup/Formats split and record ADR 0013
braden-w Jun 17, 2026
b54e6a1
feat(whispering): split transformations into Cleanup + Formats (Wave 1)
braden-w Jun 17, 2026
c7f7040
feat(whispering): run automatic Cleanup after transcription (Wave 2)
braden-w Jun 17, 2026
dff16a5
refactor(whispering): rename lingering transformation output vocabula…
braden-w Jun 17, 2026
990cc1f
docs(whispering): record Wave 2 landed and its decisions in the spec
braden-w Jun 17, 2026
5f054dd
docs(whispering): recut ADR 0013 + spec to the Dictionary + pinned-Re…
braden-w Jun 17, 2026
73c827d
docs(whispering): relock dictation rewrite to Polish + Dictionary + R…
braden-w Jun 17, 2026
b5afa23
refactor(whispering): rename Format library to Recipes (Wave 1.1)
braden-w Jun 17, 2026
d945a75
feat(whispering): replace Cleanup with Polish + Dictionary, rewire ru…
braden-w Jun 17, 2026
80bbc41
docs(whispering): mark Wave 1 of the dictation rewrite landed
braden-w Jun 17, 2026
33e2eba
feat(whispering): add the Dictation settings page (Wave 2)
braden-w Jun 17, 2026
2af937c
docs(whispering): mark Wave 2 of the dictation rewrite landed
braden-w Jun 17, 2026
b592600
feat(whispering): Polishing HUD on the floating pill + ship-raw cance…
braden-w Jun 17, 2026
9d43d0d
docs(whispering): mark Wave 3 of the dictation rewrite landed
braden-w Jun 17, 2026
989c487
Merge origin/main into whispering-cleanup-formats-restart
braden-w Jun 18, 2026
429321c
docs(whispering): record Wave 3.5 hardening plan and Polish scaffold …
braden-w Jun 18, 2026
d2d401a
feat(whispering): guard Polish against prompt injection with a fixed …
braden-w Jun 18, 2026
1e85879
feat(whispering): persist the polished transcript alongside the raw
braden-w Jun 18, 2026
1ee56f8
refactor(whispering): drop dead transformation view transitions, fix …
braden-w Jun 18, 2026
9766391
feat(whispering): add built-in recipes and the picker/library list
braden-w Jun 18, 2026
92de73f
feat(whispering): add the Recipes library page and nav item
braden-w Jun 18, 2026
81f4e44
feat(whispering): wire the in-app Recipes picker to the shortcuts
braden-w Jun 18, 2026
847a43a
feat(whispering): feed Dictionary terms into the transcription initia…
braden-w Jun 18, 2026
6a6cefb
refactor(whispering): make the recipe icon live and drop a dead getter
braden-w Jun 18, 2026
0cd8af8
docs(whispering): accept ADR 0021 and delete the spent dictation spec
braden-w Jun 18, 2026
6aa558b
perf(whispering): skip the no-op polished-transcript write in speed mode
braden-w Jun 18, 2026
2979efa
refactor(whispering): trim recipes state to its used surface
braden-w Jun 18, 2026
2e678b6
Merge origin/main into whispering-cleanup-formats-restart
braden-w Jun 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/whispering/src/lib/commands.browser.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { SatisfiedCommand } from '$lib/commands';

/**
* Browser builds contribute no extra commands. The transformation picker is
* Browser builds contribute no extra commands. The recipe picker is
* desktop-only: it captures the selection in another app via a simulated system
* copy and opens a separate Tauri window, neither of which a browser tab can do.
* The cross-platform `Run transformation on clipboard` command covers the web
* transformation path. See `commands.tauri.ts` for the desktop addition.
* The cross-platform `Run recipe on clipboard` command covers the web recipe
* path. See `commands.tauri.ts` for the desktop addition.
*/
export const platformCommands = [] as const satisfies SatisfiedCommand[];
9 changes: 4 additions & 5 deletions apps/whispering/src/lib/commands.tauri.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { SatisfiedCommand, ShortcutEventState } from '$lib/commands';
import { openTransformationPicker } from '$lib/operations/transformation-picker';
import { openRecipePicker } from '$lib/operations/recipe-picker';

/**
* Desktop-only commands, spread into the registry by the `#platform/commands`
Expand All @@ -9,8 +9,8 @@ import { openTransformationPicker } from '$lib/operations/transformation-picker'
*/
export const platformCommands = [
{
id: 'openTransformationPicker',
title: 'Open transformation picker',
id: 'openRecipePicker',
title: 'Open recipe picker',
// Fire on release, not press: the global accelerator carries a Cmd/Ctrl+Shift
// chord, and capturing on press synthesizes Cmd/Ctrl+C while that chord is
// still held, so the foreground app sees Cmd+Shift+C instead of a clean copy.
Expand All @@ -19,8 +19,7 @@ export const platformCommands = [
// in-app shortcut would never fire. The callback guard runs once, on release.
on: ['Pressed', 'Released'],
callback: (state?: ShortcutEventState) => {
if (state === 'Released' || state === undefined)
openTransformationPicker();
if (state === 'Released' || state === undefined) openRecipePicker();
},
},
] as const satisfies SatisfiedCommand[];
14 changes: 7 additions & 7 deletions apps/whispering/src/lib/commands.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { platformCommands } from '#platform/commands';
import { runRecipeOnClipboard } from '$lib/operations/recipe-clipboard';
import {
cancelRecording,
startManualRecording,
stopManualRecording,
toggleManualRecording,
toggleVadRecording,
} from '$lib/operations/recording';
import { runTransformationOnClipboard } from '$lib/operations/transformation-clipboard';

/**
* Registry of available commands in the application.
Expand All @@ -18,9 +18,9 @@ import { runTransformationOnClipboard } from '$lib/operations/transformation-cli
* command registry.
*
* Platform split: `sharedCommands` exist in every build. Desktop-only commands
* (the transformation picker, which captures a selection from another app and
* opens a Tauri window) come from the `#platform/commands` seam, so a browser
* build never imports their Tauri-only code and never offers them as shortcuts.
* (the recipe picker, which captures a selection from another app and opens a
* Tauri window) come from the `#platform/commands` seam, so a browser build
* never imports their Tauri-only code and never offers them as shortcuts.
*/

/**
Expand Down Expand Up @@ -84,10 +84,10 @@ const sharedCommands = [
callback: () => toggleVadRecording(),
},
{
id: 'runTransformationOnClipboard',
title: 'Run transformation on clipboard',
id: 'runRecipeOnClipboard',
title: 'Run recipe on clipboard',
on: ['Pressed'],
callback: () => runTransformationOnClipboard(),
callback: () => runRecipeOnClipboard(),
},
] as const satisfies SatisfiedCommand[];

Expand Down
131 changes: 0 additions & 131 deletions apps/whispering/src/lib/components/CandidateCards.svelte

This file was deleted.

74 changes: 74 additions & 0 deletions apps/whispering/src/lib/components/RecipePicker.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<script lang="ts">
import { Badge } from '@epicenter/ui/badge';
import * as Command from '@epicenter/ui/command';
import * as Modal from '@epicenter/ui/modal';
import { deliverRecipeResult } from '$lib/operations/delivery';
import { runRecipe } from '$lib/operations/run-recipe';
import { sound } from '$lib/operations/sound';
import { report } from '$lib/report';
import { isBuiltinRecipeId } from '$lib/state/builtin-recipes';
import { recipePicker } from '$lib/state/recipe-picker.svelte';
import { recipes } from '$lib/state/recipes.svelte';
import type { Recipe } from '$lib/workspace';

/**
* The in-app Recipe picker: a command palette the `openRecipePicker` /
* `runRecipeOnClipboard` shortcuts raise over the captured text. Mounted once
* in the app layout; visibility is driven entirely by the `recipePicker` rune.
* Picking a recipe runs it on the captured source and delivers the take. See
* ADR 0029.
*/

async function run(recipe: Recipe) {
const input = recipePicker.source;
recipePicker.close();
const loading = report.loading({
title: `Running ${recipe.name}...`,
description: 'Reshaping your text with AI.',
});
const { data, error } = await runRecipe({ input, recipe });
if (error) {
loading.reject({
title: `Couldn't run ${recipe.name}`,
description: error.message,
cause: error,
});
return;
}
await sound.playSoundIfEnabled('recipeComplete');
const notice = await deliverRecipeResult({ text: data, recordingId: null });
loading.resolve(notice);
}
</script>

<Modal.Root
bind:open={
() => recipePicker.isOpen, (open) => { if (!open) recipePicker.close(); }
}
>
<Modal.Content class="overflow-hidden p-0">
<Modal.Title class="sr-only">Run a recipe</Modal.Title>
<Modal.Description class="sr-only">
Pick a recipe to run on your captured text.
</Modal.Description>
<Command.Root loop>
<Command.Input placeholder="Run a recipe..." />
<Command.List>
<Command.Empty>No recipes found.</Command.Empty>
<Command.Group>
{#each recipes.pickable as recipe (recipe.id)}
<Command.Item value={recipe.name} onSelect={() => run(recipe)}>
{#if recipe.icon}
<span aria-hidden="true">{recipe.icon}</span>
{/if}
<span class="flex-1 truncate">{recipe.name}</span>
{#if isBuiltinRecipeId(recipe.id)}
<Badge variant="secondary">Built-in</Badge>
{/if}
</Command.Item>
{/each}
</Command.Group>
</Command.List>
</Command.Root>
</Modal.Content>
</Modal.Root>
Loading
Loading