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
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;
@@ -2447,8 +2670,14 @@ function isHydrating() {
2447
2670
  function hydrate(vnode, container) {
2448
2671
  _isHydrating = true;
2449
2672
  _hydrationCursor = { parent: container, index: 0 };
2673
+ _hydrationMismatchCount = 0;
2450
2674
  try {
2451
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
+ }
2452
2681
  return result;
2453
2682
  } finally {
2454
2683
  _isHydrating = false;
@@ -2471,10 +2700,37 @@ function claimNode(parent) {
2471
2700
  }
2472
2701
  return null;
2473
2702
  }
2474
- function isDevMode() {
2475
- 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
+ }
2476
2732
  }
2477
- function hydrateNode(vnode, parent) {
2733
+ function hydrateNode(vnode, parent, _componentName) {
2478
2734
  if (vnode == null || typeof vnode === "boolean") {
2479
2735
  return null;
2480
2736
  }
@@ -2482,19 +2738,21 @@ function hydrateNode(vnode, parent) {
2482
2738
  const existing = claimNode(parent);
2483
2739
  const text = String(vnode);
2484
2740
  if (existing && existing.nodeType === 3) {
2485
- if (isDevMode() && existing.textContent !== text) {
2486
- console.warn(
2487
- `[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
2488
2746
  );
2489
2747
  existing.textContent = text;
2490
2748
  }
2491
2749
  return existing;
2492
2750
  }
2493
- if (isDevMode()) {
2494
- console.warn(
2495
- `[what] Hydration mismatch: expected text node "${text}", got ${existing ? existing.nodeName : "nothing"}. Falling back to client render.`
2496
- );
2497
- }
2751
+ reportHydrationMismatch(
2752
+ `text node "${text}"`,
2753
+ existing ? existing.nodeName : "nothing",
2754
+ _componentName
2755
+ );
2498
2756
  const textNode2 = document.createTextNode(text);
2499
2757
  if (existing) {
2500
2758
  parent.replaceChild(textNode2, existing);
@@ -2505,7 +2763,7 @@ function hydrateNode(vnode, parent) {
2505
2763
  }
2506
2764
  if (typeof vnode === "function") {
2507
2765
  const initialValue = vnode();
2508
- let current = hydrateNode(initialValue, parent);
2766
+ let current = hydrateNode(initialValue, parent, _componentName);
2509
2767
  effect(() => {
2510
2768
  const value = vnode();
2511
2769
  if (!_isHydrating) {
@@ -2517,7 +2775,7 @@ function hydrateNode(vnode, parent) {
2517
2775
  if (Array.isArray(vnode)) {
2518
2776
  const nodes = [];
2519
2777
  for (const child of vnode) {
2520
- const node = hydrateNode(child, parent);
2778
+ const node = hydrateNode(child, parent, _componentName);
2521
2779
  if (node) nodes.push(node);
2522
2780
  }
2523
2781
  return nodes.length === 1 ? nodes[0] : nodes;
@@ -2526,6 +2784,7 @@ function hydrateNode(vnode, parent) {
2526
2784
  if (typeof vnode.tag === "function") {
2527
2785
  const componentStack2 = getComponentStack();
2528
2786
  const Component = vnode.tag;
2787
+ const compName = Component.displayName || Component.name || "Anonymous";
2529
2788
  const props = vnode.props || {};
2530
2789
  const children = vnode.children || [];
2531
2790
  const ctx = {
@@ -2546,7 +2805,9 @@ function hydrateNode(vnode, parent) {
2546
2805
  result = Component({ ...props, children: propsChildren });
2547
2806
  } catch (error) {
2548
2807
  componentStack2.pop();
2549
- 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
+ }
2550
2811
  return null;
2551
2812
  }
2552
2813
  componentStack2.pop();
@@ -2563,28 +2824,36 @@ function hydrateNode(vnode, parent) {
2563
2824
  }
2564
2825
  });
2565
2826
  }
2566
- return hydrateNode(result, parent);
2827
+ return hydrateNode(result, parent, compName);
2567
2828
  }
2568
2829
  const existing = claimNode(parent);
2569
2830
  const expectedTag = vnode.tag.toUpperCase();
2570
2831
  if (existing && existing.nodeType === 1 && existing.nodeName === expectedTag) {
2571
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
+ }
2572
2841
  const savedCursor = _hydrationCursor;
2573
2842
  _hydrationCursor = { parent: existing, index: 0 };
2574
2843
  const rawInner = vnode.props?.dangerouslySetInnerHTML?.__html;
2575
2844
  if (rawInner == null) {
2576
2845
  for (const child of vnode.children) {
2577
- hydrateNode(child, existing);
2846
+ hydrateNode(child, existing, _componentName);
2578
2847
  }
2579
2848
  }
2580
2849
  _hydrationCursor = savedCursor;
2581
2850
  return existing;
2582
2851
  }
2583
- if (isDevMode()) {
2584
- console.warn(
2585
- `[what] Hydration mismatch: expected <${vnode.tag}>, got ${existing ? existing.nodeName : "nothing"}. Falling back to client render.`
2586
- );
2587
- }
2852
+ reportHydrationMismatch(
2853
+ `<${vnode.tag}>`,
2854
+ existing ? existing.nodeName : "nothing",
2855
+ _componentName
2856
+ );
2588
2857
  const newEl = document.createElement(vnode.tag);
2589
2858
  for (const key in vnode.props || {}) {
2590
2859
  if (key === "children" || key === "key") continue;
@@ -2641,6 +2910,18 @@ function hydrateElementProps(el, props) {
2641
2910
  continue;
2642
2911
  }
2643
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
+ }
2644
2925
  }
2645
2926
  }
2646
2927
 
@@ -2658,16 +2939,6 @@ function getHook(ctx) {
2658
2939
  const index = ctx.hookIndex++;
2659
2940
  return { index, exists: index < ctx.hooks.length };
2660
2941
  }
2661
- function useState(initial) {
2662
- const ctx = getCtx("useState");
2663
- const { index, exists } = getHook(ctx);
2664
- if (!exists) {
2665
- const s2 = signal(typeof initial === "function" ? initial() : initial);
2666
- ctx.hooks[index] = s2;
2667
- }
2668
- const s = ctx.hooks[index];
2669
- return [s, s.set];
2670
- }
2671
2942
  function useSignal(initial) {
2672
2943
  const ctx = getCtx("useSignal");
2673
2944
  const { index, exists } = getHook(ctx);
@@ -2684,97 +2955,6 @@ function useComputed(fn) {
2684
2955
  }
2685
2956
  return ctx.hooks[index];
2686
2957
  }
2687
- function useEffect(fn, deps) {
2688
- const ctx = getCtx("useEffect");
2689
- const { index, exists } = getHook(ctx);
2690
- if (!exists) {
2691
- ctx.hooks[index] = { cleanup: null, dispose: null };
2692
- }
2693
- if (__DEV__ && Array.isArray(deps) && deps.length > 0) {
2694
- for (let i = 0; i < deps.length; i++) {
2695
- const dep = deps[i];
2696
- if (dep != null && typeof dep !== "function") {
2697
- console.warn(
2698
- `[what] useEffect dep at index ${i} is not a function. Did you mean to pass a signal? Use count instead of count()`
2699
- );
2700
- }
2701
- }
2702
- }
2703
- const hook = ctx.hooks[index];
2704
- if (hook.dispose) return;
2705
- if (deps === void 0) {
2706
- queueMicrotask(() => {
2707
- if (ctx.disposed) return;
2708
- hook.dispose = effect(() => {
2709
- if (hook.cleanup) {
2710
- try {
2711
- hook.cleanup();
2712
- } catch (e) {
2713
- }
2714
- hook.cleanup = null;
2715
- }
2716
- const result = fn();
2717
- if (typeof result === "function") hook.cleanup = result;
2718
- });
2719
- ctx.effects = ctx.effects || [];
2720
- ctx.effects.push(hook.dispose);
2721
- });
2722
- } else if (deps.length === 0) {
2723
- queueMicrotask(() => {
2724
- if (ctx.disposed) return;
2725
- const result = fn();
2726
- if (typeof result === "function") hook.cleanup = result;
2727
- });
2728
- hook.dispose = true;
2729
- } else {
2730
- queueMicrotask(() => {
2731
- if (ctx.disposed) return;
2732
- hook.dispose = effect(() => {
2733
- for (let i = 0; i < deps.length; i++) {
2734
- const dep = deps[i];
2735
- if (typeof dep === "function" && dep._signal) {
2736
- dep();
2737
- }
2738
- }
2739
- if (hook.cleanup) {
2740
- try {
2741
- hook.cleanup();
2742
- } catch (e) {
2743
- }
2744
- hook.cleanup = null;
2745
- }
2746
- const result = untrack(() => fn());
2747
- if (typeof result === "function") hook.cleanup = result;
2748
- });
2749
- ctx.effects = ctx.effects || [];
2750
- ctx.effects.push(hook.dispose);
2751
- });
2752
- }
2753
- }
2754
- function useMemo(fn, deps) {
2755
- const ctx = getCtx("useMemo");
2756
- const { index, exists } = getHook(ctx);
2757
- if (!exists) {
2758
- ctx.hooks[index] = { computed: computed(fn) };
2759
- }
2760
- return ctx.hooks[index].computed;
2761
- }
2762
- function useCallback(fn, deps) {
2763
- const ctx = getCtx("useCallback");
2764
- const { index, exists } = getHook(ctx);
2765
- if (!exists) {
2766
- ctx.hooks[index] = { callback: fn };
2767
- }
2768
- return ctx.hooks[index].callback;
2769
- }
2770
- function useRef(initial) {
2771
- const ctx = getCtx("useRef");
2772
- const { index, exists } = getHook(ctx);
2773
- if (!exists) {
2774
- ctx.hooks[index] = { current: initial };
2775
- }
2776
- return ctx.hooks[index];
2777
- }
2778
2958
  function useContext(context) {
2779
2959
  let ctx = getCurrentComponent();
2780
2960
  if (__DEV__ && !ctx) {
@@ -4843,18 +5023,6 @@ function clearCache() {
4843
5023
  lastFetchTimestamps.clear();
4844
5024
  inFlightRequests.clear();
4845
5025
  }
4846
- function __getCacheSnapshot() {
4847
- const entries = [];
4848
- for (const [key, sig] of cacheSignals) {
4849
- entries.push({
4850
- key,
4851
- data: sig.peek(),
4852
- error: errorSignals.has(key) ? errorSignals.get(key).peek() : null,
4853
- isValidating: validatingSignals.has(key) ? validatingSignals.get(key).peek() : false
4854
- });
4855
- }
4856
- return entries;
4857
- }
4858
5026
 
4859
5027
  // packages/core/src/form.js
4860
5028
  function useForm(options = {}) {
@@ -5312,213 +5480,6 @@ function ErrorMessage({ name, formState, errors, render }) {
5312
5480
  return h("span", { class: "what-error", role: "alert" }, error.message);
5313
5481
  }
5314
5482
 
5315
- // packages/core/src/errors.js
5316
- var ERROR_CODES = {
5317
- INFINITE_EFFECT: {
5318
- code: "ERR_INFINITE_EFFECT",
5319
- severity: "error",
5320
- template: 'Effect "{{effectName}}" exceeded 25 flush iterations \u2014 likely an infinite loop.',
5321
- 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.",
5322
- codeExample: `// Bad \u2014 reads and writes count, creating a cycle:
5323
- effect(() => { count(count() + 1); });
5324
-
5325
- // Good \u2014 use untrack() so the read doesn't subscribe:
5326
- effect(() => { count(untrack(count) + 1); });
5327
-
5328
- // Better \u2014 split into separate logic:
5329
- const doubled = computed(() => count() * 2);`
5330
- },
5331
- MISSING_SIGNAL_READ: {
5332
- code: "ERR_MISSING_SIGNAL_READ",
5333
- severity: "warning",
5334
- template: 'Signal "{{signalName}}" used without calling () \u2014 renders as "[Function]" instead of its value.',
5335
- suggestion: "Signals are functions. Call them to read: count() not count. In JSX: {count()} not {count}.",
5336
- codeExample: `// Bad \u2014 signal reference, not value:
5337
- <span>{count}</span> // renders "[Function]"
5338
-
5339
- // Good \u2014 call the signal:
5340
- <span>{count()}</span> // renders the actual value`
5341
- },
5342
- HYDRATION_MISMATCH: {
5343
- code: "ERR_HYDRATION_MISMATCH",
5344
- severity: "error",
5345
- template: 'Hydration mismatch in component "{{component}}": server rendered "{{serverHTML}}" but client expects "{{clientHTML}}".',
5346
- 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.",
5347
- codeExample: `// Bad \u2014 different on server vs client:
5348
- function App() {
5349
- return <p>{window.innerWidth}</p>;
5350
- }
5351
-
5352
- // Good \u2014 use onMount for client-only values:
5353
- function App() {
5354
- const width = signal(0);
5355
- onMount(() => width(window.innerWidth));
5356
- return <p>{width()}</p>;
5357
- }`
5358
- },
5359
- ORPHAN_EFFECT: {
5360
- code: "ERR_ORPHAN_EFFECT",
5361
- severity: "warning",
5362
- template: 'Effect "{{effectName}}" was created outside a reactive root \u2014 it will never be cleaned up.',
5363
- suggestion: "Wrap effect creation in createRoot() or create effects inside component functions where they are automatically tracked.",
5364
- codeExample: `// Bad \u2014 orphaned, leaks memory:
5365
- effect(() => console.log(count()));
5366
-
5367
- // Good \u2014 inside a root with cleanup:
5368
- createRoot(dispose => {
5369
- effect(() => console.log(count()));
5370
- // later: dispose() cleans up
5371
- });`
5372
- },
5373
- SIGNAL_WRITE_IN_RENDER: {
5374
- code: "ERR_SIGNAL_WRITE_IN_RENDER",
5375
- severity: "error",
5376
- template: 'Signal "{{signalName}}" written during render of component "{{component}}". This triggers re-execution.',
5377
- suggestion: "Move signal writes into event handlers, effects, or onMount(). The component body should only read signals, not write them.",
5378
- codeExample: `// Bad \u2014 write during render:
5379
- function Counter() {
5380
- count(count() + 1); // triggers infinite loop
5381
- return <span>{count()}</span>;
5382
- }
5383
-
5384
- // Good \u2014 write in event handler:
5385
- function Counter() {
5386
- return <button onclick={() => count(c => c + 1)}>{count()}</button>;
5387
- }`
5388
- },
5389
- MISSING_CLEANUP: {
5390
- code: "ERR_MISSING_CLEANUP",
5391
- severity: "warning",
5392
- template: 'Effect sets up "{{resource}}" but does not return a cleanup function.',
5393
- suggestion: "Effects that add event listeners, set timers, or open connections should return a cleanup function to prevent memory leaks.",
5394
- codeExample: `// Bad \u2014 no cleanup:
5395
- effect(() => {
5396
- window.addEventListener('resize', handler);
5397
- });
5398
-
5399
- // Good \u2014 return cleanup:
5400
- effect(() => {
5401
- window.addEventListener('resize', handler);
5402
- return () => window.removeEventListener('resize', handler);
5403
- });`
5404
- },
5405
- UNSAFE_INNERHTML: {
5406
- code: "ERR_UNSAFE_INNERHTML",
5407
- severity: "warning",
5408
- template: "innerHTML set on element without using the __html safety marker.",
5409
- suggestion: "Use the html tagged template literal or pass { __html: content } to mark innerHTML as intentional and reviewed.",
5410
- codeExample: `// Bad \u2014 raw innerHTML (XSS risk):
5411
- <div innerHTML={userInput} />
5412
-
5413
- // Good \u2014 explicit opt-in:
5414
- <div innerHTML={{ __html: sanitizedContent }} />
5415
-
5416
- // Better \u2014 use the html template literal:
5417
- html\`<div>\${sanitizedContent}</div>\``
5418
- },
5419
- MISSING_KEY: {
5420
- code: "ERR_MISSING_KEY",
5421
- severity: "warning",
5422
- template: 'List rendered without key prop in component "{{component}}". Items may re-order incorrectly.',
5423
- suggestion: "Add a unique key prop to each item in a list. Use a stable identifier (like an ID), not the array index.",
5424
- codeExample: `// Bad \u2014 no key:
5425
- <For each={items()}>{item => <li>{item.name}</li>}</For>
5426
-
5427
- // Good \u2014 stable key:
5428
- <For each={items()}>{item => <li key={item.id}>{item.name}</li>}</For>`
5429
- }
5430
- };
5431
- var WhatError = class extends Error {
5432
- constructor({ code, message, suggestion, file, line, component, signal: signal2, effect: effect2 }) {
5433
- super(message);
5434
- this.name = "WhatError";
5435
- this.code = code;
5436
- this.suggestion = suggestion;
5437
- this.file = file;
5438
- this.line = line;
5439
- this.component = component;
5440
- this.signal = signal2;
5441
- this.effect = effect2;
5442
- }
5443
- toJSON() {
5444
- return {
5445
- code: this.code,
5446
- message: this.message,
5447
- suggestion: this.suggestion,
5448
- file: this.file,
5449
- line: this.line,
5450
- component: this.component,
5451
- signal: this.signal,
5452
- effect: this.effect
5453
- };
5454
- }
5455
- };
5456
- function createWhatError(errorCode, context = {}) {
5457
- const def = typeof errorCode === "string" ? ERROR_CODES[errorCode] : errorCode;
5458
- if (!def) {
5459
- return new WhatError({
5460
- code: "ERR_UNKNOWN",
5461
- message: `Unknown error: ${errorCode}`,
5462
- suggestion: "Check the error code and try again."
5463
- });
5464
- }
5465
- let message = def.template;
5466
- for (const [key, val] of Object.entries(context)) {
5467
- message = message.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), String(val));
5468
- }
5469
- message = message.replace(/\{\{[^}]+\}\}/g, "(unknown)");
5470
- return new WhatError({
5471
- code: def.code,
5472
- message,
5473
- suggestion: def.suggestion,
5474
- file: context.file,
5475
- line: context.line,
5476
- component: context.component,
5477
- signal: context.signal || context.signalName,
5478
- effect: context.effect || context.effectName
5479
- });
5480
- }
5481
- var collectedErrors = [];
5482
- var MAX_COLLECTED = 200;
5483
- function collectError(whatError) {
5484
- if (!__DEV__) return;
5485
- collectedErrors.push({
5486
- ...whatError.toJSON(),
5487
- timestamp: Date.now()
5488
- });
5489
- if (collectedErrors.length > MAX_COLLECTED) {
5490
- collectedErrors = collectedErrors.slice(-MAX_COLLECTED);
5491
- }
5492
- }
5493
- function getCollectedErrors(since) {
5494
- if (since) return collectedErrors.filter((e) => e.timestamp > since);
5495
- return collectedErrors.slice();
5496
- }
5497
- function clearCollectedErrors() {
5498
- collectedErrors = [];
5499
- }
5500
- function classifyError(err, context = {}) {
5501
- const msg = err?.message || String(err);
5502
- if (msg.includes("infinite effect loop") || msg.includes("25 iterations")) {
5503
- return createWhatError("INFINITE_EFFECT", context);
5504
- }
5505
- if (msg.includes("hydration") || msg.includes("Hydration")) {
5506
- return createWhatError("HYDRATION_MISMATCH", context);
5507
- }
5508
- if (msg.includes("Signal.set() called inside a computed")) {
5509
- return createWhatError("SIGNAL_WRITE_IN_RENDER", {
5510
- ...context,
5511
- signalName: msg.match(/signal: (\w+)/)?.[1] || context.signalName
5512
- });
5513
- }
5514
- return new WhatError({
5515
- code: "ERR_RUNTIME",
5516
- message: msg,
5517
- suggestion: "Check the stack trace and component context for more details.",
5518
- ...context
5519
- });
5520
- }
5521
-
5522
5483
  // packages/core/src/guardrails.js
5523
5484
  var guardrails = {
5524
5485
  signalReadDetection: true,
@@ -5579,10 +5540,8 @@ var VALID_EXPORTS = /* @__PURE__ */ new Set([
5579
5540
  "getOwner",
5580
5541
  "runWithOwner",
5581
5542
  "onRootCleanup",
5582
- "__setDevToolsHooks",
5583
5543
  // Rendering
5584
5544
  "template",
5585
- "_template",
5586
5545
  "svgTemplate",
5587
5546
  "insert",
5588
5547
  "mapArray",
@@ -5593,7 +5552,6 @@ var VALID_EXPORTS = /* @__PURE__ */ new Set([
5593
5552
  "classList",
5594
5553
  "hydrate",
5595
5554
  "isHydrating",
5596
- "_$createComponent",
5597
5555
  // JSX
5598
5556
  "h",
5599
5557
  "Fragment",
@@ -5706,7 +5664,6 @@ var VALID_EXPORTS = /* @__PURE__ */ new Set([
5706
5664
  "setQueryData",
5707
5665
  "getQueryData",
5708
5666
  "clearCache",
5709
- "__getCacheSnapshot",
5710
5667
  // Form
5711
5668
  "useForm",
5712
5669
  "useField",
@@ -5768,7 +5725,7 @@ function levenshtein(a, b) {
5768
5725
  }
5769
5726
 
5770
5727
  // packages/core/src/agent-context.js
5771
- var VERSION = "0.6.0";
5728
+ var VERSION = "0.6.3";
5772
5729
  var mountedComponents2 = [];
5773
5730
  function registerComponent(component) {
5774
5731
  if (!__DEV__) return;
@@ -5881,11 +5838,6 @@ export {
5881
5838
  Textarea,
5882
5839
  VisuallyHidden,
5883
5840
  WhatError,
5884
- _$createComponent,
5885
- _$templateImpl as _$template,
5886
- __getCacheSnapshot,
5887
- __setDevToolsHooks,
5888
- template as _template,
5889
5841
  announce,
5890
5842
  announceAssertive,
5891
5843
  atom,
@@ -5919,6 +5871,7 @@ export {
5919
5871
  getCollectedErrors,
5920
5872
  getGuardrailConfig,
5921
5873
  getHealth,
5874
+ getHydrationMismatchCount,
5922
5875
  getMountedComponents,
5923
5876
  getOwner,
5924
5877
  getQueryData,
@@ -5975,12 +5928,10 @@ export {
5975
5928
  useAriaChecked,
5976
5929
  useAriaExpanded,
5977
5930
  useAriaSelected,
5978
- useCallback,
5979
5931
  useClickOutside,
5980
5932
  useComputed,
5981
5933
  useContext,
5982
5934
  useDescribedBy,
5983
- useEffect,
5984
5935
  useFetch,
5985
5936
  useField,
5986
5937
  useFocus,
@@ -5994,16 +5945,13 @@ export {
5994
5945
  useLabelledBy,
5995
5946
  useLocalStorage,
5996
5947
  useMediaQuery,
5997
- useMemo,
5998
5948
  useQuery,
5999
5949
  useReducer,
6000
- useRef,
6001
5950
  useRovingTabIndex,
6002
5951
  useSWR,
6003
5952
  useScheduledEffect,
6004
5953
  useSignal,
6005
5954
  useSkeleton,
6006
- useState,
6007
5955
  useTransition,
6008
5956
  validateImports,
6009
5957
  yupResolver,