Skip to content

Commit d6deeec

Browse files
committed
update error overlay
1 parent b12f96e commit d6deeec

1 file changed

Lines changed: 66 additions & 160 deletions

File tree

lang/libs/components/src/ErrorOverlay.ch

Lines changed: 66 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -9,133 +9,13 @@
99
// It is intentionally dependency-free (no app imports) so it can live in the
1010
// shared `components` library and be reused by any Chemical webview app.
1111
//
12-
// Optional props:
13-
// title header text (default "Application Error")
14-
// max max number of captured errors kept (default 10)
15-
// variant accent color token (default "destructive")
16-
//
17-
// App code that swallows errors (e.g. a bridge `.catch`) can still surface them
18-
// by calling `window.__reportError(message, stack?)` — the overlay listens for
19-
// it and opens the dialog just like a real uncaught error.
20-
21-
func error_overlay_styles(page : &mut HtmlPage) : *char {
22-
return #css {
23-
position: fixed;
24-
inset: 0;
25-
z-index: 1000;
26-
display: flex;
27-
align-items: flex-start;
28-
justify-content: center;
29-
padding: 2rem 1rem;
30-
background: hsl(var(--background) / 0.6);
31-
backdrop-filter: blur(2px);
32-
overflow: auto;
33-
}
34-
}
35-
36-
func error_overlay_dialog_styles(page : &mut HtmlPage) : *char {
37-
return #css {
38-
width: 100%;
39-
max-width: 46rem;
40-
border-radius: var(--radius-lg);
41-
border: 1px solid hsl(var(--destructive) / 0.4);
42-
background: hsl(var(--card));
43-
color: hsl(var(--foreground));
44-
box-shadow: var(--shadow-lg);
45-
display: flex;
46-
flex-direction: column;
47-
max-height: 80vh;
48-
overflow: hidden;
49-
}
50-
}
51-
52-
func error_overlay_header_styles(page : &mut HtmlPage) : *char {
53-
return #css {
54-
display: flex;
55-
align-items: center;
56-
justify-content: space-between;
57-
gap: 0.75rem;
58-
padding: 0.875rem 1rem;
59-
border-bottom: 1px solid hsl(var(--border));
60-
background: hsl(var(--destructive) / 0.08);
61-
}
62-
}
63-
64-
func error_overlay_title_styles(page : &mut HtmlPage) : *char {
65-
return #css {
66-
font-weight: 600;
67-
font-size: 0.95rem;
68-
margin: 0;
69-
}
70-
}
71-
72-
func error_overlay_body_styles(page : &mut HtmlPage) : *char {
73-
return #css {
74-
padding: 1rem;
75-
overflow: auto;
76-
display: flex;
77-
flex-direction: column;
78-
gap: 0.75rem;
79-
}
80-
}
81-
82-
func error_overlay_msg_styles(page : &mut HtmlPage) : *char {
83-
return #css {
84-
font-weight: 600;
85-
font-size: 0.9rem;
86-
color: hsl(var(--destructive));
87-
margin: 0;
88-
white-space: pre-wrap;
89-
word-break: break-word;
90-
}
91-
}
92-
93-
func error_overlay_pre_styles(page : &mut HtmlPage) : *char {
94-
return #css {
95-
margin: 0;
96-
padding: 0.75rem;
97-
border-radius: var(--radius-md);
98-
background: hsl(var(--muted) / 0.5);
99-
border: 1px solid hsl(var(--border));
100-
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
101-
font-size: 0.8rem;
102-
line-height: 1.35rem;
103-
white-space: pre-wrap;
104-
word-break: break-word;
105-
max-height: 40vh;
106-
overflow: auto;
107-
}
108-
}
109-
110-
func error_overlay_list_styles(page : &mut HtmlPage) : *char {
111-
return #css {
112-
display: flex;
113-
flex-wrap: wrap;
114-
gap: 0.4rem;
115-
}
116-
}
117-
118-
func error_overlay_chip_styles(page : &mut HtmlPage) : *char {
119-
return #css {
120-
font-size: 0.72rem;
121-
padding: 0.15rem 0.5rem;
122-
border-radius: 999px;
123-
border: 1px solid hsl(var(--border));
124-
background: hsl(var(--muted) / 0.4);
125-
cursor: pointer;
126-
}
127-
}
128-
129-
func error_overlay_footer_styles(page : &mut HtmlPage) : *char {
130-
return #css {
131-
display: flex;
132-
align-items: center;
133-
justify-content: flex-end;
134-
gap: 0.5rem;
135-
padding: 0.75rem 1rem;
136-
border-top: 1px solid hsl(var(--border));
137-
}
138-
}
12+
// IMPORTANT: visibility is expressed as a JSX conditional used DIRECTLY as a
13+
// child expression (`{open && errors.length > 0 ? <div>…</div> : null}`). This
14+
// framework runs component bodies once and only re-renders reactive (derived)
15+
// JSX nodes, so a control-flow `if(!open) return null` would never update when
16+
// `open` flips. The styling uses inline `style={{...}}` objects (not `#css`
17+
// helpers) because `#css` needs a server-only `page` pointer that is absent
18+
// during client hydration.
13919

14020
public #universal ErrorOverlay(props) {
14121
var title = props.title || "Application Error"
@@ -204,48 +84,74 @@ public #universal ErrorOverlay(props) {
20484
}
20585
}, [])
20686

207-
if(!open) { return null }
208-
209-
var total = errors.length
210-
var idx = selected
211-
if(idx < 0) { idx = 0 }
212-
if(idx >= total) { idx = total - 1 }
213-
var current = errors[idx]
214-
var reportText = (current.message || "") + "\n\n" + (current.stack || "") + "\n\n[source: " + (current.source || "") + " | time: " + (current.time || "") + "]"
215-
87+
var dismiss = () => { open = false }
21688
var copyReport = () => {
89+
var cur = (errors.length > 0) ? errors[selected] : null
90+
var report = (cur ? (cur.message || "") : "") + "\n\n" + (cur ? (cur.stack || "") : "") + "\n\n[source: " + (cur ? (cur.source || "") : "") + " | time: " + (cur ? (cur.time || "") : "") + "]"
21791
try {
21892
if(navigator.clipboard && navigator.clipboard.writeText) {
219-
navigator.clipboard.writeText(reportText)
93+
navigator.clipboard.writeText(report)
22094
}
22195
} catch (e) { }
22296
copied = true
22397
setTimeout(() => { copied = false }, 2000)
22498
}
225-
var dismiss = () => { open = false }
22699

227-
return <div class={error_overlay_styles(page)} onClick={dismiss}>
228-
<div class={error_overlay_dialog_styles(page)} onClick={(e) => { e.stopPropagation() }}>
229-
<div class={error_overlay_header_styles(page)}>
230-
<h3 class={error_overlay_title_styles(page)}>{title}</h3>
231-
<button type="button" class="cdm-btn" onClick={dismiss}>{"×"}</button>
232-
</div>
233-
<div class={error_overlay_body_styles(page)}>
234-
{total > 1 ? <div class={error_overlay_list_styles(page)}>
235-
{errors.map((er, i) => (
236-
<span class={error_overlay_chip_styles(page)} onClick={() => { selected = i }}>
237-
{("#" + (i + 1) + " " + (er.source || "err"))}
238-
</span>
239-
))}
240-
</div> : null}
241-
<p class={error_overlay_msg_styles(page)}>{current.message}</p>
242-
<pre class={error_overlay_pre_styles(page)}>{current.stack}</pre>
243-
</div>
244-
<div class={error_overlay_footer_styles(page)}>
245-
<span style={{ fontSize: "0.72rem", color: "hsl(var(--muted-foreground))" }}>{"source: " + (current.source || "") + " @ " + (current.time || "")}</span>
246-
<button type="button" class="cdm-btn" onClick={copyReport}>{copied ? "Copied!" : "Copy report"}</button>
247-
<button type="button" class="cdm-btn" data-variant="primary" onClick={dismiss}>{"Dismiss"}</button>
100+
return <div style={{ display: "contents" }}>
101+
{open && errors.length > 0 ? <div style={{
102+
position: "fixed",
103+
inset: "0",
104+
zIndex: "1000",
105+
display: "flex",
106+
alignItems: "flex-start",
107+
justifyContent: "center",
108+
padding: "2rem 1rem",
109+
background: "rgba(0,0,0,0.55)",
110+
overflow: "auto"
111+
}} onClick={dismiss}>
112+
<div style={{
113+
width: "100%",
114+
maxWidth: "46rem",
115+
borderRadius: "12px",
116+
border: "1px solid #e11d48",
117+
background: "#ffffff",
118+
color: "#111111",
119+
boxShadow: "0 10px 30px rgba(0,0,0,0.35)",
120+
display: "flex",
121+
flexDirection: "column",
122+
maxHeight: "80vh",
123+
overflow: "hidden",
124+
marginTop: "2rem"
125+
}} onClick={(e) => { e.stopPropagation() }}>
126+
<div style={{
127+
display: "flex",
128+
alignItems: "center",
129+
justifyContent: "space-between",
130+
gap: "0.75rem",
131+
padding: "0.875rem 1rem",
132+
borderBottom: "1px solid #e5e7eb",
133+
background: "rgba(225,29,72,0.08)"
134+
}}>
135+
<h3 style={{ fontWeight: 600, fontSize: "0.95rem", margin: 0 }}>{title}</h3>
136+
<button type="button" style={{ border: "none", background: "transparent", fontSize: "1.1rem", cursor: "pointer", color: "#111111" }} onClick={dismiss}>{"×"}</button>
137+
</div>
138+
<div style={{ padding: "1rem", overflow: "auto", display: "flex", flexDirection: "column", gap: "0.75rem" }}>
139+
{errors.length > 1 ? <div style={{ display: "flex", flexWrap: "wrap", gap: "0.4rem" }}>
140+
{errors.map((er, i) => (
141+
<span style={{ fontSize: "0.72rem", padding: "0.15rem 0.5rem", borderRadius: "999px", border: "1px solid #e5e7eb", background: "#f3f4f6", cursor: "pointer" }} onClick={() => { selected = i }}>
142+
{("#" + (i + 1) + " " + (er.source || "err"))}
143+
</span>
144+
))}
145+
</div> : null}
146+
<p style={{ fontWeight: 600, fontSize: "0.9rem", color: "#e11d48", margin: 0, whiteSpace: "pre-wrap", wordBreak: "break-word" }}>{errors[selected].message}</p>
147+
<pre style={{ margin: 0, padding: "0.75rem", borderRadius: "8px", background: "#f3f4f6", border: "1px solid #e5e7eb", fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace", fontSize: "0.8rem", lineHeight: "1.35rem", whiteSpace: "pre-wrap", wordBreak: "break-word", maxHeight: "40vh", overflow: "auto" }}>{errors[selected].stack}</pre>
148+
</div>
149+
<div style={{ display: "flex", alignItems: "center", justifyContent: "flex-end", gap: "0.5rem", padding: "0.75rem 1rem", borderTop: "1px solid #e5e7eb" }}>
150+
<span style={{ fontSize: "0.72rem", color: "#6b7280" }}>{"source: " + (errors[selected].source || "") + " @ " + (errors[selected].time || "")}</span>
151+
<button type="button" style={{ border: "1px solid #e5e7eb", background: "#ffffff", borderRadius: "8px", padding: "0.35rem 0.7rem", cursor: "pointer" }} onClick={copyReport}>{copied ? "Copied!" : "Copy report"}</button>
152+
<button type="button" style={{ border: "1px solid #e11d48", background: "#e11d48", color: "#ffffff", borderRadius: "8px", padding: "0.35rem 0.7rem", cursor: "pointer" }} onClick={dismiss}>{"Dismiss"}</button>
153+
</div>
248154
</div>
249-
</div>
155+
</div> : null}
250156
</div>
251157
}

0 commit comments

Comments
 (0)