Spaces:
Running
view-data: also wrap VARCHAR[] and TIMESTAMP columns; add app/error.tsx boundary
Browse filesThe CAST-to-VARCHAR patch wasn't enough — `[view-data] readRows
failed` still surfaced in the deploy logs with the new SQL. The
remaining unwrapped columns in eval_results_view.r.* are two
VARCHAR[] (evaluator_relationships, reporting_orgs) and four
TIMESTAMP columns (snapshot_id, evaluation_timestamp,
benchmark_updated, retrieved_timestamp). Wrap the lists with
to_json+CAST and the timestamps with a plain CAST to VARCHAR so
nothing the binding might choke on slips through r.*.
Also adds app/error.tsx so a transient backend error renders a
friendly retry card instead of Next.js's bare "Application error: a
client-side exception has occurred" overlay — useful during cold
parquet warm-up if any request still fails before the cache is
hydrated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- app/error.tsx +100 -0
- lib/view-data.ts +7 -1
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import { useEffect } from "react"
|
| 4 |
+
|
| 5 |
+
/**
|
| 6 |
+
* Top-level error boundary for every route under app/. Next.js renders
|
| 7 |
+
* this when a server component throws or a client render exception
|
| 8 |
+
* escapes the closest boundary. Replaces the bare default
|
| 9 |
+
* "Application error: a client-side exception has occurred" overlay
|
| 10 |
+
* with a recoverable affordance so a transient backend hiccup (cold
|
| 11 |
+
* parquet cache, slow first query) doesn't strand the reader on an
|
| 12 |
+
* unfriendly screen.
|
| 13 |
+
*/
|
| 14 |
+
export default function RouteError({
|
| 15 |
+
error,
|
| 16 |
+
reset,
|
| 17 |
+
}: {
|
| 18 |
+
error: Error & { digest?: string }
|
| 19 |
+
reset: () => void
|
| 20 |
+
}) {
|
| 21 |
+
useEffect(() => {
|
| 22 |
+
if (typeof window !== "undefined" && "console" in window) {
|
| 23 |
+
console.error("[route-error]", error)
|
| 24 |
+
}
|
| 25 |
+
}, [error])
|
| 26 |
+
|
| 27 |
+
return (
|
| 28 |
+
<div
|
| 29 |
+
style={{
|
| 30 |
+
minHeight: "60vh",
|
| 31 |
+
display: "flex",
|
| 32 |
+
alignItems: "center",
|
| 33 |
+
justifyContent: "center",
|
| 34 |
+
padding: "24px",
|
| 35 |
+
}}
|
| 36 |
+
>
|
| 37 |
+
<div
|
| 38 |
+
style={{
|
| 39 |
+
maxWidth: 460,
|
| 40 |
+
width: "100%",
|
| 41 |
+
padding: "28px 28px 24px",
|
| 42 |
+
border: "1px solid var(--border-soft)",
|
| 43 |
+
background: "var(--bg)",
|
| 44 |
+
}}
|
| 45 |
+
>
|
| 46 |
+
<div
|
| 47 |
+
className="font-mono uppercase"
|
| 48 |
+
style={{
|
| 49 |
+
fontSize: 10,
|
| 50 |
+
letterSpacing: "0.18em",
|
| 51 |
+
color: "var(--fg-subtle)",
|
| 52 |
+
marginBottom: 14,
|
| 53 |
+
}}
|
| 54 |
+
>
|
| 55 |
+
Something went wrong
|
| 56 |
+
</div>
|
| 57 |
+
<p
|
| 58 |
+
style={{
|
| 59 |
+
margin: 0,
|
| 60 |
+
color: "var(--fg)",
|
| 61 |
+
fontSize: 15,
|
| 62 |
+
lineHeight: 1.55,
|
| 63 |
+
}}
|
| 64 |
+
>
|
| 65 |
+
We couldn't load this view. The data backend may still be
|
| 66 |
+
warming up — most cold-start hiccups clear within a few
|
| 67 |
+
seconds.
|
| 68 |
+
</p>
|
| 69 |
+
<div
|
| 70 |
+
style={{
|
| 71 |
+
display: "flex",
|
| 72 |
+
gap: 10,
|
| 73 |
+
marginTop: 20,
|
| 74 |
+
flexWrap: "wrap",
|
| 75 |
+
}}
|
| 76 |
+
>
|
| 77 |
+
<button type="button" className="btn-ec" onClick={() => reset()}>
|
| 78 |
+
Try again
|
| 79 |
+
</button>
|
| 80 |
+
<a href="/" className="btn-ec outline">
|
| 81 |
+
Back to home
|
| 82 |
+
</a>
|
| 83 |
+
</div>
|
| 84 |
+
{error.digest && (
|
| 85 |
+
<div
|
| 86 |
+
className="font-mono"
|
| 87 |
+
style={{
|
| 88 |
+
marginTop: 18,
|
| 89 |
+
fontSize: 10,
|
| 90 |
+
color: "var(--fg-subtle)",
|
| 91 |
+
wordBreak: "break-all",
|
| 92 |
+
}}
|
| 93 |
+
>
|
| 94 |
+
ref: {error.digest}
|
| 95 |
+
</div>
|
| 96 |
+
)}
|
| 97 |
+
</div>
|
| 98 |
+
</div>
|
| 99 |
+
)
|
| 100 |
+
}
|
|
@@ -102,7 +102,13 @@ const CELL_JOIN_COLUMNS = `
|
|
| 102 |
CAST(to_json(r.eval_library) AS VARCHAR) AS eval_library,
|
| 103 |
CAST(to_json(r.aggregate_components) AS VARCHAR) AS aggregate_components,
|
| 104 |
CAST(to_json(r.evalcards_annotations) AS VARCHAR) AS evalcards_annotations,
|
| 105 |
-
CAST(to_json(r.scores_by_organization) AS VARCHAR) AS scores_by_organization
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
),
|
| 107 |
e.evaluation_name AS eval_evaluation_name,
|
| 108 |
e.canonical_display_name AS eval_canonical_display_name,
|
|
|
|
| 102 |
CAST(to_json(r.eval_library) AS VARCHAR) AS eval_library,
|
| 103 |
CAST(to_json(r.aggregate_components) AS VARCHAR) AS aggregate_components,
|
| 104 |
CAST(to_json(r.evalcards_annotations) AS VARCHAR) AS evalcards_annotations,
|
| 105 |
+
CAST(to_json(r.scores_by_organization) AS VARCHAR) AS scores_by_organization,
|
| 106 |
+
CAST(to_json(r.evaluator_relationships) AS VARCHAR) AS evaluator_relationships,
|
| 107 |
+
CAST(to_json(r.reporting_orgs) AS VARCHAR) AS reporting_orgs,
|
| 108 |
+
CAST(r.snapshot_id AS VARCHAR) AS snapshot_id,
|
| 109 |
+
CAST(r.evaluation_timestamp AS VARCHAR) AS evaluation_timestamp,
|
| 110 |
+
CAST(r.benchmark_updated AS VARCHAR) AS benchmark_updated,
|
| 111 |
+
CAST(r.retrieved_timestamp AS VARCHAR) AS retrieved_timestamp
|
| 112 |
),
|
| 113 |
e.evaluation_name AS eval_evaluation_name,
|
| 114 |
e.canonical_display_name AS eval_canonical_display_name,
|