Files
dchain/desktop/src/shell/PaneBoundary.tsx
vsecoder 481d4d2fa8 fix(desktop): global box-sizing + per-pane error boundary + Conversation defensives
Two bugs reported after v2.2.0:

1. Input fields and textareas overflowed their container — typing in
   Settings / SendModal / NewContactModal would push the border past
   the card edge because the renderer's default box-sizing was
   content-box and `width: 100%` + padding pushed widths past parents.
   Added `*, *::before, *::after { box-sizing: border-box; }` to
   index.html. Removes the need for per-element `boxSizing: 'border-box'`
   (the existing sprinkles stay for clarity but are now redundant).

2. App went blank when opening a chat — any throw inside Conversation
   propagated up through Shell and wiped the whole window, with no way
   to navigate out. Added PaneBoundary, a React error boundary scoped
   to one Shell pane, keyed on `${section}-(list|detail)` so it resets
   when the user switches section. Now a crash shows an inline error
   card with message + stack + Retry, while NavBar + StatusBar stay
   usable.

   Also hardened Conversation against edge cases that were candidates
   for the original crash:
     * `name` always falls back to shortAddr(address) if all other
       branches produce an empty string.
     * first letter used for the avatar is computed once, guarded
       against empty input with a `?` fallback.
     * Header name + short-address line get whiteSpace/overflow/ellipsis
       so very long contacts no longer escape the 32px wide sub-column
       the way they did for the reporter.

Fonts normalised in the global CSS too — inputs/textareas/buttons now
inherit `font-family` instead of the browser default, which was
breaking the visual rhythm in the Settings cards.
2026-04-22 18:53:37 +03:00

63 lines
1.9 KiB
TypeScript

// PaneBoundary — ErrorBoundary scoped to one Shell pane. A crash in
// the Conversation component shouldn't black-out the whole window; it
// should leave NavBar + List + StatusBar usable so the operator can
// switch sections and report the bug. Resets when the keyed section
// changes.
import React from 'react';
interface Props {
/** Used as React key at the callsite; also shown in the panic copy. */
sectionName: string;
children: React.ReactNode;
}
interface State {
error: Error | null;
}
export class PaneBoundary 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(`[PaneBoundary:${this.props.sectionName}]`, error, info);
}
render(): React.ReactNode {
if (!this.state.error) return this.props.children;
return (
<div style={{
padding: 20, height: '100%', overflow: 'auto',
background: '#000', color: '#fff', fontFamily: 'monospace',
}}>
<div style={{
color: '#ff6b6b', fontSize: 14, fontWeight: 700, marginBottom: 8,
}}>
{this.props.sectionName} crashed
</div>
<div style={{ color: '#fff', fontSize: 13, marginBottom: 8 }}>
{this.state.error.message}
</div>
<pre style={{
color: '#8b8b8b', fontSize: 11, lineHeight: 1.4,
whiteSpace: 'pre-wrap', wordBreak: 'break-word',
}}>
{this.state.error.stack}
</pre>
<button
onClick={() => this.setState({ error: null })}
style={{
marginTop: 10, padding: '6px 12px', borderRadius: 999,
border: '1px solid #1f1f1f', background: '#111',
color: '#fff', fontSize: 12, cursor: 'pointer',
}}
>Retry</button>
</div>
);
}
}