what-core 0.6.1 → 0.6.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.
Files changed (50) 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 +331 -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 +263 -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/src/agent-context.js +1 -1
  28. package/src/compiler.js +18 -0
  29. package/src/components.js +73 -27
  30. package/src/devtools.js +4 -0
  31. package/src/dom.js +7 -0
  32. package/src/guardrails.js +3 -4
  33. package/src/hooks.js +0 -11
  34. package/src/index.js +5 -9
  35. package/src/render.js +94 -24
  36. package/dist/a11y.js +0 -440
  37. package/dist/animation.js +0 -548
  38. package/dist/components.js +0 -229
  39. package/dist/data.js +0 -638
  40. package/dist/dom.js +0 -439
  41. package/dist/form.js +0 -509
  42. package/dist/h.js +0 -152
  43. package/dist/head.js +0 -51
  44. package/dist/helpers.js +0 -140
  45. package/dist/hooks.js +0 -210
  46. package/dist/reactive.js +0 -432
  47. package/dist/scheduler.js +0 -246
  48. package/dist/skeleton.js +0 -363
  49. package/dist/store.js +0 -83
  50. package/dist/what.js +0 -117
package/dist/index.js CHANGED
@@ -1,9 +1,6 @@
1
1
  // packages/core/src/reactive.js
2
2
  var __DEV__ = typeof process !== "undefined" ? true : true;
3
3
  var __devtools = null;
4
- function __setDevToolsHooks(hooks) {
5
- if (__DEV__) __devtools = hooks;
6
- }
7
4
  var currentEffect = null;
8
5
  var currentRoot = null;
9
6
  var currentOwner = null;
@@ -814,36 +811,60 @@ function reportError(error, startCtx) {
814
811
  return false;
815
812
  }
816
813
  function Show({ when, fallback = null, children }) {
817
- const condition = typeof when === "function" ? when() : when;
818
- return condition ? children : fallback;
814
+ if (typeof when === "function") {
815
+ return () => when() ? children : fallback;
816
+ }
817
+ return when ? children : fallback;
819
818
  }
820
- function For({ each: each2, fallback = null, children }) {
821
- const list = typeof each2 === "function" ? each2() : each2;
822
- if (!list || list.length === 0) return fallback;
819
+ function For({ each: each2, fallback = null, children, key: keyFn }) {
823
820
  const renderFn = Array.isArray(children) ? children[0] : children;
824
821
  if (typeof renderFn !== "function") {
825
- console.warn("[what] For: children must be a render function, e.g. <For each={items}>{(item) => ...}</For>");
822
+ if (__DEV__) {
823
+ console.warn("[what] For: children must be a render function, e.g. <For each={items}>{(item) => ...}</For>");
824
+ }
826
825
  return fallback;
827
826
  }
828
- return list.map((item, index) => {
827
+ const source = typeof each2 === "function" ? each2 : () => each2;
828
+ const mapFn = (item, index) => {
829
829
  const vnode = renderFn(item, index);
830
830
  if (vnode && typeof vnode === "object" && vnode.key == null) {
831
- if (item != null && typeof item === "object") {
832
- if (item.id != null) vnode.key = item.id;
833
- else if (item.key != null) vnode.key = item.key;
834
- } else if (typeof item === "string" || typeof item === "number") {
835
- vnode.key = item;
831
+ const rawItem = typeof item === "function" && item._signal ? item() : item;
832
+ if (rawItem != null && typeof rawItem === "object") {
833
+ if (rawItem.id != null) vnode.key = rawItem.id;
834
+ else if (rawItem.key != null) vnode.key = rawItem.key;
835
+ } else if (typeof rawItem === "string" || typeof rawItem === "number") {
836
+ vnode.key = rawItem;
836
837
  }
837
838
  }
838
839
  return vnode;
839
- });
840
+ };
841
+ return () => {
842
+ const list = source();
843
+ if (!list || list.length === 0) return fallback;
844
+ return list.map((item, i) => mapFn(item, i));
845
+ };
840
846
  }
841
847
  function Switch({ fallback = null, children }) {
842
848
  const kids = Array.isArray(children) ? children : [children];
849
+ const hasReactiveCondition = kids.some(
850
+ (child) => child && child.tag === Match && typeof child.props.when === "function"
851
+ );
852
+ if (hasReactiveCondition) {
853
+ return () => {
854
+ for (const child of kids) {
855
+ if (child && child.tag === Match) {
856
+ const condition = typeof child.props.when === "function" ? child.props.when() : child.props.when;
857
+ if (condition) {
858
+ return child.children;
859
+ }
860
+ }
861
+ }
862
+ return fallback;
863
+ };
864
+ }
843
865
  for (const child of kids) {
844
866
  if (child && child.tag === Match) {
845
- const condition = typeof child.props.when === "function" ? child.props.when() : child.props.when;
846
- if (condition) {
867
+ if (child.props.when) {
847
868
  return child.children;
848
869
  }
849
870
  }
@@ -854,7 +875,6 @@ function Match({ when, children }) {
854
875
  return { tag: Match, props: { when }, children, _vnode: true };
855
876
  }
856
877
  function Island({ component: Component, mode, mediaQuery, ...props }) {
857
- const placeholder = h("div", { "data-island": Component.name || "Island", "data-hydrate": mode });
858
878
  const wrapper = signal(null);
859
879
  const hydrated = signal(false);
860
880
  function doHydrate() {
@@ -925,7 +945,7 @@ function Island({ component: Component, mode, mediaQuery, ...props }) {
925
945
  return h(
926
946
  "div",
927
947
  { "data-island": Component.name || "Island", "data-hydrate": mode, ref: refCallback },
928
- hydrated() ? wrapper() : null
948
+ () => hydrated() ? wrapper() : null
929
949
  );
930
950
  }
931
951
 
@@ -1270,6 +1290,9 @@ function createDOM(vnode, parent, isSvg) {
1270
1290
  if (isVNode(vnode) && typeof vnode.tag === "function") {
1271
1291
  return createComponent(vnode, parent, isSvg);
1272
1292
  }
1293
+ if (isVNode(vnode) && (vnode.tag === "__errorBoundary" || vnode.tag === "__suspense" || vnode.tag === "__portal")) {
1294
+ return createComponent(vnode, parent, isSvg);
1295
+ }
1273
1296
  if (isVNode(vnode)) {
1274
1297
  return createElementFromVNode(vnode, parent, isSvg);
1275
1298
  }
@@ -1660,14 +1683,214 @@ function setProp(el, key, value, isSvg) {
1660
1683
  }
1661
1684
  }
1662
1685
 
1663
- // packages/core/src/render.js
1664
- function _$createComponent(Component, props, children) {
1665
- if (children && children.length > 0) {
1666
- const mergedChildren = children.length === 1 ? children[0] : children;
1667
- props = props ? { ...props, children: mergedChildren } : { children: mergedChildren };
1686
+ // packages/core/src/errors.js
1687
+ var ERROR_CODES = {
1688
+ INFINITE_EFFECT: {
1689
+ code: "ERR_INFINITE_EFFECT",
1690
+ severity: "error",
1691
+ template: 'Effect "{{effectName}}" exceeded 25 flush iterations \u2014 likely an infinite loop.',
1692
+ suggestion: "An effect is writing to a signal it also reads, creating a cycle. Use untrack() to read the signal without subscribing, or restructure so the write and read are in separate effects.",
1693
+ codeExample: `// Bad \u2014 reads and writes count, creating a cycle:
1694
+ effect(() => { count(count() + 1); });
1695
+
1696
+ // Good \u2014 use untrack() so the read doesn't subscribe:
1697
+ effect(() => { count(untrack(count) + 1); });
1698
+
1699
+ // Better \u2014 split into separate logic:
1700
+ const doubled = computed(() => count() * 2);`
1701
+ },
1702
+ MISSING_SIGNAL_READ: {
1703
+ code: "ERR_MISSING_SIGNAL_READ",
1704
+ severity: "warning",
1705
+ template: 'Signal "{{signalName}}" used without calling () \u2014 renders as "[Function]" instead of its value.',
1706
+ suggestion: "Signals are functions. Call them to read: count() not count. In JSX: {count()} not {count}.",
1707
+ codeExample: `// Bad \u2014 signal reference, not value:
1708
+ <span>{count}</span> // renders "[Function]"
1709
+
1710
+ // Good \u2014 call the signal:
1711
+ <span>{count()}</span> // renders the actual value`
1712
+ },
1713
+ HYDRATION_MISMATCH: {
1714
+ code: "ERR_HYDRATION_MISMATCH",
1715
+ severity: "error",
1716
+ template: 'Hydration mismatch in component "{{component}}": server rendered "{{serverHTML}}" but client expects "{{clientHTML}}".',
1717
+ suggestion: "Ensure server and client render identical initial HTML. Avoid reading browser-only APIs (window, localStorage) during the initial render. Use onMount() for client-only logic.",
1718
+ codeExample: `// Bad \u2014 different on server vs client:
1719
+ function App() {
1720
+ return <p>{window.innerWidth}</p>;
1721
+ }
1722
+
1723
+ // Good \u2014 use onMount for client-only values:
1724
+ function App() {
1725
+ const width = signal(0);
1726
+ onMount(() => width(window.innerWidth));
1727
+ return <p>{width()}</p>;
1728
+ }`
1729
+ },
1730
+ ORPHAN_EFFECT: {
1731
+ code: "ERR_ORPHAN_EFFECT",
1732
+ severity: "warning",
1733
+ template: 'Effect "{{effectName}}" was created outside a reactive root \u2014 it will never be cleaned up.',
1734
+ suggestion: "Wrap effect creation in createRoot() or create effects inside component functions where they are automatically tracked.",
1735
+ codeExample: `// Bad \u2014 orphaned, leaks memory:
1736
+ effect(() => console.log(count()));
1737
+
1738
+ // Good \u2014 inside a root with cleanup:
1739
+ createRoot(dispose => {
1740
+ effect(() => console.log(count()));
1741
+ // later: dispose() cleans up
1742
+ });`
1743
+ },
1744
+ SIGNAL_WRITE_IN_RENDER: {
1745
+ code: "ERR_SIGNAL_WRITE_IN_RENDER",
1746
+ severity: "error",
1747
+ template: 'Signal "{{signalName}}" written during render of component "{{component}}". This triggers re-execution.',
1748
+ suggestion: "Move signal writes into event handlers, effects, or onMount(). The component body should only read signals, not write them.",
1749
+ codeExample: `// Bad \u2014 write during render:
1750
+ function Counter() {
1751
+ count(count() + 1); // triggers infinite loop
1752
+ return <span>{count()}</span>;
1753
+ }
1754
+
1755
+ // Good \u2014 write in event handler:
1756
+ function Counter() {
1757
+ return <button onclick={() => count(c => c + 1)}>{count()}</button>;
1758
+ }`
1759
+ },
1760
+ MISSING_CLEANUP: {
1761
+ code: "ERR_MISSING_CLEANUP",
1762
+ severity: "warning",
1763
+ template: 'Effect sets up "{{resource}}" but does not return a cleanup function.',
1764
+ suggestion: "Effects that add event listeners, set timers, or open connections should return a cleanup function to prevent memory leaks.",
1765
+ codeExample: `// Bad \u2014 no cleanup:
1766
+ effect(() => {
1767
+ window.addEventListener('resize', handler);
1768
+ });
1769
+
1770
+ // Good \u2014 return cleanup:
1771
+ effect(() => {
1772
+ window.addEventListener('resize', handler);
1773
+ return () => window.removeEventListener('resize', handler);
1774
+ });`
1775
+ },
1776
+ UNSAFE_INNERHTML: {
1777
+ code: "ERR_UNSAFE_INNERHTML",
1778
+ severity: "warning",
1779
+ template: "innerHTML set on element without using the __html safety marker.",
1780
+ suggestion: "Use the html tagged template literal or pass { __html: content } to mark innerHTML as intentional and reviewed.",
1781
+ codeExample: `// Bad \u2014 raw innerHTML (XSS risk):
1782
+ <div innerHTML={userInput} />
1783
+
1784
+ // Good \u2014 explicit opt-in:
1785
+ <div innerHTML={{ __html: sanitizedContent }} />
1786
+
1787
+ // Better \u2014 use the html template literal:
1788
+ html\`<div>\${sanitizedContent}</div>\``
1789
+ },
1790
+ MISSING_KEY: {
1791
+ code: "ERR_MISSING_KEY",
1792
+ severity: "warning",
1793
+ template: 'List rendered without key prop in component "{{component}}". Items may re-order incorrectly.',
1794
+ suggestion: "Add a unique key prop to each item in a list. Use a stable identifier (like an ID), not the array index.",
1795
+ codeExample: `// Bad \u2014 no key:
1796
+ <For each={items()}>{item => <li>{item.name}</li>}</For>
1797
+
1798
+ // Good \u2014 stable key:
1799
+ <For each={items()}>{item => <li key={item.id}>{item.name}</li>}</For>`
1800
+ }
1801
+ };
1802
+ var WhatError = class extends Error {
1803
+ constructor({ code, message, suggestion, file, line, component, signal: signal2, effect: effect2 }) {
1804
+ super(message);
1805
+ this.name = "WhatError";
1806
+ this.code = code;
1807
+ this.suggestion = suggestion;
1808
+ this.file = file;
1809
+ this.line = line;
1810
+ this.component = component;
1811
+ this.signal = signal2;
1812
+ this.effect = effect2;
1813
+ }
1814
+ toJSON() {
1815
+ return {
1816
+ code: this.code,
1817
+ message: this.message,
1818
+ suggestion: this.suggestion,
1819
+ file: this.file,
1820
+ line: this.line,
1821
+ component: this.component,
1822
+ signal: this.signal,
1823
+ effect: this.effect
1824
+ };
1825
+ }
1826
+ };
1827
+ function createWhatError(errorCode, context = {}) {
1828
+ const def = typeof errorCode === "string" ? ERROR_CODES[errorCode] : errorCode;
1829
+ if (!def) {
1830
+ return new WhatError({
1831
+ code: "ERR_UNKNOWN",
1832
+ message: `Unknown error: ${errorCode}`,
1833
+ suggestion: "Check the error code and try again."
1834
+ });
1668
1835
  }
1669
- return createDOM({ tag: Component, props: props || {}, children: children || [], key: null, _vnode: true });
1836
+ let message = def.template;
1837
+ for (const [key, val] of Object.entries(context)) {
1838
+ message = message.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), String(val));
1839
+ }
1840
+ message = message.replace(/\{\{[^}]+\}\}/g, "(unknown)");
1841
+ return new WhatError({
1842
+ code: def.code,
1843
+ message,
1844
+ suggestion: def.suggestion,
1845
+ file: context.file,
1846
+ line: context.line,
1847
+ component: context.component,
1848
+ signal: context.signal || context.signalName,
1849
+ effect: context.effect || context.effectName
1850
+ });
1670
1851
  }
1852
+ var collectedErrors = [];
1853
+ var MAX_COLLECTED = 200;
1854
+ function collectError(whatError) {
1855
+ if (!__DEV__) return;
1856
+ collectedErrors.push({
1857
+ ...whatError.toJSON(),
1858
+ timestamp: Date.now()
1859
+ });
1860
+ if (collectedErrors.length > MAX_COLLECTED) {
1861
+ collectedErrors = collectedErrors.slice(-MAX_COLLECTED);
1862
+ }
1863
+ }
1864
+ function getCollectedErrors(since) {
1865
+ if (since) return collectedErrors.filter((e) => e.timestamp > since);
1866
+ return collectedErrors.slice();
1867
+ }
1868
+ function clearCollectedErrors() {
1869
+ collectedErrors = [];
1870
+ }
1871
+ function classifyError(err, context = {}) {
1872
+ const msg = err?.message || String(err);
1873
+ if (msg.includes("infinite effect loop") || msg.includes("25 iterations")) {
1874
+ return createWhatError("INFINITE_EFFECT", context);
1875
+ }
1876
+ if (msg.includes("hydration") || msg.includes("Hydration")) {
1877
+ return createWhatError("HYDRATION_MISMATCH", context);
1878
+ }
1879
+ if (msg.includes("Signal.set() called inside a computed")) {
1880
+ return createWhatError("SIGNAL_WRITE_IN_RENDER", {
1881
+ ...context,
1882
+ signalName: msg.match(/signal: (\w+)/)?.[1] || context.signalName
1883
+ });
1884
+ }
1885
+ return new WhatError({
1886
+ code: "ERR_RUNTIME",
1887
+ message: msg,
1888
+ suggestion: "Check the stack trace and component context for more details.",
1889
+ ...context
1890
+ });
1891
+ }
1892
+
1893
+ // packages/core/src/render.js
1671
1894
  var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "formAction"]);
1672
1895
  function isSafeUrl(url) {
1673
1896
  if (typeof url !== "string") return true;
@@ -2366,6 +2589,7 @@ function setProp2(el, key, value) {
2366
2589
  else if (value && typeof value === "object") value.current = el;
2367
2590
  return;
2368
2591
  }
2592
+ if (key === "key") return;
2369
2593
  if (URL_ATTRS.has(key) || URL_ATTRS.has(key.toLowerCase())) {
2370
2594
  if (!isSafeUrl(value)) {
2371
2595
  if (typeof console !== "undefined") {
@@ -2446,8 +2670,14 @@ function isHydrating() {
2446
2670
  function hydrate(vnode, container) {
2447
2671
  _isHydrating = true;
2448
2672
  _hydrationCursor = { parent: container, index: 0 };
2673
+ _hydrationMismatchCount = 0;
2449
2674
  try {
2450
2675
  const result = hydrateNode(vnode, container);
2676
+ if (__DEV__ && _hydrationMismatchCount > 0) {
2677
+ console.warn(
2678
+ `[what] Hydration completed with ${_hydrationMismatchCount} mismatch${_hydrationMismatchCount === 1 ? "" : "es"}. See previous warnings for details. This usually means server and client render different initial HTML.`
2679
+ );
2680
+ }
2451
2681
  return result;
2452
2682
  } finally {
2453
2683
  _isHydrating = false;
@@ -2470,10 +2700,37 @@ function claimNode(parent) {
2470
2700
  }
2471
2701
  return null;
2472
2702
  }
2473
- function isDevMode() {
2474
- return typeof process !== "undefined" && true;
2703
+ var _hydrationMismatchCount = 0;
2704
+ function getHydrationMismatchCount() {
2705
+ return _hydrationMismatchCount;
2706
+ }
2707
+ function reportHydrationMismatch(expected, actual, componentName) {
2708
+ _hydrationMismatchCount++;
2709
+ if (__DEV__) {
2710
+ const context = {
2711
+ component: componentName || "unknown",
2712
+ serverHTML: actual,
2713
+ clientHTML: expected
2714
+ };
2715
+ const whatError = createWhatError("HYDRATION_MISMATCH", context);
2716
+ collectError(whatError);
2717
+ if (__devtools?.onHydrationMismatch) {
2718
+ __devtools.onHydrationMismatch({
2719
+ component: componentName || "unknown",
2720
+ expected,
2721
+ actual,
2722
+ mismatchCount: _hydrationMismatchCount
2723
+ });
2724
+ }
2725
+ if (__devtools?.onError) {
2726
+ __devtools.onError(whatError, { type: "hydration", component: componentName });
2727
+ }
2728
+ console.warn(
2729
+ `[what] Hydration mismatch: expected ${expected}, got ${actual}` + (componentName ? ` (in ${componentName})` : "") + ". Falling back to client render."
2730
+ );
2731
+ }
2475
2732
  }
2476
- function hydrateNode(vnode, parent) {
2733
+ function hydrateNode(vnode, parent, _componentName) {
2477
2734
  if (vnode == null || typeof vnode === "boolean") {
2478
2735
  return null;
2479
2736
  }
@@ -2481,19 +2738,21 @@ function hydrateNode(vnode, parent) {
2481
2738
  const existing = claimNode(parent);
2482
2739
  const text = String(vnode);
2483
2740
  if (existing && existing.nodeType === 3) {
2484
- if (isDevMode() && existing.textContent !== text) {
2485
- console.warn(
2486
- `[what] Hydration mismatch: expected text "${text}", got "${existing.textContent}"`
2741
+ if (__DEV__ && existing.textContent !== text) {
2742
+ reportHydrationMismatch(
2743
+ `text "${text}"`,
2744
+ `text "${existing.textContent}"`,
2745
+ _componentName
2487
2746
  );
2488
2747
  existing.textContent = text;
2489
2748
  }
2490
2749
  return existing;
2491
2750
  }
2492
- if (isDevMode()) {
2493
- console.warn(
2494
- `[what] Hydration mismatch: expected text node "${text}", got ${existing ? existing.nodeName : "nothing"}. Falling back to client render.`
2495
- );
2496
- }
2751
+ reportHydrationMismatch(
2752
+ `text node "${text}"`,
2753
+ existing ? existing.nodeName : "nothing",
2754
+ _componentName
2755
+ );
2497
2756
  const textNode2 = document.createTextNode(text);
2498
2757
  if (existing) {
2499
2758
  parent.replaceChild(textNode2, existing);
@@ -2504,7 +2763,7 @@ function hydrateNode(vnode, parent) {
2504
2763
  }
2505
2764
  if (typeof vnode === "function") {
2506
2765
  const initialValue = vnode();
2507
- let current = hydrateNode(initialValue, parent);
2766
+ let current = hydrateNode(initialValue, parent, _componentName);
2508
2767
  effect(() => {
2509
2768
  const value = vnode();
2510
2769
  if (!_isHydrating) {
@@ -2516,7 +2775,7 @@ function hydrateNode(vnode, parent) {
2516
2775
  if (Array.isArray(vnode)) {
2517
2776
  const nodes = [];
2518
2777
  for (const child of vnode) {
2519
- const node = hydrateNode(child, parent);
2778
+ const node = hydrateNode(child, parent, _componentName);
2520
2779
  if (node) nodes.push(node);
2521
2780
  }
2522
2781
  return nodes.length === 1 ? nodes[0] : nodes;
@@ -2525,6 +2784,7 @@ function hydrateNode(vnode, parent) {
2525
2784
  if (typeof vnode.tag === "function") {
2526
2785
  const componentStack2 = getComponentStack();
2527
2786
  const Component = vnode.tag;
2787
+ const compName = Component.displayName || Component.name || "Anonymous";
2528
2788
  const props = vnode.props || {};
2529
2789
  const children = vnode.children || [];
2530
2790
  const ctx = {
@@ -2545,7 +2805,9 @@ function hydrateNode(vnode, parent) {
2545
2805
  result = Component({ ...props, children: propsChildren });
2546
2806
  } catch (error) {
2547
2807
  componentStack2.pop();
2548
- console.error("[what] Error in component during hydration:", Component.name || "Anonymous", error);
2808
+ if (__DEV__) {
2809
+ console.error("[what] Error in component during hydration:", compName, error);
2810
+ }
2549
2811
  return null;
2550
2812
  }
2551
2813
  componentStack2.pop();
@@ -2562,28 +2824,36 @@ function hydrateNode(vnode, parent) {
2562
2824
  }
2563
2825
  });
2564
2826
  }
2565
- return hydrateNode(result, parent);
2827
+ return hydrateNode(result, parent, compName);
2566
2828
  }
2567
2829
  const existing = claimNode(parent);
2568
2830
  const expectedTag = vnode.tag.toUpperCase();
2569
2831
  if (existing && existing.nodeType === 1 && existing.nodeName === expectedTag) {
2570
2832
  hydrateElementProps(existing, vnode.props || {});
2833
+ if (vnode.props?.ref) {
2834
+ const ref = vnode.props.ref;
2835
+ if (typeof ref === "function") {
2836
+ ref(existing);
2837
+ } else if (typeof ref === "object" && ref !== null) {
2838
+ ref.current = existing;
2839
+ }
2840
+ }
2571
2841
  const savedCursor = _hydrationCursor;
2572
2842
  _hydrationCursor = { parent: existing, index: 0 };
2573
2843
  const rawInner = vnode.props?.dangerouslySetInnerHTML?.__html;
2574
2844
  if (rawInner == null) {
2575
2845
  for (const child of vnode.children) {
2576
- hydrateNode(child, existing);
2846
+ hydrateNode(child, existing, _componentName);
2577
2847
  }
2578
2848
  }
2579
2849
  _hydrationCursor = savedCursor;
2580
2850
  return existing;
2581
2851
  }
2582
- if (isDevMode()) {
2583
- console.warn(
2584
- `[what] Hydration mismatch: expected <${vnode.tag}>, got ${existing ? existing.nodeName : "nothing"}. Falling back to client render.`
2585
- );
2586
- }
2852
+ reportHydrationMismatch(
2853
+ `<${vnode.tag}>`,
2854
+ existing ? existing.nodeName : "nothing",
2855
+ _componentName
2856
+ );
2587
2857
  const newEl = document.createElement(vnode.tag);
2588
2858
  for (const key in vnode.props || {}) {
2589
2859
  if (key === "children" || key === "key") continue;
@@ -2640,6 +2910,18 @@ function hydrateElementProps(el, props) {
2640
2910
  continue;
2641
2911
  }
2642
2912
  if (key === "data-hk") continue;
2913
+ if (__DEV__ && typeof value === "string") {
2914
+ const attrName = key === "className" ? "class" : key === "htmlFor" ? "for" : key;
2915
+ const serverValue = el.getAttribute(attrName);
2916
+ if (serverValue !== null && serverValue !== value) {
2917
+ reportHydrationMismatch(
2918
+ `${attrName}="${value}"`,
2919
+ `${attrName}="${serverValue}"`,
2920
+ el.tagName.toLowerCase()
2921
+ );
2922
+ setProp2(el, key, value);
2923
+ }
2924
+ }
2643
2925
  }
2644
2926
  }
2645
2927
 
@@ -2657,16 +2939,6 @@ function getHook(ctx) {
2657
2939
  const index = ctx.hookIndex++;
2658
2940
  return { index, exists: index < ctx.hooks.length };
2659
2941
  }
2660
- function useState(initial) {
2661
- const ctx = getCtx("useState");
2662
- const { index, exists } = getHook(ctx);
2663
- if (!exists) {
2664
- const s2 = signal(typeof initial === "function" ? initial() : initial);
2665
- ctx.hooks[index] = s2;
2666
- }
2667
- const s = ctx.hooks[index];
2668
- return [s, s.set];
2669
- }
2670
2942
  function useSignal(initial) {
2671
2943
  const ctx = getCtx("useSignal");
2672
2944
  const { index, exists } = getHook(ctx);
@@ -2683,97 +2955,6 @@ function useComputed(fn) {
2683
2955
  }
2684
2956
  return ctx.hooks[index];
2685
2957
  }
2686
- function useEffect(fn, deps) {
2687
- const ctx = getCtx("useEffect");
2688
- const { index, exists } = getHook(ctx);
2689
- if (!exists) {
2690
- ctx.hooks[index] = { cleanup: null, dispose: null };
2691
- }
2692
- if (__DEV__ && Array.isArray(deps) && deps.length > 0) {
2693
- for (let i = 0; i < deps.length; i++) {
2694
- const dep = deps[i];
2695
- if (dep != null && typeof dep !== "function") {
2696
- console.warn(
2697
- `[what] useEffect dep at index ${i} is not a function. Did you mean to pass a signal? Use count instead of count()`
2698
- );
2699
- }
2700
- }
2701
- }
2702
- const hook = ctx.hooks[index];
2703
- if (hook.dispose) return;
2704
- if (deps === void 0) {
2705
- queueMicrotask(() => {
2706
- if (ctx.disposed) return;
2707
- hook.dispose = effect(() => {
2708
- if (hook.cleanup) {
2709
- try {
2710
- hook.cleanup();
2711
- } catch (e) {
2712
- }
2713
- hook.cleanup = null;
2714
- }
2715
- const result = fn();
2716
- if (typeof result === "function") hook.cleanup = result;
2717
- });
2718
- ctx.effects = ctx.effects || [];
2719
- ctx.effects.push(hook.dispose);
2720
- });
2721
- } else if (deps.length === 0) {
2722
- queueMicrotask(() => {
2723
- if (ctx.disposed) return;
2724
- const result = fn();
2725
- if (typeof result === "function") hook.cleanup = result;
2726
- });
2727
- hook.dispose = true;
2728
- } else {
2729
- queueMicrotask(() => {
2730
- if (ctx.disposed) return;
2731
- hook.dispose = effect(() => {
2732
- for (let i = 0; i < deps.length; i++) {
2733
- const dep = deps[i];
2734
- if (typeof dep === "function" && dep._signal) {
2735
- dep();
2736
- }
2737
- }
2738
- if (hook.cleanup) {
2739
- try {
2740
- hook.cleanup();
2741
- } catch (e) {
2742
- }
2743
- hook.cleanup = null;
2744
- }
2745
- const result = untrack(() => fn());
2746
- if (typeof result === "function") hook.cleanup = result;
2747
- });
2748
- ctx.effects = ctx.effects || [];
2749
- ctx.effects.push(hook.dispose);
2750
- });
2751
- }
2752
- }
2753
- function useMemo(fn, deps) {
2754
- const ctx = getCtx("useMemo");
2755
- const { index, exists } = getHook(ctx);
2756
- if (!exists) {
2757
- ctx.hooks[index] = { computed: computed(fn) };
2758
- }
2759
- return ctx.hooks[index].computed;
2760
- }
2761
- function useCallback(fn, deps) {
2762
- const ctx = getCtx("useCallback");
2763
- const { index, exists } = getHook(ctx);
2764
- if (!exists) {
2765
- ctx.hooks[index] = { callback: fn };
2766
- }
2767
- return ctx.hooks[index].callback;
2768
- }
2769
- function useRef(initial) {
2770
- const ctx = getCtx("useRef");
2771
- const { index, exists } = getHook(ctx);
2772
- if (!exists) {
2773
- ctx.hooks[index] = { current: initial };
2774
- }
2775
- return ctx.hooks[index];
2776
- }
2777
2958
  function useContext(context) {
2778
2959
  let ctx = getCurrentComponent();
2779
2960
  if (__DEV__ && !ctx) {
@@ -4842,18 +5023,6 @@ function clearCache() {
4842
5023
  lastFetchTimestamps.clear();
4843
5024
  inFlightRequests.clear();
4844
5025
  }
4845
- function __getCacheSnapshot() {
4846
- const entries = [];
4847
- for (const [key, sig] of cacheSignals) {
4848
- entries.push({
4849
- key,
4850
- data: sig.peek(),
4851
- error: errorSignals.has(key) ? errorSignals.get(key).peek() : null,
4852
- isValidating: validatingSignals.has(key) ? validatingSignals.get(key).peek() : false
4853
- });
4854
- }
4855
- return entries;
4856
- }
4857
5026
 
4858
5027
  // packages/core/src/form.js
4859
5028
  function useForm(options = {}) {
@@ -5311,213 +5480,6 @@ function ErrorMessage({ name, formState, errors, render }) {
5311
5480
  return h("span", { class: "what-error", role: "alert" }, error.message);
5312
5481
  }
5313
5482
 
5314
- // packages/core/src/errors.js
5315
- var ERROR_CODES = {
5316
- INFINITE_EFFECT: {
5317
- code: "ERR_INFINITE_EFFECT",
5318
- severity: "error",
5319
- template: 'Effect "{{effectName}}" exceeded 25 flush iterations \u2014 likely an infinite loop.',
5320
- suggestion: "An effect is writing to a signal it also reads, creating a cycle. Use untrack() to read the signal without subscribing, or restructure so the write and read are in separate effects.",
5321
- codeExample: `// Bad \u2014 reads and writes count, creating a cycle:
5322
- effect(() => { count(count() + 1); });
5323
-
5324
- // Good \u2014 use untrack() so the read doesn't subscribe:
5325
- effect(() => { count(untrack(count) + 1); });
5326
-
5327
- // Better \u2014 split into separate logic:
5328
- const doubled = computed(() => count() * 2);`
5329
- },
5330
- MISSING_SIGNAL_READ: {
5331
- code: "ERR_MISSING_SIGNAL_READ",
5332
- severity: "warning",
5333
- template: 'Signal "{{signalName}}" used without calling () \u2014 renders as "[Function]" instead of its value.',
5334
- suggestion: "Signals are functions. Call them to read: count() not count. In JSX: {count()} not {count}.",
5335
- codeExample: `// Bad \u2014 signal reference, not value:
5336
- <span>{count}</span> // renders "[Function]"
5337
-
5338
- // Good \u2014 call the signal:
5339
- <span>{count()}</span> // renders the actual value`
5340
- },
5341
- HYDRATION_MISMATCH: {
5342
- code: "ERR_HYDRATION_MISMATCH",
5343
- severity: "error",
5344
- template: 'Hydration mismatch in component "{{component}}": server rendered "{{serverHTML}}" but client expects "{{clientHTML}}".',
5345
- suggestion: "Ensure server and client render identical initial HTML. Avoid reading browser-only APIs (window, localStorage) during the initial render. Use onMount() for client-only logic.",
5346
- codeExample: `// Bad \u2014 different on server vs client:
5347
- function App() {
5348
- return <p>{window.innerWidth}</p>;
5349
- }
5350
-
5351
- // Good \u2014 use onMount for client-only values:
5352
- function App() {
5353
- const width = signal(0);
5354
- onMount(() => width(window.innerWidth));
5355
- return <p>{width()}</p>;
5356
- }`
5357
- },
5358
- ORPHAN_EFFECT: {
5359
- code: "ERR_ORPHAN_EFFECT",
5360
- severity: "warning",
5361
- template: 'Effect "{{effectName}}" was created outside a reactive root \u2014 it will never be cleaned up.',
5362
- suggestion: "Wrap effect creation in createRoot() or create effects inside component functions where they are automatically tracked.",
5363
- codeExample: `// Bad \u2014 orphaned, leaks memory:
5364
- effect(() => console.log(count()));
5365
-
5366
- // Good \u2014 inside a root with cleanup:
5367
- createRoot(dispose => {
5368
- effect(() => console.log(count()));
5369
- // later: dispose() cleans up
5370
- });`
5371
- },
5372
- SIGNAL_WRITE_IN_RENDER: {
5373
- code: "ERR_SIGNAL_WRITE_IN_RENDER",
5374
- severity: "error",
5375
- template: 'Signal "{{signalName}}" written during render of component "{{component}}". This triggers re-execution.',
5376
- suggestion: "Move signal writes into event handlers, effects, or onMount(). The component body should only read signals, not write them.",
5377
- codeExample: `// Bad \u2014 write during render:
5378
- function Counter() {
5379
- count(count() + 1); // triggers infinite loop
5380
- return <span>{count()}</span>;
5381
- }
5382
-
5383
- // Good \u2014 write in event handler:
5384
- function Counter() {
5385
- return <button onclick={() => count(c => c + 1)}>{count()}</button>;
5386
- }`
5387
- },
5388
- MISSING_CLEANUP: {
5389
- code: "ERR_MISSING_CLEANUP",
5390
- severity: "warning",
5391
- template: 'Effect sets up "{{resource}}" but does not return a cleanup function.',
5392
- suggestion: "Effects that add event listeners, set timers, or open connections should return a cleanup function to prevent memory leaks.",
5393
- codeExample: `// Bad \u2014 no cleanup:
5394
- effect(() => {
5395
- window.addEventListener('resize', handler);
5396
- });
5397
-
5398
- // Good \u2014 return cleanup:
5399
- effect(() => {
5400
- window.addEventListener('resize', handler);
5401
- return () => window.removeEventListener('resize', handler);
5402
- });`
5403
- },
5404
- UNSAFE_INNERHTML: {
5405
- code: "ERR_UNSAFE_INNERHTML",
5406
- severity: "warning",
5407
- template: "innerHTML set on element without using the __html safety marker.",
5408
- suggestion: "Use the html tagged template literal or pass { __html: content } to mark innerHTML as intentional and reviewed.",
5409
- codeExample: `// Bad \u2014 raw innerHTML (XSS risk):
5410
- <div innerHTML={userInput} />
5411
-
5412
- // Good \u2014 explicit opt-in:
5413
- <div innerHTML={{ __html: sanitizedContent }} />
5414
-
5415
- // Better \u2014 use the html template literal:
5416
- html\`<div>\${sanitizedContent}</div>\``
5417
- },
5418
- MISSING_KEY: {
5419
- code: "ERR_MISSING_KEY",
5420
- severity: "warning",
5421
- template: 'List rendered without key prop in component "{{component}}". Items may re-order incorrectly.',
5422
- suggestion: "Add a unique key prop to each item in a list. Use a stable identifier (like an ID), not the array index.",
5423
- codeExample: `// Bad \u2014 no key:
5424
- <For each={items()}>{item => <li>{item.name}</li>}</For>
5425
-
5426
- // Good \u2014 stable key:
5427
- <For each={items()}>{item => <li key={item.id}>{item.name}</li>}</For>`
5428
- }
5429
- };
5430
- var WhatError = class extends Error {
5431
- constructor({ code, message, suggestion, file, line, component, signal: signal2, effect: effect2 }) {
5432
- super(message);
5433
- this.name = "WhatError";
5434
- this.code = code;
5435
- this.suggestion = suggestion;
5436
- this.file = file;
5437
- this.line = line;
5438
- this.component = component;
5439
- this.signal = signal2;
5440
- this.effect = effect2;
5441
- }
5442
- toJSON() {
5443
- return {
5444
- code: this.code,
5445
- message: this.message,
5446
- suggestion: this.suggestion,
5447
- file: this.file,
5448
- line: this.line,
5449
- component: this.component,
5450
- signal: this.signal,
5451
- effect: this.effect
5452
- };
5453
- }
5454
- };
5455
- function createWhatError(errorCode, context = {}) {
5456
- const def = typeof errorCode === "string" ? ERROR_CODES[errorCode] : errorCode;
5457
- if (!def) {
5458
- return new WhatError({
5459
- code: "ERR_UNKNOWN",
5460
- message: `Unknown error: ${errorCode}`,
5461
- suggestion: "Check the error code and try again."
5462
- });
5463
- }
5464
- let message = def.template;
5465
- for (const [key, val] of Object.entries(context)) {
5466
- message = message.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), String(val));
5467
- }
5468
- message = message.replace(/\{\{[^}]+\}\}/g, "(unknown)");
5469
- return new WhatError({
5470
- code: def.code,
5471
- message,
5472
- suggestion: def.suggestion,
5473
- file: context.file,
5474
- line: context.line,
5475
- component: context.component,
5476
- signal: context.signal || context.signalName,
5477
- effect: context.effect || context.effectName
5478
- });
5479
- }
5480
- var collectedErrors = [];
5481
- var MAX_COLLECTED = 200;
5482
- function collectError(whatError) {
5483
- if (!__DEV__) return;
5484
- collectedErrors.push({
5485
- ...whatError.toJSON(),
5486
- timestamp: Date.now()
5487
- });
5488
- if (collectedErrors.length > MAX_COLLECTED) {
5489
- collectedErrors = collectedErrors.slice(-MAX_COLLECTED);
5490
- }
5491
- }
5492
- function getCollectedErrors(since) {
5493
- if (since) return collectedErrors.filter((e) => e.timestamp > since);
5494
- return collectedErrors.slice();
5495
- }
5496
- function clearCollectedErrors() {
5497
- collectedErrors = [];
5498
- }
5499
- function classifyError(err, context = {}) {
5500
- const msg = err?.message || String(err);
5501
- if (msg.includes("infinite effect loop") || msg.includes("25 iterations")) {
5502
- return createWhatError("INFINITE_EFFECT", context);
5503
- }
5504
- if (msg.includes("hydration") || msg.includes("Hydration")) {
5505
- return createWhatError("HYDRATION_MISMATCH", context);
5506
- }
5507
- if (msg.includes("Signal.set() called inside a computed")) {
5508
- return createWhatError("SIGNAL_WRITE_IN_RENDER", {
5509
- ...context,
5510
- signalName: msg.match(/signal: (\w+)/)?.[1] || context.signalName
5511
- });
5512
- }
5513
- return new WhatError({
5514
- code: "ERR_RUNTIME",
5515
- message: msg,
5516
- suggestion: "Check the stack trace and component context for more details.",
5517
- ...context
5518
- });
5519
- }
5520
-
5521
5483
  // packages/core/src/guardrails.js
5522
5484
  var guardrails = {
5523
5485
  signalReadDetection: true,
@@ -5578,10 +5540,8 @@ var VALID_EXPORTS = /* @__PURE__ */ new Set([
5578
5540
  "getOwner",
5579
5541
  "runWithOwner",
5580
5542
  "onRootCleanup",
5581
- "__setDevToolsHooks",
5582
5543
  // Rendering
5583
5544
  "template",
5584
- "_template",
5585
5545
  "svgTemplate",
5586
5546
  "insert",
5587
5547
  "mapArray",
@@ -5592,7 +5552,6 @@ var VALID_EXPORTS = /* @__PURE__ */ new Set([
5592
5552
  "classList",
5593
5553
  "hydrate",
5594
5554
  "isHydrating",
5595
- "_$createComponent",
5596
5555
  // JSX
5597
5556
  "h",
5598
5557
  "Fragment",
@@ -5705,7 +5664,6 @@ var VALID_EXPORTS = /* @__PURE__ */ new Set([
5705
5664
  "setQueryData",
5706
5665
  "getQueryData",
5707
5666
  "clearCache",
5708
- "__getCacheSnapshot",
5709
5667
  // Form
5710
5668
  "useForm",
5711
5669
  "useField",
@@ -5767,7 +5725,7 @@ function levenshtein(a, b) {
5767
5725
  }
5768
5726
 
5769
5727
  // packages/core/src/agent-context.js
5770
- var VERSION = "0.6.0";
5728
+ var VERSION = "0.6.3";
5771
5729
  var mountedComponents2 = [];
5772
5730
  function registerComponent(component) {
5773
5731
  if (!__DEV__) return;
@@ -5880,11 +5838,6 @@ export {
5880
5838
  Textarea,
5881
5839
  VisuallyHidden,
5882
5840
  WhatError,
5883
- _$createComponent,
5884
- _$templateImpl as _$template,
5885
- __getCacheSnapshot,
5886
- __setDevToolsHooks,
5887
- template as _template,
5888
5841
  announce,
5889
5842
  announceAssertive,
5890
5843
  atom,
@@ -5918,6 +5871,7 @@ export {
5918
5871
  getCollectedErrors,
5919
5872
  getGuardrailConfig,
5920
5873
  getHealth,
5874
+ getHydrationMismatchCount,
5921
5875
  getMountedComponents,
5922
5876
  getOwner,
5923
5877
  getQueryData,
@@ -5974,12 +5928,10 @@ export {
5974
5928
  useAriaChecked,
5975
5929
  useAriaExpanded,
5976
5930
  useAriaSelected,
5977
- useCallback,
5978
5931
  useClickOutside,
5979
5932
  useComputed,
5980
5933
  useContext,
5981
5934
  useDescribedBy,
5982
- useEffect,
5983
5935
  useFetch,
5984
5936
  useField,
5985
5937
  useFocus,
@@ -5993,16 +5945,13 @@ export {
5993
5945
  useLabelledBy,
5994
5946
  useLocalStorage,
5995
5947
  useMediaQuery,
5996
- useMemo,
5997
5948
  useQuery,
5998
5949
  useReducer,
5999
- useRef,
6000
5950
  useRovingTabIndex,
6001
5951
  useSWR,
6002
5952
  useScheduledEffect,
6003
5953
  useSignal,
6004
5954
  useSkeleton,
6005
- useState,
6006
5955
  useTransition,
6007
5956
  validateImports,
6008
5957
  yupResolver,