revine 1.1.3 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ export declare const errorBoundaryComponent = "\nfunction RevineErrorDialog() {\n const error = useRouteError();\n const [expanded, setExpanded] = React.useState(false);\n\n const message = error?.message || String(error) || \"An unexpected error occurred.\";\n const stack = error?.stack || \"\";\n // Pull only the first meaningful line from the stack (skip the error message repeat)\n const stackLines = stack\n .split(\"\\n\")\n .filter((l) => l.trim().startsWith(\"at \"))\n .slice(0, 8);\n\n return React.createElement(\n \"div\",\n { style: overlayStyle },\n React.createElement(\n \"div\",\n { style: dialogStyle },\n // Header\n React.createElement(\n \"div\",\n { style: headerStyle },\n React.createElement(\"span\", { style: iconStyle }, \"\u2715\"),\n React.createElement(\"span\", { style: titleStyle }, \"Application Error\")\n ),\n // Message\n React.createElement(\"p\", { style: messageStyle }, message),\n // Stack toggle\n stackLines.length > 0 &&\n React.createElement(\n \"div\",\n null,\n React.createElement(\n \"button\",\n { onClick: () => setExpanded((v) => !v), style: toggleBtnStyle },\n expanded ? \"\u25B2 Hide stack trace\" : \"\u25BC Show stack trace\"\n ),\n expanded &&\n React.createElement(\n \"pre\",\n { style: stackStyle },\n stackLines.join(\"\\n\")\n )\n ),\n // Actions\n React.createElement(\n \"div\",\n { style: actionsStyle },\n React.createElement(\n \"button\",\n { onClick: () => window.location.reload(), style: primaryBtnStyle },\n \"Reload page\"\n ),\n React.createElement(\n \"button\",\n { onClick: () => (window.location.href = \"/\"), style: secondaryBtnStyle },\n \"Go to home\"\n )\n )\n )\n );\n}\n\nconst overlayStyle = {\n position: \"fixed\", inset: 0, background: \"rgba(0,0,0,0.65)\",\n backdropFilter: \"blur(4px)\", display: \"flex\",\n alignItems: \"center\", justifyContent: \"center\",\n zIndex: 9999, fontFamily: \"ui-monospace, 'Cascadia Code', monospace\",\n};\nconst dialogStyle = {\n background: \"#1a1a1a\", border: \"1px solid #ff4d4f55\",\n borderRadius: \"10px\", padding: \"28px 32px\",\n maxWidth: \"560px\", width: \"90%\", boxShadow: \"0 24px 64px rgba(0,0,0,0.6)\",\n color: \"#e5e5e5\",\n};\nconst headerStyle = {\n display: \"flex\", alignItems: \"center\", gap: \"10px\",\n marginBottom: \"14px\",\n};\nconst iconStyle = {\n display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\",\n width: \"26px\", height: \"26px\", borderRadius: \"50%\",\n background: \"#ff4d4f22\", color: \"#ff4d4f\", fontSize: \"13px\", fontWeight: 700,\n};\nconst titleStyle = {\n fontFamily: \"system-ui, sans-serif\",\n fontSize: \"16px\", fontWeight: 600, color: \"#fff\",\n};\nconst messageStyle = {\n fontFamily: \"ui-monospace, monospace\",\n fontSize: \"13px\", color: \"#ff7875\",\n background: \"#ff4d4f0f\", border: \"1px solid #ff4d4f22\",\n borderRadius: \"6px\", padding: \"10px 14px\",\n marginBottom: \"16px\", wordBreak: \"break-word\", lineHeight: 1.6,\n};\nconst toggleBtnStyle = {\n background: \"none\", border: \"none\", cursor: \"pointer\",\n color: \"#888\", fontSize: \"12px\", padding: \"0 0 10px 0\",\n fontFamily: \"system-ui, sans-serif\",\n};\nconst stackStyle = {\n background: \"#111\", borderRadius: \"6px\", padding: \"12px 14px\",\n fontSize: \"11px\", color: \"#aaa\", overflowX: \"auto\",\n lineHeight: 1.7, marginBottom: \"16px\",\n border: \"1px solid #2a2a2a\",\n};\nconst actionsStyle = {\n display: \"flex\", gap: \"10px\", marginTop: \"6px\",\n};\nconst primaryBtnStyle = {\n flex: 1, padding: \"9px 0\", borderRadius: \"6px\", border: \"none\",\n background: \"#ff4d4f\", color: \"#fff\", fontWeight: 600,\n fontSize: \"13px\", cursor: \"pointer\", fontFamily: \"system-ui, sans-serif\",\n};\nconst secondaryBtnStyle = {\n flex: 1, padding: \"9px 0\", borderRadius: \"6px\",\n border: \"1px solid #333\", background: \"transparent\",\n color: \"#aaa\", fontSize: \"13px\", cursor: \"pointer\",\n fontFamily: \"system-ui, sans-serif\",\n};\n";
2
+ //# sourceMappingURL=errorBoundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errorBoundary.d.ts","sourceRoot":"","sources":["../../../src/runtime/bundler/errorBoundary.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,suIAyHlC,CAAC"}
@@ -0,0 +1,122 @@
1
+ export const errorBoundaryComponent = `
2
+ function RevineErrorDialog() {
3
+ const error = useRouteError();
4
+ const [expanded, setExpanded] = React.useState(false);
5
+
6
+ const message = error?.message || String(error) || "An unexpected error occurred.";
7
+ const stack = error?.stack || "";
8
+ // Pull only the first meaningful line from the stack (skip the error message repeat)
9
+ const stackLines = stack
10
+ .split("\\n")
11
+ .filter((l) => l.trim().startsWith("at "))
12
+ .slice(0, 8);
13
+
14
+ return React.createElement(
15
+ "div",
16
+ { style: overlayStyle },
17
+ React.createElement(
18
+ "div",
19
+ { style: dialogStyle },
20
+ // Header
21
+ React.createElement(
22
+ "div",
23
+ { style: headerStyle },
24
+ React.createElement("span", { style: iconStyle }, "✕"),
25
+ React.createElement("span", { style: titleStyle }, "Application Error")
26
+ ),
27
+ // Message
28
+ React.createElement("p", { style: messageStyle }, message),
29
+ // Stack toggle
30
+ stackLines.length > 0 &&
31
+ React.createElement(
32
+ "div",
33
+ null,
34
+ React.createElement(
35
+ "button",
36
+ { onClick: () => setExpanded((v) => !v), style: toggleBtnStyle },
37
+ expanded ? "▲ Hide stack trace" : "▼ Show stack trace"
38
+ ),
39
+ expanded &&
40
+ React.createElement(
41
+ "pre",
42
+ { style: stackStyle },
43
+ stackLines.join("\\n")
44
+ )
45
+ ),
46
+ // Actions
47
+ React.createElement(
48
+ "div",
49
+ { style: actionsStyle },
50
+ React.createElement(
51
+ "button",
52
+ { onClick: () => window.location.reload(), style: primaryBtnStyle },
53
+ "Reload page"
54
+ ),
55
+ React.createElement(
56
+ "button",
57
+ { onClick: () => (window.location.href = "/"), style: secondaryBtnStyle },
58
+ "Go to home"
59
+ )
60
+ )
61
+ )
62
+ );
63
+ }
64
+
65
+ const overlayStyle = {
66
+ position: "fixed", inset: 0, background: "rgba(0,0,0,0.65)",
67
+ backdropFilter: "blur(4px)", display: "flex",
68
+ alignItems: "center", justifyContent: "center",
69
+ zIndex: 9999, fontFamily: "ui-monospace, 'Cascadia Code', monospace",
70
+ };
71
+ const dialogStyle = {
72
+ background: "#1a1a1a", border: "1px solid #ff4d4f55",
73
+ borderRadius: "10px", padding: "28px 32px",
74
+ maxWidth: "560px", width: "90%", boxShadow: "0 24px 64px rgba(0,0,0,0.6)",
75
+ color: "#e5e5e5",
76
+ };
77
+ const headerStyle = {
78
+ display: "flex", alignItems: "center", gap: "10px",
79
+ marginBottom: "14px",
80
+ };
81
+ const iconStyle = {
82
+ display: "inline-flex", alignItems: "center", justifyContent: "center",
83
+ width: "26px", height: "26px", borderRadius: "50%",
84
+ background: "#ff4d4f22", color: "#ff4d4f", fontSize: "13px", fontWeight: 700,
85
+ };
86
+ const titleStyle = {
87
+ fontFamily: "system-ui, sans-serif",
88
+ fontSize: "16px", fontWeight: 600, color: "#fff",
89
+ };
90
+ const messageStyle = {
91
+ fontFamily: "ui-monospace, monospace",
92
+ fontSize: "13px", color: "#ff7875",
93
+ background: "#ff4d4f0f", border: "1px solid #ff4d4f22",
94
+ borderRadius: "6px", padding: "10px 14px",
95
+ marginBottom: "16px", wordBreak: "break-word", lineHeight: 1.6,
96
+ };
97
+ const toggleBtnStyle = {
98
+ background: "none", border: "none", cursor: "pointer",
99
+ color: "#888", fontSize: "12px", padding: "0 0 10px 0",
100
+ fontFamily: "system-ui, sans-serif",
101
+ };
102
+ const stackStyle = {
103
+ background: "#111", borderRadius: "6px", padding: "12px 14px",
104
+ fontSize: "11px", color: "#aaa", overflowX: "auto",
105
+ lineHeight: 1.7, marginBottom: "16px",
106
+ border: "1px solid #2a2a2a",
107
+ };
108
+ const actionsStyle = {
109
+ display: "flex", gap: "10px", marginTop: "6px",
110
+ };
111
+ const primaryBtnStyle = {
112
+ flex: 1, padding: "9px 0", borderRadius: "6px", border: "none",
113
+ background: "#ff4d4f", color: "#fff", fontWeight: 600,
114
+ fontSize: "13px", cursor: "pointer", fontFamily: "system-ui, sans-serif",
115
+ };
116
+ const secondaryBtnStyle = {
117
+ flex: 1, padding: "9px 0", borderRadius: "6px",
118
+ border: "1px solid #333", background: "transparent",
119
+ color: "#aaa", fontSize: "13px", cursor: "pointer",
120
+ fontFamily: "system-ui, sans-serif",
121
+ };
122
+ `;
@@ -1 +1 @@
1
- {"version":3,"file":"revinePlugin.d.ts","sourceRoot":"","sources":["../../../src/runtime/bundler/revinePlugin.ts"],"names":[],"mappings":"AAEA,wBAAgB,YAAY,IAAI,GAAG,CA8GlC"}
1
+ {"version":3,"file":"revinePlugin.d.ts","sourceRoot":"","sources":["../../../src/runtime/bundler/revinePlugin.ts"],"names":[],"mappings":"AA0RA,wBAAgB,YAAY,IAAI,GAAG,CAmHlC"}
@@ -1,4 +1,283 @@
1
1
  const VIRTUAL_ROUTING_ID = "\0revine:routing";
2
+ const errorBoundaryComponent = `
3
+ function RevineErrorDialog() {
4
+ const error = useRouteError();
5
+ const [expanded, setExpanded] = React.useState(false);
6
+ const [copied, setCopied] = React.useState(false);
7
+
8
+ const message = error?.message || String(error) || "An unexpected error occurred.";
9
+ const stack = error?.stack || "";
10
+ const stackLines = stack
11
+ .split("\\n")
12
+ .filter((l) => l.trim().startsWith("at "))
13
+ .slice(0, 8)
14
+ .join("\\n");
15
+
16
+ const handleCopy = () => {
17
+ const text = message + (stackLines ? "\\n\\n" + stackLines : "");
18
+ navigator.clipboard.writeText(text).then(() => {
19
+ setCopied(true);
20
+ setTimeout(() => setCopied(false), 2000);
21
+ });
22
+ };
23
+
24
+ return React.createElement(
25
+ "div",
26
+ { style: overlayStyle },
27
+ React.createElement(
28
+ "div",
29
+ { style: dialogStyle },
30
+
31
+ // ── Top bar: Revine brand + badge
32
+ React.createElement(
33
+ "div",
34
+ { style: topBarStyle },
35
+ React.createElement(
36
+ "div",
37
+ { style: brandStyle },
38
+ React.createElement(
39
+ "svg",
40
+ { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none",
41
+ stroke: "#a78bfa", strokeWidth: "2.2", strokeLinecap: "round",
42
+ strokeLinejoin: "round", style: { flexShrink: 0 } },
43
+ React.createElement("polygon", { points: "13 2 3 14 12 14 11 22 21 10 12 10 13 2" })
44
+ ),
45
+ React.createElement("span", { style: brandNameStyle }, "Revine")
46
+ ),
47
+ React.createElement("span", { style: badgeStyle }, "Runtime Error")
48
+ ),
49
+
50
+ // ── Divider
51
+ React.createElement("div", { style: dividerStyle }),
52
+
53
+ // ── Error icon + title
54
+ React.createElement(
55
+ "div",
56
+ { style: headerStyle },
57
+ React.createElement(
58
+ "div",
59
+ { style: iconWrapStyle },
60
+ React.createElement(
61
+ "svg",
62
+ { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none",
63
+ stroke: "#f87171", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" },
64
+ React.createElement("circle", { cx: "12", cy: "12", r: "10" }),
65
+ React.createElement("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
66
+ React.createElement("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
67
+ )
68
+ ),
69
+ React.createElement("span", { style: titleStyle }, "Application Error")
70
+ ),
71
+
72
+ // ── Error message + copy button
73
+ React.createElement(
74
+ "div",
75
+ { style: messagePanelStyle },
76
+ React.createElement("p", { style: messageStyle }, message),
77
+ React.createElement(
78
+ "button",
79
+ { onClick: handleCopy, style: copyBtnStyle, title: "Copy error" },
80
+ copied
81
+ ? React.createElement(
82
+ "svg",
83
+ { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none",
84
+ stroke: "#4ade80", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" },
85
+ React.createElement("polyline", { points: "20 6 9 17 4 12" })
86
+ )
87
+ : React.createElement(
88
+ "svg",
89
+ { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none",
90
+ stroke: "#888", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
91
+ React.createElement("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
92
+ React.createElement("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
93
+ )
94
+ )
95
+ ),
96
+
97
+ // ── Stack trace toggle + content
98
+ stackLines.length > 0 &&
99
+ React.createElement(
100
+ "div",
101
+ { style: stackSectionStyle },
102
+ React.createElement(
103
+ "button",
104
+ { onClick: () => setExpanded((v) => !v), style: toggleBtnStyle },
105
+ React.createElement(
106
+ "svg",
107
+ { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none",
108
+ stroke: "#666", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round",
109
+ style: { transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
110
+ transition: "transform 200ms ease", flexShrink: 0 } },
111
+ React.createElement("polyline", { points: "9 18 15 12 9 6" })
112
+ ),
113
+ React.createElement("span", null, "Stack trace")
114
+ ),
115
+ expanded &&
116
+ React.createElement("pre", { style: stackStyle }, stackLines)
117
+ ),
118
+
119
+ // ── Actions
120
+ React.createElement(
121
+ "div",
122
+ { style: actionsStyle },
123
+ React.createElement(
124
+ "button",
125
+ { onClick: () => window.location.reload(), style: primaryBtnStyle },
126
+ React.createElement(
127
+ "svg",
128
+ { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none",
129
+ stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" },
130
+ React.createElement("polyline", { points: "23 4 23 10 17 10" }),
131
+ React.createElement("path", { d: "M20.49 15a9 9 0 1 1-2.12-9.36L23 10" })
132
+ ),
133
+ "Reload page"
134
+ ),
135
+ React.createElement(
136
+ "button",
137
+ { onClick: () => (window.location.href = "/"), style: secondaryBtnStyle },
138
+ React.createElement(
139
+ "svg",
140
+ { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none",
141
+ stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" },
142
+ React.createElement("path", { d: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }),
143
+ React.createElement("polyline", { points: "9 22 9 12 15 12 15 22" })
144
+ ),
145
+ "Go to home"
146
+ )
147
+ )
148
+ )
149
+ );
150
+ }
151
+
152
+ const overlayStyle = {
153
+ position: "fixed", inset: 0,
154
+ background: "rgba(0,0,0,0.72)",
155
+ backdropFilter: "blur(6px)",
156
+ display: "flex", alignItems: "center", justifyContent: "center",
157
+ zIndex: 9999,
158
+ };
159
+ const dialogStyle = {
160
+ background: "#141414",
161
+ border: "1px solid #2a2a2a",
162
+ borderRadius: "14px",
163
+ padding: "0",
164
+ maxWidth: "580px", width: "92%",
165
+ boxShadow: "0 32px 80px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.04) inset",
166
+ color: "#e5e5e5",
167
+ overflow: "hidden",
168
+ fontFamily: "system-ui, -apple-system, sans-serif",
169
+ };
170
+ const topBarStyle = {
171
+ display: "flex", alignItems: "center", justifyContent: "space-between",
172
+ padding: "12px 18px",
173
+ background: "#0e0e0e",
174
+ };
175
+ const brandStyle = {
176
+ display: "flex", alignItems: "center", gap: "7px",
177
+ };
178
+ const brandNameStyle = {
179
+ fontSize: "13px", fontWeight: 700,
180
+ color: "#c4b5fd", letterSpacing: "0.04em",
181
+ fontFamily: "system-ui, sans-serif",
182
+ };
183
+ const badgeStyle = {
184
+ fontSize: "11px", fontWeight: 600,
185
+ color: "#f87171",
186
+ background: "rgba(248,113,113,0.1)",
187
+ border: "1px solid rgba(248,113,113,0.2)",
188
+ borderRadius: "999px", padding: "2px 10px",
189
+ letterSpacing: "0.03em",
190
+ };
191
+ const dividerStyle = {
192
+ height: "1px", background: "#1f1f1f",
193
+ };
194
+ const headerStyle = {
195
+ display: "flex", alignItems: "center", gap: "10px",
196
+ padding: "20px 22px 0 22px",
197
+ };
198
+ const iconWrapStyle = {
199
+ width: "28px", height: "28px", borderRadius: "8px",
200
+ background: "rgba(248,113,113,0.1)",
201
+ border: "1px solid rgba(248,113,113,0.15)",
202
+ display: "flex", alignItems: "center", justifyContent: "center",
203
+ flexShrink: 0,
204
+ };
205
+ const titleStyle = {
206
+ fontSize: "15px", fontWeight: 650, color: "#fff",
207
+ letterSpacing: "-0.01em",
208
+ };
209
+ const messagePanelStyle = {
210
+ position: "relative",
211
+ margin: "14px 22px 0 22px",
212
+ background: "rgba(248,113,113,0.05)",
213
+ border: "1px solid rgba(248,113,113,0.12)",
214
+ borderRadius: "8px",
215
+ padding: "12px 40px 12px 14px",
216
+ };
217
+ const messageStyle = {
218
+ fontFamily: "ui-monospace, 'Cascadia Code', 'Fira Code', monospace",
219
+ fontSize: "12.5px", color: "#fca5a5",
220
+ margin: 0, lineHeight: 1.65, wordBreak: "break-word",
221
+ };
222
+ const copyBtnStyle = {
223
+ position: "absolute", top: "10px", right: "10px",
224
+ background: "rgba(255,255,255,0.05)",
225
+ border: "1px solid #2e2e2e",
226
+ borderRadius: "6px",
227
+ width: "28px", height: "28px",
228
+ display: "flex", alignItems: "center", justifyContent: "center",
229
+ cursor: "pointer", transition: "background 150ms ease",
230
+ flexShrink: 0,
231
+ };
232
+ const stackSectionStyle = {
233
+ margin: "14px 22px 0 22px",
234
+ };
235
+ const toggleBtnStyle = {
236
+ background: "none", border: "none", cursor: "pointer",
237
+ color: "#666", fontSize: "12px",
238
+ padding: "4px 0",
239
+ display: "flex", alignItems: "center", gap: "6px",
240
+ letterSpacing: "0.02em",
241
+ transition: "color 150ms ease",
242
+ };
243
+ const stackStyle = {
244
+ background: "#0a0a0a",
245
+ border: "1px solid #222",
246
+ borderRadius: "8px",
247
+ padding: "14px 16px",
248
+ fontSize: "11px", color: "#888",
249
+ overflowX: "auto", lineHeight: 1.8,
250
+ marginTop: "8px", marginBottom: 0,
251
+ whiteSpace: "pre-wrap", wordBreak: "break-all",
252
+ fontFamily: "ui-monospace, 'Cascadia Code', monospace",
253
+ };
254
+ const actionsStyle = {
255
+ display: "flex", gap: "10px",
256
+ padding: "18px 22px 22px 22px",
257
+ marginTop: "16px",
258
+ };
259
+ const primaryBtnStyle = {
260
+ flex: 1, padding: "10px 0",
261
+ borderRadius: "8px", border: "none",
262
+ background: "linear-gradient(135deg, #7c3aed, #6d28d9)",
263
+ color: "#fff", fontWeight: 600,
264
+ fontSize: "13px", cursor: "pointer",
265
+ display: "flex", alignItems: "center", justifyContent: "center", gap: "7px",
266
+ letterSpacing: "0.01em",
267
+ boxShadow: "0 2px 12px rgba(124,58,237,0.35)",
268
+ fontFamily: "system-ui, sans-serif",
269
+ };
270
+ const secondaryBtnStyle = {
271
+ flex: 1, padding: "10px 0",
272
+ borderRadius: "8px",
273
+ border: "1px solid #2e2e2e",
274
+ background: "rgba(255,255,255,0.03)",
275
+ color: "#999", fontSize: "13px", cursor: "pointer",
276
+ display: "flex", alignItems: "center", justifyContent: "center", gap: "7px",
277
+ letterSpacing: "0.01em",
278
+ fontFamily: "system-ui, sans-serif",
279
+ };
280
+ `;
2
281
  export function revinePlugin() {
3
282
  return {
4
283
  name: "revine",
@@ -11,8 +290,11 @@ export function revinePlugin() {
11
290
  load(id) {
12
291
  if (id === VIRTUAL_ROUTING_ID) {
13
292
  return `
14
- import { createBrowserRouter } from "react-router-dom";
293
+ import { createBrowserRouter, useRouteError } from "react-router-dom";
15
294
  import { lazy, Suspense, createElement } from "react";
295
+ import React from "react";
296
+
297
+ ${errorBoundaryComponent}
16
298
 
17
299
  const notFoundModules = import.meta.glob("/src/NotFound.tsx", { eager: true });
18
300
  const NotFoundComponent = Object.values(notFoundModules)[0]?.default;
@@ -81,7 +363,7 @@ const routes = pageEntries.map(([filePath, component]) => {
81
363
 
82
364
  const fallback = Loading
83
365
  ? createElement(Loading)
84
- : createElement("div", null, "Loading\\u2026");
366
+ : createElement("div", null, "Loading\u2026");
85
367
 
86
368
  const pageElement = createElement(
87
369
  Suspense,
@@ -92,6 +374,7 @@ const routes = pageEntries.map(([filePath, component]) => {
92
374
  return {
93
375
  path: routePath,
94
376
  element: layouts.length > 0 ? wrapWithLayouts(pageElement, layouts) : pageElement,
377
+ errorElement: createElement(RevineErrorDialog),
95
378
  };
96
379
  });
97
380
 
@@ -100,6 +383,7 @@ routes.push({
100
383
  element: NotFoundComponent
101
384
  ? createElement(NotFoundComponent)
102
385
  : createElement("div", null, "404 - Page Not Found"),
386
+ errorElement: createElement(RevineErrorDialog),
103
387
  });
104
388
 
105
389
  export const router = createBrowserRouter(routes);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "revine",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "A react framework, but better.",
5
5
  "license": "MIT",
6
6
  "author": "Rachit Bharadwaj",
@@ -1,5 +1,285 @@
1
1
  const VIRTUAL_ROUTING_ID = "\0revine:routing";
2
2
 
3
+ const errorBoundaryComponent = `
4
+ function RevineErrorDialog() {
5
+ const error = useRouteError();
6
+ const [expanded, setExpanded] = React.useState(false);
7
+ const [copied, setCopied] = React.useState(false);
8
+
9
+ const message = error?.message || String(error) || "An unexpected error occurred.";
10
+ const stack = error?.stack || "";
11
+ const stackLines = stack
12
+ .split("\\n")
13
+ .filter((l) => l.trim().startsWith("at "))
14
+ .slice(0, 8)
15
+ .join("\\n");
16
+
17
+ const handleCopy = () => {
18
+ const text = message + (stackLines ? "\\n\\n" + stackLines : "");
19
+ navigator.clipboard.writeText(text).then(() => {
20
+ setCopied(true);
21
+ setTimeout(() => setCopied(false), 2000);
22
+ });
23
+ };
24
+
25
+ return React.createElement(
26
+ "div",
27
+ { style: overlayStyle },
28
+ React.createElement(
29
+ "div",
30
+ { style: dialogStyle },
31
+
32
+ // ── Top bar: Revine brand + badge
33
+ React.createElement(
34
+ "div",
35
+ { style: topBarStyle },
36
+ React.createElement(
37
+ "div",
38
+ { style: brandStyle },
39
+ React.createElement(
40
+ "svg",
41
+ { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none",
42
+ stroke: "#a78bfa", strokeWidth: "2.2", strokeLinecap: "round",
43
+ strokeLinejoin: "round", style: { flexShrink: 0 } },
44
+ React.createElement("polygon", { points: "13 2 3 14 12 14 11 22 21 10 12 10 13 2" })
45
+ ),
46
+ React.createElement("span", { style: brandNameStyle }, "Revine")
47
+ ),
48
+ React.createElement("span", { style: badgeStyle }, "Runtime Error")
49
+ ),
50
+
51
+ // ── Divider
52
+ React.createElement("div", { style: dividerStyle }),
53
+
54
+ // ── Error icon + title
55
+ React.createElement(
56
+ "div",
57
+ { style: headerStyle },
58
+ React.createElement(
59
+ "div",
60
+ { style: iconWrapStyle },
61
+ React.createElement(
62
+ "svg",
63
+ { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none",
64
+ stroke: "#f87171", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" },
65
+ React.createElement("circle", { cx: "12", cy: "12", r: "10" }),
66
+ React.createElement("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
67
+ React.createElement("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
68
+ )
69
+ ),
70
+ React.createElement("span", { style: titleStyle }, "Application Error")
71
+ ),
72
+
73
+ // ── Error message + copy button
74
+ React.createElement(
75
+ "div",
76
+ { style: messagePanelStyle },
77
+ React.createElement("p", { style: messageStyle }, message),
78
+ React.createElement(
79
+ "button",
80
+ { onClick: handleCopy, style: copyBtnStyle, title: "Copy error" },
81
+ copied
82
+ ? React.createElement(
83
+ "svg",
84
+ { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none",
85
+ stroke: "#4ade80", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" },
86
+ React.createElement("polyline", { points: "20 6 9 17 4 12" })
87
+ )
88
+ : React.createElement(
89
+ "svg",
90
+ { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none",
91
+ stroke: "#888", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
92
+ React.createElement("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
93
+ React.createElement("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
94
+ )
95
+ )
96
+ ),
97
+
98
+ // ── Stack trace toggle + content
99
+ stackLines.length > 0 &&
100
+ React.createElement(
101
+ "div",
102
+ { style: stackSectionStyle },
103
+ React.createElement(
104
+ "button",
105
+ { onClick: () => setExpanded((v) => !v), style: toggleBtnStyle },
106
+ React.createElement(
107
+ "svg",
108
+ { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none",
109
+ stroke: "#666", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round",
110
+ style: { transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
111
+ transition: "transform 200ms ease", flexShrink: 0 } },
112
+ React.createElement("polyline", { points: "9 18 15 12 9 6" })
113
+ ),
114
+ React.createElement("span", null, "Stack trace")
115
+ ),
116
+ expanded &&
117
+ React.createElement("pre", { style: stackStyle }, stackLines)
118
+ ),
119
+
120
+ // ── Actions
121
+ React.createElement(
122
+ "div",
123
+ { style: actionsStyle },
124
+ React.createElement(
125
+ "button",
126
+ { onClick: () => window.location.reload(), style: primaryBtnStyle },
127
+ React.createElement(
128
+ "svg",
129
+ { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none",
130
+ stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" },
131
+ React.createElement("polyline", { points: "23 4 23 10 17 10" }),
132
+ React.createElement("path", { d: "M20.49 15a9 9 0 1 1-2.12-9.36L23 10" })
133
+ ),
134
+ "Reload page"
135
+ ),
136
+ React.createElement(
137
+ "button",
138
+ { onClick: () => (window.location.href = "/"), style: secondaryBtnStyle },
139
+ React.createElement(
140
+ "svg",
141
+ { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none",
142
+ stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" },
143
+ React.createElement("path", { d: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }),
144
+ React.createElement("polyline", { points: "9 22 9 12 15 12 15 22" })
145
+ ),
146
+ "Go to home"
147
+ )
148
+ )
149
+ )
150
+ );
151
+ }
152
+
153
+ const overlayStyle = {
154
+ position: "fixed", inset: 0,
155
+ background: "rgba(0,0,0,0.72)",
156
+ backdropFilter: "blur(6px)",
157
+ display: "flex", alignItems: "center", justifyContent: "center",
158
+ zIndex: 9999,
159
+ };
160
+ const dialogStyle = {
161
+ background: "#141414",
162
+ border: "1px solid #2a2a2a",
163
+ borderRadius: "14px",
164
+ padding: "0",
165
+ maxWidth: "580px", width: "92%",
166
+ boxShadow: "0 32px 80px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.04) inset",
167
+ color: "#e5e5e5",
168
+ overflow: "hidden",
169
+ fontFamily: "system-ui, -apple-system, sans-serif",
170
+ };
171
+ const topBarStyle = {
172
+ display: "flex", alignItems: "center", justifyContent: "space-between",
173
+ padding: "12px 18px",
174
+ background: "#0e0e0e",
175
+ };
176
+ const brandStyle = {
177
+ display: "flex", alignItems: "center", gap: "7px",
178
+ };
179
+ const brandNameStyle = {
180
+ fontSize: "13px", fontWeight: 700,
181
+ color: "#c4b5fd", letterSpacing: "0.04em",
182
+ fontFamily: "system-ui, sans-serif",
183
+ };
184
+ const badgeStyle = {
185
+ fontSize: "11px", fontWeight: 600,
186
+ color: "#f87171",
187
+ background: "rgba(248,113,113,0.1)",
188
+ border: "1px solid rgba(248,113,113,0.2)",
189
+ borderRadius: "999px", padding: "2px 10px",
190
+ letterSpacing: "0.03em",
191
+ };
192
+ const dividerStyle = {
193
+ height: "1px", background: "#1f1f1f",
194
+ };
195
+ const headerStyle = {
196
+ display: "flex", alignItems: "center", gap: "10px",
197
+ padding: "20px 22px 0 22px",
198
+ };
199
+ const iconWrapStyle = {
200
+ width: "28px", height: "28px", borderRadius: "8px",
201
+ background: "rgba(248,113,113,0.1)",
202
+ border: "1px solid rgba(248,113,113,0.15)",
203
+ display: "flex", alignItems: "center", justifyContent: "center",
204
+ flexShrink: 0,
205
+ };
206
+ const titleStyle = {
207
+ fontSize: "15px", fontWeight: 650, color: "#fff",
208
+ letterSpacing: "-0.01em",
209
+ };
210
+ const messagePanelStyle = {
211
+ position: "relative",
212
+ margin: "14px 22px 0 22px",
213
+ background: "rgba(248,113,113,0.05)",
214
+ border: "1px solid rgba(248,113,113,0.12)",
215
+ borderRadius: "8px",
216
+ padding: "12px 40px 12px 14px",
217
+ };
218
+ const messageStyle = {
219
+ fontFamily: "ui-monospace, 'Cascadia Code', 'Fira Code', monospace",
220
+ fontSize: "12.5px", color: "#fca5a5",
221
+ margin: 0, lineHeight: 1.65, wordBreak: "break-word",
222
+ };
223
+ const copyBtnStyle = {
224
+ position: "absolute", top: "10px", right: "10px",
225
+ background: "rgba(255,255,255,0.05)",
226
+ border: "1px solid #2e2e2e",
227
+ borderRadius: "6px",
228
+ width: "28px", height: "28px",
229
+ display: "flex", alignItems: "center", justifyContent: "center",
230
+ cursor: "pointer", transition: "background 150ms ease",
231
+ flexShrink: 0,
232
+ };
233
+ const stackSectionStyle = {
234
+ margin: "14px 22px 0 22px",
235
+ };
236
+ const toggleBtnStyle = {
237
+ background: "none", border: "none", cursor: "pointer",
238
+ color: "#666", fontSize: "12px",
239
+ padding: "4px 0",
240
+ display: "flex", alignItems: "center", gap: "6px",
241
+ letterSpacing: "0.02em",
242
+ transition: "color 150ms ease",
243
+ };
244
+ const stackStyle = {
245
+ background: "#0a0a0a",
246
+ border: "1px solid #222",
247
+ borderRadius: "8px",
248
+ padding: "14px 16px",
249
+ fontSize: "11px", color: "#888",
250
+ overflowX: "auto", lineHeight: 1.8,
251
+ marginTop: "8px", marginBottom: 0,
252
+ whiteSpace: "pre-wrap", wordBreak: "break-all",
253
+ fontFamily: "ui-monospace, 'Cascadia Code', monospace",
254
+ };
255
+ const actionsStyle = {
256
+ display: "flex", gap: "10px",
257
+ padding: "18px 22px 22px 22px",
258
+ marginTop: "16px",
259
+ };
260
+ const primaryBtnStyle = {
261
+ flex: 1, padding: "10px 0",
262
+ borderRadius: "8px", border: "none",
263
+ background: "linear-gradient(135deg, #7c3aed, #6d28d9)",
264
+ color: "#fff", fontWeight: 600,
265
+ fontSize: "13px", cursor: "pointer",
266
+ display: "flex", alignItems: "center", justifyContent: "center", gap: "7px",
267
+ letterSpacing: "0.01em",
268
+ boxShadow: "0 2px 12px rgba(124,58,237,0.35)",
269
+ fontFamily: "system-ui, sans-serif",
270
+ };
271
+ const secondaryBtnStyle = {
272
+ flex: 1, padding: "10px 0",
273
+ borderRadius: "8px",
274
+ border: "1px solid #2e2e2e",
275
+ background: "rgba(255,255,255,0.03)",
276
+ color: "#999", fontSize: "13px", cursor: "pointer",
277
+ display: "flex", alignItems: "center", justifyContent: "center", gap: "7px",
278
+ letterSpacing: "0.01em",
279
+ fontFamily: "system-ui, sans-serif",
280
+ };
281
+ `;
282
+
3
283
  export function revinePlugin(): any {
4
284
  return {
5
285
  name: "revine",
@@ -14,8 +294,11 @@ export function revinePlugin(): any {
14
294
  load(id: string) {
15
295
  if (id === VIRTUAL_ROUTING_ID) {
16
296
  return `
17
- import { createBrowserRouter } from "react-router-dom";
297
+ import { createBrowserRouter, useRouteError } from "react-router-dom";
18
298
  import { lazy, Suspense, createElement } from "react";
299
+ import React from "react";
300
+
301
+ ${errorBoundaryComponent}
19
302
 
20
303
  const notFoundModules = import.meta.glob("/src/NotFound.tsx", { eager: true });
21
304
  const NotFoundComponent = Object.values(notFoundModules)[0]?.default;
@@ -84,7 +367,7 @@ const routes = pageEntries.map(([filePath, component]) => {
84
367
 
85
368
  const fallback = Loading
86
369
  ? createElement(Loading)
87
- : createElement("div", null, "Loading\\u2026");
370
+ : createElement("div", null, "Loading\u2026");
88
371
 
89
372
  const pageElement = createElement(
90
373
  Suspense,
@@ -95,6 +378,7 @@ const routes = pageEntries.map(([filePath, component]) => {
95
378
  return {
96
379
  path: routePath,
97
380
  element: layouts.length > 0 ? wrapWithLayouts(pageElement, layouts) : pageElement,
381
+ errorElement: createElement(RevineErrorDialog),
98
382
  };
99
383
  });
100
384
 
@@ -103,6 +387,7 @@ routes.push({
103
387
  element: NotFoundComponent
104
388
  ? createElement(NotFoundComponent)
105
389
  : createElement("div", null, "404 - Page Not Found"),
390
+ errorElement: createElement(RevineErrorDialog),
106
391
  });
107
392
 
108
393
  export const router = createBrowserRouter(routes);
@@ -110,4 +395,4 @@ export const router = createBrowserRouter(routes);
110
395
  }
111
396
  },
112
397
  };
113
- }
398
+ }