what-devtools-mcp 0.6.0 → 0.6.2

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/client.js CHANGED
@@ -21,7 +21,7 @@ function logGrouped(badge, badgeStyle, title, data) {
21
21
  console.groupEnd();
22
22
  }
23
23
 
24
- export function connectDevToolsMCP({ port = 9229 } = {}) {
24
+ export function connectDevToolsMCP({ port = 9229, token = '' } = {}) {
25
25
  // Never connect in production
26
26
  if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') {
27
27
  return { disconnect() {}, isConnected: false, eventCount: 0 };
@@ -42,19 +42,46 @@ export function connectDevToolsMCP({ port = 9229 } = {}) {
42
42
  let hasLoggedDisconnect = false;
43
43
  let reconnectAttempts = 0;
44
44
  let unsubscribeFn = null;
45
+ let flushEventBatch = null; // Hoisted — set during subscription, called by set-signal
46
+ let discoveredToken = token;
47
+ let discoveredPort = port;
45
48
 
46
49
  // Startup banner
47
50
  console.log(
48
- '%c⚡ What DevTools MCP %c Client v0.1.0',
51
+ '%c⚡ What DevTools MCP %c Client v0.2.0',
49
52
  'background:linear-gradient(135deg,#6366f1,#a855f7);color:#fff;padding:4px 10px;border-radius:4px;font-weight:bold;font-size:13px',
50
53
  'color:#a855f7;font-weight:bold'
51
54
  );
55
+
56
+ // --- Token Auto-Discovery ---
57
+ // If no token is provided, try to discover it from the bridge's HTTP endpoint.
58
+ // The bridge serves GET http://localhost:{port+1}/__what_mcp_token
59
+ async function discoverToken() {
60
+ if (discoveredToken) return true; // Already have a token
61
+
62
+ const discoveryPort = port + 1;
63
+ try {
64
+ const res = await fetch(`http://localhost:${discoveryPort}/__what_mcp_token`);
65
+ if (res.ok) {
66
+ const data = await res.json();
67
+ discoveredToken = data.token;
68
+ discoveredPort = data.wsPort || port;
69
+ log('MCP', BADGE, `Token discovered automatically from bridge`);
70
+ return true;
71
+ }
72
+ } catch {
73
+ // Discovery endpoint not available — bridge may not be running yet
74
+ }
75
+ return false;
76
+ }
77
+
52
78
  log('MCP', BADGE, `Connecting to bridge on ws://localhost:${port}`);
53
79
 
54
80
  function connect() {
55
81
  reconnectAttempts++;
56
82
  try {
57
- ws = new WebSocket(`ws://localhost:${port}`);
83
+ const tokenParam = discoveredToken ? `?token=${encodeURIComponent(discoveredToken)}` : '';
84
+ ws = new WebSocket(`ws://localhost:${discoveredPort}${tokenParam}`);
58
85
  } catch {
59
86
  if (reconnectAttempts <= 1) {
60
87
  log('MCP', BADGE_WARN, 'Bridge not available — retrying silently in background');
@@ -91,18 +118,18 @@ export function connectDevToolsMCP({ port = 9229 } = {}) {
91
118
  let eventBatch = [];
92
119
  let batchTimer = null;
93
120
 
94
- function flushEventBatch() {
121
+ // Hoisted so set-signal can call it to flush after programmatic writes
122
+ flushEventBatch = function() {
95
123
  if (eventBatch.length === 0) return;
96
124
  if (eventBatch.length === 1) {
97
- // Single event — send normally for compatibility
98
125
  const item = eventBatch[0];
99
126
  send({ type: 'event', event: item.event, data: item.data });
100
127
  } else {
101
128
  send({ type: 'events', batch: eventBatch });
102
129
  }
103
130
  eventBatch = [];
104
- batchTimer = null;
105
- }
131
+ if (batchTimer) { clearTimeout(batchTimer); batchTimer = null; }
132
+ };
106
133
 
107
134
  unsubscribeFn = devtools.subscribe((event, data) => {
108
135
  eventCount++;
@@ -187,6 +214,8 @@ export function connectDevToolsMCP({ port = 9229 } = {}) {
187
214
  if (entry) {
188
215
  const prev = entry.ref.peek();
189
216
  entry.ref(value);
217
+ // Flush event batch immediately so what_watch captures programmatic writes
218
+ if (flushEventBatch) setTimeout(flushEventBatch, 0);
190
219
  result = { previous: devtools.safeSerialize(prev), current: devtools.safeSerialize(value) };
191
220
  log('AI →', BADGE_CMD, `${label} — signal #${signalId} "${entry.name}": ${JSON.stringify(prev)} → ${JSON.stringify(value)}`);
192
221
  } else {
@@ -221,9 +250,17 @@ export function connectDevToolsMCP({ port = 9229 } = {}) {
221
250
  // Try extended command handlers
222
251
  let extResult = null;
223
252
  try {
224
- const { handleExtendedCommand } = await import('./client-commands.js');
225
- extResult = handleExtendedCommand(command, args, devtools);
226
- } catch {}
253
+ const { handleExtendedCommand, initEventTracking } = await import('./client-commands.js');
254
+ // Auto-initialize event tracking on first extended command so
255
+ // what_signal_trace always has write history (not just after what_watch)
256
+ initEventTracking(devtools);
257
+ extResult = await handleExtendedCommand(command, args, devtools);
258
+ } catch (importErr) {
259
+ // Don't silently swallow — report the real error so it's not
260
+ // misdiagnosed as "Unknown command"
261
+ extResult = { error: `Command handler failed: ${importErr.message}` };
262
+ log('AI →', BADGE_WARN, `Extended command "${command}" threw:`, importErr);
263
+ }
227
264
 
228
265
  if (extResult !== null) {
229
266
  result = extResult;
@@ -248,9 +285,12 @@ export function connectDevToolsMCP({ port = 9229 } = {}) {
248
285
  hasLoggedDisconnect = true;
249
286
  }
250
287
  const delay = reconnectAttempts >= 5 ? MAX_RECONNECT_DELAY : reconnectDelay;
251
- reconnectTimer = setTimeout(() => {
288
+ reconnectTimer = setTimeout(async () => {
252
289
  reconnectTimer = null;
253
290
  reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY);
291
+ // Try to discover token before each reconnect attempt
292
+ // (bridge may have started since last attempt)
293
+ await discoverToken();
254
294
  connect();
255
295
  }, delay);
256
296
  }
@@ -273,7 +313,8 @@ export function connectDevToolsMCP({ port = 9229 } = {}) {
273
313
  log('MCP', BADGE, 'Disconnected');
274
314
  }
275
315
 
276
- connect();
316
+ // Initial connect — try token discovery first, then connect
317
+ discoverToken().then(() => connect());
277
318
 
278
319
  return {
279
320
  disconnect,
package/src/index.js CHANGED
@@ -138,6 +138,8 @@ server.resource(
138
138
  mimeType: 'text/markdown',
139
139
  text: `# What Framework API Reference
140
140
 
141
+ > **Signal style note:** WhatFW signals support both \`sig(newValue)\` and \`sig.set(newValue)\`. The idiomatic pattern is the function-call style: \`sig(newValue)\` to write, \`sig()\` to read.
142
+
141
143
  ## Reactive Primitives
142
144
  - \`signal(initial, debugName?)\` — create reactive value. Read: \`sig()\`. Write: \`sig(newVal)\` or \`sig(prev => next)\`.
143
145
  - \`computed(fn)\` — derived value (lazy, only recomputes when deps change AND it's read)
@@ -617,6 +619,391 @@ function UserProfile({ userId }) {
617
619
  })
618
620
  );
619
621
 
622
+ // --- Routing Resource (ported from mcp-server) ---
623
+ server.resource(
624
+ 'routing-guide',
625
+ 'what://docs/routing',
626
+ { description: 'File-based and programmatic routing in What Framework: routes, params, nested layouts, navigation' },
627
+ async () => ({
628
+ contents: [{
629
+ uri: 'what://docs/routing',
630
+ mimeType: 'text/markdown',
631
+ text: `# What Framework Routing
632
+
633
+ ## Declaring Routes
634
+
635
+ \`\`\`js
636
+ import { Router, Link, navigate, route } from 'what-framework/router';
637
+
638
+ h(Router, {
639
+ routes: [
640
+ { path: '/', component: Home },
641
+ { path: '/about', component: About },
642
+ { path: '/users/:id', component: User },
643
+ { path: '/blog/*', component: BlogLayout },
644
+ ],
645
+ fallback: h(NotFound),
646
+ });
647
+ \`\`\`
648
+
649
+ ## Navigation
650
+
651
+ \`\`\`js
652
+ // Declarative link
653
+ h(Link, { href: '/about' }, 'About');
654
+
655
+ // Programmatic navigation
656
+ navigate('/dashboard');
657
+ navigate('/login', { replace: true });
658
+ \`\`\`
659
+
660
+ ## Reactive Route State
661
+
662
+ \`\`\`js
663
+ route.path(); // current path (signal — call to read)
664
+ route.params(); // { id: '123' }
665
+ route.query(); // { page: '1' }
666
+ \`\`\`
667
+
668
+ ## Nested Layouts
669
+
670
+ \`\`\`js
671
+ {
672
+ path: '/dashboard',
673
+ component: DashboardLayout,
674
+ children: [
675
+ { path: '', component: DashboardHome },
676
+ { path: 'settings', component: Settings },
677
+ ],
678
+ }
679
+ \`\`\`
680
+
681
+ ## File-Based Routing
682
+
683
+ Drop files in \`src/pages/\` and routes are generated automatically:
684
+
685
+ | File | Route |
686
+ |------|-------|
687
+ | \`src/pages/index.jsx\` | \`/\` |
688
+ | \`src/pages/about.jsx\` | \`/about\` |
689
+ | \`src/pages/users/[id].jsx\` | \`/users/:id\` |
690
+ | \`src/pages/blog/[...slug].jsx\` | \`/blog/*\` |
691
+
692
+ ## Route Guards
693
+
694
+ \`\`\`js
695
+ {
696
+ path: '/admin',
697
+ component: AdminPanel,
698
+ beforeEnter: (to, from) => {
699
+ if (!isAuthenticated()) return '/login';
700
+ },
701
+ }
702
+ \`\`\`
703
+ `,
704
+ }],
705
+ })
706
+ );
707
+
708
+ // --- SSR/SSG Resource (ported from mcp-server) ---
709
+ server.resource(
710
+ 'ssr-ssg-guide',
711
+ 'what://docs/ssr-ssg',
712
+ { description: 'Server-side rendering, static site generation, and hybrid rendering in What Framework' },
713
+ async () => ({
714
+ contents: [{
715
+ uri: 'what://docs/ssr-ssg',
716
+ mimeType: 'text/markdown',
717
+ text: `# What Framework SSR / SSG
718
+
719
+ ## Render Modes
720
+
721
+ | Mode | Description |
722
+ |------|-------------|
723
+ | \`'static'\` | Pre-rendered at build time (SSG) |
724
+ | \`'server'\` | Rendered on each request (SSR) |
725
+ | \`'client'\` | Client-only rendering (SPA) |
726
+ | \`'hybrid'\` | Static shell + client hydration |
727
+
728
+ ## Render to String (SSR)
729
+
730
+ \`\`\`js
731
+ import { renderToString } from 'what-framework/server';
732
+
733
+ const html = await renderToString(h(App));
734
+ \`\`\`
735
+
736
+ ## Stream Rendering
737
+
738
+ \`\`\`js
739
+ import { renderToStream } from 'what-framework/server';
740
+
741
+ for await (const chunk of renderToStream(h(App))) {
742
+ response.write(chunk);
743
+ }
744
+ \`\`\`
745
+
746
+ ## Per-Page Configuration
747
+
748
+ \`\`\`js
749
+ import { definePage } from 'what-framework/server';
750
+
751
+ export const page = definePage({
752
+ mode: 'static', // 'static' | 'server' | 'client' | 'hybrid'
753
+ });
754
+ \`\`\`
755
+
756
+ ## Server-Only Components
757
+
758
+ \`\`\`js
759
+ import { server } from 'what-framework/server';
760
+
761
+ const Header = server(({ title }) => h('header', null, title));
762
+ // This component never ships JS to the client
763
+ \`\`\`
764
+
765
+ ## Islands + SSR
766
+
767
+ Combine SSR with islands for zero-JS-by-default pages that hydrate interactive parts on demand:
768
+
769
+ \`\`\`js
770
+ import { island, Island } from 'what-framework/server';
771
+
772
+ island('cart', () => import('./islands/cart.js'), {
773
+ mode: 'action', // Hydrate on first interaction
774
+ });
775
+
776
+ function Page() {
777
+ return h('div', null,
778
+ h('nav', null, 'Static nav — no JS'),
779
+ h(Island, { name: 'cart' }),
780
+ h('footer', null, 'Static footer — no JS'),
781
+ );
782
+ }
783
+ \`\`\`
784
+
785
+ ### Hydration Modes
786
+ - \`'idle'\`: Hydrate when browser is idle
787
+ - \`'visible'\`: Hydrate when visible (IntersectionObserver)
788
+ - \`'action'\`: Hydrate on first click/focus
789
+ - \`'media'\`: Hydrate when media query matches
790
+ - \`'load'\`: Hydrate immediately
791
+ - \`'static'\`: Never hydrate (server only)
792
+ `,
793
+ }],
794
+ })
795
+ );
796
+
797
+ // --- CLI Resource (ported from mcp-server) ---
798
+ server.resource(
799
+ 'cli-guide',
800
+ 'what://docs/cli',
801
+ { description: 'What Framework CLI commands and project configuration' },
802
+ async () => ({
803
+ contents: [{
804
+ uri: 'what://docs/cli',
805
+ mimeType: 'text/markdown',
806
+ text: `# What Framework CLI
807
+
808
+ ## Commands
809
+
810
+ \`\`\`bash
811
+ what dev # Dev server with HMR
812
+ what build # Production build
813
+ what preview # Preview production build
814
+ what generate # Static site generation
815
+ \`\`\`
816
+
817
+ ## Configuration
818
+
819
+ \`\`\`js
820
+ // what.config.js
821
+ export default {
822
+ mode: 'hybrid', // 'static' | 'server' | 'client' | 'hybrid'
823
+ pagesDir: 'src/pages',
824
+ outDir: 'dist',
825
+ islands: true,
826
+ port: 3000,
827
+ };
828
+ \`\`\`
829
+
830
+ ## Environment Variables
831
+
832
+ - \`WHAT_MCP_PORT\` — Port for the devtools MCP bridge (default: 9229)
833
+ - \`NODE_ENV\` — \`'development'\` enables devtools instrumentation and error collection
834
+ `,
835
+ }],
836
+ })
837
+ );
838
+
839
+ // --- Testing Resource ---
840
+ server.resource(
841
+ 'testing-guide',
842
+ 'what://docs/testing',
843
+ { description: 'How to test What Framework components, signals, and effects' },
844
+ async () => ({
845
+ contents: [{
846
+ uri: 'what://docs/testing',
847
+ mimeType: 'text/markdown',
848
+ text: `# Testing What Framework Apps
849
+
850
+ ## Unit Testing Signals
851
+
852
+ \`\`\`js
853
+ import { signal, computed, effect, flushSync } from 'what-framework';
854
+ import { describe, it, assert } from 'node:test';
855
+
856
+ describe('Counter signal', () => {
857
+ it('increments', () => {
858
+ const count = signal(0);
859
+ count(1);
860
+ assert.strictEqual(count(), 1);
861
+ });
862
+
863
+ it('computed derives correctly', () => {
864
+ const count = signal(2);
865
+ const doubled = computed(() => count() * 2);
866
+ assert.strictEqual(doubled(), 4);
867
+ count(5);
868
+ assert.strictEqual(doubled(), 10);
869
+ });
870
+ });
871
+ \`\`\`
872
+
873
+ ## Testing Effects
874
+
875
+ Effects flush asynchronously via microtask. Use \`flushSync()\` to force synchronous execution in tests:
876
+
877
+ \`\`\`js
878
+ import { signal, effect, flushSync } from 'what-framework';
879
+
880
+ it('effect tracks signal changes', () => {
881
+ const name = signal('Alice');
882
+ let captured = '';
883
+ effect(() => { captured = name(); });
884
+ flushSync();
885
+ assert.strictEqual(captured, 'Alice');
886
+
887
+ name('Bob');
888
+ flushSync();
889
+ assert.strictEqual(captured, 'Bob');
890
+ });
891
+ \`\`\`
892
+
893
+ ## Testing Components
894
+
895
+ \`\`\`js
896
+ import { mount } from 'what-framework';
897
+
898
+ it('renders component', () => {
899
+ const container = document.createElement('div');
900
+ mount(<Counter />, container);
901
+ assert.ok(container.querySelector('button'));
902
+ assert.strictEqual(container.textContent.includes('0'), true);
903
+ });
904
+ \`\`\`
905
+
906
+ ## Testing with MCP Devtools
907
+
908
+ Use the MCP tools for integration-level debugging:
909
+ 1. \`what_lint { code: "..." }\` — static analysis on code before running
910
+ 2. \`what_validate { code: "..." }\` — compile check + lint in one call
911
+ 3. \`what_snapshot { diff: true }\` — verify state changes after an action
912
+ `,
913
+ }],
914
+ })
915
+ );
916
+
917
+ // --- Project Structure Resource ---
918
+ server.resource(
919
+ 'project-structure',
920
+ 'what://docs/project-structure',
921
+ { description: 'Recommended project structure and file conventions for What Framework apps' },
922
+ async () => ({
923
+ contents: [{
924
+ uri: 'what://docs/project-structure',
925
+ mimeType: 'text/markdown',
926
+ text: `# What Framework Project Structure
927
+
928
+ ## Recommended Layout
929
+
930
+ \`\`\`
931
+ my-app/
932
+ what.config.js # Framework configuration
933
+ src/
934
+ pages/ # File-based routes (auto-discovered)
935
+ index.jsx # /
936
+ about.jsx # /about
937
+ users/
938
+ [id].jsx # /users/:id
939
+ index.jsx # /users
940
+ components/ # Shared components
941
+ Header.jsx
942
+ Footer.jsx
943
+ islands/ # Interactive islands (hydrated on demand)
944
+ Cart.jsx
945
+ SearchBar.jsx
946
+ stores/ # Global stores and shared state
947
+ auth.js
948
+ cart.js
949
+ lib/ # Utilities, API clients, helpers
950
+ api.js
951
+ format.js
952
+ styles/ # Global styles
953
+ global.css
954
+ public/ # Static assets (copied as-is)
955
+ favicon.ico
956
+ dist/ # Build output (generated)
957
+ \`\`\`
958
+
959
+ ## Naming Conventions
960
+
961
+ - **Components**: PascalCase (\`MyComponent.jsx\`)
962
+ - **Pages**: lowercase or kebab-case (\`about.jsx\`, \`blog-post.jsx\`)
963
+ - **Islands**: PascalCase, in \`islands/\` directory, export \`.island = true\`
964
+ - **Stores**: camelCase (\`authStore.js\`)
965
+ - **Signals**: camelCase variable names with optional debug name: \`signal(0, 'count')\`
966
+
967
+ ## Import Conventions
968
+
969
+ Always import from \`'what-framework'\`, not \`'what'\`:
970
+
971
+ \`\`\`js
972
+ import { signal, effect, computed, onMount } from 'what-framework';
973
+ import { Router, Link, navigate } from 'what-framework/router';
974
+ import { renderToString, definePage } from 'what-framework/server';
975
+ \`\`\`
976
+ `,
977
+ }],
978
+ })
979
+ );
980
+
981
+ // --- Tool Pipeline Guide Resource ---
982
+ server.resource(
983
+ 'tool-pipelines',
984
+ 'what://docs/tool-pipelines',
985
+ { description: 'How to call MCP tools efficiently: recommended pipelines, token costs, anti-patterns, cascade rules' },
986
+ async () => {
987
+ // Load from the markdown file at build time
988
+ let text;
989
+ try {
990
+ const { readFileSync } = await import('node:fs');
991
+ const { fileURLToPath } = await import('node:url');
992
+ const path = fileURLToPath(new URL('../TOOL-PIPELINES.md', import.meta.url));
993
+ text = readFileSync(path, 'utf-8');
994
+ } catch {
995
+ text = '# Tool Pipeline Guide\n\nFailed to load TOOL-PIPELINES.md. Check the package installation.';
996
+ }
997
+ return {
998
+ contents: [{
999
+ uri: 'what://docs/tool-pipelines',
1000
+ mimeType: 'text/markdown',
1001
+ text,
1002
+ }],
1003
+ };
1004
+ }
1005
+ );
1006
+
620
1007
  // Import and register extended tools if available
621
1008
  try {
622
1009
  const { registerExtendedTools } = await import('./tools-extended.js');
@@ -633,5 +1020,96 @@ try {
633
1020
  // Agent tools not yet available — that's fine
634
1021
  }
635
1022
 
1023
+ // Import and register interaction tools (what_click, what_fill, what_interact, etc.)
1024
+ try {
1025
+ const { registerInteractionTools } = await import('./tools-interact.js');
1026
+ registerInteractionTools(server, bridge);
1027
+ } catch {
1028
+ // Interaction tools not yet available — that's fine
1029
+ }
1030
+
1031
+ // --- MCP Prompts: agent guidance ---
1032
+
1033
+ server.prompt(
1034
+ 'what-devtools-guide',
1035
+ 'How to use WhatFW MCP DevTools effectively. Read this first.',
1036
+ {},
1037
+ async () => ({
1038
+ messages: [{
1039
+ role: 'user',
1040
+ content: {
1041
+ type: 'text',
1042
+ text: `# WhatFW MCP DevTools — Quick Reference
1043
+
1044
+ You have access to 34 MCP tools for inspecting, debugging, and INTERACTING WITH a live What Framework app running in the browser. No Playwright needed.
1045
+
1046
+ ## Connection Check
1047
+ Always start with: \`what_connection_status\` — confirms the browser is connected and shows signal/effect/component counts.
1048
+
1049
+ ## Top 5 Tools (use these most)
1050
+
1051
+ 1. **what_diagnose** — One-call health check. Finds errors, performance issues, and reactivity problems.
1052
+ 2. **what_explain** {componentId} — Everything about one component: its signals, effects, DOM, and errors.
1053
+ 3. **what_page_map_interactive** — See every interactive element on the page with exact tool + args to use.
1054
+ 4. **what_click** {text} — Click buttons/links by text. Returns what changed (signals, components, navigation).
1055
+ 5. **what_assert** {text, signalName, route} — Verify page state without screenshots.
1056
+
1057
+ ## Page Interaction (replaces Playwright)
1058
+ - \`what_page_map_interactive\` — "What can I do on this page?" Shows every button, input, link, form with exact args.
1059
+ - \`what_click\` {text|ariaLabel|testId|role} — Click by semantic selector. Returns signal changes, component mounts.
1060
+ - \`what_fill\` {label|name, value} — Fill form inputs. Returns validation state. Multi-fill: {inputs: {field: val}}.
1061
+ - \`what_interact\` {action} — submit_form, select_option, toggle, scroll_to, hover, type, clear, focus.
1062
+ - \`what_assert\` {text|signalName|selector|route} — Verify page state: text visible, signal value, element count, route.
1063
+ - \`what_wait\` {text|componentId|signalId|idle} — Wait for async conditions: text appear/gone, component mount, idle.
1064
+
1065
+ ## Visual Tools (cheapest first)
1066
+ - \`what_look\` — Text description of styles/layout (~400 tokens). Use FIRST.
1067
+ - \`what_page_map\` — Full page skeleton with landmarks, buttons, headings (~800 tokens).
1068
+ - \`what_screenshot\` {componentId} — Cropped image of ONE component (5-20KB). Use only if text isn't enough.
1069
+
1070
+ ## State Debugging
1071
+ - \`what_signals\` — See all signal values
1072
+ - \`what_signal_trace\` {signalId} — "Why did this signal change?" Shows which effects wrote to it.
1073
+ - \`what_dependency_graph\` {signalId} — Full reactive graph: signal -> effects -> downstream.
1074
+ - \`what_watch\` — Observe reactive events over a time window.
1075
+
1076
+ ## Actions
1077
+ - \`what_set_signal\` {signalId, value} — Directly change a signal value in the live app.
1078
+ - \`what_navigate\` {path} — Navigate to a different route.
1079
+
1080
+ ## Code Quality
1081
+ - \`what_lint\` — Check code for signal-read-without-(), effect cycles, missing cleanup, etc.
1082
+ - \`what_scaffold\` {type, name} — Generate idiomatic component/page/form/store boilerplate.
1083
+ - \`what_fix\` {errorCode} — Get diagnosis + fix + code example for any WhatFW error.
1084
+
1085
+ ## Example: Test a Login Flow (no Playwright)
1086
+ \`\`\`
1087
+ what_page_map_interactive // see the page
1088
+ what_fill({label: "Email", value: "test@test.com"}) // fill email
1089
+ what_fill({label: "Password", value: "secret123"}) // fill password
1090
+ what_click({text: "Log In"}) // click login -> see signal changes
1091
+ what_wait({text: "Loading", gone: true}) // wait for loading
1092
+ what_assert({route: "/dashboard"}) // verify navigation
1093
+ what_assert({text: "Welcome back", visible: true}) // verify greeting
1094
+ \`\`\`
1095
+
1096
+ ## Anti-Patterns
1097
+ - DON'T use Playwright for page interaction — use what_click, what_fill, what_interact
1098
+ - DON'T screenshot first — use what_look (10x cheaper)
1099
+ - DON'T call what_signals + what_effects + what_dom_inspect separately — use what_explain
1100
+ - DON'T use CSS selectors — use semantic selectors (text, label, ariaLabel, testId)
1101
+
1102
+ ## What Framework Basics
1103
+ - Components run ONCE (not on every render like React)
1104
+ - \`signal(value, 'name')\` for state — read with \`sig()\`, write with \`sig(newValue)\`
1105
+ - \`effect(() => { ... })\` for side effects — auto-tracks signal reads
1106
+ - \`computed(() => ...)\` for derived values — lazy, cached
1107
+ - Import from \`'what-framework'\`
1108
+ `,
1109
+ },
1110
+ }],
1111
+ })
1112
+ );
1113
+
636
1114
  const transport = new StdioServerTransport();
637
1115
  await server.connect(transport);