fix(app): strip prose em-dashes from UI copy

Replace em-dashes used as sentence punctuation (Plans, Settings, Compare,
Overview, CliErrorPanel) with cleaner punctuation; keep the standalone "—"
empty-value placeholders (a no-data convention). Update affected copy assertions.

typecheck clean; 128/128 tests pass.
This commit is contained in:
iamtoruk 2026-07-12 13:13:31 -07:00
parent 34333dc2c5
commit 144d2b3363
7 changed files with 13 additions and 13 deletions

View file

@ -16,7 +16,7 @@ export function cliErrorDisplay(error: CliError): { title: string; message: stri
if (isPermissionCliError(error)) {
return {
title: 'Permission denied',
message: 'permission denied grant Full Disk Access',
message: 'permission denied; grant Full Disk Access',
tone: 'amber',
}
}

View file

@ -81,7 +81,7 @@ export function Compare({
if (next === modelB) setModelB(nudgeDistinct(next))
}}
>
{modelRows.map(model => <option key={model.model} value={model.model}>{model.model} {model.calls.toLocaleString()} calls</option>)}
{modelRows.map(model => <option key={model.model} value={model.model}>{model.model} · {model.calls.toLocaleString()} calls</option>)}
</select>
<span className="cmp-vs">vs</span>
<select
@ -94,7 +94,7 @@ export function Compare({
if (next === modelA) setModelA(nudgeDistinct(next))
}}
>
{modelRows.map(model => <option key={model.model} value={model.model}>{model.model} {model.calls.toLocaleString()} calls</option>)}
{modelRows.map(model => <option key={model.model} value={model.model}>{model.model} · {model.calls.toLocaleString()} calls</option>)}
</select>
</div>
{modelA && modelB && modelA !== modelB && (

View file

@ -171,7 +171,7 @@ function deriveAnomalies(data: MenubarPayload, now: Date): Anomaly[] {
}
if (data.current.cacheHitPercent < 50) {
anomalies.push({ lead: 'Cache hit is low (', value: `${Math.round(data.current.cacheHitPercent)}%`, tail: ') — more of your context is uncached.' })
anomalies.push({ lead: 'Cache hit is low (', value: `${Math.round(data.current.cacheHitPercent)}%`, tail: '). More of your context is uncached.' })
}
return anomalies.slice(0, 3)
}

View file

@ -95,7 +95,7 @@ describe('Plans', () => {
expect(claudeFill).toHaveStyle({ width: '100%' })
expect(claudeFill).toHaveClass('over')
const hotPace = screen.getByText('On pace to exceed projected $254.00 by Jul 14')
const hotPace = screen.getByText('On pace to exceed; projected $254.00 by Jul 14')
expect(hotPace).toHaveClass('pace', 'hot')
expect(screen.getByText('Cursor Pro')).toBeInTheDocument()
@ -135,7 +135,7 @@ describe('Plans', () => {
render(<Plans period="30days" />)
const pace = await screen.findByText('85% of budget used projected $280.00 by Jul 14')
const pace = await screen.findByText('85% of budget used; projected $280.00 by Jul 14')
expect(pace).toHaveClass('pace', 'hot')
expect(screen.queryByText(/On pace to exceed/)).not.toBeInTheDocument()
})
@ -179,6 +179,6 @@ describe('Plans', () => {
render(<Plans period="week" />)
expect(await screen.findByText('Permission denied')).toBeInTheDocument()
expect(screen.getByText('permission denied grant Full Disk Access')).toHaveStyle({ color: 'var(--amber)' })
expect(screen.getByText('permission denied; grant Full Disk Access')).toHaveStyle({ color: 'var(--amber)' })
})
})

View file

@ -154,14 +154,14 @@ function PaceLine({ plan }: { plan: JsonPlanSummary }) {
if (plan.status === 'over' || plan.projectedMonthEnd > plan.budget) {
return (
<div className="pace hot">
On pace to exceed projected {formatUsd(plan.projectedMonthEnd)} by {endLabel}
On pace to exceed; projected {formatUsd(plan.projectedMonthEnd)} by {endLabel}
</div>
)
}
if (plan.status === 'near') {
return (
<div className="pace hot">
{fmtPct(plan.percentUsed)} of budget used projected {formatUsd(plan.projectedMonthEnd)} by {endLabel}
{fmtPct(plan.percentUsed)} of budget used; projected {formatUsd(plan.projectedMonthEnd)} by {endLabel}
</div>
)
}

View file

@ -163,7 +163,7 @@ describe('Settings', () => {
expect(await screen.findByText('toruk-mini')).toBeInTheDocument()
expect(screen.getByText('34 sessions · $41.20 this month')).toBeInTheDocument()
expect(screen.getByText('Visible')).toBeInTheDocument()
expect(screen.getByText(/pairing is interactive/)).toBeInTheDocument()
expect(screen.getByText(/Pairing is interactive/)).toBeInTheDocument()
expect(screen.queryByText('Approve')).not.toBeInTheDocument()
expect(screen.queryByText('Pull now')).not.toBeInTheDocument()
await user.click(screen.getByRole('button', { name: 'Remove' }))
@ -190,6 +190,6 @@ describe('Settings', () => {
render(<Settings period="week" />)
await user.click(screen.getByRole('button', { name: 'Devices' }))
await waitFor(() => expect(screen.getAllByText('Locate the codeburn CLI')).toHaveLength(2))
expect(screen.getByText('permission denied grant Full Disk Access')).toHaveStyle({ color: 'var(--amber)' })
expect(screen.getByText('permission denied; grant Full Disk Access')).toHaveStyle({ color: 'var(--amber)' })
})
})

View file

@ -142,7 +142,7 @@ function ProvidersPane({ period, refreshToken }: { period: Period; refreshToken:
const overview = usePolled<MenubarPayload>(() => codeburn.getOverview(period, 'all'), [period, refreshToken])
const providers = Object.entries(overview.data?.current.providers ?? {})
return <section className="set-p on">
<div><h3 className="set-h">Providers</h3><p className="set-sub">codeburn auto-detects coding tools from local session files no setup needed.</p></div>
<div><h3 className="set-h">Providers</h3><p className="set-sub">codeburn auto-detects coding tools from local session files. No setup needed.</p></div>
{overview.error ? <SettingsErrorText error={overview.error} /> : !overview.data ? <p className="set-cap">Loading detected providers</p> : providers.length === 0 ? <p className="set-cap">No providers detected.</p> : providers.map(([name, cost]) => <div className="card" key={name}><div className="set-prov-head"><ProviderLogo provider={name} /><span className="set-prov-name">{name.charAt(0).toUpperCase() + name.slice(1)}</span><span className="set-status"><span className="set-dot ok" />Detected · {formatUsd(cost)}</span></div></div>)}
</section>
}
@ -278,7 +278,7 @@ function ThisDevicePanel({ identity, shareStatus }: { identity: ReturnType<typeo
function DiscoveredPanel({ scan }: { scan: ReturnType<typeof usePolled<DeviceScanResult>> }) {
const found = scan.data?.found.filter(device => !device.paired) ?? []
return <Panel title="Discovered nearby" right={scan.loading ? 'listening…' : undefined}>{!scan.data && scan.error ? <SettingsErrorText error={scan.error} /> : !scan.data ? <p className="set-cap">listening</p> : found.length === 0 ? <p className="set-cap">No nearby devices found.</p> : found.map(device => <div className="li" key={`${device.host}:${device.port}:${device.fingerprint}`}><div className="lx"><b>{device.name}</b><span>fingerprint {shortFingerprint(device.fingerprint)}</span></div></div>)}<p className="set-cap set-device-caption">To pair a device, run <code>codeburn devices add</code> in a terminal pairing is interactive (approve on the other device).</p></Panel>
return <Panel title="Discovered nearby" right={scan.loading ? 'listening…' : undefined}>{!scan.data && scan.error ? <SettingsErrorText error={scan.error} /> : !scan.data ? <p className="set-cap">listening</p> : found.length === 0 ? <p className="set-cap">No nearby devices found.</p> : found.map(device => <div className="li" key={`${device.host}:${device.port}:${device.fingerprint}`}><div className="lx"><b>{device.name}</b><span>fingerprint {shortFingerprint(device.fingerprint)}</span></div></div>)}<p className="set-cap set-device-caption">To pair a device, run <code>codeburn devices add</code> in a terminal. Pairing is interactive (approve on the other device).</p></Panel>
}
function PairedPanel({ devices, period, onRefresh }: { devices: ReturnType<typeof usePolled<CombinedUsage>>; period: Period; onRefresh: () => void }) {