fix(copy): replace em-dashes in user-facing strings with commas/periods
Per the project's no-dashes feedback rule (see CORBEL-Main memory
feedback_no_dashes.md), em-dashes in user-visible copy are the single
biggest "AI wrote this" surface tell. Forty-two occurrences across ten
files: ten in SettingsPage's group descriptions and option labels
(audio device picker, vocabulary help, prompt placeholders, model
descriptions), four in FirstRunPage's setup steps, three in
DictationPage / FilesPage / Energy chip tooltips, plus the privacy
bullets ("100% offline, no Python required, no cloud, no accounts,
no telemetry"), the rejection toast, MicroSteps thumb labels, and the
shutdown ritual prompts.
Replacement rule: subordinate-clause em-dashes followed by lowercase
become commas; sentence-break em-dashes followed by capitals become
full stops. Code comments left intact.
One non-em-dash regression caught while reviewing: TasksPage's energy
filter "no selection" placeholder showed "—" as a literal label; the
script changed it to a comma which read as a UI bug. Replaced with
"Any" instead.
This commit is contained in:
@@ -54,7 +54,7 @@
|
||||
let tooltip = $derived(
|
||||
energy === null
|
||||
? "Tag energy (click to set)"
|
||||
: `Energy: ${labelFor(energy)} — click to change`
|
||||
: `Energy: ${labelFor(energy)}, click to change`
|
||||
);
|
||||
|
||||
// Icon dimensions. `md` is the one used on the Tasks-page main rows;
|
||||
|
||||
@@ -211,7 +211,7 @@
|
||||
</button>
|
||||
{#if rejectionReason}
|
||||
<span class="text-[11px] text-warning" role="alert">
|
||||
Couldn't bind that combo — {rejectionReason}.
|
||||
Couldn't bind that combo. {rejectionReason}.
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -263,8 +263,8 @@
|
||||
class="opacity-0 group-hover:opacity-100 p-0.5 text-text-tertiary hover:text-success
|
||||
{rating[step.id] === 1 ? '!opacity-100 text-success' : ''}"
|
||||
onclick={() => recordThumb(step, 1)}
|
||||
aria-label={rating[step.id] === 1 ? 'Remove thumbs up' : 'Thumbs up — this is a good step'}
|
||||
title="Thumbs up — train the model on this style"
|
||||
aria-label={rating[step.id] === 1 ? 'Remove thumbs up' : 'Thumbs up, this is a good step'}
|
||||
title="Thumbs up, train the model on this style"
|
||||
style={reduceMotion ? '' : 'transition: opacity var(--duration-ui), color var(--duration-ui)'}
|
||||
>
|
||||
<ThumbsUp size={10} aria-hidden="true" />
|
||||
@@ -273,8 +273,8 @@
|
||||
class="opacity-0 group-hover:opacity-100 p-0.5 text-text-tertiary hover:text-danger
|
||||
{rating[step.id] === -1 ? '!opacity-100 text-danger' : ''}"
|
||||
onclick={() => recordThumb(step, -1)}
|
||||
aria-label={rating[step.id] === -1 ? 'Remove thumbs down' : 'Thumbs down — this misses the mark'}
|
||||
title="Thumbs down — avoid this style"
|
||||
aria-label={rating[step.id] === -1 ? 'Remove thumbs down' : 'Thumbs down, this misses the mark'}
|
||||
title="Thumbs down, avoid this style"
|
||||
style={reduceMotion ? '' : 'transition: opacity var(--duration-ui), color var(--duration-ui)'}
|
||||
>
|
||||
<ThumbsDown size={10} aria-hidden="true" />
|
||||
@@ -283,7 +283,7 @@
|
||||
class="opacity-0 group-hover:opacity-100 p-0.5 text-text-tertiary hover:text-accent"
|
||||
onclick={() => startEdit(step)}
|
||||
aria-label="Edit this step (the correction trains future suggestions)"
|
||||
title="Edit — this is the strongest training signal"
|
||||
title="Edit, this is the strongest training signal"
|
||||
style={reduceMotion ? '' : 'transition: opacity var(--duration-ui)'}
|
||||
>
|
||||
<Pencil size={10} aria-hidden="true" />
|
||||
|
||||
@@ -1004,7 +1004,7 @@
|
||||
<Card classes="h-full flex flex-col">
|
||||
{#if transcriptionFailed && !transcript.trim() && !page.recording}
|
||||
<div class="flex-1 flex items-center justify-center">
|
||||
<EmptyState icon={AlertTriangle} message="Something went wrong with that transcription. Your audio is saved — try again when you're ready." />
|
||||
<EmptyState icon={AlertTriangle} message="Something went wrong with that transcription. Your audio is saved, try again when you're ready." />
|
||||
</div>
|
||||
{:else if !transcript.trim() && !page.recording && !transcribing}
|
||||
<div class="flex-1 flex items-center justify-center">
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
modelId: result.modelId ?? null,
|
||||
});
|
||||
} catch (err) {
|
||||
error = `Failed: ${fileName} — ${typeof err === "string" ? err : err.message}`;
|
||||
error = `Failed: ${fileName}. ${typeof err === "string" ? err : err.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@
|
||||
<Play size={32} strokeWidth={1.5} class="text-accent mx-auto mb-3" />
|
||||
<h2 class="text-xl font-medium text-text">Launch Magnotia at login?</h2>
|
||||
<p class="text-sm text-text-secondary mt-3 leading-relaxed">
|
||||
So Magnotia is already there when you need it — especially useful if you said yes to morning triage. Uses your OS's standard autostart. No background tricks, no telemetry.
|
||||
So Magnotia is already there when you need it, especially useful if you said yes to morning triage. Uses your OS's standard autostart. No background tricks, no telemetry.
|
||||
</p>
|
||||
<p class="text-[11px] text-text-tertiary mt-3">You can change this any time in Settings.</p>
|
||||
<div class="flex items-center justify-center gap-3 mt-6">
|
||||
@@ -264,7 +264,7 @@
|
||||
</div>
|
||||
<p class="text-xs text-text-tertiary mt-2">{downloadProgress}%</p>
|
||||
{#if estimatedMinutes > 2}
|
||||
<p class="text-xs text-text-secondary mt-3">Download in background — this may take a while</p>
|
||||
<p class="text-xs text-text-secondary mt-3">Download in background, this may take a while</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -296,7 +296,7 @@
|
||||
{#if models.length > 0}
|
||||
<div class="mt-6">
|
||||
<h3 class="text-xs font-medium text-text-tertiary uppercase tracking-wider mb-3">Pick a model</h3>
|
||||
<p class="text-xs text-text-tertiary mb-3">One tap — we handle the rest.</p>
|
||||
<p class="text-xs text-text-tertiary mb-3">One tap, we handle the rest.</p>
|
||||
<div class="space-y-2">
|
||||
{#each models as model, i}
|
||||
<button
|
||||
@@ -330,7 +330,7 @@
|
||||
style="transition-duration: var(--duration-ui)"
|
||||
onclick={skipSetup}
|
||||
>
|
||||
Skip setup — I'll configure later
|
||||
Skip setup. I'll configure later
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -223,7 +223,7 @@
|
||||
candidates.push(trimmed);
|
||||
}
|
||||
if (candidates.length === 0) {
|
||||
vocabularyError = "Nothing to import — paste one term per line or separated by commas.";
|
||||
vocabularyError = "Nothing to import, paste one term per line or separated by commas.";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1033,10 +1033,10 @@
|
||||
bind:value={settings.microphoneDevice}
|
||||
onfocus={refreshAudioDevices}
|
||||
>
|
||||
<option value="">Auto (recommended) — let Magnotia pick the working mic</option>
|
||||
<option value="">Auto (recommended), let Magnotia pick the working mic</option>
|
||||
{#each visibleAudioDevices as dev}
|
||||
<option value={dev.name} disabled={dev.is_likely_monitor}>
|
||||
{friendlyLabel(dev)}{#if dev.is_default && dev.name !== "default"} (system default){/if}{#if dev.is_likely_monitor} — speaker monitor, skip{/if}
|
||||
{friendlyLabel(dev)}{#if dev.is_default && dev.name !== "default"} (system default){/if}{#if dev.is_likely_monitor}, speaker monitor, skip{/if}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
@@ -1077,7 +1077,7 @@
|
||||
>
|
||||
<div class="animate-fade-in">
|
||||
<p class="text-[12px] text-text-tertiary mb-4">
|
||||
Words and phrases the AI cleanup pass should preserve exactly. Useful for medication names, place names, jargon, names of people in your support network, anything Whisper tends to mishear. Vocabulary is scoped to a profile — switch profiles to swap whole vocabularies.
|
||||
Words and phrases the AI cleanup pass should preserve exactly. Useful for medication names, place names, jargon, names of people in your support network, anything Whisper tends to mishear. Vocabulary is scoped to a profile, switch profiles to swap whole vocabularies.
|
||||
</p>
|
||||
|
||||
<!-- Profile selector -->
|
||||
@@ -1123,7 +1123,7 @@
|
||||
focus:outline-none focus:border-accent"
|
||||
/>
|
||||
<textarea
|
||||
placeholder="Initial prompt (optional) — extra context Whisper primes on before every session in this profile"
|
||||
placeholder="Initial prompt (optional), extra context Whisper primes on before every session in this profile"
|
||||
bind:value={newVocabProfilePrompt}
|
||||
class="bg-bg border border-border rounded-lg px-3 py-2 text-[12px] text-text resize-none h-[72px]
|
||||
focus:outline-none focus:border-accent"
|
||||
@@ -1164,7 +1164,7 @@
|
||||
<div class="mb-5">
|
||||
<p class="text-[10px] font-medium text-text-tertiary uppercase tracking-wider mb-2">Initial Prompt</p>
|
||||
<p class="text-[11px] text-text-tertiary mb-2">
|
||||
Passed to Whisper before every transcription in this profile. Used as a biasing prompt — keep it short and topic-specific (names, domain terms, tone cues).
|
||||
Passed to Whisper before every transcription in this profile. Used as a biasing prompt, keep it short and topic-specific (names, domain terms, tone cues).
|
||||
</p>
|
||||
<textarea
|
||||
bind:value={initialPromptDraft}
|
||||
@@ -1181,7 +1181,7 @@
|
||||
onclick={saveInitialPrompt}
|
||||
class="text-[11px] text-accent hover:text-accent-hover"
|
||||
>Save prompt</button>
|
||||
<span class="text-[11px] text-text-tertiary">Unsaved changes — saves on blur.</span>
|
||||
<span class="text-[11px] text-text-tertiary">Unsaved changes, saves on blur.</span>
|
||||
{:else}
|
||||
<span class="text-[11px] text-text-tertiary">Saved.</span>
|
||||
{/if}
|
||||
@@ -1228,7 +1228,7 @@
|
||||
<div class="p-3 bg-bg-input border border-border-subtle rounded-lg animate-fade-in">
|
||||
<textarea
|
||||
bind:value={bulkVocabText}
|
||||
placeholder="Paste terms — one per line, or separated by commas. Duplicates are skipped automatically."
|
||||
placeholder="Paste terms, one per line, or separated by commas. Duplicates are skipped automatically."
|
||||
rows="4"
|
||||
disabled={bulkVocabBusy}
|
||||
class="w-full bg-bg border border-border rounded-lg px-3 py-2 text-[13px] text-text font-mono
|
||||
@@ -1446,7 +1446,7 @@
|
||||
<p class="text-[11px] text-text-tertiary mt-2">
|
||||
{settings.engine === "whisper"
|
||||
? "Whisper with the currently shipped English-only models in this build"
|
||||
: "Parakeet CTC 0.6B — English-only, fast when the model is installed"}
|
||||
: "Parakeet CTC 0.6B. English-only, fast when the model is installed"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1496,7 +1496,7 @@
|
||||
{:else}
|
||||
<div class="mb-6">
|
||||
<p class="text-[10px] font-medium text-text-tertiary uppercase tracking-wider mb-2">Parakeet Model</p>
|
||||
<p class="text-[11px] text-text-tertiary mb-3">Parakeet CTC 0.6B (int8) — ~613MB, near-instant transcription</p>
|
||||
<p class="text-[11px] text-text-tertiary mb-3">Parakeet CTC 0.6B (int8). ~613MB, near-instant transcription</p>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
{#if parakeetOk}
|
||||
@@ -1815,13 +1815,13 @@
|
||||
/>
|
||||
<p class="text-[11px] text-text-tertiary mt-2">
|
||||
{#if settings.llmPromptPreset === "email"}
|
||||
Formats output as an email paragraph — tight sentences, no markdown, no auto-added greeting or signoff.
|
||||
Formats output as an email paragraph, tight sentences, no markdown, no auto-added greeting or signoff.
|
||||
{:else if settings.llmPromptPreset === "notes"}
|
||||
Formats action items as a bullet list led by imperative verbs; keeps prose informational sentences as prose.
|
||||
{:else if settings.llmPromptPreset === "code"}
|
||||
Preserves technical terms, variable names, file paths, and symbols exactly as spoken. No translation of identifiers.
|
||||
{:else}
|
||||
No preset — the active profile's prompt governs tone alone.
|
||||
No preset, the active profile's prompt governs tone alone.
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
@@ -1955,7 +1955,7 @@
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-[13px] text-text leading-tight">Launch Magnotia at login</p>
|
||||
<p class="text-[11px] text-text-tertiary mt-0.5 leading-snug">
|
||||
So Magnotia is already there at the start of the day. Uses your OS's standard autostart — no background tricks.
|
||||
So Magnotia is already there at the start of the day. Uses your OS's standard autostart, no background tricks.
|
||||
</p>
|
||||
{#if autostartSyncing}
|
||||
<p class="text-[11px] text-text-tertiary mt-1">Updating…</p>
|
||||
@@ -2117,7 +2117,7 @@
|
||||
<Toggle
|
||||
bind:checked={settings.meetingAutoCapture}
|
||||
label="Remind me when a meeting starts"
|
||||
description="Toasts when a matching app appears in the process list. You still hit the hotkey — Magnotia never records on its own."
|
||||
description="Toasts when a matching app appears in the process list. You still hit the hotkey. Magnotia never records on its own."
|
||||
/>
|
||||
{#if settings.meetingAutoCapture}
|
||||
<div class="ml-[50px] mt-2 mb-1 animate-fade-in">
|
||||
@@ -2139,7 +2139,7 @@
|
||||
<Toggle
|
||||
bind:checked={settings.soundCues}
|
||||
label="Sound cues on start / stop / complete"
|
||||
description="Short synthesised tones for eyes-off feedback. Synthesised locally via Web Audio — no assets bundled."
|
||||
description="Short synthesised tones for eyes-off feedback. Synthesised locally via Web Audio, no assets bundled."
|
||||
/>
|
||||
{#if settings.soundCues}
|
||||
<div class="ml-[50px] mt-2 mb-1 animate-fade-in flex items-center gap-3">
|
||||
@@ -2296,11 +2296,11 @@
|
||||
|
||||
<div class="space-y-1.5">
|
||||
{#each [
|
||||
"100% offline — all processing on your machine",
|
||||
"No Python required — compiled Whisper engine",
|
||||
"No cloud — audio never leaves your computer",
|
||||
"No accounts — no sign-up, no tracking",
|
||||
"No telemetry — zero data collection",
|
||||
"100% offline, all processing on your machine",
|
||||
"No Python required, compiled Whisper engine",
|
||||
"No cloud, audio never leaves your computer",
|
||||
"No accounts, no sign-up, no tracking",
|
||||
"No telemetry, zero data collection",
|
||||
] as item}
|
||||
<div class="flex items-start gap-2">
|
||||
<Check class="w-3.5 h-3.5 text-success mt-0.5 flex-shrink-0" />
|
||||
@@ -2315,7 +2315,7 @@
|
||||
<div class="mt-6 pt-5 border-t border-border-subtle">
|
||||
<p class="text-[10px] font-medium text-text-tertiary uppercase tracking-wider mb-2">Diagnostics</p>
|
||||
<p class="text-[11px] text-text-tertiary mb-3">
|
||||
If something goes wrong, generate a diagnostic report to share with the developer. The report contains your settings, recent errors, any crash dumps, and the tail of the log file. Nothing is sent automatically — you preview it first, then choose copy or save.
|
||||
If something goes wrong, generate a diagnostic report to share with the developer. The report contains your settings, recent errors, any crash dumps, and the tail of the log file. Nothing is sent automatically, you preview it first, then choose copy or save.
|
||||
</p>
|
||||
<div class="flex gap-2 mb-3">
|
||||
<button
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
<p class="text-[12px] text-text-secondary">Nothing in the list.</p>
|
||||
{:else}
|
||||
<p class="text-[11px] text-text-tertiary mb-3">
|
||||
These are still here. Naming them silences the loop — you don't have to act now.
|
||||
These are still here. Naming them silences the loop, you don't have to act now.
|
||||
</p>
|
||||
<ul class="flex flex-col gap-1">
|
||||
{#each openLoops.slice(0, 12) as task (task.id)}
|
||||
@@ -146,7 +146,7 @@
|
||||
</li>
|
||||
<li>
|
||||
<span class="font-medium text-text">Pick a closing line.</span>
|
||||
Say it out loud when you're ready — something like "I'm done for today."
|
||||
Say it out loud when you're ready, something like "I'm done for today."
|
||||
</li>
|
||||
<li>
|
||||
<span class="font-medium text-text">Let it rest.</span>
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
// rot over time.
|
||||
type EnergyOption = { value: EnergyLevel | null; label: string };
|
||||
const energyOptions: EnergyOption[] = [
|
||||
{ value: null, label: "—" },
|
||||
{ value: null, label: "Any" },
|
||||
{ value: "high", label: "High" },
|
||||
{ value: "medium", label: "Med" },
|
||||
{ value: "brain_dead", label: "Low" },
|
||||
@@ -351,7 +351,7 @@
|
||||
style="transition-duration: var(--duration-ui)"
|
||||
onclick={toggleMatchMyEnergy}
|
||||
aria-pressed={settings.matchMyEnergy}
|
||||
aria-label={settings.matchMyEnergy ? 'Match my energy is on — click to turn off' : 'Match my energy is off — click to turn on'}
|
||||
aria-label={settings.matchMyEnergy ? 'Match my energy is on, click to turn off' : 'Match my energy is off, click to turn on'}
|
||||
title="Sort matching tasks to the top (unset counts as Medium)"
|
||||
>
|
||||
<Zap size={12} aria-hidden="true" />
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
toasts.error(
|
||||
"Hotkey not registered",
|
||||
`${hotkey} — ${msg}. Reverted to ${registeredHotkey ?? "previous value"}.`,
|
||||
`${hotkey}. ${msg}. Reverted to ${registeredHotkey ?? "previous value"}.`,
|
||||
);
|
||||
// Revert the in-memory setting to the last successfully-registered hotkey
|
||||
// so the UI does not lie about what is actually bound.
|
||||
|
||||
Reference in New Issue
Block a user