@mandujs/core 0.54.28 → 0.54.30
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.
package/package.json
CHANGED
|
@@ -362,6 +362,40 @@ describe("buildClientBundles vendor shims", () => {
|
|
|
362
362
|
expect((globalThis as typeof globalThis & { __MANDU_TEST_DEFAULT_PROPS__?: unknown }).__MANDU_TEST_DEFAULT_PROPS__).toBeUndefined();
|
|
363
363
|
});
|
|
364
364
|
|
|
365
|
+
test("surfaces an island hydration failure to the DevTools hook (#324)", async () => {
|
|
366
|
+
const modulePath = path.join(rootDir, ".mandu", "client", "runtime-failing-boundary.js");
|
|
367
|
+
// A module that throws at import time → loadAndHydrate's catch runs.
|
|
368
|
+
await writeFile(modulePath, `throw new Error("boom at import");`, "utf-8");
|
|
369
|
+
|
|
370
|
+
const emitted: Array<{ type?: string; data?: { message?: string; islandId?: string } }> = [];
|
|
371
|
+
(window as unknown as { __MANDU_DEVTOOLS_HOOK__?: { emit: (e: unknown) => void } }).__MANDU_DEVTOOLS_HOOK__ = {
|
|
372
|
+
emit: (e: unknown) => {
|
|
373
|
+
emitted.push(e as { type?: string });
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
document.body.innerHTML = `
|
|
378
|
+
<div
|
|
379
|
+
data-mandu-island="failing--0"
|
|
380
|
+
data-mandu-src="${pathToFileURL(modulePath).href}?t=${Date.now()}"
|
|
381
|
+
data-hydrate="load"
|
|
382
|
+
></div>
|
|
383
|
+
`;
|
|
384
|
+
(window as typeof window & { __MANDU_ROOTS__?: Map<string, unknown> }).__MANDU_ROOTS__ = new Map();
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
const runtime = await evaluateGeneratedHydrationRuntime();
|
|
388
|
+
runtime.hydrateIslands();
|
|
389
|
+
|
|
390
|
+
await waitForRuntimeAssertion(() => emitted.some((e) => e.type === "error"));
|
|
391
|
+
const errorEvent = emitted.find((e) => e.type === "error");
|
|
392
|
+
expect(errorEvent?.data?.islandId).toBe("failing--0");
|
|
393
|
+
expect(errorEvent?.data?.message).toContain("boom at import");
|
|
394
|
+
} finally {
|
|
395
|
+
delete (window as unknown as { __MANDU_DEVTOOLS_HOOK__?: unknown }).__MANDU_DEVTOOLS_HOOK__;
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
365
399
|
test("runtime hydrates default client boundary from boundary-local props", async () => {
|
|
366
400
|
const modulePath = path.join(rootDir, ".mandu", "client", "runtime-default-boundary.js");
|
|
367
401
|
await writeFile(
|
|
@@ -160,6 +160,7 @@ class IslandErrorBoundary extends Component<IslandErrorBoundaryProps, IslandErro
|
|
|
160
160
|
|
|
161
161
|
componentDidCatch(error: Error, errorInfo: unknown): void {
|
|
162
162
|
console.error("[Mandu] Island error:", this.props.islandId, error, errorInfo);
|
|
163
|
+
emitIslandHydrationError(this.props.islandId, error);
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
reset = (): void => {
|
|
@@ -371,6 +372,35 @@ function isManduIslandExport(value: unknown): value is ManduIslandExport {
|
|
|
371
372
|
"definition" in value;
|
|
372
373
|
}
|
|
373
374
|
|
|
375
|
+
// #324: surface island hydration/render failures to the in-page DevTools so
|
|
376
|
+
// its health badge and Issues counter reflect them instead of staying
|
|
377
|
+
// "HEALTHY". Render-time errors are swallowed by IslandErrorBoundary and never
|
|
378
|
+
// reach the global error listener, so we emit them to the devtools hook here.
|
|
379
|
+
function emitIslandHydrationError(islandId: string, error: unknown): void {
|
|
380
|
+
const devtoolsHook = (window as Window & {
|
|
381
|
+
__MANDU_DEVTOOLS_HOOK__?: RuntimeDevtoolsHook;
|
|
382
|
+
}).__MANDU_DEVTOOLS_HOOK__;
|
|
383
|
+
if (!devtoolsHook) return;
|
|
384
|
+
try {
|
|
385
|
+
devtoolsHook.emit({
|
|
386
|
+
type: "error",
|
|
387
|
+
timestamp: Date.now(),
|
|
388
|
+
data: {
|
|
389
|
+
id: "island-" + islandId + "-" + Date.now(),
|
|
390
|
+
type: "runtime",
|
|
391
|
+
severity: "error",
|
|
392
|
+
message: error instanceof Error ? error.message : String(error),
|
|
393
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
394
|
+
timestamp: Date.now(),
|
|
395
|
+
url: location.href,
|
|
396
|
+
islandId,
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
} catch {
|
|
400
|
+
// DevTools must never break hydration.
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
374
404
|
async function loadAndHydrate(element: HTMLElement, src: string): Promise<void> {
|
|
375
405
|
const id = element.getAttribute("data-mandu-island");
|
|
376
406
|
if (!id) {
|
|
@@ -490,6 +520,7 @@ async function loadAndHydrate(element: HTMLElement, src: string): Promise<void>
|
|
|
490
520
|
} catch (error) {
|
|
491
521
|
console.error("[Mandu] Hydration failed for", islandId, error);
|
|
492
522
|
element.setAttribute("data-mandu-error", "true");
|
|
523
|
+
emitIslandHydrationError(islandId, error);
|
|
493
524
|
|
|
494
525
|
element.dispatchEvent(new CustomEvent("mandu:hydration-error", {
|
|
495
526
|
bubbles: true,
|
|
@@ -7,28 +7,31 @@
|
|
|
7
7
|
// Color Tokens
|
|
8
8
|
// ============================================================================
|
|
9
9
|
|
|
10
|
+
// Aligned with mandujs.com brand tokens (app/globals.css + styles/tokens.css):
|
|
11
|
+
// primary peach #FF8C66, secondary dark-brown #4A3222, warm-brown dark surfaces
|
|
12
|
+
// (#1F1B16/#2A2520/#3E3028), cream text, and the docs-grade semantic palette.
|
|
10
13
|
export const colors = {
|
|
11
14
|
brand: {
|
|
12
|
-
primary: '#
|
|
13
|
-
secondary: '#
|
|
14
|
-
accent: '#
|
|
15
|
+
primary: '#F5EDE0', // 만두피 크림 (mandujs cream surface)
|
|
16
|
+
secondary: '#4A3222', // 구운 다크 브라운 (mandujs --color-secondary)
|
|
17
|
+
accent: '#FF8C66', // 만두 시그니처 피치 오렌지 (mandujs --color-primary)
|
|
15
18
|
},
|
|
16
19
|
semantic: {
|
|
17
|
-
success: '#
|
|
18
|
-
warning: '#
|
|
19
|
-
error: '#
|
|
20
|
-
info: '#
|
|
20
|
+
success: '#6B9E47', // 올리브 그린 (mandujs --color-success)
|
|
21
|
+
warning: '#E8A93A', // 웜 앰버 (mandujs --color-warn)
|
|
22
|
+
error: '#C85450', // 머티드 레드-브라운 (mandujs --color-danger)
|
|
23
|
+
info: '#4A90C2', // 차분한 블루 (mandujs --color-info)
|
|
21
24
|
},
|
|
22
25
|
background: {
|
|
23
|
-
dark: '#
|
|
24
|
-
medium: '#
|
|
25
|
-
light: '#
|
|
26
|
-
overlay: 'rgba(
|
|
26
|
+
dark: '#1F1B16', // 딥 브라운 (mandujs --color-background-dark)
|
|
27
|
+
medium: '#2A2520', // 웜 서피스 (mandujs --color-surface-dark)
|
|
28
|
+
light: '#3E3028', // 웜 라이트 서피스 (mandujs --color-dark-surface)
|
|
29
|
+
overlay: 'rgba(20, 14, 8, 0.88)',
|
|
27
30
|
},
|
|
28
31
|
text: {
|
|
29
|
-
primary: '#
|
|
30
|
-
secondary: '#
|
|
31
|
-
muted: '#
|
|
32
|
+
primary: '#FFF0E6', // 웜 크림 화이트 (mandujs --color-dark-text)
|
|
33
|
+
secondary: '#C9BCAD', // 웜 베이지 그레이
|
|
34
|
+
muted: '#7A6B5D', // 웜 뮤트 브라운 (mandujs --color-dark-muted)
|
|
32
35
|
},
|
|
33
36
|
} as const;
|
|
34
37
|
|
|
@@ -38,8 +41,9 @@ export const colors = {
|
|
|
38
41
|
|
|
39
42
|
export const typography = {
|
|
40
43
|
fontFamily: {
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
// Aligned with mandujs.com (app/globals.css --font-mono / --font-sans).
|
|
45
|
+
mono: "'Consolas', 'Monaco', 'Ubuntu Mono', 'JetBrains Mono', ui-monospace, monospace",
|
|
46
|
+
sans: "'Pretendard Variable', 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
|
43
47
|
},
|
|
44
48
|
fontSize: {
|
|
45
49
|
xs: '10px',
|
|
@@ -99,11 +103,12 @@ export const borderWidth = {
|
|
|
99
103
|
// ============================================================================
|
|
100
104
|
|
|
101
105
|
export const shadows = {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
// Warm-tinted shadows to match the brown-toned dark surfaces.
|
|
107
|
+
sm: '0 1px 3px rgba(20, 12, 4, 0.14)',
|
|
108
|
+
md: '0 4px 8px rgba(20, 12, 4, 0.20)',
|
|
109
|
+
lg: '0 10px 20px rgba(20, 12, 4, 0.26)',
|
|
110
|
+
xl: '0 20px 30px rgba(20, 12, 4, 0.34)',
|
|
111
|
+
overlay: '0 25px 50px rgba(20, 12, 4, 0.5)',
|
|
107
112
|
} as const;
|
|
108
113
|
|
|
109
114
|
// ============================================================================
|