@mandujs/core 0.54.7 → 0.54.8

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/core",
3
- "version": "0.54.7",
3
+ "version": "0.54.8",
4
4
  "description": "Mandu Framework Core - Spec, Generator, Guard, Runtime",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -183,28 +183,28 @@ export function IslandsPanel({ islands }: IslandsPanelProps): React.ReactElement
183
183
  );
184
184
  }, [islands]);
185
185
 
186
- if (islands.length === 0) {
187
- return (
188
- <div style={styles.container}>
189
- <div style={styles.emptyState}>
190
- <p>
191
- 아직 등록된 Island가 없습니다.<br />
192
- Island 컴포넌트를 사용하면 여기에 상태가 표시됩니다.
193
- </p>
194
- </div>
195
- </div>
196
- );
197
- }
186
+ if (islands.length === 0) {
187
+ return (
188
+ <div style={styles.container}>
189
+ <div style={styles.emptyState}>
190
+ <p>
191
+ 아직 hydration 이벤트가 없습니다.<br />
192
+ 정적 상태는 mandu.runtime.status에서 확인하세요.
193
+ </p>
194
+ </div>
195
+ </div>
196
+ );
197
+ }
198
198
 
199
199
  return (
200
200
  <div style={styles.container}>
201
201
  {/* Header Stats */}
202
202
  <div style={styles.header}>
203
203
  <div style={styles.stats}>
204
- <div style={styles.stat}>
205
- <span>전체:</span>
206
- <span style={styles.statValue}>{stats.total}</span>
207
- </div>
204
+ <div style={styles.stat}>
205
+ <span>Mounts:</span>
206
+ <span style={styles.statValue}>{stats.total}</span>
207
+ </div>
208
208
  <div style={styles.stat}>
209
209
  <span>Ready</span>
210
210
  <span style={styles.statValue}>{stats.hydrated}</span>
@@ -28,7 +28,7 @@ export interface PanelContainerProps {
28
28
 
29
29
  export const TABS: TabDefinition[] = [
30
30
  { id: 'errors', label: 'Issues', icon: 'ERR', testId: testIds.tabErrors },
31
- { id: 'islands', label: 'Islands', icon: 'ISL', testId: testIds.tabIslands },
31
+ { id: 'islands', label: 'Client Mounts', icon: 'HYD', testId: testIds.tabIslands },
32
32
  { id: 'network', label: 'Network', icon: 'NET', testId: testIds.tabNetwork },
33
33
  { id: 'guard', label: 'Guard', icon: 'GRD', testId: testIds.tabGuard },
34
34
  { id: 'preview', label: 'Changes', icon: 'CHG', testId: testIds.tabPreview },
@@ -0,0 +1,34 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { RouteSpec } from "../spec/schema";
3
+ import { generatePageComponent } from "./templates";
4
+
5
+ describe("generatePageComponent", () => {
6
+ test("refuses to emit a placeholder for hydrating routes without a clientModule", () => {
7
+ const route: RouteSpec = {
8
+ id: "login",
9
+ kind: "page",
10
+ pattern: "/login",
11
+ module: "app/login/page.tsx",
12
+ componentModule: "app/login/page.tsx",
13
+ hydration: {
14
+ strategy: "full",
15
+ priority: "immediate",
16
+ preload: false,
17
+ },
18
+ };
19
+
20
+ expect(() => generatePageComponent(route)).toThrow("no clientModule");
21
+ });
22
+
23
+ test("still emits placeholders for static routes without hydration", () => {
24
+ const route: RouteSpec = {
25
+ id: "about",
26
+ kind: "page",
27
+ pattern: "/about",
28
+ module: "app/about/page.tsx",
29
+ componentModule: "app/about/page.tsx",
30
+ };
31
+
32
+ expect(generatePageComponent(route)).toContain("About Page");
33
+ });
34
+ });
@@ -1,4 +1,4 @@
1
- import type { RouteSpec } from "../spec/schema";
1
+ import { needsHydration, type RouteSpec } from "../spec/schema";
2
2
  import { GENERATED_RELATIVE_PATHS } from "../paths";
3
3
 
4
4
  export function generateApiHandler(route: RouteSpec): string {
@@ -238,14 +238,21 @@ function computeSlotImportPath(slotModule: string, fromDir: string): string {
238
238
  return result;
239
239
  }
240
240
 
241
- export function generatePageComponent(route: RouteSpec): string {
242
- // Island-First: clientModule이 있으면 Island render를 SSR에서 직접 사용
243
- if (route.clientModule) {
244
- return generatePageComponentWithIsland(route);
245
- }
246
-
247
- // slotModule이 있으면 PageHandler 형식으로 생성 (filling 포함)
248
- if (route.slotModule) {
241
+ export function generatePageComponent(route: RouteSpec): string {
242
+ // Island-First: clientModule이 있으면 Island render를 SSR에서 직접 사용
243
+ if (route.clientModule) {
244
+ return generatePageComponentWithIsland(route);
245
+ }
246
+
247
+ if (needsHydration(route)) {
248
+ throw new Error(
249
+ `[${route.id}] Route has hydration strategy "${route.hydration?.strategy}" but no clientModule. ` +
250
+ "Refusing to generate a placeholder page because it would disagree with runtime hydration state.",
251
+ );
252
+ }
253
+
254
+ // slotModule이 있으면 PageHandler 형식으로 생성 (filling 포함)
255
+ if (route.slotModule) {
249
256
  return generatePageHandlerWithSlot(route);
250
257
  }
251
258