@pajh/buldng 0.0.3 → 0.0.5

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.
@@ -146,3 +146,133 @@ button {
146
146
  background: var(--button);
147
147
  }
148
148
 
149
+ /* --------------------------------------------------
150
+ MODAL SYSTEM
151
+ -------------------------------------------------- */
152
+
153
+ #b-modal-root {
154
+ position: fixed;
155
+ inset: 0;
156
+ z-index: 1000;
157
+ pointer-events: none;
158
+ }
159
+
160
+ #b-modal-root .buldng-modal-backdrop {
161
+ position: absolute;
162
+ inset: 0;
163
+ display: flex;
164
+ align-items: center;
165
+ justify-content: center;
166
+ padding: 20px;
167
+ background: var(--modal-backdrop-color, rgba(0, 0, 0, 0.5));
168
+ pointer-events: auto;
169
+ }
170
+ #b-modal-root .buldng-modal {
171
+ display: flex;
172
+ flex-direction: column;
173
+ width: min(100%, 480px);
174
+ max-height: min(100%, 720px);
175
+ overflow: auto;
176
+ color: var(--text, #222222);
177
+ background: var(--modal-bg, var(--panel, #ffffff));
178
+ border: 2px solid var(--modal-accent-color, #0066cc);
179
+ border-radius: var(--modal-radius, 12px);
180
+ box-shadow: var(--modal-shadow, 0 4px 20px rgba(0, 0, 0, 0.3));
181
+ }
182
+
183
+ #b-modal-root .buldng-modal-header {
184
+ display: flex;
185
+ align-items: center;
186
+ gap: 12px;
187
+ padding: var(--modal-padding, 20px) var(--modal-padding, 20px) 0;
188
+ }
189
+
190
+ #b-modal-root .buldng-modal-icon {
191
+ display: inline-flex;
192
+ align-items: center;
193
+ justify-content: center;
194
+ flex: 0 0 32px;
195
+ width: 32px;
196
+ height: 32px;
197
+ color: var(--modal-accent-color, #0066cc);
198
+ font-weight: 700;
199
+ border: 2px solid currentColor;
200
+ border-radius: 50%;
201
+ }
202
+
203
+ #b-modal-root .buldng-modal-title {
204
+ margin: 0;
205
+ color: inherit;
206
+ font-size: 1.2em;
207
+ }
208
+
209
+ #b-modal-root .buldng-modal-content {
210
+ padding: var(--modal-padding, 20px);
211
+ overflow: auto;
212
+ }
213
+
214
+ #b-modal-root .buldng-modal-footer {
215
+ display: flex;
216
+ justify-content: flex-end;
217
+ gap: 10px;
218
+ padding: 0 var(--modal-padding, 20px) var(--modal-padding, 20px);
219
+ }
220
+
221
+ #b-modal-root .buldng-modal-button {
222
+ min-width: 88px;
223
+ min-height: 44px;
224
+ padding: 8px 16px;
225
+ color: var(--text, #222222);
226
+ font: inherit;
227
+ font-weight: 600;
228
+ background: var(--button, #f0f0f0);
229
+ border: 1px solid var(--line, #9c9c9c);
230
+ border-radius: 4px;
231
+ cursor: pointer;
232
+ }
233
+
234
+ #b-modal-root .buldng-modal-button-primary {
235
+ color: #ffffff;
236
+ background: var(--modal-accent-color, #0066cc);
237
+ border-color: var(--modal-accent-color, #0066cc);
238
+ }
239
+
240
+ #b-modal-root .buldng-modal-button:focus-visible {
241
+ outline: 3px solid var(--highlight1, #4aa3ff);
242
+ outline-offset: 2px;
243
+ }
244
+
245
+ #b-modal-root .buldng-modal.info {
246
+ --modal-accent-color: #0066cc;
247
+ }
248
+
249
+ #b-modal-root .buldng-modal.confirmation {
250
+ --modal-accent-color: #444444;
251
+ }
252
+
253
+ #b-modal-root .buldng-modal.destructive,
254
+ #b-modal-root .buldng-modal.error {
255
+ --modal-accent-color: #cc0000;
256
+ }
257
+
258
+ @media (max-width: 520px) {
259
+ #b-modal-root .buldng-modal-backdrop {
260
+ align-items: flex-end;
261
+ padding: 0;
262
+ }
263
+
264
+ #b-modal-root .buldng-modal {
265
+ width: 100%;
266
+ max-height: 90%;
267
+ border-radius: var(--modal-radius, 12px) var(--modal-radius, 12px) 0 0;
268
+ }
269
+
270
+ #b-modal-root .buldng-modal-footer {
271
+ flex-direction: column-reverse;
272
+ }
273
+
274
+ #b-modal-root .buldng-modal-button {
275
+ width: 100%;
276
+ }
277
+ }
278
+
@@ -0,0 +1,16 @@
1
+ // Development-only override. setErrorPage() owns the event listeners.
2
+ console.log("[dev-errors] overriding window.__error_handler");
3
+
4
+ window.__error_handler = (event, page) => {
5
+ const reason =
6
+ event.type === "error"
7
+ ? event.error ?? event.message
8
+ : event.reason;
9
+
10
+ const message =
11
+ reason instanceof Error
12
+ ? reason.message
13
+ : String(reason || "Unknown error");
14
+
15
+ alert(`BULDR DEV ERROR:\n\n${message}\n\nCheck DevTools for full details.`);
16
+ };
@@ -0,0 +1,171 @@
1
+ /**
2
+ * ============================================================
3
+ * BULDR PRODUCTION ERROR SUBSYSTEM — layout-err.ts
4
+ * ============================================================
5
+ *
6
+ * This module is bundled ONLY by layout.ts.
7
+ * It owns ALL production error logic:
8
+ * - capturing errors
9
+ * - building payloads
10
+ * - storing payloads
11
+ * - redirecting to the error page
12
+ *
13
+ * buldng-web.ts owns:
14
+ * - reading payloads
15
+ * - consuming payloads
16
+ * - rendering payloads
17
+ * - runtime utilities
18
+ *
19
+ * error-main.ts owns:
20
+ * - UI for the error page
21
+ *
22
+ * ============================================================
23
+ */
24
+
25
+ const ERROR_STORAGE_PREFIX = "buldng:error:";
26
+
27
+ // ------------------------------------------------------------
28
+ // PUBLIC API
29
+ // ------------------------------------------------------------
30
+
31
+ /**
32
+ * Install the production error handler wrapper.
33
+ * layout.ts calls this once during startup.
34
+ */
35
+ export function installErrorHandler() {
36
+ window.__error_handler = (event, page) => {
37
+ handleError(event, page);
38
+ };
39
+ console.log("BULDR: Production error handler installed");
40
+ }
41
+
42
+ // ------------------------------------------------------------
43
+ // CORE PRODUCTION LOGIC
44
+ // ------------------------------------------------------------
45
+
46
+ function handleError(
47
+ event: ErrorEvent | PromiseRejectionEvent,
48
+ page: string
49
+ ) {
50
+ if (window.__buldngErrorOverlay) return;
51
+
52
+ const info =
53
+ event instanceof ErrorEvent
54
+ ? {
55
+ type: "error" as const,
56
+ reason: event.error ?? event.message,
57
+ fallbackMessage: event.message,
58
+ }
59
+ : {
60
+ type: "unhandledrejection" as const,
61
+ reason: event.reason,
62
+ fallbackMessage: "Unhandled promise rejection",
63
+ };
64
+
65
+ const payload = buildPayload(info);
66
+ const stored = saveErrorPayload(payload);
67
+
68
+ redirect(payload, stored, page);
69
+ }
70
+
71
+ // ------------------------------------------------------------
72
+ // PAYLOAD BUILDING
73
+ // ------------------------------------------------------------
74
+
75
+ export type ErrorPayload = {
76
+ id: string;
77
+ type: "error" | "unhandledrejection";
78
+ message: string;
79
+ stack: string;
80
+ time: string;
81
+ url: string;
82
+ userAgent: string;
83
+ platform: string;
84
+ language: string;
85
+ };
86
+
87
+ function buildPayload(info: {
88
+ type: ErrorPayload["type"];
89
+ reason: unknown;
90
+ fallbackMessage: string;
91
+ }): ErrorPayload {
92
+ const id = createId();
93
+ const error = info.reason instanceof Error ? info.reason : null;
94
+
95
+ return {
96
+ id,
97
+ type: info.type,
98
+ message: getErrorMessage(info.reason, info.fallbackMessage),
99
+ stack: error?.stack ?? getErrorStack(info.reason),
100
+ time: new Date().toISOString(),
101
+ url: location.href,
102
+ userAgent: navigator.userAgent,
103
+ platform: navigator.platform,
104
+ language: navigator.language,
105
+ };
106
+ }
107
+
108
+ // ------------------------------------------------------------
109
+ // REDIRECT
110
+ // ------------------------------------------------------------
111
+
112
+ function redirect(payload: ErrorPayload, stored: boolean, page: string) {
113
+ const destination = new URL(page, location.href);
114
+
115
+ if (stored) {
116
+ destination.searchParams.set("errorId", payload.id);
117
+ } else {
118
+ destination.searchParams.set("type", payload.type);
119
+ destination.searchParams.set("message", payload.message.slice(0, 500));
120
+ }
121
+
122
+ window.location.href = destination.href;
123
+ }
124
+
125
+ // ------------------------------------------------------------
126
+ // STORAGE (production only)
127
+ // ------------------------------------------------------------
128
+
129
+ function saveErrorPayload(payload: ErrorPayload): boolean {
130
+ try {
131
+ localStorage.setItem(storageKey(payload.id), JSON.stringify(payload));
132
+ return true;
133
+ } catch {
134
+ return false;
135
+ }
136
+ }
137
+
138
+ function storageKey(id: string) {
139
+ return `${ERROR_STORAGE_PREFIX}${id}`;
140
+ }
141
+
142
+ function createId() {
143
+ if (typeof crypto.randomUUID === "function") return crypto.randomUUID();
144
+ return `${Date.now().toString(36)}-${Math.random()
145
+ .toString(36)
146
+ .slice(2)}`;
147
+ }
148
+
149
+ // ------------------------------------------------------------
150
+ // UTILITIES (production only)
151
+ // ------------------------------------------------------------
152
+
153
+ function getErrorMessage(reason: unknown, fallback: string) {
154
+ if (reason instanceof Error && reason.message) return reason.message;
155
+ if (typeof reason === "string" && reason) return reason;
156
+
157
+ if (reason !== null && reason !== undefined) {
158
+ try {
159
+ const text = JSON.stringify(reason);
160
+ if (text) return text;
161
+ } catch {}
162
+ }
163
+
164
+ return fallback;
165
+ }
166
+
167
+ function getErrorStack(reason: unknown) {
168
+ if (!reason || typeof reason !== "object") return "";
169
+ const stack = (reason as { stack?: unknown }).stack;
170
+ return typeof stack === "string" ? stack : "";
171
+ }
@@ -1,3 +1,7 @@
1
+ import { installErrorHandler } from "./layout-err";
2
+
3
+ installErrorHandler();
4
+
1
5
  /* --------------------------------------------------
2
6
  GLOBAL VIEWPORT STATE
3
7
  -------------------------------------------------- */