1376c2dee8
Audit-pass over CODE-AUDIT.md (~48 items closed this pass; all verified — typecheck + 234 tests + eslint + prettier green). Correctness / bugs: - B3: match the release checksum to the asset's filename line (no wrong-hash verify) - B4: newline-safe metadata probe (one --print with a unit-separator delimiter) - B5 / L88: guard the meta event against canceled items; progress no longer promotes a queued item outside pump() - B7: cookie-login promise always resolves (handles destroy-without-close) - L146: trim parser rejects >2 colon-group times; M36: Library selection counts only actionable rows - L11 / L50 / L156 / L57 / L159 / L15 / L3: live queue count, empty-cookie message, schedule picker min, dead-code/comment cleanup Type safety: - Enable noUncheckedIndexedAccess + noFallthroughCasesInSwitch (15 real edge cases fixed) Resilience / Windows / metadata: - R5: settings write failure handled (no unhandled IPC rejection; reconciles to truth) - W1 / W5 / W6: min window size, seeded folder picker, parented sign-in window; L147 dead macOS branches removed - CL1: shared stdout markers; package/builder metadata (license, homepage, repository, copyright, tsbuildinfo glob) Copy / docs / tests: - M37 / SR9 dev-jargon cleanup in hints; M8 / M25 / M26 / L66 / L80 / L81 reconciled - New unit tests for L35 (isValidMediaItem) and L36 (compareVersions) This commit also checkpoints the previously-uncommitted feat/tray-background-clipboard work it builds on: background running + auto-download, library clipboard detection, tray, binary management & library scale, credential encryption at rest, the shared jsonStore and ui/ primitives, and the eslint/prettier tooling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
101 lines
3.0 KiB
TypeScript
101 lines
3.0 KiB
TypeScript
import React from 'react'
|
||
|
||
interface Props {
|
||
children: React.ReactNode
|
||
}
|
||
interface State {
|
||
error: Error | null
|
||
}
|
||
|
||
/**
|
||
* Top-level renderer error boundary (M16). Before this, an exception thrown in
|
||
* render by any view unmounted the whole shell to a blank white window with no
|
||
* way out. This catches it, logs it (so it reaches the dev console / future log
|
||
* sink), and shows a recover affordance.
|
||
*
|
||
* The fallback is intentionally dependency-free — no Fluent components, no theme
|
||
* provider — because the error may have come from inside that very tree. It paints
|
||
* its own full-window dark surface (matching the app's dark charcoal + toffee
|
||
* accent) rather than reading theme tokens, so it renders identically regardless
|
||
* of the active theme and uses only inline CSS that can't itself throw.
|
||
*/
|
||
export class ErrorBoundary extends React.Component<Props, State> {
|
||
state: State = { error: null }
|
||
|
||
static getDerivedStateFromError(error: Error): State {
|
||
return { error }
|
||
}
|
||
|
||
componentDidCatch(error: Error, info: React.ErrorInfo): void {
|
||
console.error('[AeroFetch] renderer crashed:', error, info.componentStack)
|
||
}
|
||
|
||
private handleReload = (): void => {
|
||
// A full reload re-runs the renderer from a clean state; persisted data
|
||
// (settings/history/sources) lives in main, so nothing is lost.
|
||
window.location.reload()
|
||
}
|
||
|
||
render(): React.ReactNode {
|
||
const { error } = this.state
|
||
if (!error) return this.props.children
|
||
|
||
return (
|
||
<div
|
||
role="alert"
|
||
style={{
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
gap: 16,
|
||
minHeight: '100vh',
|
||
padding: 32,
|
||
textAlign: 'center',
|
||
fontFamily: 'Segoe UI, system-ui, sans-serif',
|
||
color: '#c8c6c4',
|
||
background: '#201f1e'
|
||
}}
|
||
>
|
||
<div style={{ fontSize: 18, fontWeight: 600, color: '#f3f2f1' }}>Something went wrong</div>
|
||
<div style={{ maxWidth: 440, fontSize: 13, lineHeight: 1.5 }}>
|
||
AeroFetch hit an unexpected error and couldn’t continue. Your downloads and settings are
|
||
saved — reloading the window should bring you back.
|
||
</div>
|
||
<pre
|
||
style={{
|
||
maxWidth: 480,
|
||
maxHeight: 120,
|
||
overflow: 'auto',
|
||
margin: 0,
|
||
padding: '8px 12px',
|
||
fontSize: 11,
|
||
textAlign: 'left',
|
||
color: '#d29ca0',
|
||
background: '#2b2a29',
|
||
borderRadius: 6
|
||
}}
|
||
>
|
||
{error.message || String(error)}
|
||
</pre>
|
||
<button
|
||
type="button"
|
||
onClick={this.handleReload}
|
||
style={{
|
||
padding: '8px 20px',
|
||
fontSize: 14,
|
||
fontWeight: 600,
|
||
color: '#1c1611',
|
||
background: '#b5917d',
|
||
border: 'none',
|
||
borderRadius: 8,
|
||
cursor: 'pointer'
|
||
}}
|
||
>
|
||
Reload AeroFetch
|
||
</button>
|
||
</div>
|
||
)
|
||
}
|
||
}
|