@mandujs/mcp 0.37.1 โ†’ 0.37.3

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/README.md CHANGED
@@ -136,10 +136,9 @@ bunx @mandujs/mcp --root /path/to/project
136
136
 
137
137
  ### Semantic Slots (RFC-001) ๐Ÿ†•
138
138
 
139
- | Tool | Description |
140
- |------|-------------|
141
- | `mandu_validate_slot` | Validate slot against constraints |
142
- | `mandu_validate_slots` | Batch validate multiple slots |
139
+ | Tool | Description |
140
+ |------|-------------|
141
+ | `mandu_get_slot_constraints` | Get recommended slot constraint presets |
143
142
 
144
143
  ### Architecture Negotiation (RFC-001) ๐Ÿ†•
145
144
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/mcp",
3
- "version": "0.37.1",
3
+ "version": "0.37.3",
4
4
  "description": "Mandu MCP Server - Agent-native interface for Mandu framework operations",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -34,9 +34,9 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@mandujs/core": "^0.54.1",
38
- "@mandujs/ate": "^0.26.0",
39
- "@mandujs/skills": "^0.20.0",
37
+ "@mandujs/core": "^0.54.3",
38
+ "@mandujs/ate": "^0.26.1",
39
+ "@mandujs/skills": "^0.20.1",
40
40
  "@modelcontextprotocol/sdk": "^1.25.3"
41
41
  },
42
42
  "engines": {
package/src/prompts.ts CHANGED
@@ -48,10 +48,10 @@ Follow these steps using Mandu MCP tools:
48
48
  1. Read current route manifest: Resource mandu://routes
49
49
  2. Read project config: Resource mandu://config
50
50
  3. Negotiate the feature spec: Tool mandu.negotiate
51
- 4. Generate scaffold: Tool mandu.negotiate.scaffold
52
- 5. If client interactivity needed, create island: Tool mandu_create_island
53
- Use the declarative pattern: island('visible', Component)
54
- 6. If data requirements exist, create slot: Tool mandu_create_slot
51
+ 4. Generate scaffold: Tool mandu.negotiate.scaffold
52
+ 5. If client interactivity needed, create island: Tool mandu_create_island
53
+ Use @mandujs/core/client with wrapComponent(Component) or island({ setup, render })
54
+ 6. If data requirements exist, create slot: Tool mandu_create_slot
55
55
  Slots are server-side data loaders that run before render
56
56
  7. If API is exposed, define contract: Tool mandu_create_contract
57
57
  Contracts are Zod schemas for validation and OpenAPI generation
@@ -377,21 +377,21 @@ Island Hydration์€ ํŽ˜์ด์ง€์˜ ์ผ๋ถ€๋ถ„๋งŒ ํด๋ผ์ด์–ธํŠธ์—์„œ ์ธํ„ฐ๋ž™ํ‹ฐ
377
377
  | \`idle\` | ๋ธŒ๋ผ์šฐ์ € ์œ ํœด ์‹œ | ๋น„์ค‘์š” ๊ธฐ๋Šฅ |
378
378
  | \`interaction\` | ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ ์‹œ | ํด๋ฆญํ•ด์•ผ ํ™œ์„ฑํ™” |
379
379
 
380
- ## Island ๋งŒ๋“ค๊ธฐ
381
-
382
- ### 1. ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ์ž‘์„ฑ
383
-
384
- \`\`\`tsx
385
- // app/counter/client.tsx
386
-
387
- "use client";
388
-
389
- import { useState } from "react";
390
-
391
- export default function Counter({ initial = 0 }: { initial?: number }) {
392
- const [count, setCount] = useState(initial);
393
-
394
- return (
380
+ ## Inline client region ๋งŒ๋“ค๊ธฐ
381
+
382
+ ### 1. ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ์ž‘์„ฑ
383
+
384
+ \`\`\`tsx
385
+ // app/counter/client.tsx
386
+
387
+ "use client";
388
+
389
+ import { useState } from "react";
390
+
391
+ export function Counter({ initial = 0 }: { initial?: number }) {
392
+ const [count, setCount] = useState(initial);
393
+
394
+ return (
395
395
  <div>
396
396
  <p>Count: {count}</p>
397
397
  <button onClick={() => setCount(c => c - 1)}>-</button>
@@ -399,34 +399,43 @@ export default function Counter({ initial = 0 }: { initial?: number }) {
399
399
  </div>
400
400
  );
401
401
  }
402
- \`\`\`
403
-
404
- ### 2. ํŽ˜์ด์ง€์—์„œ ์‚ฌ์šฉ
405
-
406
- \`\`\`tsx
407
- // app/counter/page.tsx
408
-
409
- import Counter from "./client";
410
-
411
- export default function CounterPage() {
412
- return (
402
+ \`\`\`
403
+
404
+ ### 2. ์„œ๋ฒ„ ํŽ˜์ด์ง€์—์„œ partial๋กœ ์‚ฌ์šฉ
405
+
406
+ \`\`\`tsx
407
+ // app/counter/page.tsx
408
+
409
+ import { partial } from "@mandujs/core/client";
410
+ import { Counter } from "./client";
411
+
412
+ const CounterPartial = partial({
413
+ component: Counter,
414
+ priority: "visible",
415
+ });
416
+
417
+ export default function CounterPage() {
418
+ return (
413
419
  <div>
414
420
  <h1>Counter Demo</h1>
415
421
  <p>์ด ํ…์ŠคํŠธ๋Š” ์ •์  HTML์ž…๋‹ˆ๋‹ค.</p>
416
-
417
- {/* ์ด ๋ถ€๋ถ„๋งŒ hydration๋ฉ๋‹ˆ๋‹ค */}
418
- <Counter initial={10} />
419
- </div>
420
- );
421
- }
422
- \`\`\`
423
-
424
- ## Mandu.island() API
425
-
426
- ๊ณ ๊ธ‰ Island ํŒจํ„ด์„ ์œ„ํ•œ API:
427
-
428
- \`\`\`typescript
429
- // spec/slots/todos.client.ts
422
+
423
+ {/* ์ด ๋ถ€๋ถ„๋งŒ hydration๋ฉ๋‹ˆ๋‹ค */}
424
+ <CounterPartial.Render initial={10} />
425
+ </div>
426
+ );
427
+ }
428
+ \`\`\`
429
+
430
+ ## Mandu.island() API
431
+
432
+ ๊ณ ๊ธ‰ page-level Island ํŒจํ„ด์„ ์œ„ํ•œ API์ž…๋‹ˆ๋‹ค. \`Mandu.island()\`๋Š” ๋‹จ์ผ
433
+ ์ •์˜ ๊ฐ์ฒด๋งŒ ๋ฐ›์Šต๋‹ˆ๋‹ค. \`island("visible", Component)\` ํ˜•ํƒœ๋Š” ์ง€์›ํ•˜์ง€
434
+ ์•Š์œผ๋ฉฐ, ์„œ๋ฒ„ ํŽ˜์ด์ง€ ์•ˆ์— inline์œผ๋กœ ๋ Œ๋”๋งํ•  ์˜์—ญ์€ \`partial()\`์„
435
+ ์‚ฌ์šฉํ•˜์„ธ์š”.
436
+
437
+ \`\`\`typescript
438
+ // spec/slots/todos.client.ts
430
439
 
431
440
  import { Mandu } from "@mandujs/core/client";
432
441
  import { useState, useCallback } from "react";
@@ -138,8 +138,8 @@ export function createServerSupabase() {
138
138
  Mandu ์˜ client island ์—์„œ Realtime ๊ตฌ๋…:
139
139
 
140
140
  ```tsx
141
- // app/messages/MessagesIsland.client.tsx
142
- import { island } from "@mandujs/core/client";
141
+ // app/messages/MessagesIsland.client.tsx
142
+ import { wrapComponent } from "@mandujs/core/client";
143
143
  import { useEffect, useState } from "react";
144
144
  import { supabase } from "@/lib/supabase";
145
145
 
@@ -174,7 +174,7 @@ function Messages() {
174
174
  );
175
175
  }
176
176
 
177
- export default island("visible", <Messages />);
177
+ export default wrapComponent(Messages);
178
178
  ```
179
179
 
180
180
  ### 3.3 Storage
@@ -57,17 +57,25 @@ Reference these guidelines when:
57
57
  - `hydration-data-server` - Access server data with useServerData
58
58
  - `hydration-data-event` - Communicate between Islands with useIslandEvent
59
59
 
60
- ## Hydration Strategies
61
-
62
- | Strategy | Description | Use Case |
63
- |----------|-------------|----------|
64
- | `none` | No JavaScript | Pure static pages |
65
- | `island` | Partial hydration (default) | Static + interactive mix |
66
- | `full` | Full hydration | SPA-style pages |
67
-
68
- ## Client Hooks
69
-
70
- ```typescript
60
+ ## Hydration Strategies
61
+
62
+ | Strategy | Description | Use Case |
63
+ |----------|-------------|----------|
64
+ | `none` | No JavaScript | Pure static pages |
65
+ | `island` | Partial hydration (default) | Static + interactive mix |
66
+ | `full` | Full hydration | SPA-style pages |
67
+
68
+ ## Runtime API Constraints
69
+
70
+ - `island()` / `Mandu.island()` takes one definition object: `island({ setup, render })`.
71
+ - Do not call `island("visible", Component)`; use `wrapComponent(Component)` for a simple page-level island wrapper.
72
+ - Islands are page-level client bundles. Do not render them as inline JSX like `<MyIsland />`.
73
+ - For an embedded interactive region inside a server page, use `partial()`: put it in `*.partial.tsx`, export `partial({ id, component })`, and render the returned `.Render` component from the server page.
74
+ - A server page that renders partials must opt into hydration, for example `export const hydration = { strategy: "island", priority: "visible", preload: false }`.
75
+
76
+ ## Client Hooks
77
+
78
+ ```typescript
71
79
  import {
72
80
  useServerData,
73
81
  useHydrated,
@@ -5,13 +5,60 @@ impactDescription: Proper Island structure
5
5
  tags: hydration, island, setup
6
6
  ---
7
7
 
8
- ## Use Mandu.island() with Setup Function
9
-
10
- For complex Islands, use `Mandu.island()` API to separate state logic from rendering.
11
-
12
- **Incorrect (mixed concerns):**
13
-
14
- ```tsx
8
+ ## Use Mandu.island() with Setup Function
9
+
10
+ For complex Islands, use `Mandu.island()` API to separate state logic from rendering.
11
+ `Mandu.island()` takes a single definition object. The shorthand
12
+ `island("visible", Component)` is not a supported Mandu runtime API.
13
+
14
+ Islands are page-level client bundles. They are discovered and rendered by
15
+ the framework wrapper, not embedded directly as inline JSX. Use `partial()`
16
+ when a server page needs an inline interactive region.
17
+
18
+ ```tsx
19
+ // Simple page-level island wrapper
20
+ "use client";
21
+
22
+ import { wrapComponent } from "@mandujs/core/client";
23
+
24
+ function Counter() {
25
+ return <button>Count</button>;
26
+ }
27
+
28
+ export default wrapComponent(Counter);
29
+ ```
30
+
31
+ ```tsx
32
+ // Inline client region inside a server-rendered page
33
+ // app/Counter.partial.tsx
34
+ import { partial } from "@mandujs/core/client";
35
+ import { Counter } from "./counter.client";
36
+
37
+ export default partial({
38
+ id: "Counter",
39
+ component: Counter,
40
+ priority: "visible",
41
+ });
42
+ ```
43
+
44
+ ```tsx
45
+ // app/page.tsx
46
+ import CounterPartial from "./Counter.partial";
47
+
48
+ export const hydration = {
49
+ strategy: "island",
50
+ priority: "visible",
51
+ preload: false,
52
+ };
53
+
54
+ export default function Page() {
55
+ return <CounterPartial.Render initial={0} />;
56
+ }
57
+ ```
58
+
59
+ **Incorrect (mixed concerns):**
60
+
61
+ ```tsx
15
62
  "use client";
16
63
 
17
64
  export default function TodoList({ initialTodos }) {
@@ -18,43 +18,66 @@ Set hydration priority based on when the component needs to be interactive.
18
18
  | `idle` | Browser idle | Non-critical features (analytics widgets) |
19
19
  | `interaction` | User action | Click-to-activate components |
20
20
 
21
- ## Examples
22
-
23
- ### Immediate: Always-visible interactions
24
-
25
- ```tsx
26
- // Header with navigation - needs to work immediately
27
- <Island priority="immediate">
28
- <HeaderNav />
29
- </Island>
30
- ```
31
-
32
- ### Visible: Below-fold content (default)
33
-
34
- ```tsx
35
- // Comments section - load when scrolled into view
36
- <Island priority="visible">
37
- <CommentsSection postId={postId} />
38
- </Island>
39
- ```
40
-
41
- ### Idle: Background features
42
-
43
- ```tsx
44
- // Chat widget - can wait until browser is idle
45
- <Island priority="idle">
46
- <ChatWidget />
47
- </Island>
48
- ```
49
-
50
- ### Interaction: On-demand activation
51
-
52
- ```tsx
53
- // Video player - only hydrate when user clicks play
54
- <Island priority="interaction">
55
- <VideoPlayer videoId={videoId} />
56
- </Island>
57
- ```
21
+ ## Examples
22
+
23
+ Inline client regions live in `*.partial.tsx` files and use
24
+ `partial({ id, component, priority })`, then render the returned `.Render`
25
+ component from the server page. The page must export a non-`none` hydration
26
+ config.
27
+
28
+ ### Immediate: Always-visible interactions
29
+
30
+ ```tsx
31
+ // Header with navigation - needs to work immediately
32
+ import { partial } from "@mandujs/core/client";
33
+
34
+ const HeaderNavPartial = partial({
35
+ id: "HeaderNav",
36
+ component: HeaderNav,
37
+ priority: "immediate",
38
+ });
39
+
40
+ <HeaderNavPartial.Render />
41
+ ```
42
+
43
+ ### Visible: Below-fold content (default)
44
+
45
+ ```tsx
46
+ // Comments section - load when scrolled into view
47
+ const CommentsPartial = partial({
48
+ id: "Comments",
49
+ component: CommentsSection,
50
+ priority: "visible",
51
+ });
52
+
53
+ <CommentsPartial.Render postId={postId} />
54
+ ```
55
+
56
+ ### Idle: Background features
57
+
58
+ ```tsx
59
+ // Chat widget - can wait until browser is idle
60
+ const ChatPartial = partial({
61
+ id: "Chat",
62
+ component: ChatWidget,
63
+ priority: "idle",
64
+ });
65
+
66
+ <ChatPartial.Render />
67
+ ```
68
+
69
+ ### Interaction: On-demand activation
70
+
71
+ ```tsx
72
+ // Video player - only hydrate when user clicks play
73
+ const VideoPartial = partial({
74
+ id: "Video",
75
+ component: VideoPlayer,
76
+ priority: "interaction",
77
+ });
78
+
79
+ <VideoPartial.Render videoId={videoId} />
80
+ ```
58
81
 
59
82
  ## Performance Impact
60
83
 
@@ -469,24 +469,30 @@ export default function Counter({ initial = 0, step = 1 }: CounterProps) {
469
469
  }
470
470
  \`\`\`
471
471
 
472
- ## Step 2: ํŽ˜์ด์ง€์—์„œ ์‚ฌ์šฉ
473
-
474
- \`\`\`tsx
475
- // app/counter/page.tsx
476
-
477
- import Counter from "./client";
478
-
479
- export default function CounterPage() {
480
- return (
472
+ ## Step 2: ์„œ๋ฒ„ ํŽ˜์ด์ง€์—์„œ partial๋กœ ์‚ฌ์šฉ
473
+
474
+ \`\`\`tsx
475
+ // app/counter/page.tsx
476
+
477
+ import { partial } from "@mandujs/core/client";
478
+ import Counter from "./client";
479
+
480
+ const CounterPartial = partial({
481
+ component: Counter,
482
+ priority: "visible",
483
+ });
484
+
485
+ export default function CounterPage() {
486
+ return (
481
487
  <div style={{ padding: "20px" }}>
482
488
  <h1>Counter Demo</h1>
483
489
  <p>์•„๋ž˜ ์นด์šดํ„ฐ๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ hydration๋ฉ๋‹ˆ๋‹ค.</p>
484
-
485
- {/* Island ์ปดํฌ๋„ŒํŠธ */}
486
- <Counter initial={10} step={5} />
487
-
488
- <p style={{ marginTop: "20px", color: "#666" }}>
489
- ์ด ํ…์ŠคํŠธ๋Š” ์ •์  HTML์ž…๋‹ˆ๋‹ค.
490
+
491
+ {/* Inline client region */}
492
+ <CounterPartial.Render initial={10} step={5} />
493
+
494
+ <p style={{ marginTop: "20px", color: "#666" }}>
495
+ ์ด ํ…์ŠคํŠธ๋Š” ์ •์  HTML์ž…๋‹ˆ๋‹ค.
490
496
  </p>
491
497
  </div>
492
498
  );
@@ -618,10 +624,13 @@ export default function UserList() {
618
624
 
619
625
  ## Mandu.island() API (๊ณ ๊ธ‰)
620
626
 
621
- ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ์™€ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ๋ฅผ ๋ถ„๋ฆฌํ•˜๋ ค๋ฉด:
622
-
623
- \`\`\`typescript
624
- // spec/slots/todos.client.ts
627
+ ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ์™€ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” page-level Island๊ฐ€ ํ•„์š”ํ•˜๋ฉด
628
+ \`Mandu.island({ setup, render })\`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. \`island("visible",
629
+ Component)\`๋Š” ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„ ํŽ˜์ด์ง€ ์•ˆ์— inline์œผ๋กœ ๋ Œ๋”๋งํ• 
630
+ ์˜์—ญ์€ \`partial()\`์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
631
+
632
+ \`\`\`typescript
633
+ // spec/slots/todos.client.ts
625
634
 
626
635
  import { Mandu } from "@mandujs/core/client";
627
636
  import { useState } from "react";
package/src/tools/ate.ts CHANGED
@@ -105,7 +105,11 @@ export const ateToolDefinitions: Tool[] = [
105
105
  type: "string",
106
106
  description: "Dev server URL (default: http://localhost:3333). Must match the running mandu dev server.",
107
107
  },
108
- ci: { type: "boolean", description: "CI mode: stricter timeouts, no interactive prompts" },
108
+ ci: { type: "boolean", description: "CI mode: stricter timeouts, no interactive prompts" },
109
+ timeoutMs: {
110
+ type: "number",
111
+ description: "Hard timeout for the whole Playwright process in milliseconds (default: 600000).",
112
+ },
109
113
  headless: { type: "boolean", description: "Run browsers headlessly (default: true)" },
110
114
  browsers: {
111
115
  type: "array",
@@ -260,7 +264,11 @@ export const ateToolDefinitions: Tool[] = [
260
264
  enum: ["L0", "L1", "L2", "L3"],
261
265
  description: "Assertion depth: L0=smoke, L1=HTTP status, L2=contract schema, L3=full behavioral",
262
266
  },
263
- ci: { type: "boolean", description: "CI mode: stricter timeouts" },
267
+ ci: { type: "boolean", description: "CI mode: stricter timeouts" },
268
+ timeoutMs: {
269
+ type: "number",
270
+ description: "Hard timeout for the whole Playwright process in milliseconds (default: 600000).",
271
+ },
264
272
  useImpactAnalysis: {
265
273
  type: "boolean",
266
274
  description: "Run impact analysis first and only test changed routes (faster in CI)",
@@ -378,7 +386,7 @@ export function ateTools(projectRoot: string, server?: Server) {
378
386
  return await ateRun(input);
379
387
  } catch (err) {
380
388
  const message = err instanceof Error ? err.message : String(err);
381
- const isTimeout = /timed out/i.test(message);
389
+ const isTimeout = /timeout|timed out|ํƒ€์ž„์•„์›ƒ/i.test(message);
382
390
  const snap = tracker.snapshot();
383
391
  const partial: PartialRunResults = {
384
392
  runId: snap.runId ?? `unknown-${Date.now()}`,
@@ -439,19 +447,21 @@ export function ateTools(projectRoot: string, server?: Server) {
439
447
  browsers,
440
448
  onlyFiles,
441
449
  onlyRoutes,
442
- grep,
443
- progressToken,
444
- } = args as {
450
+ grep,
451
+ timeoutMs,
452
+ progressToken,
453
+ } = args as {
445
454
  repoRoot: string;
446
455
  baseURL?: string;
447
456
  ci?: boolean;
448
457
  headless?: boolean;
449
458
  browsers?: ("chromium" | "firefox" | "webkit")[];
450
459
  onlyFiles?: string[];
451
- onlyRoutes?: string[];
452
- grep?: string;
453
- progressToken?: string | number;
454
- };
460
+ onlyRoutes?: string[];
461
+ grep?: string;
462
+ timeoutMs?: number;
463
+ progressToken?: string | number;
464
+ };
455
465
  return await runWithObservability(
456
466
  {
457
467
  repoRoot,
@@ -459,10 +469,11 @@ export function ateTools(projectRoot: string, server?: Server) {
459
469
  ci,
460
470
  headless,
461
471
  browsers,
462
- onlyFiles,
463
- onlyRoutes,
464
- grep,
465
- },
472
+ onlyFiles,
473
+ onlyRoutes,
474
+ grep,
475
+ timeoutMs,
476
+ },
466
477
  { progressToken },
467
478
  );
468
479
  },
@@ -518,9 +529,10 @@ export function ateTools(projectRoot: string, server?: Server) {
518
529
  },
519
530
  "mandu.ate.auto_pipeline": async (args: Record<string, unknown>) => {
520
531
  const {
521
- repoRoot, baseURL, oracleLevel, ci, useImpactAnalysis,
522
- base, head, autoHeal, tsconfigPath, routeGlobs, buildSalt,
523
- } = args as {
532
+ repoRoot, baseURL, oracleLevel, ci, useImpactAnalysis,
533
+ base, head, autoHeal, tsconfigPath, routeGlobs, buildSalt,
534
+ timeoutMs,
535
+ } = args as {
524
536
  repoRoot: string;
525
537
  baseURL?: string;
526
538
  oracleLevel?: OracleLevel;
@@ -530,13 +542,14 @@ export function ateTools(projectRoot: string, server?: Server) {
530
542
  head?: string;
531
543
  autoHeal?: boolean;
532
544
  tsconfigPath?: string;
533
- routeGlobs?: string[];
534
- buildSalt?: string;
535
- };
536
- return await runFullPipeline({
537
- repoRoot, baseURL, oracleLevel, ci, useImpactAnalysis,
538
- base, head, autoHeal, tsconfigPath, routeGlobs, buildSalt,
539
- });
545
+ routeGlobs?: string[];
546
+ buildSalt?: string;
547
+ timeoutMs?: number;
548
+ };
549
+ return await runFullPipeline({
550
+ repoRoot, baseURL, oracleLevel, ci, useImpactAnalysis,
551
+ base, head, autoHeal, tsconfigPath, routeGlobs, buildSalt, timeoutMs,
552
+ });
540
553
  },
541
554
  "mandu.ate.feedback": async (args: Record<string, unknown>) => {
542
555
  const { repoRoot, runId, autoApply } = args as {
@@ -60,10 +60,10 @@ export const compositeToolDefinitions: Tool[] = [
60
60
  },
61
61
  },
62
62
  {
63
- name: "mandu.island.add",
64
- description:
65
- "Create an island component with correct @mandujs/core/client imports and hydration strategy. " +
66
- "Generates a .island.tsx file in app/{route}/ with the island() wrapper.",
63
+ name: "mandu.island.add",
64
+ description:
65
+ "Create an island component with correct @mandujs/core/client imports and hydration strategy. " +
66
+ "Generates a .island.tsx file in app/{route}/ with the wrapComponent() island definition.",
67
67
  annotations: {
68
68
  destructiveHint: true,
69
69
  readOnlyHint: false,
@@ -474,10 +474,10 @@ function generateMiddlewareSource(preset: string, options: Record<string, string
474
474
  return templates[preset] ?? templates.default;
475
475
  }
476
476
 
477
- function generateIslandSource(name: string, strategy: string): string {
478
- return `"use client";
479
- import { island } from "@mandujs/core/client";
480
- import { useState } from "react";
477
+ function generateIslandSource(name: string, strategy: string): string {
478
+ return `"use client";
479
+ import { wrapComponent } from "@mandujs/core/client";
480
+ import { useState } from "react";
481
481
 
482
482
  interface ${name}Props {
483
483
  [key: string]: unknown;
@@ -493,6 +493,8 @@ function ${name}Inner(props: ${name}Props) {
493
493
  );
494
494
  }
495
495
 
496
- export default island("${strategy}", ${name}Inner);
497
- `;
498
- }
496
+ // Route hydration priority should be set on the page/manifest route.
497
+ // Suggested priority for this island: "${strategy}".
498
+ export default wrapComponent(${name}Inner);
499
+ `;
500
+ }