mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-10-19 18:23:41 +00:00
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.
This commit is contained in:
parent
1bb697b7cf
commit
a0e66e3fcd
6 changed files with 67 additions and 5 deletions
|
|
@ -30,7 +30,7 @@ export const NO_UPDATE_STATUS: UpdateStatus = { currentVersion: '', latestVersio
|
|||
|
||||
// Result envelope: handlers never throw across IPC so the structured error
|
||||
// `kind` survives contextBridge serialization. preload.ts unwraps it.
|
||||
export type Envelope<T = unknown> = { ok: true; value: T } | { ok: false; error: { kind: string; message: string; cold?: true } }
|
||||
export type Envelope<T = unknown> = { ok: true; value: T } | { ok: false; error: { kind: string; message: string; cold?: true; stage?: string } }
|
||||
|
||||
// The first overview fetch after boot hydrates a cold cache from scratch (a full
|
||||
// history parse). That can far exceed the 45s read timeout, and killing it means
|
||||
|
|
@ -388,8 +388,8 @@ function priceOverrideArgs(model: string, rates: PriceRates | undefined): string
|
|||
]
|
||||
}
|
||||
|
||||
function toEnvelopeError(err: unknown): { kind: string; message: string } {
|
||||
if (err instanceof CliError) return { kind: err.kind, message: sanitizeError(err.message) }
|
||||
function toEnvelopeError(err: unknown): { kind: string; message: string; stage?: string } {
|
||||
if (err instanceof CliError) return { kind: err.kind, message: sanitizeError(err.message), ...(err.kind === 'not-found' && err.detail ? { stage: err.detail } : {}) }
|
||||
return { kind: 'nonzero', message: sanitizeError(err instanceof Error ? err.message : String(err)) }
|
||||
}
|
||||
|
||||
|
|
@ -526,7 +526,7 @@ export function createBridgeHandlers(deps: Deps): Record<string, Handler> {
|
|||
const bootedAt = Date.now()
|
||||
const stillCold = (): boolean =>
|
||||
!overviewWarmed && Date.now() - (coldStartBegan ?? bootedAt) < WARMUP_TIMEOUT_MS
|
||||
const coldError = (err: unknown): { kind: string; message: string; cold?: true } => {
|
||||
const coldError = (err: unknown): { kind: string; message: string; cold?: true; stage?: string } => {
|
||||
const error = toEnvelopeError(err)
|
||||
return stillCold() && error.kind === 'timeout' ? { ...error, cold: true } : error
|
||||
}
|
||||
|
|
|
|||
24
app/renderer/components/CliErrorPanel.test.tsx
Normal file
24
app/renderer/components/CliErrorPanel.test.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// @vitest-environment jsdom
|
||||
import { render } from '@testing-library/react'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { CliErrorPanel } from './CliErrorPanel'
|
||||
|
||||
describe('CliErrorPanel not-found', () => {
|
||||
it('gives app advice, not npm, when the bundled engine is unreachable', () => {
|
||||
const { container } = render(<CliErrorPanel error={{ kind: 'not-found', message: 'x', stage: 'bundled-missing' }} />)
|
||||
expect(container.textContent).toContain('reinstall the app from codeburn.app')
|
||||
expect(container.textContent).not.toContain('npm')
|
||||
})
|
||||
|
||||
it('tells the user to allow CodeBurn when access was denied', () => {
|
||||
const { container } = render(<CliErrorPanel error={{ kind: 'not-found', message: 'x', stage: 'bundled-denied' }} />)
|
||||
expect(container.textContent).toContain('Security software')
|
||||
expect(container.textContent).not.toContain('npm')
|
||||
})
|
||||
|
||||
it('keeps the npm advice when no bundled CLI is configured', () => {
|
||||
const { container } = render(<CliErrorPanel error={{ kind: 'not-found', message: 'x', stage: 'no-path-match' }} />)
|
||||
expect(container.textContent).toContain('npm i -g codeburn')
|
||||
})
|
||||
})
|
||||
|
|
@ -12,6 +12,10 @@ 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 {
|
||||
|
|
@ -20,6 +24,13 @@ export function cliErrorDisplay(error: CliError): { title: string; message: stri
|
|||
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'),
|
||||
|
|
@ -50,6 +61,13 @@ export function CliErrorText({ error }: { error: CliError }) {
|
|||
|
||||
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}>
|
||||
|
|
|
|||
|
|
@ -87,6 +87,9 @@ export const shell: SectionCatalog = {
|
|||
'shell.error.notFound.pathSuffix': " command, but it isn't on your PATH yet.",
|
||||
'shell.error.notFound.installPrefix': 'Install it with ',
|
||||
'shell.error.notFound.installSuffix': ', then reopen this window.',
|
||||
'shell.error.notFound.bundledTitle': "CodeBurn can't reach its engine",
|
||||
'shell.error.notFound.bundledMessage': 'Quit and reopen CodeBurn. If it keeps happening, reinstall the app from codeburn.app.',
|
||||
'shell.error.notFound.bundledDenied': 'Security software may be blocking CodeBurn. Allow it, then quit and reopen CodeBurn. If it keeps happening, reinstall the app from codeburn.app.',
|
||||
'shell.error.permission.title': 'Permission denied',
|
||||
'shell.error.permission.message': 'permission denied; grant Full Disk Access',
|
||||
'shell.error.generic.title': "Couldn't read data",
|
||||
|
|
@ -184,6 +187,9 @@ export const shell: SectionCatalog = {
|
|||
'shell.error.notFound.pathSuffix': ", mais elle n'est pas encore dans votre PATH.",
|
||||
'shell.error.notFound.installPrefix': 'Installez-la avec ',
|
||||
'shell.error.notFound.installSuffix': ', puis rouvrez cette fenêtre.',
|
||||
'shell.error.notFound.bundledTitle': 'CodeBurn ne trouve pas son moteur',
|
||||
'shell.error.notFound.bundledMessage': "Quittez et rouvrez CodeBurn. Si le problème persiste, réinstallez l'app depuis codeburn.app.",
|
||||
'shell.error.notFound.bundledDenied': "Un logiciel de sécurité bloque peut-être CodeBurn. Autorisez-le, puis quittez et rouvrez CodeBurn. Si le problème persiste, réinstallez l'app depuis codeburn.app.",
|
||||
'shell.error.permission.title': 'Autorisation refusée',
|
||||
'shell.error.permission.message': "autorisation refusée ; accordez l'accès complet au disque",
|
||||
'shell.error.generic.title': 'Impossible de lire les données',
|
||||
|
|
@ -281,6 +287,9 @@ export const shell: SectionCatalog = {
|
|||
'shell.error.notFound.pathSuffix': ' コマンドを実行しますが、まだ PATH に含まれていません。',
|
||||
'shell.error.notFound.installPrefix': '',
|
||||
'shell.error.notFound.installSuffix': ' でインストールし、このウィンドウを開き直してください。',
|
||||
'shell.error.notFound.bundledTitle': 'CodeBurn がエンジンに接続できません',
|
||||
'shell.error.notFound.bundledMessage': 'CodeBurn を終了して開き直してください。改善しない場合は codeburn.app からアプリを再インストールしてください。',
|
||||
'shell.error.notFound.bundledDenied': 'セキュリティソフトが CodeBurn をブロックしている可能性があります。許可したうえで、CodeBurn を終了して開き直してください。改善しない場合は codeburn.app からアプリを再インストールしてください。',
|
||||
'shell.error.permission.title': 'アクセスが拒否されました',
|
||||
'shell.error.permission.message': 'アクセスが拒否されました。フルディスクアクセスを許可してください',
|
||||
'shell.error.generic.title': 'データを読み込めませんでした',
|
||||
|
|
@ -378,6 +387,9 @@ export const shell: SectionCatalog = {
|
|||
'shell.error.notFound.pathSuffix': ' 명령을 실행하지만, 아직 PATH에 없습니다.',
|
||||
'shell.error.notFound.installPrefix': '',
|
||||
'shell.error.notFound.installSuffix': '으로 설치한 다음 이 창을 다시 여세요.',
|
||||
'shell.error.notFound.bundledTitle': 'CodeBurn이 엔진에 연결할 수 없습니다',
|
||||
'shell.error.notFound.bundledMessage': 'CodeBurn을 종료한 다음 다시 여세요. 계속되면 codeburn.app에서 앱을 다시 설치하세요.',
|
||||
'shell.error.notFound.bundledDenied': '보안 소프트웨어가 CodeBurn을 차단하고 있을 수 있습니다. 허용한 다음 CodeBurn을 종료하고 다시 여세요. 계속되면 codeburn.app에서 앱을 다시 설치하세요.',
|
||||
'shell.error.permission.title': '권한이 거부됨',
|
||||
'shell.error.permission.message': '권한이 거부됨. 전체 디스크 접근 권한을 허용하세요',
|
||||
'shell.error.generic.title': '데이터를 읽을 수 없습니다',
|
||||
|
|
@ -475,6 +487,9 @@ export const shell: SectionCatalog = {
|
|||
'shell.error.notFound.pathSuffix': ' 命令,但它还不在您的 PATH 中。',
|
||||
'shell.error.notFound.installPrefix': '使用 ',
|
||||
'shell.error.notFound.installSuffix': ' 安装,然后重新打开此窗口。',
|
||||
'shell.error.notFound.bundledTitle': 'CodeBurn 无法连接其引擎',
|
||||
'shell.error.notFound.bundledMessage': '请退出并重新打开 CodeBurn。如果问题持续,请从 codeburn.app 重新安装应用。',
|
||||
'shell.error.notFound.bundledDenied': '安全软件可能拦截了 CodeBurn。请允许它运行,然后退出并重新打开 CodeBurn。如果问题持续,请从 codeburn.app 重新安装应用。',
|
||||
'shell.error.permission.title': '权限被拒绝',
|
||||
'shell.error.permission.message': '权限被拒绝;请授予完全磁盘访问权限',
|
||||
'shell.error.generic.title': '无法读取数据',
|
||||
|
|
@ -572,6 +587,9 @@ export const shell: SectionCatalog = {
|
|||
'shell.error.notFound.pathSuffix': ' 命令,但它尚未加入您的 PATH。',
|
||||
'shell.error.notFound.installPrefix': '使用 ',
|
||||
'shell.error.notFound.installSuffix': ' 安裝,然後重新開啟此視窗。',
|
||||
'shell.error.notFound.bundledTitle': 'CodeBurn 無法連線至其引擎',
|
||||
'shell.error.notFound.bundledMessage': '請結束並重新開啟 CodeBurn。如果問題持續,請從 codeburn.app 重新安裝應用程式。',
|
||||
'shell.error.notFound.bundledDenied': '安全軟體可能封鎖了 CodeBurn。請允許它執行,然後結束並重新開啟 CodeBurn。如果問題持續,請從 codeburn.app 重新安裝應用程式。',
|
||||
'shell.error.permission.title': '權限遭拒',
|
||||
'shell.error.permission.message': '權限遭拒;請授予完整磁碟取用權限',
|
||||
'shell.error.generic.title': '無法讀取資料',
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export const codeburn: CodeburnBridge = window.codeburn
|
|||
export function normalizeCliError(err: unknown): CliError {
|
||||
if (err && typeof err === 'object' && 'kind' in err && typeof (err as CliError).kind === 'string') {
|
||||
const e = err as CliError
|
||||
return { kind: e.kind, message: e.message ?? 'codeburn CLI error', ...(e.cold ? { cold: true as const } : {}) }
|
||||
return { kind: e.kind, message: e.message ?? 'codeburn CLI error', ...(e.cold ? { cold: true as const } : {}), ...(e.stage ? { stage: e.stage } : {}) }
|
||||
}
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
return { kind: 'nonzero', message }
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ export interface CliError {
|
|||
* broken install, so the UI keeps its splash/progress state. Optional so an
|
||||
* older preload simply never sets it. */
|
||||
cold?: true
|
||||
/** For `not-found` only: the resolution stage enum from the main process. */
|
||||
stage?: string
|
||||
}
|
||||
|
||||
export type AliasRow = { from: string; to: string }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue