streetui 1.0.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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -0
  3. package/dist/bin.cjs +894 -0
  4. package/dist/bin.cjs.map +1 -0
  5. package/dist/bin.d.cts +1 -0
  6. package/dist/bin.d.ts +1 -0
  7. package/dist/bin.js +892 -0
  8. package/dist/bin.js.map +1 -0
  9. package/dist/compile-B0q07Hzq.d.cts +656 -0
  10. package/dist/compile-B0q07Hzq.d.ts +656 -0
  11. package/dist/create-bin.cjs +896 -0
  12. package/dist/create-bin.cjs.map +1 -0
  13. package/dist/create-bin.d.cts +1 -0
  14. package/dist/create-bin.d.ts +1 -0
  15. package/dist/create-bin.js +894 -0
  16. package/dist/create-bin.js.map +1 -0
  17. package/dist/hydration-diagnostics-BE6xVWD1.d.cts +89 -0
  18. package/dist/hydration-diagnostics-Bck5dMbz.d.ts +89 -0
  19. package/dist/index.cjs +4284 -0
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/index.d.cts +1759 -0
  22. package/dist/index.d.ts +1759 -0
  23. package/dist/index.js +4114 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/server-84Rz4g8W.d.cts +165 -0
  26. package/dist/server-D9GPmB49.d.ts +165 -0
  27. package/dist/server.cjs +972 -0
  28. package/dist/server.cjs.map +1 -0
  29. package/dist/server.d.cts +2 -0
  30. package/dist/server.d.ts +2 -0
  31. package/dist/server.js +940 -0
  32. package/dist/server.js.map +1 -0
  33. package/dist/testing.cjs +1754 -0
  34. package/dist/testing.cjs.map +1 -0
  35. package/dist/testing.d.cts +113 -0
  36. package/dist/testing.d.ts +113 -0
  37. package/dist/testing.js +1719 -0
  38. package/dist/testing.js.map +1 -0
  39. package/package.json +113 -0
  40. package/templates/basic/README.md +39 -0
  41. package/templates/basic/_gitignore +15 -0
  42. package/templates/basic/_package.json +21 -0
  43. package/templates/basic/public/styles.css +40 -0
  44. package/templates/basic/src/app.ts +62 -0
  45. package/templates/basic/src/main.ts +39 -0
  46. package/templates/basic/src/server.ts +40 -0
  47. package/templates/basic/streetui.config.ts +6 -0
  48. package/templates/basic/tsconfig.json +16 -0
  49. package/templates/ssr/README.md +46 -0
  50. package/templates/ssr/_gitignore +15 -0
  51. package/templates/ssr/_package.json +21 -0
  52. package/templates/ssr/public/favicon.svg +4 -0
  53. package/templates/ssr/public/styles.css +61 -0
  54. package/templates/ssr/src/app.ts +135 -0
  55. package/templates/ssr/src/main.ts +53 -0
  56. package/templates/ssr/src/server.ts +47 -0
  57. package/templates/ssr/streetui.config.ts +14 -0
  58. package/templates/ssr/tsconfig.json +16 -0
@@ -0,0 +1,1759 @@
1
+ import { a as Signal, b as Subscriber, U as Unsubscribe, R as ReadonlySignal, A as ApplicationId, D as DiagnosticCollector, c as ApplicationGraph, G as GraphNode, C as CompiledApplication, d as SemanticNodeType, P as PageDSL, e as ContainerDSL, f as SignalKind } from './compile-B0q07Hzq.js';
2
+ export { g as A11yOptions, h as AppBuilder, i as AppDSL, j as AppOptions, k as ApplicationGraphOptions, B as BaseNode, l as Bindable, m as BindableText, n as BoundInputOptions, o as ButtonOptions, p as CompileOptions, q as ContainerBuilder, r as ContainerBuilderImpl, s as ContainerOptions, t as ContentDSL, u as ControlledInputOptions, v as DerivedSignal, w as Diagnostic, x as DiagnosticError, y as DiagnosticLocation, z as DiagnosticSeverity, E as ErrorBoundaryOptions, F as ErrorFallbackBuilder, H as ErrorSource, I as EventDescriptor, J as FormBuilder, K as FormBuilderImpl, L as FormDSL, M as FormOptions, N as GraphNodeData, O as HandlerFn, Q as HeadingOptions, T as ImageOptions, W as InputOptions, X as InputOptionsBase, Y as LinkOptions, Z as ListBuilder, _ as ListBuilderImpl, $ as ListDSL, a0 as ListOptions, a1 as NodeId, a2 as NodeMetadata, a3 as PageBuilder, a4 as PageBuilderImpl, a5 as PropValue, a6 as Props, a7 as ReactiveConsumer, a8 as ReactiveSource, a9 as SectionBuilder, aa as SectionBuilderImpl, ab as SectionDSL, ac as SectionOptions, ad as SerializedGraph, ae as SerializedNode, af as StateRef, S as StreetApp, ag as StreetUI, ah as TextOptions, ai as TextValue, V as VERSION, aj as batch, ak as compile, al as compileGraph, am as createNodeId, an as derived, ao as effect, ap as formatDiagnostic, aq as generateApplicationId, ar as generateNodeId, as as isBatching, at as nextId, au as nodeIdPrefix, av as observerCount, aw as reactiveListItemKey, ax as reactiveListItemSignature, ay as resetIdCounter, az as signal, aA as signalKind, aB as streetui } from './compile-B0q07Hzq.js';
3
+ import { R as RenderHandle, S as StreetRenderer, a as HydrationDiagnosticSink } from './hydration-diagnostics-Bck5dMbz.js';
4
+ export { H as HydrationDiagnostic, b as HydrationMismatchType, c as consoleHydrationDiagnosticSink, d as createHydrationDiagnosticCollector, f as formatHydrationDiagnostic } from './hydration-diagnostics-Bck5dMbz.js';
5
+ import { D as DOMAdapter } from './server-D9GPmB49.js';
6
+ export { R as RenderToStringOptions, S as STATE_MARKER_ATTR, a as ServerDOMAdapter, r as readState, b as renderToString, s as serializeState, c as serverDOMAdapter } from './server-D9GPmB49.js';
7
+
8
+ /**
9
+ * A simple reactive store built on top of signals.
10
+ * Useful for structured state with multiple fields.
11
+ */
12
+
13
+ type StoreState = Record<string, unknown>;
14
+ declare class Store<T extends StoreState> {
15
+ private readonly _signals;
16
+ constructor(initial: T);
17
+ get<K extends keyof T>(key: K): T[K];
18
+ set<K extends keyof T>(key: K, value: T[K]): void;
19
+ signal<K extends keyof T>(key: K): Signal<T[K]>;
20
+ subscribe<K extends keyof T>(key: K, fn: Subscriber<T[K]>): Unsubscribe;
21
+ getSnapshot(): T;
22
+ }
23
+ declare function createStore<T extends StoreState>(initial: T): Store<T>;
24
+
25
+ /**
26
+ * StreetUI async resources — framework-native asynchronous data.
27
+ *
28
+ * A `resource` wraps a Promise-returning loader and exposes its lifecycle as
29
+ * ordinary StreetUI signals (status / data / error), so it composes with
30
+ * `derived`, `effect`, `when()`, `listOf` and the renderer with no second
31
+ * reactive system.
32
+ *
33
+ * State machine:
34
+ *
35
+ * idle ──(load)──▶ loading ──(resolve)──▶ success
36
+ * │
37
+ * └────(reject)──────▶ error
38
+ *
39
+ * Refetch keeps the previously-loaded `data` visible while `status` is
40
+ * `'loading'` again (see `isRefetching`) — there is no separate `'refetching'`
41
+ * status; it is expressed through `status === 'loading'` with `data` still set.
42
+ *
43
+ * The resource is transport-agnostic: the loader is any function returning a
44
+ * value or a Promise. When it accepts the provided `AbortSignal`, in-flight
45
+ * work is cancelled on `dispose()` or when a newer request supersedes it.
46
+ */
47
+
48
+ type ResourceStatus = 'idle' | 'loading' | 'success' | 'error';
49
+ /** Context handed to the loader; carries an AbortSignal for cancellation. */
50
+ interface ResourceLoaderContext {
51
+ readonly signal: AbortSignal;
52
+ }
53
+ /** Any value-or-Promise producing function. Receives an abort-aware context. */
54
+ type ResourceLoader<T> = (ctx: ResourceLoaderContext) => Promise<T> | T;
55
+ interface ResourceOptions<T = unknown> {
56
+ /** Load immediately on creation. Defaults to `true`. When `false`, stays `idle` until `refetch()`. */
57
+ readonly immediate?: boolean;
58
+ /**
59
+ * Explicit reactive dependencies. When any listed signal changes, the
60
+ * resource refetches. Dependencies are explicit (not auto-tracked from the
61
+ * loader body) so there is no risk of an accidental infinite refetch loop.
62
+ */
63
+ readonly watch?: ReadonlyArray<ReadonlySignal<unknown>>;
64
+ /**
65
+ * Optional teardown registrar (e.g. a route's `ctx.onCleanup`). When given,
66
+ * the resource registers its own `dispose` so it is cleaned up automatically
67
+ * when its owner is removed.
68
+ */
69
+ readonly onCleanup?: (fn: () => void) => void;
70
+ /**
71
+ * Server-provided initial value for hydration. When present the resource
72
+ * starts in `'success'` with this data already visible, and the initial
73
+ * auto-load is skipped (so the client does not refetch data the server
74
+ * already resolved). This is the client half of SSR resource transfer; the
75
+ * server side awaits `refetch()` before serializing. Set `immediate: true`
76
+ * explicitly to force a client refetch anyway.
77
+ */
78
+ readonly initialData?: T;
79
+ /** Server-provided initial error for hydration (mirrors `initialData`). */
80
+ readonly initialError?: unknown;
81
+ /**
82
+ * Explicit initial status override. Rarely needed — inferred as `'success'`
83
+ * from `initialData` or `'error'` from `initialError`.
84
+ */
85
+ readonly initialStatus?: ResourceStatus;
86
+ }
87
+ interface Resource<T> {
88
+ /** Reactive lifecycle status. */
89
+ readonly status: ReadonlySignal<ResourceStatus>;
90
+ /** The last successfully-loaded value, or `undefined` before first success. */
91
+ readonly data: ReadonlySignal<T | undefined>;
92
+ /** The most recent error, or `undefined` when there is none. Typed `unknown` — never `any`. */
93
+ readonly error: ReadonlySignal<unknown>;
94
+ /** Convenience: `status === 'loading'`. */
95
+ readonly loading: ReadonlySignal<boolean>;
96
+ /** Convenience: loading while previously-loaded data is still present (a refetch). */
97
+ readonly isRefetching: ReadonlySignal<boolean>;
98
+ /** Trigger a new request. Resolves when the request settles (or is superseded). */
99
+ refetch(): Promise<void>;
100
+ /** Cancel in-flight work, drop watchers, and ignore any late results. Idempotent. */
101
+ dispose(): void;
102
+ }
103
+ declare function resource<T>(loader: ResourceLoader<T>, options?: ResourceOptions<T>): Resource<T>;
104
+
105
+ /**
106
+ * Deterministic accessibility id helpers.
107
+ *
108
+ * Accessible markup often needs stable id relationships — a `<label for>` (or
109
+ * `aria-labelledby`) pointing at an input, an `aria-describedby` pointing at a
110
+ * hint/error, an `aria-labelledby` on a dialog pointing at its title. Those ids
111
+ * must be IDENTICAL on the server and the client, otherwise a hydrated subtree
112
+ * that re-renders (e.g. a toggled `when()` branch) would compute a different id
113
+ * than the server emitted and break the association.
114
+ *
115
+ * These helpers derive ids purely from a caller-supplied stable base string
116
+ * (typically a form field name or a dialog name). They use NO incrementing
117
+ * counter and NO randomness, so `a11yIds('email')` yields the same ids in every
118
+ * environment and on every call — which is exactly what SSR + hydration needs.
119
+ */
120
+ /** Normalise an arbitrary base into a token safe for use in an id/selector. */
121
+ declare function toIdToken(base: string): string;
122
+ interface A11yIds {
123
+ /** The normalised base token. */
124
+ readonly base: string;
125
+ /** Id for the primary interactive element (e.g. the input). */
126
+ readonly input: string;
127
+ /** Id for a label element / labelling text. */
128
+ readonly label: string;
129
+ /** Id for descriptive/help text. */
130
+ readonly description: string;
131
+ /** Id for an error message element. */
132
+ readonly error: string;
133
+ /** Id for a title element (e.g. a dialog title). */
134
+ readonly title: string;
135
+ /** Derive an arbitrary suffixed id from the same base. */
136
+ id(suffix: string): string;
137
+ }
138
+ /**
139
+ * Build a set of deterministic, SSR-stable ids from a base string.
140
+ *
141
+ * @example
142
+ * const ids = a11yIds('email');
143
+ * // ids.input === 'email-input', ids.label === 'email-label', ...
144
+ * input({ bind: value, id: ids.input, ariaLabelledBy: ids.label, ariaDescribedBy: ids.error });
145
+ * text('Email', { id: ids.label });
146
+ */
147
+ declare function a11yIds(base: string): A11yIds;
148
+
149
+ /**
150
+ * Application and component lifecycle primitives.
151
+ *
152
+ * Lifecycle phases:
153
+ * created → mounted → active ⇄ updating → unmounting → destroyed
154
+ */
155
+ type LifecyclePhase = 'created' | 'mounted' | 'active' | 'updating' | 'unmounting' | 'destroyed';
156
+ type LifecycleHook = () => void | Promise<void>;
157
+ declare class Lifecycle {
158
+ private _phase;
159
+ private readonly _hooks;
160
+ get phase(): LifecyclePhase;
161
+ get isMounted(): boolean;
162
+ get isDestroyed(): boolean;
163
+ on(phase: LifecyclePhase, hook: LifecycleHook): () => void;
164
+ transition(to: LifecyclePhase): Promise<void>;
165
+ onMount(hook: LifecycleHook): () => void;
166
+ onUnmount(hook: LifecycleHook): () => void;
167
+ onDestroy(hook: LifecycleHook): () => void;
168
+ }
169
+ /** A simple cleanup registry — collect teardown functions and run them all at once. */
170
+ declare class CleanupRegistry {
171
+ private readonly _fns;
172
+ add(fn: () => void): void;
173
+ run(): void;
174
+ }
175
+
176
+ /**
177
+ * Environment detection and capability flags.
178
+ * The framework behaves slightly differently in browser vs. server vs. test.
179
+ *
180
+ * We use `typeof` checks throughout to remain safe across environments
181
+ * without depending on @types/node.
182
+ */
183
+ type EnvironmentKind = 'browser' | 'server' | 'worker' | 'test' | 'unknown';
184
+ interface EnvironmentCapabilities {
185
+ readonly hasDom: boolean;
186
+ readonly hasWindow: boolean;
187
+ readonly hasDocument: boolean;
188
+ readonly isSecureContext: boolean;
189
+ }
190
+ declare class Environment {
191
+ readonly kind: EnvironmentKind;
192
+ readonly capabilities: EnvironmentCapabilities;
193
+ constructor(kind?: EnvironmentKind);
194
+ get isBrowser(): boolean;
195
+ get isServer(): boolean;
196
+ get isTest(): boolean;
197
+ get isWorker(): boolean;
198
+ }
199
+ /** The singleton environment for this execution context. */
200
+ declare const environment: Environment;
201
+
202
+ /**
203
+ * Top-level Application primitive.
204
+ * Owns lifecycle, identity, and the root of the application graph.
205
+ */
206
+
207
+ interface ApplicationOptions {
208
+ readonly name: string;
209
+ readonly version?: string;
210
+ readonly environment?: Environment;
211
+ }
212
+ declare class Application {
213
+ readonly id: ApplicationId;
214
+ readonly name: string;
215
+ readonly version: string;
216
+ readonly lifecycle: Lifecycle;
217
+ readonly cleanup: CleanupRegistry;
218
+ readonly diagnostics: DiagnosticCollector;
219
+ readonly environment: Environment;
220
+ constructor(options: ApplicationOptions);
221
+ mount(): Promise<void>;
222
+ unmount(): Promise<void>;
223
+ onMount(fn: () => void | Promise<void>): void;
224
+ onUnmount(fn: () => void | Promise<void>): void;
225
+ }
226
+ /** Factory convenience wrapper. */
227
+ declare function createApplication(options: ApplicationOptions): Application;
228
+
229
+ /**
230
+ * Observability boundary — a tiny, optional logging seam plus contextual
231
+ * framework errors.
232
+ *
233
+ * StreetUI never ships a telemetry service, never sends anything over the
234
+ * network, and never logs on its own by default. Instead an application MAY
235
+ * hand the framework a `DiagnosticSink` — any object with the log methods it
236
+ * cares about — and the framework will route the diagnostics it already
237
+ * produces (runtime errors, resource failures, hydration mismatches, router
238
+ * transitions) to it. With no sink attached there is no logging and no cost.
239
+ *
240
+ * This is deliberately smaller than a logging library: it duplicates neither
241
+ * `console` nor any structured-diagnostic type. It is a boundary, not a logger.
242
+ */
243
+ /**
244
+ * Where a framework diagnostic originated. Every field is optional so a caller
245
+ * supplies only what is meaningful for the situation. Values are intended to be
246
+ * non-sensitive identifiers — never tokens, secrets, cookies, or form values.
247
+ */
248
+ interface DiagnosticContext {
249
+ /** The package that produced the diagnostic, e.g. `streetui`. */
250
+ readonly package?: string;
251
+ /** The operation underway, e.g. `hydrate`, `compile`, `navigate`. */
252
+ readonly operation?: string;
253
+ /** The graph node id involved, when applicable. */
254
+ readonly nodeId?: string;
255
+ /** The route path involved, when applicable. */
256
+ readonly route?: string;
257
+ /** A resource identifier involved, when applicable. */
258
+ readonly resource?: string;
259
+ }
260
+ /**
261
+ * The application-provided logging seam. Every method is optional; the
262
+ * framework calls only the ones present. Implementations must not throw.
263
+ */
264
+ interface DiagnosticSink {
265
+ debug?(message: string, context?: DiagnosticContext): void;
266
+ info?(message: string, context?: DiagnosticContext): void;
267
+ warn?(message: string, context?: DiagnosticContext): void;
268
+ error?(message: string, context?: DiagnosticContext): void;
269
+ }
270
+ /** Format a context object as a compact ` [k=v, …]` suffix (empty when bare). */
271
+ declare function formatDiagnosticContext(context?: DiagnosticContext): string;
272
+ /**
273
+ * A framework error whose message carries structured, non-sensitive context so
274
+ * a developer immediately sees which package/operation/node was involved. The
275
+ * message never embeds a stack or environment values; production stack
276
+ * disclosure decisions stay with the server layer.
277
+ */
278
+ declare class StreetFrameworkError extends Error {
279
+ readonly context: DiagnosticContext | undefined;
280
+ constructor(message: string, context?: DiagnosticContext);
281
+ }
282
+ /** Build a `StreetFrameworkError` with the given context. */
283
+ declare function frameworkError(message: string, context?: DiagnosticContext): StreetFrameworkError;
284
+ /**
285
+ * Route a diagnostic to a sink if it implements the matching level. Safe to
286
+ * call with `undefined` — it simply does nothing, which is the default (no
287
+ * logging) posture. Never throws even if the sink method does.
288
+ */
289
+ declare function reportDiagnostic(sink: DiagnosticSink | undefined, level: 'debug' | 'info' | 'warn' | 'error', message: string, context?: DiagnosticContext): void;
290
+ /** A sink that forwards to a `console`-like object, one call per level. */
291
+ declare function consoleDiagnosticSink(logger?: Partial<Record<'debug' | 'info' | 'warn' | 'error', (msg: string) => void>>): DiagnosticSink;
292
+
293
+ /**
294
+ * Compiler-phase validation of the ApplicationGraph.
295
+ *
296
+ * This runs after the DSL has built the graph but before the runtime
297
+ * receives a CompiledApplication. More checks live here than in the
298
+ * graph's own validate() because the compiler has broader context.
299
+ */
300
+
301
+ declare function validateGraph(graph: ApplicationGraph): DiagnosticCollector;
302
+
303
+ /**
304
+ * Graph transformation pass.
305
+ *
306
+ * After validation, the transformer prepares the graph for the runtime by:
307
+ * - Resolving implicit defaults (e.g. heading level defaults to 1)
308
+ * - Normalizing prop names
309
+ * - Assigning deterministic render keys where missing
310
+ * - Flattening / hoisting where beneficial
311
+ */
312
+
313
+ declare function transformGraph(graph: ApplicationGraph): void;
314
+
315
+ /**
316
+ * RuntimeNodeInstance — the runtime's live representation of a GraphNode.
317
+ *
318
+ * Each GraphNode in the compiled application gets a corresponding
319
+ * RuntimeNodeInstance during mounting. The instance owns:
320
+ * - the DOM node(s) produced for this graph node
321
+ * - all signal subscriptions that drive updates
322
+ * - all DOM event listeners
323
+ * - child instances
324
+ */
325
+
326
+ interface NodeInstanceOptions {
327
+ readonly graphNode: GraphNode;
328
+ readonly domNode: Node;
329
+ }
330
+ declare class RuntimeNodeInstance {
331
+ readonly graphNode: GraphNode;
332
+ domNode: Node;
333
+ readonly children: RuntimeNodeInstance[];
334
+ readonly cleanup: CleanupRegistry;
335
+ private _mounted;
336
+ constructor(options: NodeInstanceOptions);
337
+ get isMounted(): boolean;
338
+ mount(): void;
339
+ unmount(): void;
340
+ addChild(instance: RuntimeNodeInstance): void;
341
+ /** Subscribe to a signal and register the unsubscribe for cleanup. */
342
+ trackSignal<T>(signal: Signal<T> | ReadonlySignal<T>, handler: (value: T) => void): void;
343
+ /** Register an arbitrary cleanup function (e.g. DOM event removal). */
344
+ trackCleanup(fn: () => void): void;
345
+ }
346
+
347
+ /**
348
+ * StreetUI update scheduler.
349
+ *
350
+ * Responsibilities:
351
+ * - Queue update callbacks
352
+ * - Batch synchronous enqueues into a single microtask flush
353
+ * - Guarantee ordering: higher priority jobs flush first
354
+ * - Prevent duplicate work for the same job key
355
+ * - Allow synchronous flush for tests
356
+ */
357
+ type Priority = 'immediate' | 'normal' | 'idle';
358
+ interface Job {
359
+ /** Unique key — if another job with the same key is already queued, it is replaced. */
360
+ readonly key: string;
361
+ readonly priority: Priority;
362
+ readonly fn: () => void;
363
+ }
364
+ /**
365
+ * Optional error-reporting hook (v0.9 §26/§27). Structurally compatible with
366
+ * `streetui`'s `DiagnosticSink` (the `error` method) so an application can
367
+ * route swallowed scheduler-job failures through its own logger instead of the
368
+ * default `console.error`. Kept as a local structural type so the scheduler
369
+ * stays dependency-free; no network, no telemetry. When unset, behaviour is
370
+ * exactly as before.
371
+ */
372
+ interface SchedulerDiagnostics {
373
+ error?(message: string, context?: unknown): void;
374
+ }
375
+ declare class Scheduler {
376
+ private readonly _queue;
377
+ private _flushScheduled;
378
+ private _flushing;
379
+ private _diagnostics;
380
+ /**
381
+ * Install an optional diagnostic sink for swallowed job errors. Pass
382
+ * `undefined` to restore the default `console.error` reporting. Additive and
383
+ * opt-in — the scheduler never sends anything anywhere on its own.
384
+ */
385
+ setDiagnostics(sink: SchedulerDiagnostics | undefined): void;
386
+ /** Total jobs currently queued. */
387
+ get size(): number;
388
+ /** True if a flush has been scheduled but not yet executed. */
389
+ get isPending(): boolean;
390
+ /**
391
+ * Enqueue a job. If a job with the same key exists, the new one replaces it
392
+ * (allowing callers to coalesce repeated updates for the same node).
393
+ */
394
+ schedule(job: Job): void;
395
+ /** Schedule multiple jobs atomically. */
396
+ scheduleAll(jobs: readonly Job[]): void;
397
+ /**
398
+ * Cancel a queued job by key. No-op if not queued.
399
+ */
400
+ cancel(key: string): void;
401
+ /**
402
+ * Synchronously flush all queued jobs (sorted by priority).
403
+ * Useful in tests and for immediate rendering.
404
+ */
405
+ flush(): void;
406
+ /** Clear all pending jobs without executing them. */
407
+ clear(): void;
408
+ private _scheduleMicrotask;
409
+ }
410
+ /** The shared global scheduler instance. */
411
+ declare const scheduler: Scheduler;
412
+ /** Convenience: schedule a normal-priority job. */
413
+ declare function scheduleUpdate(key: string, fn: () => void): void;
414
+ /** Convenience: schedule an immediate-priority job. */
415
+ declare function scheduleImmediate(key: string, fn: () => void): void;
416
+ /** Convenience: flush the global scheduler synchronously. */
417
+ declare function flushSync(): void;
418
+
419
+ /**
420
+ * StreetUI Runtime.
421
+ *
422
+ * Owns:
423
+ * - Signal binding — wires signal subscriptions to renderer update calls
424
+ * - Event dispatch — calls registered handlers from graph events
425
+ * - Lifecycle — orchestrates mount, update cycles, unmount
426
+ *
427
+ * The runtime does NOT create DOM nodes. It calls into StreetRenderer
428
+ * for all DOM operations.
429
+ */
430
+
431
+ interface RuntimeOptions {
432
+ readonly renderer: StreetRenderer;
433
+ readonly scheduler?: Scheduler;
434
+ }
435
+ interface MountedApplication {
436
+ readonly renderHandle: RenderHandle;
437
+ readonly runtime: Runtime;
438
+ unmount(): void;
439
+ flush(): void;
440
+ }
441
+ declare class Runtime {
442
+ private readonly _renderer;
443
+ private readonly _scheduler;
444
+ private readonly _cleanup;
445
+ private _renderHandle;
446
+ private _mounted;
447
+ constructor(options: RuntimeOptions);
448
+ get isMounted(): boolean;
449
+ /**
450
+ * Mount the compiled application into the given DOM container.
451
+ */
452
+ mount(compiled: CompiledApplication, container: Element): MountedApplication;
453
+ unmount(): void;
454
+ /**
455
+ * Hydrate a container that already holds server-rendered HTML for this
456
+ * application. Delegates to the renderer's `hydrate` (adopting the existing
457
+ * DOM instead of recreating it) and falls back to `mount` for renderers that
458
+ * cannot hydrate. Signal binding is identical to `mount`, so the live client
459
+ * lifecycle is established the same way.
460
+ */
461
+ hydrate(compiled: CompiledApplication, container: Element): MountedApplication;
462
+ /**
463
+ * Walk the graph and subscribe to all signal-bound nodes.
464
+ * When a signal changes, schedule a renderer update for that node.
465
+ */
466
+ private _bindSignals;
467
+ }
468
+ /**
469
+ * Convenience factory — create a runtime, mount, and return the handle.
470
+ */
471
+ declare function createRuntime(options: RuntimeOptions): Runtime;
472
+
473
+ /**
474
+ * StreetUI event type catalogue.
475
+ * Framework events are distinct from raw DOM events.
476
+ */
477
+ type StreetEventType = 'click' | 'dblclick' | 'input' | 'change' | 'submit' | 'focus' | 'blur' | 'keydown' | 'keyup' | 'keypress' | 'mouseenter' | 'mouseleave' | 'mousemove' | 'mousedown' | 'mouseup' | 'pointerdown' | 'pointerup' | 'pointermove' | 'pointerenter' | 'pointerleave' | 'scroll' | 'resize' | 'mount' | 'unmount' | 'update';
478
+ interface StreetEvent<T = unknown> {
479
+ readonly type: StreetEventType | string;
480
+ readonly target: unknown;
481
+ readonly data: T | undefined;
482
+ readonly originalEvent: Event | undefined;
483
+ readonly timestamp: number;
484
+ defaultPrevented: boolean;
485
+ stopPropagation(): void;
486
+ preventDefault(): void;
487
+ }
488
+ declare function createStreetEvent<T = unknown>(type: StreetEventType | string, target: unknown, data?: T, originalEvent?: Event): StreetEvent<T>;
489
+ type EventHandler<T = unknown> = (event: StreetEvent<T>) => void;
490
+
491
+ /**
492
+ * Framework-internal event bus.
493
+ * Decouples emitters from handlers across subsystems.
494
+ */
495
+
496
+ declare class EventBus {
497
+ private readonly _handlers;
498
+ on<T = unknown>(type: string, handler: EventHandler<T>): () => void;
499
+ off<T = unknown>(type: string, handler: EventHandler<T>): void;
500
+ once<T = unknown>(type: string, handler: EventHandler<T>): () => void;
501
+ emit<T = unknown>(event: StreetEvent<T>): void;
502
+ clear(type?: string): void;
503
+ listenerCount(type: string): number;
504
+ }
505
+ declare const globalEventBus: EventBus;
506
+
507
+ /**
508
+ * DOM ↔ StreetUI event bridge.
509
+ *
510
+ * Attaches native DOM event listeners and translates them into
511
+ * StreetUI events dispatched to registered handlers.
512
+ * The renderer uses this to wire events without coupling
513
+ * DOM event mechanics into the render pipeline directly.
514
+ */
515
+
516
+ interface DomBinding {
517
+ remove(): void;
518
+ }
519
+ /**
520
+ * Attach a DOM event listener that fires the given StreetUI handler.
521
+ * Returns a binding whose `remove()` detaches the listener.
522
+ */
523
+ declare function bindDomEvent<T extends Event = Event>(element: EventTarget, domEventType: StreetEventType | string, handler: EventHandler, options?: AddEventListenerOptions): DomBinding;
524
+ /**
525
+ * A registry that tracks all DOM bindings for a single node,
526
+ * making bulk teardown easy.
527
+ */
528
+ declare class DomEventRegistry {
529
+ private readonly _bindings;
530
+ bind(element: EventTarget, type: StreetEventType | string, handler: EventHandler, options?: AddEventListenerOptions): void;
531
+ removeAll(): void;
532
+ get count(): number;
533
+ }
534
+
535
+ /**
536
+ * Browser implementation of DOMAdapter — delegates directly to browser APIs.
537
+ */
538
+
539
+ declare class BrowserDOMAdapter implements DOMAdapter {
540
+ createElement(tag: string, ns?: string): Element;
541
+ createTextNode(data: string): Text;
542
+ createComment(data: string): Comment;
543
+ createFragment(): DocumentFragment;
544
+ appendChild(parent: Node, child: Node): void;
545
+ insertBefore(parent: Node, child: Node, reference: Node | null): void;
546
+ removeChild(parent: Node, child: Node): void;
547
+ replaceChild(parent: Node, newChild: Node, oldChild: Node): void;
548
+ setAttribute(element: Element, name: string, value: string): void;
549
+ removeAttribute(element: Element, name: string): void;
550
+ getAttribute(element: Element, name: string): string | null;
551
+ setProperty(element: Element, name: string, value: unknown): void;
552
+ setTextContent(node: Node, text: string): void;
553
+ getTextContent(node: Node): string | null;
554
+ addEventListener(target: EventTarget, type: string, handler: EventListener, options?: AddEventListenerOptions): void;
555
+ removeEventListener(target: EventTarget, type: string, handler: EventListener, options?: EventListenerOptions): void;
556
+ querySelector(root: Element | Document, selector: string): Element | null;
557
+ querySelectorAll(root: Element | Document, selector: string): NodeListOf<Element>;
558
+ getElementById(id: string): Element | null;
559
+ focus(element: Element): void;
560
+ isElement(node: Node): node is Element;
561
+ isTextNode(node: Node): node is Text;
562
+ tagName(element: Element): string;
563
+ parentNode(node: Node): Node | null;
564
+ nextSibling(node: Node): Node | null;
565
+ firstChild(node: Node): Node | null;
566
+ childNodes(node: Node): Node[];
567
+ }
568
+ declare const browserDOMAdapter: BrowserDOMAdapter;
569
+
570
+ /**
571
+ * Server-side DOM node model.
572
+ *
573
+ * A tiny, dependency-free tree of plain objects that mirrors just enough of the
574
+ * browser DOM for StreetUI's renderer to build a tree on the server and
575
+ * serialize it to an HTML string. There is NO browser global here — these are
576
+ * ordinary classes usable in any JavaScript environment (Node, workers, tests).
577
+ *
578
+ * The renderer never touches these types directly; it goes through the
579
+ * `DOMAdapter` interface, and `ServerDOMAdapter` translates adapter calls into
580
+ * operations on this model.
581
+ */
582
+ type ServerNodeKind = 'element' | 'text' | 'comment' | 'fragment';
583
+ interface ServerNode {
584
+ readonly kind: ServerNodeKind;
585
+ parent: ServerParent | null;
586
+ }
587
+ type ServerParent = ServerElement | ServerFragment;
588
+ /** A minimal inline-style holder mirroring `element.style.setProperty`. */
589
+ declare class ServerStyle {
590
+ readonly declarations: Map<string, string>;
591
+ setProperty(name: string, value: string): void;
592
+ get isEmpty(): boolean;
593
+ toCss(): string;
594
+ }
595
+ declare class ServerText implements ServerNode {
596
+ readonly kind: "text";
597
+ parent: ServerParent | null;
598
+ data: string;
599
+ constructor(data: string);
600
+ }
601
+ declare class ServerComment implements ServerNode {
602
+ readonly kind: "comment";
603
+ parent: ServerParent | null;
604
+ data: string;
605
+ constructor(data: string);
606
+ }
607
+ declare class ServerFragment implements ServerNode {
608
+ readonly kind: "fragment";
609
+ parent: ServerParent | null;
610
+ readonly children: ServerNode[];
611
+ }
612
+ declare class ServerElement implements ServerNode {
613
+ readonly kind: "element";
614
+ parent: ServerParent | null;
615
+ readonly tagName: string;
616
+ readonly attributes: Map<string, string>;
617
+ /** JS properties set via `setProperty` (e.g. input `value`, `checked`). */
618
+ readonly properties: Map<string, unknown>;
619
+ readonly children: ServerNode[];
620
+ readonly style: ServerStyle;
621
+ constructor(tagName: string);
622
+ }
623
+ /** Escape text node content. */
624
+ declare function escapeHtmlText(value: string): string;
625
+ /** Escape a double-quoted attribute value. */
626
+ declare function escapeHtmlAttr(value: string): string;
627
+ /** Serialize a single server node (element/text/comment/fragment) to HTML. */
628
+ declare function serializeServerNode(node: ServerNode): string;
629
+ /** Serialize the children of an element or fragment (its "inner HTML"). */
630
+ declare function serializeChildren(node: ServerElement | ServerFragment): string;
631
+
632
+ /**
633
+ * Focus helpers built on the {@link DOMAdapter} abstraction.
634
+ *
635
+ * These are the minimal, genuinely-useful focus operations an app needs:
636
+ * focus a specific element (e.g. the first field when a route or modal opens)
637
+ * or focus the first focusable element inside a container (e.g. move focus
638
+ * into a dialog). Both go through the adapter, so they are no-ops on the server
639
+ * (`ServerDOMAdapter.querySelector` returns null / `focus` does nothing) and
640
+ * therefore safe to call from universal code.
641
+ */
642
+
643
+ /** Default selector for natively focusable / tabbable elements. */
644
+ declare const FOCUSABLE_SELECTOR = "a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex=\"-1\"])";
645
+ /**
646
+ * Focus the element with the given id, scoped to `root`.
647
+ * Returns true if an element was found and focused.
648
+ */
649
+ declare function focusById(dom: DOMAdapter, root: Element | Document, id: string): boolean;
650
+ /**
651
+ * Focus the first focusable element inside `container`.
652
+ * Returns true if a focusable element was found and focused.
653
+ */
654
+ declare function focusFirst(dom: DOMAdapter, container: Element | Document, selector?: string): boolean;
655
+
656
+ /**
657
+ * NodeInstance — the renderer's live counterpart to a GraphNode.
658
+ *
659
+ * Tracks the actual DOM node(s), all signal subscriptions that drive
660
+ * targeted DOM updates, and DOM event listener teardowns.
661
+ */
662
+
663
+ declare class NodeInstance {
664
+ readonly graphNode: GraphNode;
665
+ /** The primary DOM node for this instance (element or text node). */
666
+ domNode: Node;
667
+ readonly children: NodeInstance[];
668
+ readonly cleanup: CleanupRegistry;
669
+ constructor(graphNode: GraphNode, domNode: Node);
670
+ addChild(child: NodeInstance): void;
671
+ /** Subscribe to a signal; auto-cleanup on unmount. */
672
+ trackSignal<T>(sig: ReadonlySignal<T>, handler: (v: T) => void): void;
673
+ /** Register a raw cleanup fn (DOM event removal, etc.). */
674
+ trackCleanup(fn: () => void): void;
675
+ dispose(): void;
676
+ }
677
+
678
+ /**
679
+ * RenderContext — shared state for a single mount operation.
680
+ *
681
+ * Passed through the render pipeline so every sub-function has access
682
+ * to the DOM adapter, graph, and instance map without prop-drilling.
683
+ */
684
+
685
+ interface RenderContext {
686
+ readonly dom: DOMAdapter;
687
+ readonly graph: ApplicationGraph;
688
+ /** Maps GraphNode.id → its live NodeInstance */
689
+ readonly instances: Map<string, NodeInstance>;
690
+ /** The root container element. */
691
+ readonly container: Element;
692
+ /**
693
+ * Optional dev-only sink that observes hydration mismatch repairs. When
694
+ * absent (the default) the hydration path does no extra work — this is how
695
+ * DevTools/diagnostics stay off the production runtime path.
696
+ */
697
+ readonly hydrationDiagnostics?: HydrationDiagnosticSink;
698
+ }
699
+ declare function createRenderContext(dom: DOMAdapter, graph: ApplicationGraph, container: Element, hydrationDiagnostics?: HydrationDiagnosticSink): RenderContext;
700
+
701
+ /**
702
+ * Attribute and property application helpers.
703
+ *
704
+ * Decides whether a prop should be set as a DOM attribute or a JS property,
705
+ * handling special cases (boolean attrs, event-like props, style, class).
706
+ */
707
+
708
+ declare function applyProp(dom: DOMAdapter, element: Element, name: string, value: unknown): void;
709
+ declare function patchProp(dom: DOMAdapter, element: Element, name: string, oldValue: unknown, newValue: unknown): void;
710
+
711
+ /**
712
+ * Event wiring for the renderer.
713
+ *
714
+ * Given a GraphNode with event descriptors, this wires DOM listeners
715
+ * that call the handlers stored in the graph's handler registry.
716
+ */
717
+
718
+ declare function wireEvents(dom: DOMAdapter, graph: ApplicationGraph, node: GraphNode, element: Element, instance: NodeInstance): void;
719
+
720
+ /**
721
+ * Initial mount — creates DOM nodes for every GraphNode and
722
+ * attaches them into the container.
723
+ *
724
+ * This is a recursive depth-first walk. For each GraphNode:
725
+ * 1. Create the DOM element (or text node)
726
+ * 2. Apply props/attributes
727
+ * 3. Wire events
728
+ * 4. Wire signal subscriptions for reactive props
729
+ * 5. Recurse into children
730
+ * 6. Insert into the DOM
731
+ */
732
+
733
+ declare function mountGraph(ctx: RenderContext): NodeInstance;
734
+ declare function mountNode(ctx: RenderContext, graphNode: GraphNode, parentDom: Node): NodeInstance;
735
+ /**
736
+ * Per-node-type reactive-binding factories. Each returns the `onUpdate`
737
+ * callback that `wireSignalBindings` invokes when a bound signal changes.
738
+ * Extracted so both the browser mount path and the hydration path apply the
739
+ * exact same DOM mutation semantics for each prop — no duplicated rendering
740
+ * logic.
741
+ */
742
+ declare function textUpdate(dom: DOMAdapter, el: Element, textNode: Text): (propKey: string, value: unknown) => void;
743
+ declare function headingUpdate(dom: DOMAdapter, el: Element): (propKey: string, value: unknown) => void;
744
+ declare function inputUpdate(dom: DOMAdapter, el: Element): (propKey: string, value: unknown) => void;
745
+ declare function buttonUpdate(dom: DOMAdapter, el: Element): (propKey: string, value: unknown) => void;
746
+ declare function applyNodeProps(ctx: RenderContext, graphNode: GraphNode, el: Element): void;
747
+ declare function wireSignalBindings(ctx: RenderContext, graphNode: GraphNode, instance: NodeInstance, onUpdate: (propKey: string, value: unknown) => void): void;
748
+ /**
749
+ * Subscribe a reactive-list instance to its driving signal. On each change the
750
+ * DSL-registered build factory produces the desired child graph nodes, which
751
+ * are reconciled against the live DOM with the keyed reconciler.
752
+ */
753
+ declare function wireReactiveList(ctx: RenderContext, graphNode: GraphNode, instance: NodeInstance, el: Element): void;
754
+
755
+ /**
756
+ * Patch — targeted DOM updates driven by signal changes.
757
+ *
758
+ * When a signal fires, we look up the NodeInstance and apply
759
+ * only the changed prop — no full re-render, no tree diffing.
760
+ */
761
+
762
+ declare function patchNode(ctx: RenderContext, graphNode: GraphNode, propKey: string, newValue: unknown): void;
763
+
764
+ /**
765
+ * Reconciliation — diff-based child list updates.
766
+ *
767
+ * When the children of a node change (e.g. a list driven by state),
768
+ * this reconciler:
769
+ * 1. Matches old instances to new graph nodes by key
770
+ * 2. Reuses matched instances (updates their props)
771
+ * 3. Applies a targeted content update to a reused item whose data changed
772
+ * 4. Creates new instances for additions
773
+ * 5. Removes stale instances (and prunes their handler registrations)
774
+ * 6. Moves DOM nodes to match new order
775
+ *
776
+ * This is keyed reconciliation over the semantic graph — there is no virtual
777
+ * DOM. A reused item keeps its own DOM element; only its changed content is
778
+ * updated in place (falling back to remounting a subtree only where its shape
779
+ * actually changed).
780
+ */
781
+
782
+ type MountFn = (node: GraphNode, parent: Element) => NodeInstance;
783
+ interface ReconcileResult {
784
+ /** Instances in the new order. */
785
+ instances: NodeInstance[];
786
+ /** Instances that were removed and must be disposed. */
787
+ removed: NodeInstance[];
788
+ }
789
+ /**
790
+ * Reconcile children of a container element against a new list of graph nodes.
791
+ *
792
+ * @param ctx Render context
793
+ * @param parentDom The DOM parent element
794
+ * @param oldInstances Current child instances (in order)
795
+ * @param newNodes New graph children (in desired order)
796
+ * @param mountFn Factory to create a new NodeInstance for a graph node
797
+ */
798
+ declare function reconcileChildren(ctx: RenderContext, parentDom: Element, oldInstances: NodeInstance[], newNodes: readonly GraphNode[], mountFn: MountFn): ReconcileResult;
799
+
800
+ /**
801
+ * StreetUI Renderer — framework-owned DOM renderer.
802
+ *
803
+ * No React. No Vue. No virtual-dom. No external rendering library.
804
+ *
805
+ * Pipeline:
806
+ * CompiledApplication
807
+ * → mountGraph (creates all DOM nodes)
808
+ * → signal subscriptions drive patchNode (targeted updates)
809
+ * → flush() propagates any pending scheduler jobs
810
+ * → unmount() disposes everything
811
+ */
812
+
813
+ interface StreetRendererOptions {
814
+ /** Override the DOM adapter (e.g. for testing). Defaults to BrowserDOMAdapter. */
815
+ readonly domAdapter?: DOMAdapter;
816
+ /**
817
+ * Optional dev-only sink that observes hydration mismatch repairs. Attach one
818
+ * to surface server/client divergences during development; leave it unset in
819
+ * production so hydration does no extra work.
820
+ */
821
+ readonly hydrationDiagnostics?: HydrationDiagnosticSink;
822
+ }
823
+ declare class StreetRendererImpl implements StreetRenderer {
824
+ private readonly _dom;
825
+ private readonly _hydrationDiagnostics?;
826
+ constructor(options?: StreetRendererOptions);
827
+ mount(compiled: CompiledApplication, container: Element): RenderHandle;
828
+ /**
829
+ * Hydrate a container that already holds server-rendered HTML for this
830
+ * application. Instead of recreating the DOM, it walks the semantic graph
831
+ * against the existing nodes, adopting matching elements and attaching
832
+ * behavior (events + signal subscriptions). Mismatched subtrees are locally
833
+ * replaced. Returns the same handle type as `mount`.
834
+ */
835
+ hydrate(compiled: CompiledApplication, container: Element): RenderHandle;
836
+ private _wireSignals;
837
+ }
838
+ /**
839
+ * Create the default StreetUI renderer using the browser's DOM APIs.
840
+ */
841
+ declare function createRenderer(options?: StreetRendererOptions): StreetRendererImpl;
842
+
843
+ /**
844
+ * StreetRenderHandle — the live handle returned by both `mount` and `hydrate`.
845
+ *
846
+ * Owns teardown for a mounted/hydrated application: disposes every NodeInstance
847
+ * (removing event listeners and signal subscriptions) and clears the container
848
+ * through the DOM adapter (never raw browser globals), so the same handle works
849
+ * for browser and — in principle — server-driven teardown.
850
+ */
851
+
852
+ declare class StreetRenderHandle implements RenderHandle {
853
+ private _disposed;
854
+ private readonly _ctx;
855
+ private readonly _rootInstance;
856
+ constructor(ctx: RenderContext, rootInstance: NodeInstance);
857
+ flush(): void;
858
+ unmount(): void;
859
+ }
860
+
861
+ /**
862
+ * Hydration — attach a live StreetUI runtime to server-rendered HTML.
863
+ *
864
+ * `hydrate` walks the semantic application graph top-down against the DOM that
865
+ * the server already produced. For every graph node it *adopts* the matching
866
+ * existing element (creating a `NodeInstance` that points at it) and attaches
867
+ * behavior — event listeners and signal subscriptions — using the exact same
868
+ * helpers the browser mount path uses (`wireEvents`, `wireSignalBindings`,
869
+ * `wireReactiveList`, and the per-type update factories). Nothing is recreated
870
+ * when the DOM matches.
871
+ *
872
+ * Matching is positional and works because every non-application graph node
873
+ * maps to exactly one element (see mount.ts). When the element at a position
874
+ * does not match the expected tag (or is missing), only that subtree is
875
+ * repaired: the fresh subtree is mounted and spliced into place, leaving the
876
+ * rest of the hydrated tree untouched. A local mismatch never tears down the
877
+ * whole app.
878
+ */
879
+
880
+ /** Hydrate the whole application graph against `ctx.container`. */
881
+ declare function hydrateGraph(ctx: RenderContext): NodeInstance;
882
+
883
+ /**
884
+ * Maps semantic node types to HTML tag names.
885
+ */
886
+
887
+ declare function resolveTag(type: SemanticNodeType): string;
888
+
889
+ /**
890
+ * StreetUI Router — public type surface.
891
+ *
892
+ * The router sits ABOVE the DSL/compiler/runtime/renderer and composes them.
893
+ * It introduces no new rendering path and no second reactive system: route
894
+ * state is a StreetUI `signal`, route cleanup reuses the core `CleanupRegistry`,
895
+ * and each route renders as an ordinary compiled StreetUI application tree.
896
+ */
897
+
898
+ /**
899
+ * Context handed to a route builder when its route becomes active.
900
+ *
901
+ * `params` are the values captured from dynamic segments (`/users/:id`),
902
+ * `query` is the parsed query string, and `onCleanup` registers work to run
903
+ * when the router navigates away from this route (subscriptions, effects,
904
+ * timers). It is backed by a route-scoped `CleanupRegistry` — there is no
905
+ * separate cleanup mechanism.
906
+ */
907
+ interface RouteContext {
908
+ /** Full matched pathname, e.g. `/users/123`. */
909
+ readonly path: string;
910
+ /** The route pattern that matched, e.g. `/users/:id` or `*`. */
911
+ readonly pattern: string;
912
+ /** Dynamic segment values captured from the path. */
913
+ readonly params: Readonly<Record<string, string>>;
914
+ /** Parsed query string (everything after `?`). */
915
+ readonly query: URLSearchParams;
916
+ /** Register a callback to run when navigating away from this route. */
917
+ onCleanup(fn: () => void): void;
918
+ }
919
+ /** Builds a route's page tree. Receives the page scope and the route context. */
920
+ type RouteBuilder = (page: PageDSL, ctx: RouteContext) => void;
921
+ /** A single route: a path pattern and the builder that renders it. */
922
+ interface RouteDefinition {
923
+ /**
924
+ * Path pattern. Supports:
925
+ * - static segments: `/`, `/docs`, `/docs/getting-started`
926
+ * - dynamic `:param`: `/users/:id`
927
+ * - catch-all wildcard: `*` (matches anything — use for a 404 route)
928
+ */
929
+ readonly path: string;
930
+ readonly builder: RouteBuilder;
931
+ }
932
+ /** The result of resolving a location against the route table. */
933
+ interface RouteMatch {
934
+ /** The active pathname (no query string). */
935
+ readonly path: string;
936
+ /** The pattern of the matched route. */
937
+ readonly pattern: string;
938
+ /** Captured dynamic params. */
939
+ readonly params: Readonly<Record<string, string>>;
940
+ /** Parsed query string. */
941
+ readonly query: URLSearchParams;
942
+ /** The route definition that produced this match. */
943
+ readonly route: RouteDefinition;
944
+ /** True when this match came from the wildcard catch-all (`*`) — i.e. a 404. */
945
+ readonly isFallback: boolean;
946
+ }
947
+
948
+ /**
949
+ * Route matching — pure functions, no DOM, no reactivity.
950
+ *
951
+ * A pattern is matched segment-by-segment against a pathname:
952
+ * - a literal segment must equal the path segment,
953
+ * - a `:name` segment captures the path segment into `params.name`,
954
+ * - a `*` segment (or a whole-pattern `*`) is a catch-all that matches the
955
+ * remainder of the path and captures it into `params['*']`.
956
+ *
957
+ * Matching is intentionally small: no optional segments, no regex constraints,
958
+ * no nested route trees. Composition of layouts is done in the DSL, not here.
959
+ */
960
+ /** Normalize a pathname: ensure a single leading slash, drop a trailing slash. */
961
+ declare function normalizePath(path: string): string;
962
+ /**
963
+ * Try to match a single pattern against a pathname.
964
+ * Returns the captured params on success, or `null` on no match.
965
+ */
966
+ declare function matchPattern(pattern: string, pathname: string): Record<string, string> | null;
967
+ interface MatchResult<R> {
968
+ readonly route: R;
969
+ readonly params: Record<string, string>;
970
+ }
971
+ /**
972
+ * Match a pathname against an ordered list of routes. The first route whose
973
+ * pattern matches wins (definition order), so more specific routes should be
974
+ * listed before a `*` fallback.
975
+ */
976
+ declare function matchRoutes<R extends {
977
+ path: string;
978
+ }>(routes: readonly R[], pathname: string): MatchResult<R> | null;
979
+ /** Split a `to` target into its pathname and (already-stripped) search string. */
980
+ declare function splitTarget(to: string): {
981
+ pathname: string;
982
+ search: string;
983
+ };
984
+
985
+ /**
986
+ * Router history — a small abstraction over the navigation source so the router
987
+ * can run both in the browser (real `window.history` + `popstate`) and in tests
988
+ * (an in-memory stack, fully deterministic, no globals).
989
+ *
990
+ * Internal navigation never triggers a full-page reload: the browser history
991
+ * uses `pushState`/`replaceState` and notifies listeners synchronously.
992
+ */
993
+ interface RouterLocation {
994
+ /** Pathname, always normalized with a single leading slash. */
995
+ readonly pathname: string;
996
+ /** Query string without the leading `?`. */
997
+ readonly search: string;
998
+ }
999
+ interface RouterHistory {
1000
+ /** The current location. */
1001
+ location(): RouterLocation;
1002
+ /** Push a new entry and notify listeners. */
1003
+ push(pathname: string, search: string): void;
1004
+ /** Replace the current entry and notify listeners. */
1005
+ replace(pathname: string, search: string): void;
1006
+ /** Go back one entry. */
1007
+ back(): void;
1008
+ /** Go forward one entry. */
1009
+ forward(): void;
1010
+ /** Subscribe to location changes. Returns an unsubscribe function. */
1011
+ listen(cb: () => void): () => void;
1012
+ /** Detach any global listeners (browser only). */
1013
+ dispose(): void;
1014
+ }
1015
+ /**
1016
+ * Browser history backed by `window.history`. `pushState`/`replaceState` do not
1017
+ * emit `popstate`, so we notify listeners ourselves after those calls; genuine
1018
+ * back/forward navigation arrives via the `popstate` event.
1019
+ */
1020
+ declare function createBrowserHistory(): RouterHistory;
1021
+ /**
1022
+ * In-memory history for tests and non-DOM environments. Maintains an explicit
1023
+ * stack and cursor so `back()`/`forward()` are deterministic.
1024
+ */
1025
+ declare function createMemoryHistory(initial?: string): RouterHistory;
1026
+
1027
+ /**
1028
+ * StreetUI Router core — renderer-agnostic.
1029
+ *
1030
+ * Holds the route table, resolves the current location into a `RouteMatch`,
1031
+ * and exposes that match as a StreetUI `signal`. Navigation is delegated to a
1032
+ * `RouterHistory`; when the location changes (via `navigate`, `back`, `forward`,
1033
+ * or a browser `popstate`) the router recomputes the match and updates the
1034
+ * signal, which is how every consumer (`isActive`, the mount integration, any
1035
+ * `derived` the app builds) stays in sync. No second reactive system.
1036
+ */
1037
+
1038
+ interface RouterOptions {
1039
+ /** The route table. Order matters — the first matching pattern wins. */
1040
+ readonly routes: readonly RouteDefinition[];
1041
+ /**
1042
+ * Navigation source. Defaults to a browser history. Pass a memory history
1043
+ * for tests or non-DOM environments.
1044
+ */
1045
+ readonly history?: RouterHistory;
1046
+ /**
1047
+ * Fallback route used when nothing else matches and no `*` route is present.
1048
+ * Defaults to a built-in 404 page (a normal StreetUI tree — no special path).
1049
+ */
1050
+ readonly notFound?: RouteDefinition;
1051
+ }
1052
+ interface NavigateOptions {
1053
+ /** Replace the current history entry instead of pushing a new one. */
1054
+ readonly replace?: boolean;
1055
+ }
1056
+ interface IsActiveOptions {
1057
+ /** Require an exact pathname match rather than a prefix match. */
1058
+ readonly exact?: boolean;
1059
+ }
1060
+ interface Router {
1061
+ /** Reactive current match. Consumers subscribe via StreetUI signals. */
1062
+ readonly currentRoute: ReadonlySignal<RouteMatch>;
1063
+ /** Navigate to a target path (may include a query string). */
1064
+ navigate(to: string, options?: NavigateOptions): void;
1065
+ /** Go back one history entry. */
1066
+ back(): void;
1067
+ /** Go forward one history entry. */
1068
+ forward(): void;
1069
+ /** Reactive predicate: is `path` the active route (or a prefix of it)? */
1070
+ isActive(path: string, options?: IsActiveOptions): ReadonlySignal<boolean>;
1071
+ /** Tear down history listeners. */
1072
+ destroy(): void;
1073
+ }
1074
+ declare function createRouter(options: RouterOptions): Router;
1075
+
1076
+ /**
1077
+ * StreetUI Router — DOM integration.
1078
+ *
1079
+ * `mountRouter` wires a `Router` to real DOM:
1080
+ *
1081
+ * 1. Mounts an optional persistent shell (layout + navigation) ONCE. The shell
1082
+ * declares an outlet element (see `routerOutlet`) into which route content
1083
+ * is rendered.
1084
+ * 2. Subscribes to `router.currentRoute`. On every change it disposes the
1085
+ * previous route (route-scoped `CleanupRegistry.run()` + `runtime.unmount()`)
1086
+ * and mounts the new route's compiled application into the outlet.
1087
+ * Only the outlet subtree is re-created — the shell persists.
1088
+ * 3. Intercepts clicks on internal `<a>` elements for client-side navigation.
1089
+ * External links (absolute URLs, `target="_blank"`, `mailto:`/`tel:`) keep
1090
+ * their normal browser behaviour, and `link()` is used unchanged.
1091
+ *
1092
+ * Each route is an ordinary compiled StreetUI application — no special renderer
1093
+ * path, no virtual DOM, no full-application rerender on navigation.
1094
+ */
1095
+
1096
+ /** Default id used for the route outlet element inside a shell. */
1097
+ declare const ROUTER_OUTLET_ID = "streetui-router-outlet";
1098
+ /**
1099
+ * Declare the route outlet inside a shell builder. The router replaces this
1100
+ * element's contents on every navigation.
1101
+ */
1102
+ declare function routerOutlet(scope: ContainerDSL, id?: string): void;
1103
+ type ShellBuilder = (shell: PageDSL, router: Router) => void;
1104
+ interface MountRouterOptions {
1105
+ /** Element to mount into. With a shell, the shell fills this; otherwise routes do. */
1106
+ readonly container: Element;
1107
+ /** Optional persistent layout. Must include a `routerOutlet(...)`. */
1108
+ readonly shell?: ShellBuilder;
1109
+ /** Id of the outlet element within the shell. Defaults to `ROUTER_OUTLET_ID`. */
1110
+ readonly outletId?: string;
1111
+ /** Override the renderer (e.g. a custom DOM adapter for tests). */
1112
+ readonly renderer?: StreetRenderer;
1113
+ /** Intercept internal `<a>` clicks for client-side navigation. Defaults to true. */
1114
+ readonly interceptLinks?: boolean;
1115
+ /**
1116
+ * Hydrate server-rendered HTML already present in the container instead of
1117
+ * mounting fresh. The shell and the *initial* route adopt the existing DOM;
1118
+ * subsequent client-side navigations mount normally. Defaults to false.
1119
+ */
1120
+ readonly hydrate?: boolean;
1121
+ }
1122
+ interface MountedRouter {
1123
+ /** The element route content is rendered into. */
1124
+ readonly outlet: Element;
1125
+ /** Tear down the current route, the shell, link interception and the router. */
1126
+ unmount(): void;
1127
+ }
1128
+ declare function mountRouter(router: Router, options: MountRouterOptions): MountedRouter;
1129
+
1130
+ /**
1131
+ * A small, sync validation system.
1132
+ *
1133
+ * A `Validator` maps a string field value to an error message, or `undefined`
1134
+ * when the value is acceptable. This is deliberately tiny — the built-ins cover
1135
+ * the common cases (`required`, `minLength`, `maxLength`, `email`, `pattern`)
1136
+ * and anything else is just a plain function `(value: string) => string | undefined`.
1137
+ *
1138
+ * Validators for a field run in order and the FIRST error wins, so list
1139
+ * `required` first if a field is mandatory.
1140
+ *
1141
+ * Async validation is intentionally NOT part of this core. It can be layered on
1142
+ * top with `streetui`'s `resource()` (kick off a resource on value
1143
+ * change and surface `resource.error` alongside the field error) without
1144
+ * destabilising the synchronous validity model here.
1145
+ */
1146
+ type Validator = (value: string) => string | undefined;
1147
+ /** Fails when the trimmed value is empty. */
1148
+ declare function required(message?: string): Validator;
1149
+ /** Fails when the value is shorter than `length` characters. */
1150
+ declare function minLength(length: number, message?: string): Validator;
1151
+ /** Fails when the value is longer than `length` characters. */
1152
+ declare function maxLength(length: number, message?: string): Validator;
1153
+ /** Fails when a non-empty value is not a plausible email address. */
1154
+ declare function email(message?: string): Validator;
1155
+ /** Fails when a non-empty value does not match `regex`. */
1156
+ declare function pattern(regex: RegExp, message?: string): Validator;
1157
+ /** Run a validator (or ordered list) and return the first error, if any. */
1158
+ declare function runValidators(value: string, validators: Validator | ReadonlyArray<Validator> | undefined): string | undefined;
1159
+
1160
+ /**
1161
+ * Reactive form model built entirely on `streetui` signals.
1162
+ *
1163
+ * There is no second state system here: every piece of form state (`values`,
1164
+ * `errors`, `touched`, `dirty`, `valid`, submission status) is a signal or a
1165
+ * derived signal, so it composes with the renderer's existing reactive bindings.
1166
+ * A field's `value` is a writable `Signal<string>`, which plugs straight into
1167
+ * the DSL's `input({ bind })` — typing updates form state and programmatic
1168
+ * updates update the input, through the one binding the renderer already wires.
1169
+ */
1170
+
1171
+ /** Form values are a flat, typed record of string fields (HTML input values). */
1172
+ type FormValues = Record<string, string>;
1173
+ interface Field {
1174
+ readonly name: string;
1175
+ /** Writable value signal — pass to `input({ bind: field.value })`. */
1176
+ readonly value: Signal<string>;
1177
+ /** Current validation error, or `undefined` when the field is valid. */
1178
+ readonly error: ReadonlySignal<string | undefined>;
1179
+ /** True once the field has received a genuine user interaction. */
1180
+ readonly touched: ReadonlySignal<boolean>;
1181
+ /** True when the value differs from its initial value. */
1182
+ readonly dirty: ReadonlySignal<boolean>;
1183
+ /** True when the field has no validation error. */
1184
+ readonly valid: ReadonlySignal<boolean>;
1185
+ /** Programmatically set the value (does not mark the field touched). */
1186
+ setValue(next: string): void;
1187
+ /** Force the touched flag (defaults to true). */
1188
+ markTouched(touched?: boolean): void;
1189
+ /** Restore this field's initial value and clear its touched flag. */
1190
+ reset(): void;
1191
+ }
1192
+ type FormValidators<T extends FormValues> = {
1193
+ readonly [K in keyof T]?: Validator | ReadonlyArray<Validator>;
1194
+ };
1195
+ interface FormConfig<T extends FormValues> {
1196
+ readonly initialValues: T;
1197
+ readonly validators?: FormValidators<T>;
1198
+ /** Called by `submit()` once all fields are valid. May be async. */
1199
+ readonly onSubmit?: (values: T) => void | Promise<void>;
1200
+ }
1201
+ type SubmitStatus = 'idle' | 'submitting' | 'success' | 'error';
1202
+ interface Form<T extends FormValues> {
1203
+ readonly values: ReadonlySignal<T>;
1204
+ readonly errors: ReadonlySignal<Partial<Record<keyof T, string>>>;
1205
+ readonly touched: ReadonlySignal<Partial<Record<keyof T, boolean>>>;
1206
+ readonly dirty: ReadonlySignal<boolean>;
1207
+ readonly valid: ReadonlySignal<boolean>;
1208
+ readonly submitting: ReadonlySignal<boolean>;
1209
+ readonly submitted: ReadonlySignal<boolean>;
1210
+ readonly status: ReadonlySignal<SubmitStatus>;
1211
+ readonly submitError: ReadonlySignal<unknown>;
1212
+ /** Access the reactive state + setters for one field. */
1213
+ field<K extends keyof T & string>(name: K): Field;
1214
+ /** Merge a partial set of values in (does not mark fields touched). */
1215
+ setValues(partial: Partial<T>): void;
1216
+ /** Validate, mark all fields touched, then run `onSubmit` if valid. */
1217
+ submit(): Promise<void>;
1218
+ /** Restore initial values and clear errors/touched/dirty/submission state. */
1219
+ reset(): void;
1220
+ /** Tear down all field subscriptions and derived signals. */
1221
+ dispose(): void;
1222
+ }
1223
+ declare function createForm<T extends FormValues>(config: FormConfig<T>): Form<T>;
1224
+
1225
+ /**
1226
+ * streetui — build-time provider/consumer scoping.
1227
+ *
1228
+ * StreetUI builds its semantic tree synchronously, top-down, when the DSL
1229
+ * builders run. A `Context` mirrors that shape: `provide(value, run)` pushes a
1230
+ * value for the duration of the synchronous `run()` (during which the child
1231
+ * DSL builders execute and may `consume()`), then pops it. Consumers resolve
1232
+ * the *nearest* enclosing provider, falling back to the context default.
1233
+ *
1234
+ * This is deliberately NOT a second reactive system. A context value can be a
1235
+ * signal (see streetui); reactivity then belongs to that signal and is
1236
+ * torn down by the normal node lifecycle when the consuming subtree unmounts —
1237
+ * the context itself holds no subscriptions and leaves no refs behind after a
1238
+ * `provide()` call returns.
1239
+ */
1240
+ interface Context<T> {
1241
+ /** Unique identity for this context (useful for debugging/inspection). */
1242
+ readonly id: symbol;
1243
+ /** The value returned by {@link consume} when no provider is active. */
1244
+ readonly defaultValue: T;
1245
+ /**
1246
+ * Provide `value` to any `consume()` calls made synchronously inside `run`.
1247
+ * The value is popped again as soon as `run` returns (even if it throws),
1248
+ * so nesting resolves to the nearest active provider.
1249
+ */
1250
+ provide<R>(value: T, run: () => R): R;
1251
+ /** Read the nearest active provider's value, or {@link defaultValue}. */
1252
+ consume(): T;
1253
+ /** True while at least one provider is active for this context. */
1254
+ hasProvider(): boolean;
1255
+ }
1256
+ /**
1257
+ * Create a typed context with a required default value, so `consume()` always
1258
+ * returns a `T` (never `undefined` unless `T` itself permits it).
1259
+ */
1260
+ declare function createContext<T>(defaultValue: T, description?: string): Context<T>;
1261
+
1262
+ /**
1263
+ * Minimal, framework-native internationalization for StreetUI.
1264
+ *
1265
+ * Built entirely on `streetui` signals — there is no second reactive
1266
+ * system. The active locale is a writable signal; `t()` returns a derived
1267
+ * signal that recomputes when the locale changes, so translations plug
1268
+ * straight into the DSL's reactive text bindings (`text(() => i18n.t(...).get())`
1269
+ * or `text(i18n.t(...))`).
1270
+ *
1271
+ * Translation is deterministic: a missing key resolves to the key itself, and
1272
+ * the same (locale, key, params) always produces the same string on the server
1273
+ * and the client. That determinism is what keeps `renderToString()` and
1274
+ * `hydrate()` in agreement — provided the app boots the client with the same
1275
+ * initial locale it rendered with on the server.
1276
+ */
1277
+
1278
+ /** A flat dictionary of message templates for a single locale. */
1279
+ type MessageMap = Record<string, string>;
1280
+ /** Values allowed in interpolation params. */
1281
+ type InterpolationParams = Record<string, string | number>;
1282
+ interface I18nConfig<M extends MessageMap> {
1283
+ /** The initial (and server-rendered) locale. */
1284
+ readonly locale: string;
1285
+ /** Messages keyed by locale, e.g. `{ en: {...}, fr: {...} }`. */
1286
+ readonly messages: Readonly<Record<string, M>>;
1287
+ /** Locale consulted when a key is absent from the active locale. */
1288
+ readonly fallbackLocale?: string;
1289
+ }
1290
+ interface I18n<M extends MessageMap> {
1291
+ /** The active locale as a reactive, read-only signal. */
1292
+ readonly locale: ReadonlySignal<string>;
1293
+ /** Switch the active locale; all `t()`/`plural()` signals recompute. */
1294
+ setLocale(locale: string): void;
1295
+ /** The locales that have a message map, in declaration order. */
1296
+ readonly locales: ReadonlyArray<string>;
1297
+ /** Reactive translation. Returns a derived signal — call `.get()` to read. */
1298
+ t(key: keyof M & string, params?: InterpolationParams): ReadonlySignal<string>;
1299
+ /** Non-reactive translation for the current locale (a one-shot read). */
1300
+ translate(key: keyof M & string, params?: InterpolationParams): string;
1301
+ /**
1302
+ * Reactive pluralization via `Intl.PluralRules`. Selects the message whose
1303
+ * key is `${key}.${category}` (e.g. `items.one`), falling back to
1304
+ * `${key}.other`. `count` is available to interpolation as `{count}`.
1305
+ */
1306
+ plural(key: string, count: number, params?: InterpolationParams): ReadonlySignal<string>;
1307
+ /** True when the active (or fallback) locale defines `key`. */
1308
+ has(key: string): boolean;
1309
+ }
1310
+ /** Replace `{name}` placeholders using `params`; unknown names are left intact. */
1311
+ declare function interpolate(template: string, params?: InterpolationParams): string;
1312
+ declare function createI18n<M extends MessageMap>(config: I18nConfig<M>): I18n<M>;
1313
+
1314
+ /**
1315
+ * StreetUI DevTools — graph inspector and debug utilities.
1316
+ */
1317
+
1318
+ interface InspectedNode {
1319
+ id: string;
1320
+ type: string;
1321
+ key: string | undefined;
1322
+ props: Record<string, unknown>;
1323
+ eventTypes: string[];
1324
+ stateBindings: string[];
1325
+ children: InspectedNode[];
1326
+ depth: number;
1327
+ }
1328
+ declare function inspectGraph(graph: ApplicationGraph): InspectedNode;
1329
+ /** Print a human-readable tree of the graph to a string. */
1330
+ declare function printGraph(graph: ApplicationGraph): string;
1331
+ /** Print compilation diagnostics to a string. */
1332
+ declare function printDiagnostics(compiled: CompiledApplication): string;
1333
+ /** Returns node counts per type. */
1334
+ declare function nodeTypeStats(graph: ApplicationGraph): Record<string, number>;
1335
+
1336
+ /**
1337
+ * DevTools foundation (v0.6, Phase 18). A single read-only entry point that
1338
+ * aggregates everything an eventual DevTools UI would need — application
1339
+ * identity, the graph tree, signal bindings, page/route surface, node
1340
+ * statistics, and diagnostics — WITHOUT introducing a second representation of
1341
+ * the graph. It reuses `inspectGraph`/`nodeTypeStats` from the inspector and
1342
+ * reads `CompiledApplication` metadata directly. This is a foundation, not a
1343
+ * UI: it returns plain data so a UI (or a test, or a CLI command) can render it.
1344
+ */
1345
+
1346
+ /** Stable identity of a compiled application. */
1347
+ interface ApplicationIdentity {
1348
+ readonly name: string;
1349
+ readonly version: string;
1350
+ /** Epoch millis the application was compiled. */
1351
+ readonly compiledAt: number;
1352
+ }
1353
+ /** A page node reachable as a direct child of the application root. */
1354
+ interface InspectedPage {
1355
+ readonly id: string;
1356
+ /** The page key when one was supplied in the DSL. */
1357
+ readonly key: string | undefined;
1358
+ }
1359
+ /** Compilation diagnostics summarised for display. */
1360
+ interface DiagnosticsSummary {
1361
+ readonly errors: number;
1362
+ readonly warnings: number;
1363
+ readonly messages: string[];
1364
+ }
1365
+ /**
1366
+ * Cheap, count-only performance snapshot (v0.7 §20). These are structural
1367
+ * counts derived from a single graph walk — NOT timings and NOT a profiler.
1368
+ * They let a DevTools panel or a CI check spot the shapes that correlate with
1369
+ * slow apps (very large graphs, deep trees, big lists, many subscriptions)
1370
+ * without measuring anything at runtime.
1371
+ */
1372
+ interface PerfSnapshot {
1373
+ /** Total GraphNodes in the tree (root included). */
1374
+ readonly totalNodes: number;
1375
+ /** Maximum nesting depth (root = 0). */
1376
+ readonly maxDepth: number;
1377
+ /** Total event handler registrations across all nodes. */
1378
+ readonly eventHandlers: number;
1379
+ /** Total signal→prop bindings across all nodes. */
1380
+ readonly stateBindings: number;
1381
+ /** Distinct signals referenced anywhere in the graph. */
1382
+ readonly distinctSignals: number;
1383
+ /** Largest single-node child count (a proxy for the biggest list/section). */
1384
+ readonly largestChildCount: number;
1385
+ }
1386
+ /**
1387
+ * The complete read-only snapshot of a compiled application. Everything here is
1388
+ * derived from the single `CompiledApplication` graph — no state is duplicated.
1389
+ */
1390
+ interface ApplicationInspection {
1391
+ readonly identity: ApplicationIdentity;
1392
+ readonly graph: InspectedNode;
1393
+ /** Count of nodes per DSL type (e.g. `{ section: 2, button: 3 }`). */
1394
+ readonly nodeStats: Record<string, number>;
1395
+ /** Unique signal ids bound anywhere in the graph, sorted. */
1396
+ readonly signals: string[];
1397
+ /** Page nodes directly under the root — the app's top-level route surface. */
1398
+ readonly pages: InspectedPage[];
1399
+ readonly diagnostics: DiagnosticsSummary;
1400
+ /** Cheap structural performance counters (v0.7 §20). */
1401
+ readonly perf: PerfSnapshot;
1402
+ }
1403
+ /**
1404
+ * Build the full inspection snapshot for a compiled application. Pure and
1405
+ * side-effect free — safe to call in a server, a test, or a DevTools panel.
1406
+ */
1407
+ declare function inspectApplication(compiled: CompiledApplication): ApplicationInspection;
1408
+
1409
+ /**
1410
+ * Dev-only performance diagnostics (v0.7 §21).
1411
+ *
1412
+ * A pure, cheap, count-based check that flags graph *shapes* known to correlate
1413
+ * with slow apps — very large graphs, deep trees, oversized lists/sections, and
1414
+ * heavy reactive fan-out. It is NOT wired into mount/render and adds ZERO cost
1415
+ * to the runtime hot path; a developer (or a CLI command, or a test) calls it
1416
+ * explicitly. Thresholds are advisory and overridable.
1417
+ *
1418
+ * This intentionally reuses the counts already produced by `inspectApplication`
1419
+ * — it introduces no second graph walk of its own beyond reading that snapshot.
1420
+ */
1421
+
1422
+ interface PerfThresholds {
1423
+ /** Warn when the graph exceeds this many nodes. */
1424
+ readonly maxNodes: number;
1425
+ /** Warn when nesting depth exceeds this. */
1426
+ readonly maxDepth: number;
1427
+ /** Warn when any single node has more than this many children (big list). */
1428
+ readonly maxChildCount: number;
1429
+ /** Warn when distinct signals exceed this (reactive fan-out). */
1430
+ readonly maxSignals: number;
1431
+ }
1432
+ declare const DEFAULT_PERF_THRESHOLDS: PerfThresholds;
1433
+ type PerfDiagnosticCode = 'large-graph' | 'deep-tree' | 'large-list' | 'high-signal-fanout';
1434
+ interface PerfDiagnostic {
1435
+ readonly code: PerfDiagnosticCode;
1436
+ readonly message: string;
1437
+ /** The observed count that tripped the threshold. */
1438
+ readonly observed: number;
1439
+ /** The threshold it exceeded. */
1440
+ readonly threshold: number;
1441
+ }
1442
+ /**
1443
+ * Return advisory performance diagnostics for a compiled application. An empty
1444
+ * array means nothing tripped a threshold. Never throws; never mutates.
1445
+ */
1446
+ declare function diagnosePerformance(compiled: CompiledApplication, thresholds?: Partial<PerfThresholds>): PerfDiagnostic[];
1447
+
1448
+ /**
1449
+ * Reactive-surface inspection for DevTools.
1450
+ *
1451
+ * These functions turn the framework's live objects — signals, resources,
1452
+ * router, forms, context, i18n — into plain, read-only snapshots suitable for a
1453
+ * DevTools panel. They never mutate anything and never subscribe; each call is a
1454
+ * one-shot `peek`. Sensitive-by-default surfaces (resource payloads, form field
1455
+ * values) are omitted unless the caller explicitly opts in, so a panel cannot
1456
+ * accidentally display tokens, passwords, or private data.
1457
+ *
1458
+ * Router/forms/context/i18n are described by *structural* interfaces rather than
1459
+ * imported types, so DevTools stays decoupled from those packages (no extra
1460
+ * dependencies) while still inspecting them when present.
1461
+ */
1462
+
1463
+ interface SignalInspection {
1464
+ /** Whether the signal is writable or a derived computation. */
1465
+ readonly kind: SignalKind;
1466
+ /** The current value (redacted if requested). */
1467
+ readonly value: unknown;
1468
+ /** Live observer count when the signal exposes it, else undefined. */
1469
+ readonly observerCount: number | undefined;
1470
+ }
1471
+ interface InspectSignalOptions {
1472
+ /**
1473
+ * Redact the value: `true` replaces it with `'[redacted]'`; a function maps
1474
+ * the raw value to whatever should be shown. Use for signals that may hold
1475
+ * sensitive data. Omitted → the value is shown as-is.
1476
+ */
1477
+ readonly redact?: boolean | ((value: unknown) => unknown);
1478
+ }
1479
+ /** Snapshot a signal's kind, current value, and observer count. Read-only. */
1480
+ declare function inspectSignal(source: ReadonlySignal<unknown>, options?: InspectSignalOptions): SignalInspection;
1481
+ /** The read-only slice of a resource this module needs. */
1482
+ interface ResourceLike {
1483
+ readonly status: ReadonlySignal<ResourceStatus>;
1484
+ readonly data: ReadonlySignal<unknown>;
1485
+ readonly error: ReadonlySignal<unknown>;
1486
+ readonly loading: ReadonlySignal<boolean>;
1487
+ readonly isRefetching: ReadonlySignal<boolean>;
1488
+ }
1489
+ interface ResourceInspection {
1490
+ readonly status: ResourceStatus;
1491
+ readonly loading: boolean;
1492
+ readonly isRefetching: boolean;
1493
+ readonly hasData: boolean;
1494
+ readonly hasError: boolean;
1495
+ /** The error's constructor name (safe — no message/payload). */
1496
+ readonly errorName: string | undefined;
1497
+ /** The error message — only present when `includeData` is set. */
1498
+ readonly errorMessage?: string;
1499
+ /** The loaded value — only present when `includeData` is set. */
1500
+ readonly data?: unknown;
1501
+ }
1502
+ interface InspectResourceOptions {
1503
+ /**
1504
+ * Include the loaded `data` and the error `message`. Off by default because a
1505
+ * resource payload commonly carries user or secret data.
1506
+ */
1507
+ readonly includeData?: boolean;
1508
+ }
1509
+ /** Snapshot a resource's lifecycle. Payload/message hidden unless opted in. */
1510
+ declare function inspectResource(resource: ResourceLike, options?: InspectResourceOptions): ResourceInspection;
1511
+ interface RouteMatchLike {
1512
+ readonly path: string;
1513
+ readonly pattern: string;
1514
+ readonly params: Readonly<Record<string, string>>;
1515
+ readonly query: URLSearchParams;
1516
+ readonly isFallback?: boolean;
1517
+ }
1518
+ interface RouterLike {
1519
+ readonly currentRoute: ReadonlySignal<RouteMatchLike>;
1520
+ }
1521
+ interface RouterInspection {
1522
+ readonly path: string;
1523
+ readonly pattern: string;
1524
+ readonly params: Record<string, string>;
1525
+ readonly query: Record<string, string>;
1526
+ readonly isFallback: boolean;
1527
+ }
1528
+ /** Snapshot the router's current route. Read-only. */
1529
+ declare function inspectRouter(router: RouterLike): RouterInspection;
1530
+ interface FormLike {
1531
+ readonly values: ReadonlySignal<Record<string, unknown>>;
1532
+ readonly errors: ReadonlySignal<Record<string, string | undefined>>;
1533
+ readonly touched: ReadonlySignal<Record<string, boolean | undefined>>;
1534
+ readonly dirty: ReadonlySignal<boolean>;
1535
+ readonly valid: ReadonlySignal<boolean>;
1536
+ readonly status: ReadonlySignal<string>;
1537
+ }
1538
+ interface FormInspection {
1539
+ readonly fields: string[];
1540
+ /** Per-field validation messages (safe — not the entered values). */
1541
+ readonly errors: Record<string, string>;
1542
+ readonly touched: Record<string, boolean>;
1543
+ readonly dirty: boolean;
1544
+ readonly valid: boolean;
1545
+ readonly status: string;
1546
+ /** Entered field values — only present when `includeValues` is set. */
1547
+ readonly values?: Record<string, unknown>;
1548
+ }
1549
+ interface InspectFormOptions {
1550
+ /**
1551
+ * Include the entered field `values`. Off by default because form fields
1552
+ * frequently hold passwords or other secrets.
1553
+ */
1554
+ readonly includeValues?: boolean;
1555
+ }
1556
+ /** Snapshot form validation state. Entered values hidden unless opted in. */
1557
+ declare function inspectForm(form: FormLike, options?: InspectFormOptions): FormInspection;
1558
+ interface ContextLike {
1559
+ readonly id: symbol;
1560
+ hasProvider(): boolean;
1561
+ }
1562
+ interface ContextInspection {
1563
+ /** The context's descriptive label (from its Symbol). */
1564
+ readonly description: string;
1565
+ /** Whether a provider is currently active. */
1566
+ readonly hasProvider: boolean;
1567
+ }
1568
+ /** Snapshot a context's identity and provider presence. No value dumped. */
1569
+ declare function inspectContext(context: ContextLike): ContextInspection;
1570
+ interface I18nLike {
1571
+ readonly locale: ReadonlySignal<string>;
1572
+ readonly locales: ReadonlyArray<string>;
1573
+ has(key: string): boolean;
1574
+ }
1575
+ interface I18nInspection {
1576
+ readonly locale: string;
1577
+ readonly locales: string[];
1578
+ /** Of the probed keys, those with no translation in the active/fallback locale. */
1579
+ readonly missingKeys?: string[];
1580
+ }
1581
+ interface InspectI18nOptions {
1582
+ /** Keys to probe for presence; any absent ones are reported as missing. */
1583
+ readonly checkKeys?: readonly string[];
1584
+ }
1585
+ /** Snapshot i18n locale state and (optionally) missing translation keys. */
1586
+ declare function inspectI18n(i18n: I18nLike, options?: InspectI18nOptions): I18nInspection;
1587
+
1588
+ /**
1589
+ * DevTools session & panels — the first real StreetUI DevTools surface.
1590
+ *
1591
+ * This is a *headless* DevTools layer: it composes the existing read-only
1592
+ * inspection functions (`inspectApplication`, `diagnosePerformance`, and the
1593
+ * reactive inspectors) into the panels a DevTools UI shows — Application, Graph,
1594
+ * Signals, Router, Resources, Forms, Context, i18n, and Performance — and
1595
+ * returns them as plain data plus a text formatter. Any host (a browser panel, a
1596
+ * CLI command, a test) can render that data.
1597
+ *
1598
+ * Design constraints honoured here:
1599
+ * - No second graph and no second reactive system — everything is derived from
1600
+ * the one `CompiledApplication` and the app's own live signals.
1601
+ * - Explicit activation: nothing in the runtime imports this. A session only
1602
+ * exists once dev code calls `createDevTools`, so production pays no cost.
1603
+ * - Live updates use a simple explicit `refresh()` — DevTools never subscribes
1604
+ * to or instruments the reactive graph.
1605
+ * - Sensitive surfaces (resource payloads, form values) stay hidden unless the
1606
+ * caller opts in per the underlying inspectors.
1607
+ */
1608
+
1609
+ interface ApplicationPanel {
1610
+ readonly identity: ApplicationIdentity;
1611
+ readonly nodeCount: number;
1612
+ readonly maxDepth: number;
1613
+ readonly pages: readonly InspectedPage[];
1614
+ readonly signalCount: number;
1615
+ readonly eventHandlers: number;
1616
+ readonly stateBindings: number;
1617
+ readonly errors: number;
1618
+ readonly warnings: number;
1619
+ }
1620
+ interface SignalsPanel {
1621
+ /** Distinct signal ids referenced anywhere in the graph (structural). */
1622
+ readonly boundSignalIds: readonly string[];
1623
+ /** Live inspections for signals the app registered with DevTools, by label. */
1624
+ readonly live: Readonly<Record<string, SignalInspection>>;
1625
+ }
1626
+ interface PerformancePanel {
1627
+ readonly snapshot: ApplicationInspection['perf'];
1628
+ readonly diagnostics: readonly PerfDiagnostic[];
1629
+ }
1630
+ /** All panels captured at one `refresh()`. */
1631
+ interface DevToolsSnapshot {
1632
+ readonly application: ApplicationPanel;
1633
+ readonly graph: InspectedNode;
1634
+ readonly signals: SignalsPanel;
1635
+ readonly performance: PerformancePanel;
1636
+ readonly router?: RouterInspection;
1637
+ readonly resources?: Readonly<Record<string, ResourceInspection>>;
1638
+ readonly forms?: Readonly<Record<string, FormInspection>>;
1639
+ readonly contexts?: Readonly<Record<string, ContextInspection>>;
1640
+ readonly i18n?: I18nInspection;
1641
+ }
1642
+ /**
1643
+ * The app's own live reactive objects, handed to DevTools explicitly so it can
1644
+ * inspect them. The compiled graph knows signal *ids* but not the live `Signal`
1645
+ * instances, so the app registers whichever surfaces it wants visible. Every
1646
+ * field is optional — a session works with none of them (structure-only).
1647
+ */
1648
+ interface DevToolsSources {
1649
+ /** Live signals to inspect, keyed by a human label shown in the panel. */
1650
+ readonly signals?: Readonly<Record<string, ReadonlySignal<unknown>>>;
1651
+ /** Live resources to inspect, keyed by label. */
1652
+ readonly resources?: Readonly<Record<string, ResourceLike>>;
1653
+ /** The app router, if any. */
1654
+ readonly router?: RouterLike;
1655
+ /** Live forms to inspect, keyed by label. */
1656
+ readonly forms?: Readonly<Record<string, FormLike>>;
1657
+ /** Live contexts to inspect, keyed by label. */
1658
+ readonly contexts?: Readonly<Record<string, ContextLike>>;
1659
+ /** The app i18n instance, if any. */
1660
+ readonly i18n?: I18nLike;
1661
+ }
1662
+ interface DevToolsOptions {
1663
+ /** Thresholds forwarded to `diagnosePerformance`. */
1664
+ readonly perfThresholds?: PerfThresholds;
1665
+ /**
1666
+ * Redact live signal values by default (passed to `inspectSignal`). Use in
1667
+ * shared or recorded sessions so values never reach the panel. Off by default.
1668
+ */
1669
+ readonly redactSignals?: boolean | ((value: unknown) => unknown);
1670
+ /** i18n keys to probe for missing translations, forwarded to `inspectI18n`. */
1671
+ readonly i18nCheckKeys?: readonly string[];
1672
+ }
1673
+ /**
1674
+ * A headless DevTools session over one compiled application.
1675
+ *
1676
+ * The session holds the compiled app plus the app's registered live sources and
1677
+ * produces an immutable {@link DevToolsSnapshot} on demand. Live values are read
1678
+ * only when `refresh()` is called (explicit-refresh protocol, §7): the session
1679
+ * never subscribes to signals or instruments the reactive graph, so it adds no
1680
+ * cost to the running app between refreshes.
1681
+ */
1682
+ interface DevToolsSession {
1683
+ /** The most recent snapshot. Recomputed by `refresh()`. */
1684
+ readonly snapshot: DevToolsSnapshot;
1685
+ /**
1686
+ * Recompute every panel from the current live state and return the new
1687
+ * snapshot. This is the only way values change — DevTools pulls, it never
1688
+ * gets pushed to.
1689
+ */
1690
+ refresh(): DevToolsSnapshot;
1691
+ /**
1692
+ * Find a node in the graph by id and return that subtree, or `undefined`.
1693
+ * Backs a UI tree inspector's node-selection (§8) without a second graph.
1694
+ */
1695
+ selectNode(id: string): InspectedNode | undefined;
1696
+ /** Render the current snapshot as a plain-text report (for CLI/tests/logs). */
1697
+ format(): string;
1698
+ }
1699
+ /**
1700
+ * Create a DevTools session. Nothing in the runtime calls this — a session only
1701
+ * exists once dev code opts in, so production never pays for it.
1702
+ */
1703
+ declare function createDevTools(compiled: CompiledApplication, sources?: DevToolsSources, options?: DevToolsOptions): DevToolsSession;
1704
+
1705
+ /**
1706
+ * StreetUI project configuration (Phase 9). The config is intentionally tiny:
1707
+ * every field has a sensible default so `streetui.config.ts` is optional. A
1708
+ * project with no config file still builds and runs.
1709
+ *
1710
+ * The file is authored as TypeScript (`streetui.config.ts`) and compiled with
1711
+ * esbuild to a temporary ESM module before import, so we never depend on the
1712
+ * host having a TS loader registered.
1713
+ */
1714
+ /** User-facing configuration shape (all fields optional). */
1715
+ interface StreetUIConfig {
1716
+ /** Dev server / preview port. Default 3000. */
1717
+ readonly port?: number;
1718
+ /** Host to bind. Default 'localhost'. */
1719
+ readonly host?: string;
1720
+ /** Client/browser entry, relative to project root. Default 'src/main.ts'. */
1721
+ readonly clientEntry?: string;
1722
+ /** Server entry used for SSR, relative to project root. Default 'src/server.ts'. */
1723
+ readonly serverEntry?: string;
1724
+ /** Output directory for `build`. Default 'dist'. */
1725
+ readonly outDir?: string;
1726
+ /** Static assets directory copied verbatim. Default 'public'. */
1727
+ readonly publicDir?: string;
1728
+ }
1729
+ /** Fully-resolved config: every field present, all paths absolute. */
1730
+ interface ResolvedConfig {
1731
+ readonly root: string;
1732
+ readonly port: number;
1733
+ readonly host: string;
1734
+ readonly clientEntry: string;
1735
+ readonly serverEntry: string;
1736
+ readonly outDir: string;
1737
+ readonly publicDir: string;
1738
+ }
1739
+
1740
+ /**
1741
+ * Project-configuration surface for `streetui.config.ts`.
1742
+ *
1743
+ * The configuration *type* is single-sourced from the internal CLI module (so
1744
+ * there is exactly one authoritative `StreetUIConfig` shape). `defineConfig` is
1745
+ * the standard one-line identity helper used purely for editor type-inference on
1746
+ * the exported config object.
1747
+ *
1748
+ * This lives in its own tiny module — rather than re-exporting `defineConfig`
1749
+ * from the CLI barrel — so that importing `streetui` for application code does
1750
+ * **not** drag the CLI's build machinery (and its `esbuild` dependency) into the
1751
+ * client runtime bundle. The CLI's own `loadConfig` reads the default export of
1752
+ * `streetui.config.ts` regardless of which identity helper wrapped it, so the
1753
+ * behaviour is identical to configuring via the CLI directly.
1754
+ */
1755
+
1756
+ /** Identity helper that gives `streetui.config.ts` full type-checking + inference. */
1757
+ declare function defineConfig(config: StreetUIConfig): StreetUIConfig;
1758
+
1759
+ export { type A11yIds, Application, ApplicationGraph, ApplicationId, type ApplicationIdentity, type ApplicationInspection, type ApplicationOptions, type ApplicationPanel, BrowserDOMAdapter, CleanupRegistry, CompiledApplication, ContainerDSL, type Context, type ContextInspection, type ContextLike, DEFAULT_PERF_THRESHOLDS, DOMAdapter, type DevToolsOptions, type DevToolsSession, type DevToolsSnapshot, type DevToolsSources, DiagnosticCollector, type DiagnosticContext, type DiagnosticSink, type DiagnosticsSummary, type DomBinding, DomEventRegistry, Environment, type EnvironmentCapabilities, type EnvironmentKind, EventBus, type EventHandler, FOCUSABLE_SELECTOR, type Field, type Form, type FormConfig, type FormInspection, type FormLike, type FormValidators, type FormValues, GraphNode, HydrationDiagnosticSink, type I18n, type I18nConfig, type I18nInspection, type I18nLike, type InspectFormOptions, type InspectI18nOptions, type InspectResourceOptions, type InspectSignalOptions, type InspectedNode, type InspectedPage, type InterpolationParams, type IsActiveOptions, type Job, Lifecycle, type LifecycleHook, type LifecyclePhase, type MatchResult, type MessageMap, type MountFn, type MountRouterOptions, type MountedApplication, type MountedRouter, type NavigateOptions, NodeInstance, type NodeInstanceOptions, PageDSL, type PerfDiagnostic, type PerfDiagnosticCode, type PerfSnapshot, type PerfThresholds, type PerformancePanel, type Priority, ROUTER_OUTLET_ID, ReadonlySignal, type ReconcileResult, type RenderContext, RenderHandle, type ResolvedConfig, type Resource, type ResourceInspection, type ResourceLike, type ResourceLoader, type ResourceLoaderContext, type ResourceOptions, type ResourceStatus, type RouteBuilder, type RouteContext, type RouteDefinition, type RouteMatch, type RouteMatchLike, type Router, type RouterHistory, type RouterInspection, type RouterLike, type RouterLocation, type RouterOptions, Runtime, RuntimeNodeInstance, type RuntimeOptions, Scheduler, type SchedulerDiagnostics, SemanticNodeType, ServerComment, ServerElement, ServerFragment, type ServerNode, type ServerNodeKind, type ServerParent, ServerStyle, ServerText, type ShellBuilder, Signal, type SignalInspection, SignalKind, type SignalsPanel, Store, type StoreState, type StreetEvent, type StreetEventType, StreetFrameworkError, StreetRenderHandle, StreetRenderer, StreetRendererImpl, type StreetRendererOptions, type StreetUIConfig, type SubmitStatus, Subscriber, Unsubscribe, type Validator, a11yIds, applyNodeProps, applyProp, bindDomEvent, browserDOMAdapter, buttonUpdate, consoleDiagnosticSink, createApplication, createBrowserHistory, createContext, createDevTools, createForm, createI18n, createMemoryHistory, createRenderContext, createRenderer, createRouter, createRuntime, createStore, createStreetEvent, defineConfig, diagnosePerformance, email, environment, escapeHtmlAttr, escapeHtmlText, flushSync, focusById, focusFirst, formatDiagnosticContext, frameworkError, globalEventBus, headingUpdate, hydrateGraph, inputUpdate, inspectApplication, inspectContext, inspectForm, inspectGraph, inspectI18n, inspectResource, inspectRouter, inspectSignal, interpolate, matchPattern, matchRoutes, maxLength, minLength, mountGraph, mountNode, mountRouter, nodeTypeStats, normalizePath, patchNode, patchProp, pattern, printDiagnostics, printGraph, reconcileChildren, reportDiagnostic, required, resolveTag, resource, routerOutlet, runValidators, scheduleImmediate, scheduleUpdate, scheduler, serializeChildren, serializeServerNode, splitTarget, textUpdate, toIdToken, transformGraph, validateGraph, wireEvents, wireReactiveList, wireSignalBindings };