what-core 0.6.2 → 0.6.5

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.
Files changed (52) hide show
  1. package/README.md +2 -0
  2. package/compiler.d.ts +30 -0
  3. package/devtools.d.ts +2 -0
  4. package/dist/compiler.js +1787 -0
  5. package/dist/compiler.js.map +7 -0
  6. package/dist/compiler.min.js +2 -0
  7. package/dist/compiler.min.js.map +7 -0
  8. package/dist/devtools.js +10 -0
  9. package/dist/devtools.js.map +7 -0
  10. package/dist/devtools.min.js +2 -0
  11. package/dist/devtools.min.js.map +7 -0
  12. package/dist/index.js +330 -382
  13. package/dist/index.js.map +4 -4
  14. package/dist/index.min.js +62 -62
  15. package/dist/index.min.js.map +4 -4
  16. package/dist/render.js +262 -21
  17. package/dist/render.js.map +4 -4
  18. package/dist/render.min.js +58 -1
  19. package/dist/render.min.js.map +4 -4
  20. package/dist/testing.js +3 -0
  21. package/dist/testing.js.map +2 -2
  22. package/dist/testing.min.js +1 -1
  23. package/dist/testing.min.js.map +2 -2
  24. package/index.d.ts +176 -1
  25. package/jsx-runtime.d.ts +622 -0
  26. package/package.json +20 -2
  27. package/render.d.ts +1 -1
  28. package/src/agent-context.js +1 -1
  29. package/src/compiler.js +18 -0
  30. package/src/components.js +73 -27
  31. package/src/devtools.js +4 -0
  32. package/src/dom.js +7 -0
  33. package/src/guardrails.js +3 -4
  34. package/src/hooks.js +0 -11
  35. package/src/index.js +5 -9
  36. package/src/render.js +91 -24
  37. package/testing.d.ts +1 -1
  38. package/dist/a11y.js +0 -440
  39. package/dist/animation.js +0 -548
  40. package/dist/components.js +0 -229
  41. package/dist/data.js +0 -638
  42. package/dist/dom.js +0 -439
  43. package/dist/form.js +0 -509
  44. package/dist/h.js +0 -152
  45. package/dist/head.js +0 -51
  46. package/dist/helpers.js +0 -140
  47. package/dist/hooks.js +0 -210
  48. package/dist/reactive.js +0 -432
  49. package/dist/scheduler.js +0 -246
  50. package/dist/skeleton.js +0 -363
  51. package/dist/store.js +0 -83
  52. package/dist/what.js +0 -117
@@ -0,0 +1,18 @@
1
+ // What Framework - Compiler Runtime Internals
2
+ // Stable import target for generated compiler output. Not for app code.
3
+
4
+ export {
5
+ _$createComponent,
6
+ _$template,
7
+ _template,
8
+ template,
9
+ insert,
10
+ mapArray,
11
+ spread,
12
+ setProp,
13
+ delegateEvents,
14
+ on,
15
+ classList,
16
+ effect,
17
+ untrack,
18
+ } from './render.js';
package/src/components.js CHANGED
@@ -154,42 +154,73 @@ export function reportError(error, startCtx) {
154
154
 
155
155
  // --- Show ---
156
156
  // Conditional rendering component. Cleaner than ternaries.
157
+ // Reactively shows/hides children based on the `when` condition.
157
158
 
158
159
  export function Show({ when, fallback = null, children }) {
159
- // when can be a signal or a value
160
- const condition = typeof when === 'function' ? when() : when;
161
- return condition ? children : fallback;
160
+ // If `when` is a signal or function, return a reactive function
161
+ // so the DOM runtime tracks changes via its effect wrapper
162
+ if (typeof when === 'function') {
163
+ return () => when() ? children : fallback;
164
+ }
165
+ // Static value — just return directly
166
+ return when ? children : fallback;
162
167
  }
163
168
 
164
169
  // --- For ---
165
- // Efficient list rendering with keyed reconciliation.
166
-
167
- export function For({ each, fallback = null, children }) {
168
- const list = typeof each === 'function' ? each() : each;
169
- if (!list || list.length === 0) return fallback;
170
-
170
+ // Reactive list rendering with keyed reconciliation.
171
+ // Takes a signal (or function returning an array) as `each` and reactively
172
+ // adds/removes/moves DOM nodes when the array changes.
173
+ // Uses mapArray from render.js for efficient keyed reconciliation with LIS.
174
+ //
175
+ // Usage: <For each={items}>{(item, index) => <div>{item}</div>}</For>
176
+ // - `each`: signal or function returning an array
177
+ // - `children`: render function (item, index) => vnode
178
+ // - `fallback`: shown when array is empty
179
+ // - `key`: optional key function (item) => key for keyed reconciliation
180
+
181
+ export function For({ each, fallback = null, children, key: keyFn }) {
171
182
  // children should be a function (item, index) => vnode
172
183
  const renderFn = Array.isArray(children) ? children[0] : children;
173
184
  if (typeof renderFn !== 'function') {
174
- console.warn('[what] For: children must be a render function, e.g. <For each={items}>{(item) => ...}</For>');
185
+ if (__DEV__) {
186
+ console.warn('[what] For: children must be a render function, e.g. <For each={items}>{(item) => ...}</For>');
187
+ }
175
188
  return fallback;
176
189
  }
177
190
 
178
- return list.map((item, index) => {
191
+ // Normalize `each` to a function that returns the current array
192
+ const source = typeof each === 'function' ? each : () => each;
193
+
194
+ // Build the map function that wraps renderFn with auto-key detection
195
+ const mapFn = (item, index) => {
179
196
  const vnode = renderFn(item, index);
180
197
  // Auto-detect keys for efficient keyed reconciliation
181
198
  if (vnode && typeof vnode === 'object' && vnode.key == null) {
182
- if (item != null && typeof item === 'object') {
183
- // Use item.id or item.key if available
184
- if (item.id != null) vnode.key = item.id;
185
- else if (item.key != null) vnode.key = item.key;
186
- } else if (typeof item === 'string' || typeof item === 'number') {
187
- // Primitive items can be their own key
188
- vnode.key = item;
199
+ const rawItem = typeof item === 'function' && item._signal ? item() : item;
200
+ if (rawItem != null && typeof rawItem === 'object') {
201
+ if (rawItem.id != null) vnode.key = rawItem.id;
202
+ else if (rawItem.key != null) vnode.key = rawItem.key;
203
+ } else if (typeof rawItem === 'string' || typeof rawItem === 'number') {
204
+ vnode.key = rawItem;
189
205
  }
190
206
  }
191
207
  return vnode;
192
- });
208
+ };
209
+
210
+ // Return a reactive function. The DOM runtime (createDOM in dom.js)
211
+ // wraps functions in effects, so this will re-evaluate whenever the
212
+ // source signal changes. For simple cases (no mapArray integration),
213
+ // this provides correct reactivity by re-rendering the list on change.
214
+ //
215
+ // The effect wrapper in createDOM (lines 140-190 in dom.js) handles:
216
+ // - Creating DOM nodes for new vnodes
217
+ // - Removing old DOM nodes
218
+ // - Inserting between comment markers
219
+ return () => {
220
+ const list = source();
221
+ if (!list || list.length === 0) return fallback;
222
+ return list.map((item, i) => mapFn(item, i));
223
+ };
193
224
  }
194
225
 
195
226
  // --- Switch / Match ---
@@ -198,12 +229,29 @@ export function For({ each, fallback = null, children }) {
198
229
  export function Switch({ fallback = null, children }) {
199
230
  const kids = Array.isArray(children) ? children : [children];
200
231
 
232
+ const hasReactiveCondition = kids.some(
233
+ child => child && child.tag === Match && typeof child.props.when === 'function'
234
+ );
235
+
236
+ if (hasReactiveCondition) {
237
+ return () => {
238
+ for (const child of kids) {
239
+ if (child && child.tag === Match) {
240
+ const condition = typeof child.props.when === 'function'
241
+ ? child.props.when()
242
+ : child.props.when;
243
+ if (condition) {
244
+ return child.children;
245
+ }
246
+ }
247
+ }
248
+ return fallback;
249
+ };
250
+ }
251
+
201
252
  for (const child of kids) {
202
253
  if (child && child.tag === Match) {
203
- const condition = typeof child.props.when === 'function'
204
- ? child.props.when()
205
- : child.props.when;
206
- if (condition) {
254
+ if (child.props.when) {
207
255
  return child.children;
208
256
  }
209
257
  }
@@ -223,8 +271,6 @@ export function Match({ when, children }) {
223
271
  // The babel plugin compiles <Counter client:idle /> into this.
224
272
 
225
273
  export function Island({ component: Component, mode, mediaQuery, ...props }) {
226
- const placeholder = h('div', { 'data-island': Component.name || 'Island', 'data-hydrate': mode });
227
-
228
274
  // We need to return a vnode that the reconciler can handle.
229
275
  // The actual hydration scheduling happens after mount via an effect.
230
276
  const wrapper = signal(null);
@@ -304,8 +350,8 @@ export function Island({ component: Component, mode, mediaQuery, ...props }) {
304
350
  if (el) scheduleHydration(el);
305
351
  };
306
352
 
307
- // Return: show placeholder until hydrated, then show the real component
353
+ // Return: reactive function so DOM runtime tracks hydration state changes
308
354
  return h('div', { 'data-island': Component.name || 'Island', 'data-hydrate': mode, ref: refCallback },
309
- hydrated() ? wrapper() : null
355
+ () => hydrated() ? wrapper() : null
310
356
  );
311
357
  }
@@ -0,0 +1,4 @@
1
+ // What Framework - DevTools bridge
2
+ // Internal integration surface consumed by what-devtools.
3
+
4
+ export { __setDevToolsHooks } from './reactive.js';
package/src/dom.js CHANGED
@@ -204,6 +204,13 @@ export function createDOM(vnode, parent, isSvg) {
204
204
  return createComponent(vnode, parent, isSvg);
205
205
  }
206
206
 
207
+ // Special boundary vnodes (returned by ErrorBoundary, Suspense, Portal components)
208
+ // These have string tags like '__errorBoundary' and must be routed to createComponent
209
+ // which contains the special-case handlers for them.
210
+ if (isVNode(vnode) && (vnode.tag === '__errorBoundary' || vnode.tag === '__suspense' || vnode.tag === '__portal')) {
211
+ return createComponent(vnode, parent, isSvg);
212
+ }
213
+
207
214
  // VNode with string tag — create element
208
215
  if (isVNode(vnode)) {
209
216
  return createElementFromVNode(vnode, parent, isSvg);
package/src/guardrails.js CHANGED
@@ -125,11 +125,10 @@ function capitalize(str) {
125
125
  const VALID_EXPORTS = new Set([
126
126
  // Reactive primitives
127
127
  'signal', 'computed', 'effect', 'signalMemo', 'batch', 'untrack', 'flushSync',
128
- 'createRoot', 'getOwner', 'runWithOwner', 'onRootCleanup', '__setDevToolsHooks',
128
+ 'createRoot', 'getOwner', 'runWithOwner', 'onRootCleanup',
129
129
  // Rendering
130
- 'template', '_template', 'svgTemplate', 'insert', 'mapArray', 'spread',
130
+ 'template', 'svgTemplate', 'insert', 'mapArray', 'spread',
131
131
  'setProp', 'delegateEvents', 'on', 'classList', 'hydrate', 'isHydrating',
132
- '_$createComponent',
133
132
  // JSX
134
133
  'h', 'Fragment', 'html',
135
134
  // DOM
@@ -163,7 +162,7 @@ const VALID_EXPORTS = new Set([
163
162
  'IslandSkeleton', 'useSkeleton', 'Placeholder', 'LoadingDots', 'Spinner',
164
163
  // Data fetching
165
164
  'useFetch', 'useSWR', 'useQuery', 'useInfiniteQuery', 'invalidateQueries',
166
- 'prefetchQuery', 'setQueryData', 'getQueryData', 'clearCache', '__getCacheSnapshot',
165
+ 'prefetchQuery', 'setQueryData', 'getQueryData', 'clearCache',
167
166
  // Form
168
167
  'useForm', 'useField', 'rules', 'simpleResolver', 'zodResolver', 'yupResolver',
169
168
  'Input', 'Textarea', 'Select', 'Checkbox', 'Radio', 'ErrorMessage',
package/src/hooks.js CHANGED
@@ -371,14 +371,3 @@ export function createResource(fetcher, options = {}) {
371
371
  return [data, { loading, error, refetch, mutate }];
372
372
  }
373
373
 
374
- // --- Dep comparison (kept for potential external use) ---
375
-
376
- function depsChanged(oldDeps, newDeps) {
377
- if (oldDeps === undefined) return true;
378
- if (!oldDeps || !newDeps) return true;
379
- if (oldDeps.length !== newDeps.length) return true;
380
- for (let i = 0; i < oldDeps.length; i++) {
381
- if (!Object.is(oldDeps[i], newDeps[i])) return true;
382
- }
383
- return false;
384
- }
package/src/index.js CHANGED
@@ -2,10 +2,10 @@
2
2
  // The closest framework to vanilla JS.
3
3
 
4
4
  // Reactive primitives
5
- export { signal, computed, effect, memo as signalMemo, batch, untrack, flushSync, createRoot, getOwner, runWithOwner, onCleanup as onRootCleanup, __setDevToolsHooks } from './reactive.js';
5
+ export { signal, computed, effect, memo as signalMemo, batch, untrack, flushSync, createRoot, getOwner, runWithOwner, onCleanup as onRootCleanup } from './reactive.js';
6
6
 
7
7
  // Fine-grained rendering primitives
8
- export { template, _template, _$template, svgTemplate, insert, mapArray, spread, setProp, delegateEvents, on, classList, hydrate, isHydrating, _$createComponent } from './render.js';
8
+ export { template, svgTemplate, insert, mapArray, spread, setProp, delegateEvents, on, classList, hydrate, isHydrating, getHydrationMismatchCount } from './render.js';
9
9
 
10
10
  // JSX factory — Fragment and html tagged template are public APIs.
11
11
  // h is exported for internal package use only (jsx-runtime, server, router, react-compat).
@@ -15,15 +15,12 @@ export { h, Fragment, html } from './h.js';
15
15
  // DOM mounting & rendering (fine-grained, no VDOM reconciler)
16
16
  export { mount } from './dom.js';
17
17
 
18
- // Hooks (React-compatible API)
18
+ // Hooks — Solid-style API (signal-first)
19
+ // React-style hooks (useState, useEffect, useMemo, useCallback, useRef)
20
+ // are available via 'what-framework/react-compat' only.
19
21
  export {
20
- useState,
21
22
  useSignal,
22
23
  useComputed,
23
- useEffect,
24
- useMemo,
25
- useCallback,
26
- useRef,
27
24
  useContext,
28
25
  useReducer,
29
26
  createContext,
@@ -134,7 +131,6 @@ export {
134
131
  setQueryData,
135
132
  getQueryData,
136
133
  clearCache,
137
- __getCacheSnapshot,
138
134
  } from './data.js';
139
135
 
140
136
  // Form utilities
package/src/render.js CHANGED
@@ -2,8 +2,9 @@
2
2
  // Solid-style rendering: components run once, signals create individual DOM effects.
3
3
  // No VDOM diffing — direct DOM manipulation with surgical signal-driven updates.
4
4
 
5
- import { effect, untrack, createRoot, signal, __DEV__ } from './reactive.js';
5
+ import { effect, untrack, createRoot, signal, __DEV__, __devtools } from './reactive.js';
6
6
  import { createDOM, disposeTree, getCurrentComponent, getComponentStack } from './dom.js';
7
+ import { createWhatError, collectError } from './errors.js';
7
8
 
8
9
  export { effect, untrack };
9
10
 
@@ -980,9 +981,16 @@ export function isHydrating() {
980
981
  export function hydrate(vnode, container) {
981
982
  _isHydrating = true;
982
983
  _hydrationCursor = { parent: container, index: 0 };
984
+ _hydrationMismatchCount = 0;
983
985
 
984
986
  try {
985
987
  const result = hydrateNode(vnode, container);
988
+ if (__DEV__ && _hydrationMismatchCount > 0) {
989
+ console.warn(
990
+ `[what] Hydration completed with ${_hydrationMismatchCount} mismatch${_hydrationMismatchCount === 1 ? '' : 'es'}. ` +
991
+ 'See previous warnings for details. This usually means server and client render different initial HTML.'
992
+ );
993
+ }
986
994
  return result;
987
995
  } finally {
988
996
  _isHydrating = false;
@@ -1012,11 +1020,46 @@ function claimNode(parent) {
1012
1020
  return null;
1013
1021
  }
1014
1022
 
1015
- function isDevMode() {
1016
- return typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production';
1023
+ // Track hydration mismatch count for diagnostics
1024
+ let _hydrationMismatchCount = 0;
1025
+
1026
+ /** Returns the number of hydration mismatches encountered during the last hydrate() call. */
1027
+ export function getHydrationMismatchCount() {
1028
+ return _hydrationMismatchCount;
1017
1029
  }
1018
1030
 
1019
- function hydrateNode(vnode, parent) {
1031
+ function reportHydrationMismatch(expected, actual, componentName) {
1032
+ _hydrationMismatchCount++;
1033
+ if (__DEV__) {
1034
+ const context = {
1035
+ component: componentName || 'unknown',
1036
+ serverHTML: actual,
1037
+ clientHTML: expected,
1038
+ };
1039
+ const whatError = createWhatError('HYDRATION_MISMATCH', context);
1040
+ collectError(whatError);
1041
+ // Notify devtools so MCP agents see hydration mismatches as live events
1042
+ if (__devtools?.onHydrationMismatch) {
1043
+ __devtools.onHydrationMismatch({
1044
+ component: componentName || 'unknown',
1045
+ expected,
1046
+ actual,
1047
+ mismatchCount: _hydrationMismatchCount,
1048
+ });
1049
+ }
1050
+ // Also notify the error handler so it appears in devtools error log
1051
+ if (__devtools?.onError) {
1052
+ __devtools.onError(whatError, { type: 'hydration', component: componentName });
1053
+ }
1054
+ console.warn(
1055
+ `[what] Hydration mismatch: expected ${expected}, got ${actual}` +
1056
+ (componentName ? ` (in ${componentName})` : '') +
1057
+ '. Falling back to client render.'
1058
+ );
1059
+ }
1060
+ }
1061
+
1062
+ function hydrateNode(vnode, parent, _componentName) {
1020
1063
  if (vnode == null || typeof vnode === 'boolean') {
1021
1064
  return null;
1022
1065
  }
@@ -1028,9 +1071,9 @@ function hydrateNode(vnode, parent) {
1028
1071
 
1029
1072
  if (existing && existing.nodeType === 3) {
1030
1073
  // Reuse text node — check for mismatch in dev
1031
- if (isDevMode() && existing.textContent !== text) {
1032
- console.warn(
1033
- `[what] Hydration mismatch: expected text "${text}", got "${existing.textContent}"`
1074
+ if (__DEV__ && existing.textContent !== text) {
1075
+ reportHydrationMismatch(
1076
+ `text "${text}"`, `text "${existing.textContent}"`, _componentName
1034
1077
  );
1035
1078
  existing.textContent = text;
1036
1079
  }
@@ -1038,11 +1081,9 @@ function hydrateNode(vnode, parent) {
1038
1081
  }
1039
1082
 
1040
1083
  // Mismatch: expected text node, got element or nothing
1041
- if (isDevMode()) {
1042
- console.warn(
1043
- `[what] Hydration mismatch: expected text node "${text}", got ${existing ? existing.nodeName : 'nothing'}. Falling back to client render.`
1044
- );
1045
- }
1084
+ reportHydrationMismatch(
1085
+ `text node "${text}"`, existing ? existing.nodeName : 'nothing', _componentName
1086
+ );
1046
1087
  const textNode = document.createTextNode(text);
1047
1088
  if (existing) {
1048
1089
  parent.replaceChild(textNode, existing);
@@ -1056,7 +1097,7 @@ function hydrateNode(vnode, parent) {
1056
1097
  if (typeof vnode === 'function') {
1057
1098
  // Unwrap to get the initial value for hydration
1058
1099
  const initialValue = vnode();
1059
- let current = hydrateNode(initialValue, parent);
1100
+ let current = hydrateNode(initialValue, parent, _componentName);
1060
1101
 
1061
1102
  // Set up reactive effect for future updates (normal rendering path)
1062
1103
  effect(() => {
@@ -1073,7 +1114,7 @@ function hydrateNode(vnode, parent) {
1073
1114
  if (Array.isArray(vnode)) {
1074
1115
  const nodes = [];
1075
1116
  for (const child of vnode) {
1076
- const node = hydrateNode(child, parent);
1117
+ const node = hydrateNode(child, parent, _componentName);
1077
1118
  if (node) nodes.push(node);
1078
1119
  }
1079
1120
  return nodes.length === 1 ? nodes[0] : nodes;
@@ -1085,6 +1126,7 @@ function hydrateNode(vnode, parent) {
1085
1126
  if (typeof vnode.tag === 'function') {
1086
1127
  const componentStack = getComponentStack();
1087
1128
  const Component = vnode.tag;
1129
+ const compName = Component.displayName || Component.name || 'Anonymous';
1088
1130
  const props = vnode.props || {};
1089
1131
  const children = vnode.children || [];
1090
1132
 
@@ -1111,7 +1153,9 @@ function hydrateNode(vnode, parent) {
1111
1153
  result = Component({ ...props, children: propsChildren });
1112
1154
  } catch (error) {
1113
1155
  componentStack.pop();
1114
- console.error('[what] Error in component during hydration:', Component.name || 'Anonymous', error);
1156
+ if (__DEV__) {
1157
+ console.error('[what] Error in component during hydration:', compName, error);
1158
+ }
1115
1159
  return null;
1116
1160
  }
1117
1161
 
@@ -1128,7 +1172,7 @@ function hydrateNode(vnode, parent) {
1128
1172
  });
1129
1173
  }
1130
1174
 
1131
- return hydrateNode(result, parent);
1175
+ return hydrateNode(result, parent, compName);
1132
1176
  }
1133
1177
 
1134
1178
  // Element — claim existing DOM element
@@ -1139,6 +1183,16 @@ function hydrateNode(vnode, parent) {
1139
1183
  // Match! Reuse this element. Apply props/bindings.
1140
1184
  hydrateElementProps(existing, vnode.props || {});
1141
1185
 
1186
+ // Handle ref callback — this was previously missing during hydration
1187
+ if (vnode.props?.ref) {
1188
+ const ref = vnode.props.ref;
1189
+ if (typeof ref === 'function') {
1190
+ ref(existing);
1191
+ } else if (typeof ref === 'object' && ref !== null) {
1192
+ ref.current = existing;
1193
+ }
1194
+ }
1195
+
1142
1196
  // Hydrate children
1143
1197
  const savedCursor = _hydrationCursor;
1144
1198
  _hydrationCursor = { parent: existing, index: 0 };
@@ -1146,7 +1200,7 @@ function hydrateNode(vnode, parent) {
1146
1200
  const rawInner = vnode.props?.dangerouslySetInnerHTML?.__html;
1147
1201
  if (rawInner == null) {
1148
1202
  for (const child of vnode.children) {
1149
- hydrateNode(child, existing);
1203
+ hydrateNode(child, existing, _componentName);
1150
1204
  }
1151
1205
  }
1152
1206
 
@@ -1155,11 +1209,9 @@ function hydrateNode(vnode, parent) {
1155
1209
  }
1156
1210
 
1157
1211
  // Mismatch — fall back to client render for this subtree
1158
- if (isDevMode()) {
1159
- console.warn(
1160
- `[what] Hydration mismatch: expected <${vnode.tag}>, got ${existing ? existing.nodeName : 'nothing'}. Falling back to client render.`
1161
- );
1162
- }
1212
+ reportHydrationMismatch(
1213
+ `<${vnode.tag}>`, existing ? existing.nodeName : 'nothing', _componentName
1214
+ );
1163
1215
 
1164
1216
  // Create the element from scratch
1165
1217
  const newEl = document.createElement(vnode.tag);
@@ -1230,8 +1282,23 @@ function hydrateElementProps(el, props) {
1230
1282
  continue;
1231
1283
  }
1232
1284
 
1233
- // Static props — skip attributes already set from SSR
1234
- // Only attach non-serializable props or ones that may differ
1285
+ // Static props — verify attributes match in dev mode
1235
1286
  if (key === 'data-hk') continue;
1287
+
1288
+ // In dev mode, check that the server-rendered attribute matches the client value
1289
+ // to catch hydration mismatches early (e.g., class="foo" vs class="bar")
1290
+ if (__DEV__ && typeof value === 'string') {
1291
+ const attrName = key === 'className' ? 'class' : key === 'htmlFor' ? 'for' : key;
1292
+ const serverValue = el.getAttribute(attrName);
1293
+ if (serverValue !== null && serverValue !== value) {
1294
+ reportHydrationMismatch(
1295
+ `${attrName}="${value}"`,
1296
+ `${attrName}="${serverValue}"`,
1297
+ el.tagName.toLowerCase()
1298
+ );
1299
+ // Apply client value to fix the mismatch
1300
+ setProp(el, key, value);
1301
+ }
1302
+ }
1236
1303
  }
1237
1304
  }
package/testing.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // What Framework - Testing Utilities Type Definitions
2
2
 
3
- import { VNode, Signal } from './index';
3
+ import { VNode, Signal } from './index.js';
4
4
 
5
5
  // Setup and Cleanup
6
6
  export function setupDOM(): HTMLElement | null;