codeburn/app/renderer/components/CliErrorPanel.tsx
Resham Joshi a0e66e3fcd
fix: desktop not-found panel gives app advice, not npm (#1701)
For a bundled CLI the not-found panel now tells desktop users to reopen or reinstall the app instead of installing from npm. Strings in all six locales.
2026-10-09 12:31:19 +02:00

90 lines
3.2 KiB
TypeScript

import { t } from '../i18n'
import { Panel } from './Panel'
import type { CliError } from '../lib/types'
export function isPermissionCliError(error: CliError | null): boolean {
return error?.kind === 'nonzero' && /permission|full disk access|eacces/i.test(error.message)
}
/** A failure the main process attributed to a still-running cold hydration. The
* data is coming, so nothing here is broken — never paint it red. */
export function isColdHydrating(error: CliError | null): boolean {
return error?.cold === true
}
function isBundledNotFound(error: CliError): boolean {
return error.kind === 'not-found' && error.stage?.startsWith('bundled-') === true
}
export function cliErrorDisplay(error: CliError): { title: string; message: string; tone: 'amber' | 'red' | 'muted' } {
if (isColdHydrating(error)) {
return {
title: t('shell.error.stillIndexing.title'),
message: t('shell.error.stillIndexing.message'),
tone: 'muted',
}
}
if (isBundledNotFound(error)) {
return {
title: t('shell.error.notFound.bundledTitle'),
message: t(error.stage === 'bundled-denied' ? 'shell.error.notFound.bundledDenied' : 'shell.error.notFound.bundledMessage'),
tone: 'muted',
}
}
if (error.kind === 'not-found') {
return {
title: t('shell.error.notFound.title'),
message: t('shell.error.notFound.installMessage'),
tone: 'muted',
}
}
if (isPermissionCliError(error)) {
return {
title: t('shell.error.permission.title'),
message: t('shell.error.permission.message'),
tone: 'amber',
}
}
return { title: t('shell.error.generic.title'), message: error.message, tone: 'red' }
}
function colorForTone(tone: 'amber' | 'red' | 'muted'): string {
if (tone === 'amber') return 'var(--warn)'
if (tone === 'red') return 'var(--bad)'
return 'var(--mut2)'
}
export function CliErrorText({ error }: { error: CliError }) {
const display = cliErrorDisplay(error)
return <p style={{ color: colorForTone(display.tone), margin: 0, fontSize: 'var(--fs-meta)' }}>{display.message}</p>
}
export function CliErrorPanel({ error, subject = 'usage' }: { error: CliError; subject?: string }) {
const display = cliErrorDisplay(error)
if (isBundledNotFound(error)) {
return (
<Panel title={display.title}>
<p style={{ color: 'var(--mut)', margin: 0, fontSize: 'var(--fs-body)' }}>{display.message}</p>
</Panel>
)
}
if (error.kind === 'not-found') {
return (
<Panel title={display.title}>
<p style={{ color: 'var(--mut)', margin: '0 0 6px', fontSize: 'var(--fs-body)' }}>
{t('shell.error.notFound.pathPrefix', { subject })}
<code style={{ fontFamily: 'var(--mono)', color: 'var(--accent)' }}>codeburn</code>
{t('shell.error.notFound.pathSuffix', { subject })}
</p>
<p style={{ color: colorForTone(display.tone), margin: 0, fontSize: 'var(--fs-meta)' }}>
{t('shell.error.notFound.installPrefix')}<code style={{ fontFamily: 'var(--mono)', color: 'var(--accent)' }}>npm i -g codeburn</code>{t('shell.error.notFound.installSuffix')}
</p>
</Panel>
)
}
return (
<Panel title={display.title}>
<CliErrorText error={error} />
</Panel>
)
}