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.
This commit is contained in:
62
desktop/src/shell/PaneBoundary.tsx
Normal file
62
desktop/src/shell/PaneBoundary.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
// 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user