what-core 0.11.7 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -24,7 +24,7 @@ export interface Computed<T> {
24
24
  _signal: true;
25
25
  }
26
26
 
27
- export function signal<T>(initial: T): Signal<T>;
27
+ export function signal<T>(initial: T, debugName?: string): Signal<T>;
28
28
  export function computed<T>(fn: () => T): Computed<T>;
29
29
  export function effect(fn: () => void | (() => void), opts?: { stable?: boolean }): () => void;
30
30
  export function signalMemo<T>(fn: () => T): Computed<T>;
@@ -33,12 +33,36 @@ export function untrack<T>(fn: () => T): T;
33
33
  export function flushSync(): void;
34
34
  export function createRoot<T>(fn: (dispose: () => void) => T): T;
35
35
 
36
+ /** Opaque ownership scope handle. Pair with runWithOwner() for async work. */
37
+ export interface Owner {
38
+ disposals: Array<() => void>;
39
+ }
40
+
41
+ export function getOwner(): Owner | null;
42
+ export function runWithOwner<T>(owner: Owner | null, fn: () => T): T;
43
+ /** Register a cleanup with the current owner/root (root-level onCleanup). */
44
+ export function onRootCleanup(fn: () => void): void;
45
+
36
46
  // --- Virtual DOM ---
37
47
 
38
48
  export type PrimitiveChild = string | number | boolean | null | undefined;
39
49
  export type VNodeChild = PrimitiveChild | VNode | (() => VNodeChild) | VNodeChild[];
40
50
 
41
- export type Component<P = {}> = (props: P & { children?: VNodeChild }) => VNodeChild;
51
+ /** A component may legitimately render nothing, so `null` is part of the contract. */
52
+ export type Component<P = {}> = ((props: P & { children?: VNodeChild }) => VNode | null) & {
53
+ /**
54
+ * Opt out of realizing compiled children before the component runs.
55
+ *
56
+ * A component that establishes a scope its children depend on (a context
57
+ * provider, an error or suspense boundary) receives `props.children` as a
58
+ * zero-argument factory instead of built nodes, and the runtime realizes it
59
+ * once that scope exists. `ErrorBoundary`, `Suspense` and `Context.Provider`
60
+ * set this themselves, and the compiler keeps a component that only forwards
61
+ * its children lazy, so this is only needed by a component that both
62
+ * inspects its children and forwards them into a boundary or a provider.
63
+ */
64
+ _deferChildren?: boolean;
65
+ };
42
66
 
43
67
  export interface VNode<P = Record<string, any>> {
44
68
  tag: string | Component<P>;
@@ -54,15 +78,20 @@ export function h<P extends Record<string, any>>(
54
78
  ...children: VNodeChild[]
55
79
  ): VNode<P>;
56
80
 
57
- export function Fragment(props: { children?: VNodeChild }): VNodeChild;
81
+ export function Fragment(props: { children?: VNodeChild }): VNode;
58
82
  export function html(strings: TemplateStringsArray, ...values: any[]): VNode | VNode[];
59
83
 
60
84
  // --- DOM ---
61
85
 
62
86
  export function mount(vnode: VNodeChild, container: string | Element): () => void;
63
87
 
88
+ /** Attach reactive bindings to server-rendered DOM instead of creating it. */
89
+ export function hydrate(vnode: VNodeChild, container: Element): Node | null;
90
+ export function isHydrating(): boolean;
91
+
64
92
  // Fine-grained rendering primitives
65
93
  export function template(html: string): () => Element;
94
+ export function svgTemplate(html: string): () => Element;
66
95
  export function insert(parent: Node, child: any, marker?: Node | null): any;
67
96
  export function mapArray<T>(
68
97
  source: () => T[],
@@ -132,18 +161,18 @@ export function Show(props: {
132
161
  when: boolean | (() => boolean);
133
162
  fallback?: VNodeChild;
134
163
  children?: VNodeChild;
135
- }): VNodeChild;
164
+ }): VNode;
136
165
 
137
166
  export function For<T>(props: {
138
167
  each: T[] | (() => T[]);
139
168
  fallback?: VNodeChild;
140
169
  children: ((item: T, index: number) => VNodeChild) | VNodeChild;
141
- }): VNodeChild;
170
+ }): VNode;
142
171
 
143
172
  export function Switch(props: {
144
173
  fallback?: VNodeChild;
145
174
  children?: VNodeChild;
146
- }): VNodeChild;
175
+ }): VNode;
147
176
 
148
177
  export function Match(props: {
149
178
  when: boolean | (() => boolean);
@@ -198,6 +227,37 @@ export function Head(props: {
198
227
  }): null;
199
228
  export function clearHead(): void;
200
229
 
230
+ /** Per-render head accumulator used by the SSR renderer. */
231
+ export interface HeadSink {
232
+ title: string | null;
233
+ metas: Map<string, Record<string, string>>;
234
+ links: Map<string, Record<string, string>>;
235
+ }
236
+
237
+ export function beginHeadCollection(): HeadSink;
238
+ export function endHeadCollection(sink: HeadSink | null): string;
239
+
240
+ // --- Loader Data ---
241
+
242
+ export function useLoaderData<T = any>(): T | undefined;
243
+ export function getLoaderData<T = any>(): T | undefined;
244
+ export function getResource<T = any>(key: string): T | undefined;
245
+
246
+ // --- Server Context ---
247
+
248
+ export interface ServerContext {
249
+ loaderData?: any;
250
+ head?: HeadSink;
251
+ resources?: Record<string, any>;
252
+ [key: string]: any;
253
+ }
254
+
255
+ /** The active render context, or null on the client / outside a render. */
256
+ export function getServerContext(): ServerContext | null;
257
+ /** Set the active context and return the previous one so callers can restore it. */
258
+ export function setServerContext(ctx: ServerContext | null): ServerContext | null;
259
+ export function runWithServerContext<T>(ctx: ServerContext, fn: () => T): T;
260
+
201
261
  // --- Scheduler ---
202
262
 
203
263
  export function scheduleRead(fn: () => void): () => void;
@@ -334,17 +394,17 @@ export function onKeys(keys: string[], handler: (e: KeyboardEvent) => void): (e:
334
394
 
335
395
  // --- Skeleton ---
336
396
 
337
- export function Skeleton(props?: Record<string, any>): VNodeChild;
397
+ export function Skeleton(props?: Record<string, any>): VNode;
338
398
  export function SkeletonText(props?: Record<string, any>): VNode;
339
- export function SkeletonAvatar(props?: Record<string, any>): VNodeChild;
399
+ export function SkeletonAvatar(props?: Record<string, any>): VNode;
340
400
  export function SkeletonCard(props?: Record<string, any>): VNode;
341
401
  export function SkeletonTable(props?: Record<string, any>): VNode;
342
- export function IslandSkeleton(props?: Record<string, any>): VNodeChild;
402
+ export function IslandSkeleton(props?: Record<string, any>): VNode;
343
403
  export function useSkeleton<T>(asyncFn: () => Promise<T> | T, deps?: unknown[]): {
344
404
  isLoading: () => boolean;
345
405
  data: () => T | null;
346
406
  error: () => any;
347
- Skeleton: (props?: Record<string, any>) => VNodeChild;
407
+ Skeleton: (props?: Record<string, any>) => VNode;
348
408
  };
349
409
  export function Placeholder(props?: Record<string, any>): VNode;
350
410
  export function LoadingDots(props?: Record<string, any>): VNode;
@@ -504,4 +564,115 @@ export function ErrorMessage(props: {
504
564
  formState?: FormState;
505
565
  errors?: Record<string, FieldError> | (() => Record<string, FieldError>);
506
566
  render?: (args: { message?: string; type?: string }) => VNodeChild;
507
- }): VNodeChild;
567
+ }): VNode;
568
+
569
+ // --- Structured Errors ---
570
+
571
+ export interface ErrorCodeDefinition {
572
+ code: string;
573
+ severity: 'error' | 'warning';
574
+ template: string;
575
+ suggestion: string;
576
+ codeExample?: string;
577
+ }
578
+
579
+ export const ERROR_CODES: Record<string, ErrorCodeDefinition>;
580
+
581
+ export interface WhatErrorJSON {
582
+ code: string;
583
+ message: string;
584
+ suggestion?: string;
585
+ file?: string;
586
+ line?: number;
587
+ component?: string;
588
+ signal?: string;
589
+ effect?: string;
590
+ }
591
+
592
+ export class WhatError extends Error {
593
+ constructor(init: {
594
+ code: string;
595
+ message: string;
596
+ suggestion?: string;
597
+ file?: string;
598
+ line?: number;
599
+ component?: string;
600
+ signal?: string;
601
+ effect?: string;
602
+ });
603
+ code: string;
604
+ suggestion?: string;
605
+ file?: string;
606
+ line?: number;
607
+ component?: string;
608
+ signal?: string;
609
+ effect?: string;
610
+ toJSON(): WhatErrorJSON;
611
+ }
612
+
613
+ export function createWhatError(
614
+ errorCode: string | ErrorCodeDefinition,
615
+ context?: Record<string, any>,
616
+ ): WhatError;
617
+ export function classifyError(err: unknown, context?: Record<string, any>): WhatError;
618
+ export function collectError(error: WhatError): void;
619
+ export function getCollectedErrors(since?: number): Array<WhatErrorJSON & { timestamp: number }>;
620
+ export function clearCollectedErrors(): void;
621
+
622
+ // --- Guardrails ---
623
+
624
+ export interface GuardrailConfig {
625
+ signalReadDetection: boolean;
626
+ componentNaming: boolean;
627
+ importValidation: boolean;
628
+ }
629
+
630
+ export function configureGuardrails(overrides: Partial<GuardrailConfig>): void;
631
+ export function getGuardrailConfig(): GuardrailConfig;
632
+ export function installSignalReadGuardrail<T>(signalFn: T, debugName?: string): T;
633
+
634
+ // --- Agent Context ---
635
+
636
+ export interface HealthReport {
637
+ effectCycleRisk: boolean;
638
+ orphanEffects: number;
639
+ signalLeaks: number;
640
+ memoryPressure: 'low' | 'medium' | 'high';
641
+ recentErrorCount: number;
642
+ totalSignals: number;
643
+ totalComponents: number;
644
+ }
645
+
646
+ export function getHealth(): HealthReport;
647
+ /** Expose globalThis.__WHAT_AGENT__ for agent tooling (dev mode only). */
648
+ export function installAgentContext(): void;
649
+
650
+ /** Warn when a component function is not PascalCase. Dev-mode only; returns null in production. */
651
+ export function checkComponentName(name: string): GuardrailWarning | null;
652
+
653
+ export interface GuardrailWarning {
654
+ code: string;
655
+ name: string;
656
+ suggestion: string;
657
+ }
658
+
659
+ export interface InvalidImport {
660
+ name: string;
661
+ message: string;
662
+ suggestion: string;
663
+ }
664
+
665
+ /** Report import names that are not valid exports of what-framework. Dev-mode only. */
666
+ export function validateImports(importNames: readonly string[]): InvalidImport[];
667
+
668
+ // --- Agent registries ---
669
+ // The devtools bridge and MCP server read these to enumerate the live graph.
670
+ // Registration is a no-op in production builds; the getters always return a copy.
671
+
672
+ export function registerComponent(component: unknown): void;
673
+ export function unregisterComponent(component: unknown): void;
674
+ export function getMountedComponents(): unknown[];
675
+
676
+ export function registerSignal(sig: unknown): void;
677
+ export function unregisterSignal(sig: unknown): void;
678
+ export function getActiveSignals(): unknown[];
@@ -1,9 +1,9 @@
1
1
  // What Framework — JSX dev-runtime type definitions.
2
2
  // Re-exports the runtime types (including the JSX namespace) and adds jsxDEV,
3
3
  // which the "react-jsxdev" transform emits in development builds.
4
- import type { VNode } from './index';
4
+ import type { VNode } from './index.js';
5
5
 
6
- export * from './jsx-runtime';
6
+ export * from './jsx-runtime.js';
7
7
 
8
8
  export function jsxDEV(
9
9
  type: any,
package/jsx-runtime.d.ts CHANGED
@@ -11,9 +11,9 @@
11
11
  // documentation, while an index signature keeps arbitrary/custom attributes and
12
12
  // web-component tags valid (never false-flagging code the runtime accepts).
13
13
 
14
- import type { VNode, VNodeChild } from './index';
14
+ import type { VNode, VNodeChild } from './index.js';
15
15
 
16
- export { Fragment } from './index';
16
+ export { Fragment } from './index.js';
17
17
 
18
18
  /** A JSX attribute value in What may be static or a reactive `() => value` thunk. */
19
19
  export type Reactive<T> = T | (() => T);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "what-core",
3
- "version": "0.11.7",
3
+ "version": "0.12.0",
4
4
  "description": "What Framework - Signal-based UI framework built for AI agents",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/render.d.ts CHANGED
@@ -9,7 +9,8 @@ export {
9
9
  classList,
10
10
  effect,
11
11
  untrack,
12
- } from './index';
12
+ } from './index.js';
13
+ import type { VNodeChild } from './index.js';
13
14
 
14
15
  // Compiler-internal template alias — identical to template() but never
15
16
  // dev-warns. Compiled output imports this (SPRINT v0.11 C5).
@@ -28,3 +29,16 @@ export function setChecked(el: Element, value: any): void;
28
29
  // exported from the package index). Emitted by the compiler for branch
29
30
  // memoization of conditional JSX (SPRINT v0.11 C1).
30
31
  export function memo<T>(fn: () => T): (() => T) & { peek(): T };
32
+
33
+ // --- Hydration ---
34
+ // hydrate() adopts server-rendered DOM instead of recreating it: it walks the
35
+ // existing nodes, attaches event handlers and reactive bindings in place, and
36
+ // restarts the useId sequence so client ids reproduce the server's.
37
+ export function hydrate(vnode: VNodeChild, container: Element): Node | Node[] | null;
38
+
39
+ /** True while a hydration pass is walking existing DOM. */
40
+ export function isHydrating(): boolean;
41
+
42
+ // SVG counterpart to template(): elements are created in the SVG namespace, which
43
+ // a plain innerHTML template cannot do.
44
+ export function svgTemplate(html: string): () => Element;
package/src/a11y.js CHANGED
@@ -4,6 +4,7 @@
4
4
  import { signal, effect } from './reactive.js';
5
5
  import { h } from './h.js';
6
6
  import { getCurrentComponent } from './dom.js';
7
+ import { getServerContext } from './server-context.js';
7
8
 
8
9
  // --- Focus Management ---
9
10
 
@@ -401,17 +402,48 @@ export function LiveRegion({ children, priority = 'polite', atomic = true }) {
401
402
  // --- ID Generator ---
402
403
  // Generate unique IDs for ARIA attributes
403
404
 
405
+ // The counter is render-scoped on the server and module-global on the client.
406
+ //
407
+ // A bare module-global counter is wrong on the server in two independent ways.
408
+ // Ids drift between the SSR pass and hydration, which breaks exactly the
409
+ // relationships useId exists to create (`for`/`id`, `aria-labelledby`,
410
+ // `aria-describedby`), and concurrent requests interleave into each other's
411
+ // sequence, so two visitors can be served HTML whose ids were allocated in the
412
+ // order the event loop happened to run. Every framework in the cohort ships this
413
+ // primitive as SSR-stable because that is the whole point of shipping it.
414
+ //
415
+ // getServerContext() is the render-scoped store the SSR keystone already
416
+ // maintains (AsyncLocalStorage-backed in Node), so this is wiring rather than
417
+ // new machinery, and it adds no public API.
404
418
  let idCounter = 0;
405
419
 
420
+ function nextIdSuffix() {
421
+ const ctx = getServerContext();
422
+ if (ctx) {
423
+ ctx.idCounter = (ctx.idCounter || 0) + 1;
424
+ return ctx.idCounter;
425
+ }
426
+ return ++idCounter;
427
+ }
428
+
429
+ /**
430
+ * Reset the client-side counter. Called at the start of hydration so the client
431
+ * reproduces the server's id sequence instead of continuing past it.
432
+ * @internal
433
+ */
434
+ export function __resetIdCounter() {
435
+ idCounter = 0;
436
+ }
437
+
406
438
  export function useId(prefix = 'what') {
407
- const id = `${prefix}-${++idCounter}`;
439
+ const id = `${prefix}-${nextIdSuffix()}`;
408
440
  return () => id;
409
441
  }
410
442
 
411
443
  export function useIds(count, prefix = 'what') {
412
444
  const ids = [];
413
445
  for (let i = 0; i < count; i++) {
414
- ids.push(`${prefix}-${++idCounter}`);
446
+ ids.push(`${prefix}-${nextIdSuffix()}`);
415
447
  }
416
448
  return ids;
417
449
  }
@@ -8,7 +8,7 @@ import { getCollectedErrors } from './errors.js';
8
8
  // --- Version ---
9
9
  // Keep in sync with packages/core/package.json (checked by
10
10
  // core/test/guardrails.test.js so it can't silently go stale again).
11
- const VERSION = '0.11.7';
11
+ const VERSION = '0.12.0';
12
12
 
13
13
  // --- Component Registry ---
14
14
  // Tracks mounted components for agent inspection.