what-devtools-mcp 0.6.0

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/src/index.js ADDED
@@ -0,0 +1,637 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * what-devtools-mcp — MCP server entry point.
4
+ * Creates WS bridge, registers tools + resources, connects MCP stdio transport.
5
+ */
6
+
7
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
+ import { createBridge } from './bridge.js';
10
+ import { registerTools } from './tools.js';
11
+
12
+ const port = parseInt(process.env.WHAT_MCP_PORT || '9229', 10);
13
+
14
+ const bridge = createBridge({ port });
15
+
16
+ const server = new McpServer({
17
+ name: 'what-devtools-mcp',
18
+ version: '0.2.0',
19
+ });
20
+
21
+ registerTools(server, bridge);
22
+
23
+ // --- MCP Resources: static docs for agent context ---
24
+
25
+ server.resource(
26
+ 'reactivity-model',
27
+ 'what://docs/reactivity-model',
28
+ { description: 'How What Framework reactivity works — signals, effects, computed, batch' },
29
+ async () => ({
30
+ contents: [{
31
+ uri: 'what://docs/reactivity-model',
32
+ mimeType: 'text/markdown',
33
+ text: `# What Framework Reactivity Model
34
+
35
+ ## Signals
36
+ A signal is a reactive value. Read with \`sig()\`, write with \`sig(newValue)\` or \`sig(prev => next)\`.
37
+ Signals track which effects read them and notify those effects when they change.
38
+
39
+ \`\`\`js
40
+ const count = signal(0, 'count'); // second arg is optional debug name
41
+ count() // read (returns 0)
42
+ count(5) // write (sets to 5, notifies subscribers)
43
+ count.peek() // read without tracking (no effect subscription)
44
+ \`\`\`
45
+
46
+ ## Effects
47
+ An effect runs a function and auto-tracks which signals it reads. When any tracked signal changes, the effect re-runs.
48
+
49
+ \`\`\`js
50
+ effect(() => {
51
+ console.log('Count is:', count()); // auto-tracks count
52
+ });
53
+ \`\`\`
54
+
55
+ Effects flush asynchronously via microtask, NOT synchronously.
56
+
57
+ ## Computed
58
+ Derived signal. Lazy — only recomputes when deps change AND it's read.
59
+
60
+ \`\`\`js
61
+ const doubled = computed(() => count() * 2);
62
+ \`\`\`
63
+
64
+ ## Batch
65
+ Group signal writes; effects run once at the end.
66
+
67
+ \`\`\`js
68
+ batch(() => {
69
+ name('Alice');
70
+ age(30);
71
+ // effects that read name or age run once after batch, not twice
72
+ });
73
+ \`\`\`
74
+
75
+ ## Common Bugs
76
+ - **Signal read in event handler**: Event handlers are wrapped in untrack(). Reading a signal in onclick doesn't create a subscription.
77
+ - **Effect writes to signal it reads**: Creates an infinite loop. Use untrack() to read without subscribing.
78
+ - **Stale closure**: Effect function captures old value. Read the signal inside the effect, not outside.
79
+ `,
80
+ }],
81
+ })
82
+ );
83
+
84
+ server.resource(
85
+ 'debugging-guide',
86
+ 'what://docs/debugging-guide',
87
+ { description: 'How to debug What Framework apps using the MCP devtools' },
88
+ async () => ({
89
+ contents: [{
90
+ uri: 'what://docs/debugging-guide',
91
+ mimeType: 'text/markdown',
92
+ text: `# Debugging What Framework Apps
93
+
94
+ ## Step 1: Check connection
95
+ Call \`what_connection_status\` to verify the app is connected.
96
+
97
+ ## Step 2: Get the lay of the land
98
+ Call \`what_diagnose\` for a comprehensive health check, or \`what_snapshot\` for raw data.
99
+
100
+ ## Step 3: Investigate specific issues
101
+
102
+ ### "UI isn't updating"
103
+ 1. \`what_signals { filter: "relevant_name" }\` — is the signal value what you expect?
104
+ 2. \`what_watch { duration: 5000 }\` — ask user to trigger the action. Do signal:updated events appear?
105
+ 3. If no updates: the event handler isn't calling sig(newValue). Check the source code.
106
+ 4. If updates appear but UI doesn't change: the component isn't reading the signal reactively. Check for stale closures or peek() usage.
107
+
108
+ ### "Infinite re-render / effect loop"
109
+ 1. \`what_effects { minRunCount: 50 }\` — find effects with high run counts
110
+ 2. \`what_dependency_graph { effectId: N }\` — see what signals this effect reads and writes
111
+ 3. If an effect reads and writes the same signal: use untrack() for the read
112
+
113
+ ### "Slow performance"
114
+ 1. \`what_diagnose { focus: "performance" }\` — identify hot effects
115
+ 2. \`what_effects { minRunCount: 20 }\` — find frequently-running effects
116
+ 3. Consider using batch() to group signal writes
117
+
118
+ ### "Component shows wrong data"
119
+ 1. \`what_component_tree\` — verify component hierarchy
120
+ 2. \`what_dom_inspect { componentId: N }\` — see actual rendered output
121
+ 3. \`what_signals\` — check if signal values are correct
122
+
123
+ ### "Route not working"
124
+ 1. \`what_route\` — check current path, params, matched pattern
125
+ 2. \`what_navigate { path: "/expected" }\` — test navigation programmatically
126
+ `,
127
+ }],
128
+ })
129
+ );
130
+
131
+ server.resource(
132
+ 'api-reference',
133
+ 'what://docs/api-reference',
134
+ { description: 'Complete API reference for What Framework core, hooks, components, data, stores, forms, and utilities' },
135
+ async () => ({
136
+ contents: [{
137
+ uri: 'what://docs/api-reference',
138
+ mimeType: 'text/markdown',
139
+ text: `# What Framework API Reference
140
+
141
+ ## Reactive Primitives
142
+ - \`signal(initial, debugName?)\` — create reactive value. Read: \`sig()\`. Write: \`sig(newVal)\` or \`sig(prev => next)\`.
143
+ - \`computed(fn)\` — derived value (lazy, only recomputes when deps change AND it's read)
144
+ - \`memo(fn)\` — derived value (eager, deduped — only propagates when value actually changes)
145
+ - \`effect(fn)\` — side effect, auto-tracks deps. Returns dispose function. Flushes async via microtask.
146
+ - \`batch(fn)\` — group writes, effects run once after
147
+ - \`untrack(fn)\` — read signals without subscribing
148
+ - \`flushSync()\` — force pending effects to run synchronously
149
+ - \`createRoot(fn)\` — isolated reactive scope with ownership tree
150
+ - \`getOwner()\` — get current ownership context
151
+ - \`runWithOwner(owner, fn)\` — run within a specific owner context
152
+ - \`onCleanup(fn)\` — register cleanup with current root (alias: onRootCleanup)
153
+
154
+ ## Component Hooks
155
+ - \`useState(initial)\` — React-compatible state hook (returns [getter, setter])
156
+ - \`useSignal(initial)\` — signal scoped to component
157
+ - \`useComputed(fn)\` — computed scoped to component
158
+ - \`useEffect(fn, deps?)\` — effect with optional dep array
159
+ - \`useRef(initial)\` — mutable ref that persists across renders
160
+ - \`useMemo(fn, deps)\` — memoized value
161
+ - \`useCallback(fn, deps)\` — memoized callback
162
+ - \`useContext(Context)\` — read context value
163
+ - \`useReducer(reducer, initial)\` — reducer pattern
164
+ - \`createContext(defaultValue)\` — create a context
165
+ - \`onMount(fn)\` — run once after component mounts (client-only)
166
+ - \`onCleanup(fn)\` — run when component unmounts
167
+ - \`createResource(fetcher)\` — async resource with loading/error states
168
+
169
+ ## Built-in Components
170
+ - \`<Show when={signal()} fallback={...}>\` — conditional rendering
171
+ - \`<For each={items()}>{item => ...}</For>\` — list rendering (keyed)
172
+ - \`<Switch><Match when={...}>...</Match></Switch>\` — multi-branch conditional
173
+ - \`<Suspense fallback={...}>\` — async loading boundary
174
+ - \`<ErrorBoundary fallback={...}>\` — error catching boundary
175
+ - \`<Island>\` — client-hydrated island
176
+ - \`lazy(() => import(...))\` — code-split component
177
+ - \`memo(Component)\` — memoized component (no-op in run-once model)
178
+
179
+ ## Data Fetching
180
+ - \`useSWR(key, fetcher)\` — returns \`{ data(), isLoading(), error() }\` as signal getters
181
+ - \`useQuery(key, fetcher, opts?)\` — query with caching and revalidation
182
+ - \`useFetch(url, opts?)\` — simple fetch wrapper
183
+ - \`useInfiniteQuery(key, fetcher)\` — paginated data
184
+ - \`invalidateQueries(key)\` — force refetch
185
+ - \`prefetchQuery(key, fetcher)\` — prefetch data
186
+ - \`setQueryData(key, data)\` / \`getQueryData(key)\` — manual cache manipulation
187
+ - \`clearCache()\` — clear all cached queries
188
+
189
+ ## DOM
190
+ - \`mount(vnode, selector)\` — mount app to DOM
191
+ - \`h(tag, props, ...children)\` — create virtual node (internal — use JSX)
192
+ - \`html\\\`...\\\`\` — tagged template literal for HTML
193
+ - \`Fragment\` — fragment component
194
+ - Event handlers: lowercase in h() (\`onclick\`), camelCase in JSX (\`onClick\`)
195
+
196
+ ## Stores
197
+ - \`createStore(initialState)\` — reactive store with \`{ state, set, derived }\`
198
+ - \`derived(fn)\` — derive from store state
199
+ - \`storeComputed(fn)\` — computed from store
200
+ - \`atom(initial)\` — lightweight single-value store
201
+
202
+ ## Forms
203
+ - \`useForm({ fields, onSubmit })\` — form management with validation
204
+ - \`useField(name, rules)\` — individual field management
205
+ - \`rules.required(msg)\` / \`rules.email(msg)\` / \`rules.minLength(n, msg)\` etc.
206
+ - \`zodResolver(schema)\` / \`yupResolver(schema)\` — schema validation adapters
207
+ - \`<Input />\` / \`<Textarea />\` / \`<Select />\` / \`<Checkbox />\` / \`<Radio />\` — form components
208
+
209
+ ## Head Management
210
+ - \`<Head><title>...</title></Head>\` — manage document head
211
+ - \`clearHead()\` — reset head tags
212
+
213
+ ## Animation
214
+ - \`spring(target, config?)\` — spring-based animation
215
+ - \`tween(from, to, config?)\` — tween animation
216
+ - \`useTransition(signal, config?)\` — transition between values
217
+ - \`useGesture(element, handlers)\` — gesture recognition
218
+ - \`cssTransition(classes)\` — CSS-based transitions
219
+
220
+ ## Accessibility
221
+ - \`useFocus()\` / \`useFocusTrap()\` / \`useFocusRestore()\` — focus management
222
+ - \`useRovingTabIndex()\` — keyboard navigation
223
+ - \`announce(msg)\` / \`announceAssertive(msg)\` — screen reader announcements
224
+ - \`<SkipLink />\` / \`<VisuallyHidden />\` / \`<LiveRegion />\` — a11y components
225
+ - \`useAriaExpanded()\` / \`useAriaSelected()\` / \`useAriaChecked()\` — ARIA state
226
+ - \`Keys\` / \`onKey()\` / \`onKeys()\` — keyboard event helpers
227
+ - \`useId()\` / \`useIds()\` — unique ID generation
228
+
229
+ ## Scheduler
230
+ - \`scheduleRead(fn)\` / \`scheduleWrite(fn)\` — prevent layout thrashing
231
+ - \`measure(fn)\` / \`mutate(fn)\` — batch DOM reads and writes
232
+ - \`nextFrame(fn)\` / \`raf(fn)\` — animation frame scheduling
233
+ - \`onResize(el, fn)\` / \`onIntersect(el, fn)\` — observer utilities
234
+
235
+ ## Utilities
236
+ - \`cls(...args)\` — conditional class names
237
+ - \`style(obj)\` — reactive style object
238
+ - \`debounce(fn, ms)\` / \`throttle(fn, ms)\` — rate limiting
239
+ - \`useMediaQuery(query)\` — reactive media query
240
+ - \`useLocalStorage(key, initial)\` — persistent signal
241
+ - \`useClickOutside(ref, fn)\` — detect outside clicks
242
+ - \`<Portal target={selector}>\` — render into a different DOM node
243
+
244
+ ## Error System (Agent-First)
245
+ - \`WhatError\` — structured error class with code, suggestion, context
246
+ - \`ERROR_CODES\` — all error code definitions
247
+ - \`createWhatError(code, context)\` — create a structured error
248
+ - \`classifyError(err, context)\` — classify a raw Error into WhatError
249
+ - \`getCollectedErrors(since?)\` — retrieve accumulated errors (dev mode)
250
+
251
+ ## Agent Guardrails
252
+ - \`configureGuardrails(overrides)\` — enable/disable specific guardrails
253
+ - \`validateImports(names)\` — check that import names are valid exports
254
+ - \`checkComponentName(name)\` — verify PascalCase naming
255
+
256
+ ## Agent Context
257
+ - \`installAgentContext()\` — expose \`window.__WHAT_AGENT__\` for AI agents
258
+ - \`getHealth()\` — health check: cycle risk, orphan effects, signal leaks, memory pressure
259
+ `,
260
+ }],
261
+ })
262
+ );
263
+
264
+ // --- Agent Guide Resource ---
265
+ server.resource(
266
+ 'agent-guide',
267
+ 'what://docs/agent-guide',
268
+ { description: 'Complete guide for AI coding agents working with What Framework' },
269
+ async () => ({
270
+ contents: [{
271
+ uri: 'what://docs/agent-guide',
272
+ mimeType: 'text/markdown',
273
+ text: `# What Framework Agent Guide
274
+
275
+ ## Overview
276
+ What Framework is the first framework built for AI agents. It uses fine-grained reactivity (signals + effects) instead of virtual DOM diffing. Components run ONCE — signals handle all updates directly.
277
+
278
+ ## Key Mental Model
279
+ 1. **Components run once.** The function body executes a single time. There is no "re-render."
280
+ 2. **Signals are the state.** Read with \`sig()\`, write with \`sig(newVal)\`. Signals auto-track which effects read them.
281
+ 3. **Effects handle side-effects.** They auto-track signal reads and re-run when those signals change.
282
+ 4. **DOM updates are fine-grained.** When a signal changes, only the specific DOM node that reads it updates — no diffing, no reconciliation.
283
+
284
+ ## The #1 Mistake: Missing Signal Calls
285
+ Signals are functions. You MUST call them to read the value:
286
+ \`\`\`js
287
+ // WRONG — renders "[Function]"
288
+ <span>{count}</span>
289
+
290
+ // CORRECT — renders the actual value
291
+ <span>{count()}</span>
292
+ \`\`\`
293
+
294
+ ## Creating Components
295
+ \`\`\`js
296
+ import { signal, effect, onMount } from 'what-framework';
297
+
298
+ function Counter() {
299
+ const count = signal(0, 'count');
300
+
301
+ return (
302
+ <div>
303
+ <span>{count()}</span>
304
+ <button onclick={() => count(c => c + 1)}>+1</button>
305
+ </div>
306
+ );
307
+ }
308
+ \`\`\`
309
+
310
+ ## MCP Devtools Workflow
311
+ 1. \`what_connection_status\` — verify app is connected
312
+ 2. \`what_diagnose\` — comprehensive health check
313
+ 3. \`what_lint { code: "..." }\` — static analysis on code you write
314
+ 4. \`what_scaffold { type: "component", name: "MyComponent" }\` — generate boilerplate
315
+ 5. \`what_snapshot { diff: true }\` — see what changed after an action
316
+ 6. \`what_fix { error: "ERR_INFINITE_EFFECT" }\` — get fix for any error code
317
+ 7. \`what_perf\` — performance snapshot with hot effects and memory estimate
318
+
319
+ ## Common Patterns
320
+
321
+ ### Conditional rendering
322
+ \`\`\`js
323
+ <Show when={isLoggedIn()} fallback={<LoginForm />}>
324
+ <Dashboard />
325
+ </Show>
326
+ \`\`\`
327
+
328
+ ### List rendering
329
+ \`\`\`js
330
+ <For each={items()}>{(item) =>
331
+ <li key={item.id}>{item.name}</li>
332
+ }</For>
333
+ \`\`\`
334
+
335
+ ### Data fetching
336
+ \`\`\`js
337
+ const { data, isLoading, error } = useSWR('/api/users', fetchJSON);
338
+ // data(), isLoading(), error() are signals — call them!
339
+ \`\`\`
340
+
341
+ ### Derived values
342
+ \`\`\`js
343
+ const total = computed(() => items().reduce((sum, i) => sum + i.price, 0));
344
+ \`\`\`
345
+
346
+ ### Event handlers
347
+ \`\`\`js
348
+ // JSX uses camelCase — compiler transforms to lowercase
349
+ <button onClick={() => count(c => c + 1)}>Add</button>
350
+
351
+ // In h() calls, use lowercase
352
+ h('button', { onclick: () => count(c => c + 1) }, 'Add')
353
+ \`\`\`
354
+
355
+ ## Error Codes Quick Reference
356
+ | Code | What it means | Fix |
357
+ |------|---------------|-----|
358
+ | ERR_INFINITE_EFFECT | Effect reads and writes same signal | Use untrack() for the read |
359
+ | ERR_MISSING_SIGNAL_READ | Signal used without () | Add () to read: count() |
360
+ | ERR_HYDRATION_MISMATCH | Server/client HTML differ | Use onMount() for client-only code |
361
+ | ERR_ORPHAN_EFFECT | Effect outside reactive root | Wrap in createRoot() |
362
+ | ERR_SIGNAL_WRITE_IN_RENDER | Signal written in component body | Move write to event handler |
363
+ | ERR_MISSING_CLEANUP | Effect has no cleanup return | Return cleanup function |
364
+ | ERR_UNSAFE_INNERHTML | innerHTML without __html marker | Use { __html: content } |
365
+ | ERR_MISSING_KEY | List without key prop | Add key={item.id} |
366
+ `,
367
+ }],
368
+ })
369
+ );
370
+
371
+ // --- Error Codes Resource ---
372
+ server.resource(
373
+ 'error-codes',
374
+ 'what://docs/error-codes',
375
+ { description: 'All What Framework error codes with explanations, fixes, and code examples' },
376
+ async () => ({
377
+ contents: [{
378
+ uri: 'what://docs/error-codes',
379
+ mimeType: 'text/markdown',
380
+ text: `# What Framework Error Codes
381
+
382
+ ## ERR_INFINITE_EFFECT
383
+ **Severity:** error
384
+ **Cause:** An effect reads and writes the same signal, creating a cycle. Each write triggers a re-run, which reads again, which writes again...
385
+ **Fix:** Use \`untrack()\` to read the signal without subscribing:
386
+ \`\`\`js
387
+ // Before (broken):
388
+ effect(() => { count(count() + 1); });
389
+
390
+ // After (fixed):
391
+ effect(() => { count(untrack(count) + 1); });
392
+ \`\`\`
393
+
394
+ ## ERR_MISSING_SIGNAL_READ
395
+ **Severity:** warning
396
+ **Cause:** A signal function reference is used where its VALUE was intended. Signals are functions that must be called.
397
+ **Fix:** Add \`()\` after the signal name:
398
+ \`\`\`js
399
+ // Before (broken — renders "[Function]"):
400
+ <span>{count}</span>
401
+
402
+ // After (fixed):
403
+ <span>{count()}</span>
404
+ \`\`\`
405
+
406
+ ## ERR_HYDRATION_MISMATCH
407
+ **Severity:** error
408
+ **Cause:** Server-rendered HTML differs from what the client expects. Usually caused by reading browser APIs during initial render.
409
+ **Fix:** Use \`onMount()\` for client-only logic:
410
+ \`\`\`js
411
+ // Before (broken):
412
+ function App() { return <p>{window.innerWidth}</p>; }
413
+
414
+ // After (fixed):
415
+ function App() {
416
+ const width = signal(0);
417
+ onMount(() => width(window.innerWidth));
418
+ return <p>{width()}</p>;
419
+ }
420
+ \`\`\`
421
+
422
+ ## ERR_ORPHAN_EFFECT
423
+ **Severity:** warning
424
+ **Cause:** An effect created outside any reactive root or component function. It will never be cleaned up.
425
+ **Fix:** Create effects inside components or wrap in \`createRoot()\`.
426
+
427
+ ## ERR_SIGNAL_WRITE_IN_RENDER
428
+ **Severity:** error
429
+ **Cause:** A signal is written during the component function body (render phase), causing immediate re-execution.
430
+ **Fix:** Move writes to event handlers, effects, or \`onMount()\`.
431
+
432
+ ## ERR_MISSING_CLEANUP
433
+ **Severity:** warning
434
+ **Cause:** An effect sets up a resource (listener, timer, subscription) but returns no cleanup.
435
+ **Fix:** Return a cleanup function:
436
+ \`\`\`js
437
+ effect(() => {
438
+ window.addEventListener('resize', handler);
439
+ return () => window.removeEventListener('resize', handler);
440
+ });
441
+ \`\`\`
442
+
443
+ ## ERR_UNSAFE_INNERHTML
444
+ **Severity:** warning
445
+ **Cause:** innerHTML set without the __html safety marker. XSS risk.
446
+ **Fix:** Use \`{ __html: content }\` or the \`html\` tagged template.
447
+
448
+ ## ERR_MISSING_KEY
449
+ **Severity:** warning
450
+ **Cause:** List items rendered without unique key props, causing incorrect reordering.
451
+ **Fix:** Add \`key={item.id}\` using a stable identifier.
452
+ `,
453
+ }],
454
+ })
455
+ );
456
+
457
+ // --- Patterns Resource ---
458
+ server.resource(
459
+ 'patterns',
460
+ 'what://docs/patterns',
461
+ { description: 'Common What Framework patterns: component, form, store, list, island' },
462
+ async () => ({
463
+ contents: [{
464
+ uri: 'what://docs/patterns',
465
+ mimeType: 'text/markdown',
466
+ text: `# What Framework Common Patterns
467
+
468
+ ## Component Pattern
469
+ \`\`\`js
470
+ import { signal, onMount } from 'what-framework';
471
+
472
+ function MyComponent({ title }) {
473
+ const isActive = signal(false, 'isActive');
474
+
475
+ onMount(() => {
476
+ // Client-only initialization
477
+ });
478
+
479
+ return (
480
+ <div class={cls({ active: isActive() })}>
481
+ <h2>{title}</h2>
482
+ <button onclick={() => isActive(v => !v)}>Toggle</button>
483
+ </div>
484
+ );
485
+ }
486
+ \`\`\`
487
+
488
+ ## Form Pattern
489
+ \`\`\`js
490
+ import { useForm, Input, ErrorMessage, rules } from 'what-framework';
491
+
492
+ function LoginForm() {
493
+ const { fields, handleSubmit, isSubmitting } = useForm({
494
+ fields: {
495
+ email: { initial: '', rules: [rules.required(), rules.email()] },
496
+ password: { initial: '', rules: [rules.required(), rules.minLength(8)] },
497
+ },
498
+ onSubmit: async (values) => {
499
+ await login(values.email, values.password);
500
+ },
501
+ });
502
+
503
+ return (
504
+ <form onsubmit={handleSubmit}>
505
+ <Input field={fields.email} type="email" placeholder="Email" />
506
+ <ErrorMessage field={fields.email} />
507
+ <Input field={fields.password} type="password" placeholder="Password" />
508
+ <ErrorMessage field={fields.password} />
509
+ <button type="submit" disabled={isSubmitting()}>
510
+ {isSubmitting() ? 'Logging in...' : 'Login'}
511
+ </button>
512
+ </form>
513
+ );
514
+ }
515
+ \`\`\`
516
+
517
+ ## Store Pattern
518
+ \`\`\`js
519
+ import { createStore, derived } from 'what-framework';
520
+
521
+ const store = createStore({
522
+ items: [],
523
+ filter: 'all',
524
+ });
525
+
526
+ const filteredItems = derived(state =>
527
+ state.filter === 'all' ? state.items : state.items.filter(i => i.status === state.filter)
528
+ );
529
+
530
+ export function addItem(item) {
531
+ store.set(s => ({ ...s, items: [...s.items, item] }));
532
+ }
533
+
534
+ export function setFilter(filter) {
535
+ store.set(s => ({ ...s, filter }));
536
+ }
537
+
538
+ export { store, filteredItems };
539
+ \`\`\`
540
+
541
+ ## List Pattern
542
+ \`\`\`js
543
+ import { signal, For } from 'what-framework';
544
+
545
+ function TodoList() {
546
+ const todos = signal([], 'todos');
547
+ const input = signal('', 'input');
548
+
549
+ const addTodo = () => {
550
+ if (input().trim()) {
551
+ todos(t => [...t, { id: Date.now(), text: input(), done: false }]);
552
+ input('');
553
+ }
554
+ };
555
+
556
+ return (
557
+ <div>
558
+ <input
559
+ value={input()}
560
+ oninput={e => input(e.target.value)}
561
+ onkeydown={e => e.key === 'Enter' && addTodo()}
562
+ />
563
+ <For each={todos()}>
564
+ {todo => <li key={todo.id}>{todo.text}</li>}
565
+ </For>
566
+ </div>
567
+ );
568
+ }
569
+ \`\`\`
570
+
571
+ ## Island Pattern
572
+ \`\`\`js
573
+ // islands/InteractiveWidget.jsx
574
+ import { signal, onMount } from 'what-framework';
575
+
576
+ function InteractiveWidget({ initialData }) {
577
+ const data = signal(initialData, 'data');
578
+
579
+ onMount(() => {
580
+ // Hydrate with fresh data from API
581
+ fetch('/api/widget').then(r => r.json()).then(data);
582
+ });
583
+
584
+ return (
585
+ <div data-island="interactive-widget">
586
+ <span>{data()?.title}</span>
587
+ </div>
588
+ );
589
+ }
590
+
591
+ InteractiveWidget.island = true;
592
+ export default InteractiveWidget;
593
+ \`\`\`
594
+
595
+ ## Data Fetching Pattern
596
+ \`\`\`js
597
+ import { useSWR, Show } from 'what-framework';
598
+
599
+ function UserProfile({ userId }) {
600
+ const { data, isLoading, error } = useSWR(
601
+ \`/api/users/\${userId}\`,
602
+ (url) => fetch(url).then(r => r.json())
603
+ );
604
+
605
+ return (
606
+ <Show when={!isLoading()} fallback={<Spinner />}>
607
+ <Show when={!error()} fallback={<p>Error: {error()?.message}</p>}>
608
+ <h1>{data()?.name}</h1>
609
+ <p>{data()?.email}</p>
610
+ </Show>
611
+ </Show>
612
+ );
613
+ }
614
+ \`\`\`
615
+ `,
616
+ }],
617
+ })
618
+ );
619
+
620
+ // Import and register extended tools if available
621
+ try {
622
+ const { registerExtendedTools } = await import('./tools-extended.js');
623
+ registerExtendedTools(server, bridge);
624
+ } catch {
625
+ // Extended tools not yet available — that's fine
626
+ }
627
+
628
+ // Import and register agent-first tools
629
+ try {
630
+ const { registerAgentTools } = await import('./tools-agent.js');
631
+ registerAgentTools(server, bridge);
632
+ } catch {
633
+ // Agent tools not yet available — that's fine
634
+ }
635
+
636
+ const transport = new StdioServerTransport();
637
+ await server.connect(transport);