streetui 1.8.0 → 2.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.
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
- import { c as Signal, d as Subscriber, U as Unsubscribe, R as ReadonlySignal, A as ApplicationId, D as DiagnosticCollector, e as ApplicationGraph, G as GraphNode, C as CompiledApplication, f as SemanticNodeType, P as PageDSL, g as ContainerDSL, h as SignalKind } from './compile-CJJQYi71.js';
2
- export { i as A11yOptions, j as AppBuilder, k as AppDSL, l as AppOptions, m as ApplicationGraphOptions, B as BaseNode, n as Bindable, o as BindableText, p as BoundInputOptions, q as ButtonOptions, r as CompileOptions, b as ComponentChildren, s as ComponentContext, a as ComponentDefinition, t as ComponentRender, u as ComponentSetup, b as ContainerBuilder, v as ContainerBuilderImpl, w as ContainerOptions, x as ContentDSL, y as ControlledInputOptions, z as DerivedSignal, E as Diagnostic, F as DiagnosticError, H as DiagnosticLocation, I as DiagnosticSeverity, J as ErrorBoundaryOptions, K as ErrorFallbackBuilder, L as ErrorSource, M as EventDescriptor, N as FormBuilder, O as FormBuilderImpl, Q as FormDSL, T as FormOptions, W as GraphNodeData, X as HandlerFn, Y as HeadingOptions, Z as ImageOptions, _ as InputOptions, $ as InputOptionsBase, a0 as LinkOptions, a1 as ListBuilder, a2 as ListBuilderImpl, a3 as ListDSL, a4 as ListOptions, a5 as ListPlanEntry, a6 as NodeId, a7 as NodeMetadata, a8 as OverlayOptions, a9 as PageBuilder, aa as PageBuilderImpl, ab as PortalOptions, ac as PropValue, ad as Props, ae as ReactiveConsumer, af as ReactiveSource, ag as SectionBuilder, ah as SectionBuilderImpl, ai as SectionDSL, aj as SectionOptions, ak as SerializedGraph, al as SerializedNode, am as StateRef, S as StreetApp, an as StreetUI, ao as TextOptions, ap as TextValue, V as VERSION, aq as batch, ar as compile, as as compileGraph, at as component, au as createNodeId, av as derived, aw as effect, ax as formatDiagnostic, ay as generateApplicationId, az as generateNodeId, aA as isBatching, aB as isComponentDefinition, aC as nextId, aD as nodeIdPrefix, aE as observerCount, aF as reactiveListItemKey, aG as reactiveListItemSignature, aH as resetIdCounter, aI as signal, aJ as signalKind, aK as streetui } from './compile-CJJQYi71.js';
3
- import { R as RenderHandle, S as StreetRenderer, a as HydrationDiagnosticSink } from './hydration-diagnostics-DNAJumYF.js';
4
- export { H as HydrationDiagnostic, b as HydrationMismatchType, c as consoleHydrationDiagnosticSink, d as createHydrationDiagnosticCollector, f as formatHydrationDiagnostic } from './hydration-diagnostics-DNAJumYF.js';
5
- import { D as DOMAdapter } from './server-DNlg7ou2.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-DNlg7ou2.js';
1
+ import { c as Signal, d as Subscriber, U as Unsubscribe, R as ReadonlySignal, e as ResourceStatus, f as ResourceOptions, g as Resource, h as ResourceLoaderContext, A as ApplicationId, D as DiagnosticCollector, i as ApplicationGraph, G as GraphNode, C as CompiledApplication, j as HydrationDiagnosticSink, k as SemanticNodeType, P as PageDSL, T as TransitionConfig, l as ContainerDSL, m as SignalKind } from './hydration-diagnostics-CFYDXBlm.js';
2
+ export { n as A11yOptions, o as AppBuilder, p as AppDSL, q as AppOptions, r as ApplicationGraphOptions, s as AsyncBoundaryBranches, B as BaseNode, t as Bindable, u as BindableString, v as BindableText, w as BoundInputOptions, x as ButtonOptions, y as CompileOptions, b as ComponentChildren, z as ComponentContext, a as ComponentDefinition, E as ComponentRender, F as ComponentSetup, b as ContainerBuilder, I as ContainerBuilderImpl, J as ContainerOptions, K as ContentDSL, L as ControlledInputOptions, M as DerivedSignal, N as Diagnostic, O as DiagnosticError, Q as DiagnosticLocation, W as DiagnosticSeverity, X as ErrorBoundaryOptions, Y as ErrorFallbackBuilder, Z as ErrorSource, _ as EventDescriptor, $ as FormBuilder, a0 as FormBuilderImpl, a1 as FormDSL, a2 as FormOptions, a3 as GraphNodeData, a4 as HandlerFn, a5 as HeadContribution, a6 as HeadEntry, a7 as HeadMetadata, a8 as HeadingOptions, H as HydrationDiagnostic, a9 as HydrationMismatchType, aa as ImageOptions, ab as InputOptions, ac as InputOptionsBase, ad as LinkDescriptor, ae as LinkOptions, af as ListBuilder, ag as ListBuilderImpl, ah as ListDSL, ai as ListOptions, aj as ListPlanEntry, ak as MetaDescriptor, al as NodeId, am as NodeMetadata, an as OverlayOptions, ao as PageBuilder, ap as PageBuilderImpl, aq as PortalOptions, ar as PropValue, as as Props, at as ReactiveConsumer, au as ReactiveSource, av as ResolvedTransition, aw as ResourceLoader, ax as SectionBuilder, ay as SectionBuilderImpl, az as SectionDSL, aA as SectionOptions, aB as SerializedGraph, aC as SerializedNode, aD as StateRef, S as StreetApp, aE as StreetUI, aF as TextOptions, aG as TextValue, V as VERSION, aH as WhenOptions, aI as batch, aJ as compile, aK as compileGraph, aL as component, aM as consoleHydrationDiagnosticSink, aN as createHydrationDiagnosticCollector, aO as createNodeId, aP as derived, aQ as effect, aR as formatDiagnostic, aS as formatHydrationDiagnostic, aT as generateApplicationId, aU as generateNodeId, aV as isBatching, aW as isComponentDefinition, aX as isHeadContribution, aY as isTransitionConfig, aZ as nextId, a_ as nodeIdPrefix, a$ as observerCount, b0 as reactiveListItemKey, b1 as reactiveListItemSignature, b2 as resetIdCounter, b3 as resolveHead, b4 as resolveTransition, b5 as resource, b6 as signal, b7 as signalKind, b8 as streetui } from './hydration-diagnostics-CFYDXBlm.js';
3
+ import { L as Lifecycle, C as CleanupRegistry, D as DOMAdapter, N as NodeInstance, R as RenderContext } from './server-C0PvFBzD.js';
4
+ export { H as HeadManager, a as LifecycleHook, b as LifecyclePhase, c as RenderToStringOptions, S as STATE_MARKER_ATTR, d as ServerDOMAdapter, e as createRenderContext, r as readState, f as renderHead, g as renderToString, s as serializeState, h as serverDOMAdapter, w as wireHeadBehavior } from './server-C0PvFBzD.js';
5
+ import { R as RenderHandle, S as StreetRenderer } from './renderer-interface-B_-hWnKk.js';
7
6
 
8
7
  /**
9
8
  * A simple reactive store built on top of signals.
@@ -23,84 +22,229 @@ declare class Store<T extends StoreState> {
23
22
  declare function createStore<T extends StoreState>(initial: T): Store<T>;
24
23
 
25
24
  /**
26
- * StreetUI async resources — framework-native asynchronous data.
25
+ * StreetUI mutations — framework-native asynchronous *writes*.
27
26
  *
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.
27
+ * A `resource` models a read: a loader that runs on creation / on demand and
28
+ * whose value the UI observes. A `mutation` is its write-side counterpart: an
29
+ * explicit, argument-taking async action (create / update / delete, a form
30
+ * submit, a "mark as done" click) whose lifecycle is exposed as ordinary
31
+ * StreetUI signals so it composes with `derived`, `when()`, and the renderer
32
+ * with NO second reactive system.
32
33
  *
33
- * State machine:
34
+ * State machine (mirrors `resource`, but only ever advances on an explicit
35
+ * `mutate()` — a mutation never runs on its own):
34
36
  *
35
- * idle ──(load)──▶ loading ──(resolve)──▶ success
36
- * │
37
- * └────(reject)──────▶ error
37
+ * idle ──(mutate)──▶ loading ──(resolve)──▶ success
38
+ * │
39
+ * └────(reject)──────▶ error
38
40
  *
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.
41
+ * There is deliberately NO global cache and NO automatic invalidation registry
42
+ * (that would be a second state system with its own lifetime and coherency
43
+ * rules). Invalidation is explicit and local: pass an `onSuccess` that calls the
44
+ * `refetch()` of whichever resources the write affected. This keeps data flow
45
+ * one-directional and readable — the write says exactly what it invalidates.
46
46
  */
47
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;
48
+ /** A mutation shares the resource status vocabulary (idle/loading/success/error). */
49
+ type MutationStatus = ResourceStatus;
50
+ /** The async action a mutation runs. Receives the caller's argument. */
51
+ type Mutator<TArgs, TResult> = (args: TArgs) => Promise<TResult> | TResult;
52
+ interface MutationOptions<TArgs, TResult> {
58
53
  /**
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.
54
+ * Run after a successful mutation, before `mutate()`'s promise resolves. The
55
+ * natural place to invalidate reads: call the affected resources' `refetch()`.
56
+ * May be async; its completion is awaited so callers can rely on reads being
57
+ * up to date once `mutate()` resolves.
62
58
  */
63
- readonly watch?: ReadonlyArray<ReadonlySignal<unknown>>;
59
+ readonly onSuccess?: (result: TResult, args: TArgs) => void | Promise<void>;
60
+ /** Run after a failed mutation (the thrown value is passed through). */
61
+ readonly onError?: (error: unknown, args: TArgs) => void | Promise<void>;
62
+ /** Run after success OR error, once the lifecycle has settled. */
63
+ readonly onSettled?: (args: TArgs) => void | Promise<void>;
64
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.
65
+ * Optional teardown registrar (e.g. a component's `ctx.onCleanup`). When
66
+ * given, the mutation registers its own `dispose` so late results from an
67
+ * in-flight `mutate()` are ignored once the owner is removed.
68
68
  */
69
69
  readonly onCleanup?: (fn: () => void) => void;
70
+ }
71
+ interface Mutation<TArgs, TResult> {
72
+ /** Reactive lifecycle status. */
73
+ readonly status: ReadonlySignal<MutationStatus>;
74
+ /** The most recent successful result, or `undefined` before first success. */
75
+ readonly data: ReadonlySignal<TResult | undefined>;
76
+ /** The most recent error, or `undefined` when there is none. Typed `unknown`. */
77
+ readonly error: ReadonlySignal<unknown>;
78
+ /** Convenience: `status === 'loading'` (an in-flight write). */
79
+ readonly pending: ReadonlySignal<boolean>;
80
+ /**
81
+ * Run the mutation. Resolves with the result on success. On failure the
82
+ * rejection is surfaced through `error`/`status` AND re-thrown, so a caller
83
+ * that wants to react imperatively can `try/catch`; a caller that only wants
84
+ * the reactive state can ignore the returned promise. Superseded/disposed
85
+ * runs never write state (race guard), matching `resource`.
86
+ */
87
+ mutate(args: TArgs): Promise<TResult>;
88
+ /** Reset back to `idle` with no data/error. */
89
+ reset(): void;
90
+ /** Ignore any in-flight result and mark the mutation inert. Idempotent. */
91
+ dispose(): void;
92
+ }
93
+ /**
94
+ * Create a {@link Mutation}. The zero-argument form is written
95
+ * `mutation<void, T>(() => …)` and invoked as `mutate(undefined)`.
96
+ */
97
+ declare function mutation<TArgs, TResult>(mutator: Mutator<TArgs, TResult>, options?: MutationOptions<TArgs, TResult>): Mutation<TArgs, TResult>;
98
+
99
+ /**
100
+ * Optional HTTP data client (2.0 §17) — the integration path between StreetUI's
101
+ * transport-agnostic `resource`/`mutation` primitives and a real backend (a
102
+ * StreetJS server, or any HTTP/JSON API).
103
+ *
104
+ * This is deliberately OPTIONAL and dependency-free: it imports nothing from
105
+ * StreetJS (or any server framework), so StreetUI's core stays independent — an
106
+ * app that never calls `createClient` never pays for it, and StreetUI does not
107
+ * take on a backend dependency. It is a thin, honest convenience over the
108
+ * standard `fetch`: URL joining, JSON encode/decode, header merging, abort
109
+ * propagation, and a typed error. All state still flows through the existing
110
+ * `resource`/`mutation` signals — there is no cache and no second data system.
111
+ *
112
+ * ```ts
113
+ * const api = createClient({ baseUrl: '/api' });
114
+ * const users = api.resource<User[]>('/users'); // a read
115
+ * const create = api.mutation<NewUser, User>('POST', '/users', {
116
+ * onSuccess: () => users.refetch(), // explicit invalidation
117
+ * });
118
+ * ```
119
+ */
120
+
121
+ /** A `fetch`-compatible function. Injectable for tests / non-browser runtimes. */
122
+ type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
123
+ interface ClientConfig {
124
+ /** Prefix joined to every request path (e.g. `/api` or `https://x/api`). */
125
+ readonly baseUrl?: string;
126
+ /** Headers merged into every request (per-request headers win). */
127
+ readonly headers?: Readonly<Record<string, string>>;
70
128
  /**
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.
129
+ * The fetch implementation to use. Defaults to the global `fetch`. Injecting
130
+ * one keeps the client testable and usable where no global fetch exists.
77
131
  */
78
- readonly initialData?: T;
79
- /** Server-provided initial error for hydration (mirrors `initialData`). */
80
- readonly initialError?: unknown;
132
+ readonly fetch?: FetchLike;
133
+ }
134
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
135
+ interface RequestConfig {
136
+ /** Extra headers for this request (merged over the client's). */
137
+ readonly headers?: Readonly<Record<string, string>>;
138
+ /** Abort signal — pass a resource/mutation loader's `ctx.signal` for cancellation. */
139
+ readonly signal?: AbortSignal;
140
+ /** Query parameters appended to the URL. */
141
+ readonly query?: Readonly<Record<string, string | number | boolean>>;
142
+ }
143
+ /**
144
+ * A failed HTTP response (non-2xx). Carries the status and the parsed body when
145
+ * one was returned. NOTE: the body may contain server-supplied detail; the §7
146
+ * error reporter never enumerates an error's own-properties, so `HttpError.body`
147
+ * never leaks into a diagnostics report unless an app deliberately reads it.
148
+ */
149
+ declare class HttpError extends Error {
150
+ readonly status: number;
151
+ readonly statusText: string;
152
+ readonly url: string;
153
+ readonly body: unknown;
154
+ constructor(status: number, statusText: string, url: string, body: unknown);
155
+ }
156
+ interface Client {
157
+ /** Issue a request and return the parsed JSON body (throws `HttpError` on non-2xx). */
158
+ request<T>(method: HttpMethod, path: string, body?: unknown, config?: RequestConfig): Promise<T>;
159
+ get<T>(path: string, config?: RequestConfig): Promise<T>;
160
+ post<T>(path: string, body?: unknown, config?: RequestConfig): Promise<T>;
161
+ put<T>(path: string, body?: unknown, config?: RequestConfig): Promise<T>;
162
+ patch<T>(path: string, body?: unknown, config?: RequestConfig): Promise<T>;
163
+ del<T>(path: string, config?: RequestConfig): Promise<T>;
81
164
  /**
82
- * Explicit initial status override. Rarely needed — inferred as `'success'`
83
- * from `initialData` or `'error'` from `initialError`.
165
+ * A GET-backed {@link Resource}. The loader forwards the resource's abort
166
+ * signal, so `dispose()`/supersede cancels the request.
84
167
  */
85
- readonly initialStatus?: ResourceStatus;
168
+ resource<T>(path: string, options?: ResourceOptions<T> & {
169
+ readonly query?: RequestConfig['query'];
170
+ }): Resource<T>;
171
+ /**
172
+ * A {@link Mutation} that issues `method path` with the mutate() argument as
173
+ * the JSON body. Pair with `onSuccess` to refetch affected resources.
174
+ */
175
+ mutation<TArgs, TResult>(method: HttpMethod, path: string, options?: MutationOptions<TArgs, TResult>): Mutation<TArgs, TResult>;
176
+ }
177
+ /** Create an optional HTTP data client. Uses global `fetch` unless one is injected. */
178
+ declare function createClient(config?: ClientConfig): Client;
179
+
180
+ /**
181
+ * Optional auth session primitive (2.0 §19) — reactive authentication state for
182
+ * building sign-in UIs and protected routes, composed entirely from the existing
183
+ * `resource` (the "who am I" read) and `mutation` (logout / refresh writes).
184
+ *
185
+ * There is no new auth framework here and no credential handling: the app
186
+ * supplies a `loadUser` function (however it authenticates — a StreetJS session
187
+ * cookie, a bearer token, anything) that returns the current user or `null`. The
188
+ * primitive turns that into the states a UI switches on — `loading`,
189
+ * `authenticated`, `unauthenticated`, `error` — plus `refresh()` and `logout()`.
190
+ *
191
+ * How the pieces the spec names fit together (all EXISTING seams, no new ones):
192
+ * - loading / unauth / auth → switch UI with `when(session.authenticated, …)`
193
+ * etc. (the renderer's existing conditional).
194
+ * - refresh / logout → `session.refresh()` / `session.logout()`.
195
+ * - protected route → in a route's setup, read `session.status`; when
196
+ * `unauthenticated`, navigate to the login route (router). Throwing inside a
197
+ * guarded builder is caught by `errorBoundary` for an error fallback. This
198
+ * module stays router-agnostic so core has no router dependency — the app
199
+ * wires the navigation, exactly as with any other signal.
200
+ */
201
+
202
+ /** The four states an auth-aware UI switches on. */
203
+ type AuthStatus = 'loading' | 'authenticated' | 'unauthenticated' | 'error';
204
+ /** Load the current user, or `null`/`undefined` when nobody is signed in. */
205
+ type LoadUser<TUser> = (ctx: ResourceLoaderContext) => Promise<TUser | null | undefined> | TUser | null | undefined;
206
+ interface AuthSessionConfig<TUser> {
207
+ /** Resolve the current user (or null when unauthenticated). Abort-aware. */
208
+ readonly loadUser: LoadUser<TUser>;
209
+ /**
210
+ * Perform the server-side logout (clear the cookie/token). Optional — when
211
+ * omitted, `logout()` just re-checks the session. After it resolves the
212
+ * session refreshes, so `loadUser` should then return null.
213
+ */
214
+ readonly logout?: () => Promise<void> | void;
215
+ /** Skip the initial load; stays `loading` until the first `refresh()`. */
216
+ readonly immediate?: boolean;
217
+ /** Teardown registrar (e.g. a component's `ctx.onCleanup`). */
218
+ readonly onCleanup?: (fn: () => void) => void;
86
219
  }
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`. */
220
+ interface AuthSession<TUser> {
221
+ /** The current coarse auth state. */
222
+ readonly status: ReadonlySignal<AuthStatus>;
223
+ /** The signed-in user, or `undefined` when not authenticated. */
224
+ readonly user: ReadonlySignal<TUser | undefined>;
225
+ /** The most recent load/logout error, or `undefined`. */
93
226
  readonly error: ReadonlySignal<unknown>;
94
- /** Convenience: `status === 'loading'`. */
227
+ /** `status === 'authenticated'`. */
228
+ readonly authenticated: ReadonlySignal<boolean>;
229
+ /** `status === 'unauthenticated'`. */
230
+ readonly unauthenticated: ReadonlySignal<boolean>;
231
+ /** `status === 'loading'` (the initial who-am-I is still in flight). */
95
232
  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. */
233
+ /** True while a `logout()` is in flight. */
234
+ readonly loggingOut: ReadonlySignal<boolean>;
235
+ /** Re-run `loadUser` (e.g. after a token refresh or a focus regain). */
236
+ refresh(): Promise<void>;
237
+ /** Run the configured server logout, then refresh (→ unauthenticated). */
238
+ logout(): Promise<void>;
239
+ /** Cancel in-flight work and detach. Idempotent. */
101
240
  dispose(): void;
102
241
  }
103
- declare function resource<T>(loader: ResourceLoader<T>, options?: ResourceOptions<T>): Resource<T>;
242
+ /**
243
+ * Create an {@link AuthSession}. The session starts in `loading` and resolves to
244
+ * `authenticated`/`unauthenticated` once `loadUser` settles (unless
245
+ * `immediate: false`).
246
+ */
247
+ declare function createAuthSession<TUser>(config: AuthSessionConfig<TUser>): AuthSession<TUser>;
104
248
 
105
249
  /**
106
250
  * Deterministic accessibility id helpers.
@@ -124,14 +268,24 @@ interface A11yIds {
124
268
  readonly base: string;
125
269
  /** Id for the primary interactive element (e.g. the input). */
126
270
  readonly input: string;
127
- /** Id for a label element / labelling text. */
271
+ /** Id for a label element / labelling text (target of `aria-labelledby`). */
128
272
  readonly label: string;
129
- /** Id for descriptive/help text. */
273
+ /** Id for descriptive/help text (target of `aria-describedby`). */
130
274
  readonly description: string;
131
275
  /** Id for an error message element. */
132
276
  readonly error: string;
133
- /** Id for a title element (e.g. a dialog title). */
277
+ /** Id for a title element (e.g. a dialog title; target of `aria-labelledby`). */
134
278
  readonly title: string;
279
+ /**
280
+ * Id for a control that triggers a popup (button/summary). Pair with
281
+ * `controls` on the popup it opens (`ariaControls: ids.controls`,
282
+ * `ariaExpanded: open`).
283
+ */
284
+ readonly trigger: string;
285
+ /** Id for a popup/region a `trigger` controls (target of `aria-controls`). */
286
+ readonly controls: string;
287
+ /** Id for a subtree owned out-of-DOM-order (target of `aria-owns`). */
288
+ readonly owns: string;
135
289
  /** Derive an arbitrary suffixed id from the same base. */
136
290
  id(suffix: string): string;
137
291
  }
@@ -143,35 +297,14 @@ interface A11yIds {
143
297
  * // ids.input === 'email-input', ids.label === 'email-label', ...
144
298
  * input({ bind: value, id: ids.input, ariaLabelledBy: ids.label, ariaDescribedBy: ids.error });
145
299
  * text('Email', { id: ids.label });
146
- */
147
- declare function a11yIds(base: string): A11yIds;
148
-
149
- /**
150
- * Application and component lifecycle primitives.
151
300
  *
152
- * Lifecycle phases:
153
- * created → mounted → active ⇄ updating → unmounting → destroyed
301
+ * @example
302
+ * // A menu button that controls its popup, wired by shared ids:
303
+ * const m = a11yIds('actions');
304
+ * button('Actions', { id: m.trigger, ariaControls: m.controls, ariaExpanded: open });
305
+ * page.dropdown('menu', { open, id: m.controls }, (d) => { … });
154
306
  */
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
- }
307
+ declare function a11yIds(base: string): A11yIds;
175
308
 
176
309
  /**
177
310
  * Environment detection and capability flags.
@@ -256,6 +389,30 @@ interface DiagnosticContext {
256
389
  readonly route?: string;
257
390
  /** A resource identifier involved, when applicable. */
258
391
  readonly resource?: string;
392
+ /**
393
+ * The component name involved (from a `component()` definition's `name`), when
394
+ * the diagnostic arises inside a component. A stable identifier, never a value.
395
+ */
396
+ readonly component?: string;
397
+ /**
398
+ * A signal identifier involved (e.g. a named/derived signal's debug name),
399
+ * when applicable. This is the signal's *identity*, never its current value —
400
+ * signal contents may be user data and are never placed in a diagnostic.
401
+ */
402
+ readonly signal?: string;
403
+ /**
404
+ * Where in a node's lifecycle the diagnostic arose: `render` (building the
405
+ * subtree), `effect` (a reactive effect), `setup` (a component's setup),
406
+ * `loader` (a resource loader), `event` (a DOM handler), or `hydrate`.
407
+ */
408
+ readonly phase?: 'render' | 'effect' | 'setup' | 'loader' | 'event' | 'hydrate';
409
+ /**
410
+ * A non-sensitive DOM association for where the error surfaced, e.g.
411
+ * `div#app` or `button.primary`. Callers must pass only structural
412
+ * identifiers (tag / id / class) — never `textContent`, attribute *values*,
413
+ * or anything that could carry user data.
414
+ */
415
+ readonly element?: string;
259
416
  }
260
417
  /**
261
418
  * The application-provided logging seam. Every method is optional; the
@@ -289,6 +446,65 @@ declare function frameworkError(message: string, context?: DiagnosticContext): S
289
446
  declare function reportDiagnostic(sink: DiagnosticSink | undefined, level: 'debug' | 'info' | 'warn' | 'error', message: string, context?: DiagnosticContext): void;
290
447
  /** A sink that forwards to a `console`-like object, one call per level. */
291
448
  declare function consoleDiagnosticSink(logger?: Partial<Record<'debug' | 'info' | 'warn' | 'error', (msg: string) => void>>): DiagnosticSink;
449
+ /**
450
+ * Options controlling how much of an error is disclosed in a report.
451
+ *
452
+ * Everything defaults to the SAFE (production) posture: no stack, no cause
453
+ * chain. A stack trace can embed absolute file paths and, in some runtimes,
454
+ * source fragments, so it is opt-in and belongs to development / trusted server
455
+ * logging — never to a report that might reach a browser or a third party.
456
+ */
457
+ interface ErrorReportOptions {
458
+ /** Include `error.stack` in the report. Default `false` (production-safe). */
459
+ readonly includeStack?: boolean;
460
+ /**
461
+ * Follow and describe `error.cause` (recursively, up to a small depth) when
462
+ * present. Default `false`. The cause is described with the SAME redaction
463
+ * rules — only its name/message/(optional)stack, never arbitrary properties.
464
+ */
465
+ readonly includeCause?: boolean;
466
+ }
467
+ /**
468
+ * A production-safe, serializable description of an error plus the framework
469
+ * context in which it surfaced. This is what §7 asks a diagnostics sink to
470
+ * receive: enough to locate the failure (component / route / resource / signal /
471
+ * phase / DOM association via {@link DiagnosticContext}) WITHOUT any sensitive
472
+ * data. It deliberately carries ONLY the error's class name and message (both
473
+ * author-controlled), never enumerated own-properties (which frequently hold
474
+ * request bodies, tokens, or user records), and the stack only when explicitly
475
+ * opted in.
476
+ */
477
+ interface ErrorReport {
478
+ /** The error's constructor name, e.g. `TypeError` (`Error` when unknown). */
479
+ readonly name: string;
480
+ /** The error's message. For a non-Error throw, its `String(...)` form. */
481
+ readonly message: string;
482
+ /** Whether the thrown value was a real `Error` instance. */
483
+ readonly isError: boolean;
484
+ /** The framework context, when supplied. */
485
+ readonly context?: DiagnosticContext;
486
+ /** The stack, only when `includeStack` was set and one exists. */
487
+ readonly stack?: string;
488
+ /** The described cause, only when `includeCause` was set and one exists. */
489
+ readonly cause?: ErrorReport;
490
+ }
491
+ /**
492
+ * Build a {@link ErrorReport} from any thrown value and (optionally) the
493
+ * framework {@link DiagnosticContext} it surfaced in. Production-safe by
494
+ * default: no stack, no cause, no enumerated properties. This is a pure
495
+ * function — it performs no logging and has no side effects, so it is safe to
496
+ * call from any layer (an `errorBoundary` `onError`, an `asyncBoundary` error
497
+ * branch, a resource loader `catch`).
498
+ */
499
+ declare function describeError(error: unknown, context?: DiagnosticContext, options?: ErrorReportOptions): ErrorReport;
500
+ /**
501
+ * Convenience bridge: build a production-safe {@link ErrorReport} and route it
502
+ * to a {@link DiagnosticSink} at the `error` level. The sink receives the
503
+ * report's message and the structured {@link DiagnosticContext}; the full
504
+ * report is returned to the caller for forwarding elsewhere (e.g. an app's own
505
+ * crash reporter). Safe with `undefined` sink (no-op) and never throws.
506
+ */
507
+ declare function reportError(sink: DiagnosticSink | undefined, error: unknown, context?: DiagnosticContext, options?: ErrorReportOptions): ErrorReport;
292
508
 
293
509
  /**
294
510
  * Compiler-phase validation of the ApplicationGraph.
@@ -558,6 +774,7 @@ declare class BrowserDOMAdapter implements DOMAdapter {
558
774
  getElementById(id: string): Element | null;
559
775
  focus(element: Element): void;
560
776
  body(): Element | null;
777
+ head(): Element | null;
561
778
  activeElement(): Element | null;
562
779
  contains(ancestor: Element, node: Node): boolean;
563
780
  matches(element: Element, selector: string): boolean;
@@ -703,72 +920,65 @@ declare function focusInitial(dom: DOMAdapter, container: Element, initialFocusI
703
920
  * cleanup is still callable.
704
921
  */
705
922
  declare function trapFocus(dom: DOMAdapter, container: Element): () => void;
706
- /**
707
- * Modal containment: if focus moves to an element outside `container`, redirect
708
- * it back inside. Listens on the document body (focusin bubbles there) and
709
- * returns a cleanup function. Server-safe no-op (body() is null).
710
- */
711
923
  declare function containFocus(dom: DOMAdapter, container: Element): () => void;
712
924
  /**
713
925
  * Invoke `handler` when Escape is pressed while focus is within `target`.
714
926
  * Returns a cleanup function. Server-safe no-op.
715
927
  */
716
928
  declare function onEscape(dom: DOMAdapter, target: Element, handler: () => void): () => void;
717
-
718
929
  /**
719
- * NodeInstance — the renderer's live counterpart to a GraphNode.
930
+ * Roving-focus keyboard navigation for a menu (role="menu") container: the
931
+ * arrow keys move focus between the container's focusable items (wrap-around at
932
+ * both ends), Home/End jump to the first/last item, and Enter/Space activate
933
+ * the currently-focused item (a native `click`, so an item's `onClick` fires).
934
+ * `Tab` and `Escape` are deliberately left alone — the overlay layer wires
935
+ * Escape-to-close separately and a menu does not trap Tab.
720
936
  *
721
- * Tracks the actual DOM node(s), all signal subscriptions that drive
722
- * targeted DOM updates, and DOM event listener teardowns.
937
+ * Items are re-queried on every key (via {@link getFocusable}) so a menu whose
938
+ * items change reactively is always navigated against the live set, and items
939
+ * disabled after mount are skipped. Attaches a keydown listener to the
940
+ * container and returns a cleanup function. Server-safe: `addEventListener` is
941
+ * a no-op and the returned cleanup is still callable.
723
942
  */
724
-
725
- declare class NodeInstance {
726
- readonly graphNode: GraphNode;
727
- /** The primary DOM node for this instance (element or text node). */
728
- domNode: Node;
729
- readonly children: NodeInstance[];
730
- readonly cleanup: CleanupRegistry;
731
- constructor(graphNode: GraphNode, domNode: Node);
732
- addChild(child: NodeInstance): void;
733
- /** Subscribe to a signal; auto-cleanup on unmount. */
734
- trackSignal<T>(sig: ReadonlySignal<T>, handler: (v: T) => void): void;
735
- /** Register a raw cleanup fn (DOM event removal, etc.). */
736
- trackCleanup(fn: () => void): void;
737
- dispose(): void;
738
- }
943
+ declare function rovingMenu(dom: DOMAdapter, container: Element, selector?: string): () => void;
739
944
 
740
945
  /**
741
- * RenderContext — shared state for a single mount operation.
946
+ * ARIA live-region announcer (§15).
742
947
  *
743
- * Passed through the render pipeline so every sub-function has access
744
- * to the DOM adapter, graph, and instance map without prop-drilling.
948
+ * Screen readers announce text that appears inside an `aria-live` region. The
949
+ * naive approach — append a fresh `<div aria-live>` per message — leaks a
950
+ * growing pile of stale nodes and (because a node inserted *already carrying*
951
+ * its text is often not re-announced) is unreliable. This announcer instead
952
+ * keeps exactly TWO persistent regions on `<body>` — one `polite`, one
953
+ * `assertive` — and mutates their text to speak. Announcing clears the region
954
+ * first and writes on a microtask so that repeating the same string still
955
+ * triggers a DOM mutation the AT will pick up.
956
+ *
957
+ * Built entirely on the {@link DOMAdapter}, so it is server-safe: when
958
+ * `dom.body()` is null (SSR / headless) construction returns an inert announcer
959
+ * whose `announce`/`clear`/`destroy` are no-ops. There is never any SSR markup
960
+ * for a live region — announcements are a runtime-only concept.
745
961
  */
746
962
 
747
- interface RenderContext {
748
- readonly dom: DOMAdapter;
749
- readonly graph: ApplicationGraph;
750
- /** Maps GraphNode.id → its live NodeInstance */
751
- readonly instances: Map<string, NodeInstance>;
752
- /** The root container element. */
753
- readonly container: Element;
963
+ interface Announcer {
754
964
  /**
755
- * Optional dev-only sink that observes hydration mismatch repairs. When
756
- * absent (the default) the hydration path does no extra work — this is how
757
- * DevTools/diagnostics stay off the production runtime path.
965
+ * Announce `message`. `assertive` (default false) routes to the assertive
966
+ * region (interrupts the user) instead of the polite one (waits for a pause).
758
967
  */
759
- readonly hydrationDiagnostics?: HydrationDiagnosticSink;
760
- /**
761
- * Optional SSR-only static-subtree plan (v1.7). Maps a maximal
762
- * static-subtree root GraphNode.id → its precomputed, verbatim HTML string.
763
- * Present only on the server render path when a plan has been built; on the
764
- * browser mount path it is always `undefined`, so the client hot path is
765
- * unaffected (a single `=== undefined` check short-circuits). When a mounted
766
- * node's id is in this map, the renderer emits the precomputed HTML via
767
- * `dom.createRawHTML` instead of recursively constructing the subtree.
768
- */
769
- readonly staticHTML?: ReadonlyMap<string, string>;
968
+ announce(message: string, options?: {
969
+ assertive?: boolean;
970
+ }): void;
971
+ /** Clear both regions without announcing anything. */
972
+ clear(): void;
973
+ /** Remove both regions from the DOM. Idempotent. */
974
+ destroy(): void;
770
975
  }
771
- declare function createRenderContext(dom: DOMAdapter, graph: ApplicationGraph, container: Element, hydrationDiagnostics?: HydrationDiagnosticSink, staticHTML?: ReadonlyMap<string, string>): RenderContext;
976
+ /**
977
+ * Create a live-region announcer bound to `dom`. Idempotent per call — each
978
+ * call owns its own pair of regions, so an app that wants a single shared
979
+ * announcer should create one and reuse it (and `destroy()` it on teardown).
980
+ */
981
+ declare function createAnnouncer(dom: DOMAdapter): Announcer;
772
982
 
773
983
  /**
774
984
  * Attribute and property application helpers.
@@ -815,6 +1025,7 @@ declare function textUpdate(dom: DOMAdapter, el: Element, textNode: Text): (prop
815
1025
  declare function headingUpdate(dom: DOMAdapter, el: Element): (propKey: string, value: unknown) => void;
816
1026
  declare function inputUpdate(dom: DOMAdapter, el: Element): (propKey: string, value: unknown) => void;
817
1027
  declare function buttonUpdate(dom: DOMAdapter, el: Element): (propKey: string, value: unknown) => void;
1028
+ declare function linkUpdate(dom: DOMAdapter, el: Element): (propKey: string, value: unknown) => void;
818
1029
  declare function applyNodeProps(ctx: RenderContext, graphNode: GraphNode, el: Element): void;
819
1030
  declare function wireSignalBindings(ctx: RenderContext, graphNode: GraphNode, instance: NodeInstance, onUpdate: (propKey: string, value: unknown) => void): void;
820
1031
  /**
@@ -824,7 +1035,7 @@ declare function wireSignalBindings(ctx: RenderContext, graphNode: GraphNode, in
824
1035
  * (spec §15). A `conditional` node has no plan handler and falls back to the
825
1036
  * eager build factory (it only ever renders 0..1 branch, so eager is fine).
826
1037
  */
827
- declare function wireReactiveList(ctx: RenderContext, graphNode: GraphNode, instance: NodeInstance, el: Element): void;
1038
+ declare function wireReactiveList(ctx: RenderContext, graphNode: GraphNode, instance: NodeInstance, el: Element, runAppear?: boolean): void;
828
1039
  /**
829
1040
  * Attach overlay focus/keyboard behavior to a mounted portal. Server-safe: on
830
1041
  * the server `dom.body()` is null so this returns immediately (SSR emits inert
@@ -859,6 +1070,122 @@ declare function wireComponentBehavior(ctx: RenderContext, graphNode: GraphNode,
859
1070
 
860
1071
  declare function patchNode(ctx: RenderContext, graphNode: GraphNode, propKey: string, newValue: unknown): void;
861
1072
 
1073
+ /**
1074
+ * CSS class-based enter/leave transition controller (§2–§8).
1075
+ *
1076
+ * This is the browser-only runtime that consumes the `__transition__<nodeId>`
1077
+ * descriptor the DSL registers (a pre-resolved {@link ResolvedTransitionLike}).
1078
+ * It is deliberately structural about that descriptor — like the renderer's
1079
+ * `__overlay__`/`__component__` handling — so the renderer takes NO compile-time
1080
+ * dependency on the DSL package.
1081
+ *
1082
+ * Engine (confirmed decision): CSS classes, no Web Animations API, no
1083
+ * browser-only API referenced at module scope. Every timer / rAF / event
1084
+ * binding is reached lazily through `globalThis` and only ever runs when
1085
+ * `dom.body() !== null` (the same SSR guard `wireOverlayBehavior` uses), so:
1086
+ * - server output is byte-identical (nothing here runs during SSR — §21);
1087
+ * - happy-dom, which dispatches no `transitionend`/`animationend`, still
1088
+ * completes deterministically via the fallback timeout (§24).
1089
+ *
1090
+ * The controller is created once per reactive container instance (reactive-list
1091
+ * or conditional) so its `leaving` map survives across reconcile passes — that
1092
+ * is what makes leave→enter reclaim (§6) and keyed-identity list leave (§7)
1093
+ * correct.
1094
+ */
1095
+
1096
+ /**
1097
+ * The renderer's structural view of the resolved transition the DSL stores in
1098
+ * `__transition__<id>`. Mirrors `streetui`'s `ResolvedTransition` without
1099
+ * importing it (no renderer→dsl dependency).
1100
+ */
1101
+ interface ResolvedTransitionLike {
1102
+ readonly enterActive: readonly string[];
1103
+ readonly enterFrom: readonly string[];
1104
+ readonly enterTo: readonly string[];
1105
+ readonly leaveActive: readonly string[];
1106
+ readonly leaveFrom: readonly string[];
1107
+ readonly leaveTo: readonly string[];
1108
+ readonly appear: boolean;
1109
+ readonly duration: number;
1110
+ }
1111
+ /**
1112
+ * Hooks handed to the reconciler so it can (a) reclaim an instance that is
1113
+ * mid-leave when its key re-enters (leave→enter cancellation), (b) defer the
1114
+ * remove/dispose/forget/detach chain for a leaving instance until its animation
1115
+ * ends, and (c) play the enter animation for a freshly-inserted instance. When
1116
+ * no transition applies (or we are on the server) every hook degrades to the
1117
+ * pre-transition synchronous behaviour.
1118
+ */
1119
+ interface TransitionHooks {
1120
+ /**
1121
+ * If an instance for `key` is currently animating out, cancel its leave and
1122
+ * return it for reuse; otherwise undefined. The caller re-mounts nothing and
1123
+ * reuses the returned instance's live DOM node.
1124
+ */
1125
+ takeLeaving(key: string): NodeInstance | undefined;
1126
+ /**
1127
+ * Begin a leave animation for a removed instance. Returns true when the whole
1128
+ * teardown chain has been deferred to animation-end (caller must NOT remove,
1129
+ * dispose, forget or detach it), or false when there is no transition / no
1130
+ * browser and the caller should tear it down synchronously as before.
1131
+ */
1132
+ beginLeave(inst: NodeInstance): boolean;
1133
+ /** Play the enter animation for a freshly-inserted (or reclaimed) instance. */
1134
+ onEnter(inst: NodeInstance): void;
1135
+ }
1136
+ /** Read the pre-resolved transition descriptor for a node, if any. */
1137
+ declare function getResolvedTransition(graph: ApplicationGraph, nodeId: string): ResolvedTransitionLike | undefined;
1138
+ /** Which half of a transition to play on a bare element. */
1139
+ type TransitionPhase = 'enter' | 'leave';
1140
+ /**
1141
+ * Play one enter/leave transition on a bare DOM element, outside the keyed
1142
+ * reconciler — the seam the router uses for route leave/enter (§9) and any other
1143
+ * consumer that owns a single host element rather than a reactive container.
1144
+ *
1145
+ * Reuses the exact same {@link startRun} mechanics as list/conditional
1146
+ * transitions (from+active applied immediately, next-frame flip to `to`,
1147
+ * completion on transitionend/animationend or the fallback timer), so there is
1148
+ * one transition engine, not two. The returned handle's `cancel()` settles the
1149
+ * run immediately WITHOUT invoking `onDone` — the caller uses it to abort an
1150
+ * in-flight enter when the same host is about to start leaving (rapid
1151
+ * navigation), avoiding overlapping runs/duplicate listeners on one element.
1152
+ *
1153
+ * On the server (or any non-element target) there is nothing to animate, so
1154
+ * `onDone` runs synchronously and `cancel()` is a no-op — the caller's teardown
1155
+ * still happens exactly once.
1156
+ */
1157
+ declare function runElementTransition(dom: DOMAdapter, el: Element, rt: ResolvedTransitionLike, phase: TransitionPhase, onDone: () => void): {
1158
+ cancel(): void;
1159
+ };
1160
+ /**
1161
+ * Per-container transition controller. One instance is created for each
1162
+ * reactive-list / conditional NodeInstance in {@link wireReactiveList}; its
1163
+ * `leaving` map persists across every reconcile of that container.
1164
+ */
1165
+ declare class TransitionController {
1166
+ private readonly dom;
1167
+ private readonly graph;
1168
+ /** Full teardown of a leaving instance (remove + dispose + forget + detach). */
1169
+ private readonly finalize;
1170
+ private readonly leaving;
1171
+ constructor(dom: DOMAdapter, graph: ApplicationGraph,
1172
+ /** Full teardown of a leaving instance (remove + dispose + forget + detach). */
1173
+ finalize: (inst: NodeInstance) => void);
1174
+ /** True only in a real DOM environment (browser). */
1175
+ private get browser();
1176
+ private keyOf;
1177
+ private resolved;
1178
+ /** Run the enter animation for `inst` if it carries a transition (browser only). */
1179
+ enter(inst: NodeInstance): void;
1180
+ /**
1181
+ * Play `appear` for any initial child that opted into it (fresh browser mount
1182
+ * only — hydration must never animate appear, §22, and this is called only on
1183
+ * the mount path).
1184
+ */
1185
+ appear(children: readonly NodeInstance[]): void;
1186
+ hooks(): TransitionHooks;
1187
+ }
1188
+
862
1189
  /**
863
1190
  * Reconciliation — diff-based child list updates.
864
1191
  *
@@ -911,7 +1238,7 @@ interface PlanEntry {
911
1238
  * @param newNodes New graph children (in desired order)
912
1239
  * @param mountFn Factory to create a new NodeInstance for a graph node
913
1240
  */
914
- declare function reconcileChildren(ctx: RenderContext, parentDom: Element, oldInstances: NodeInstance[], newNodes: readonly GraphNode[], mountFn: MountFn): ReconcileResult;
1241
+ declare function reconcileChildren(ctx: RenderContext, parentDom: Element, oldInstances: NodeInstance[], newNodes: readonly GraphNode[], mountFn: MountFn, hooks?: TransitionHooks): ReconcileResult;
915
1242
  /**
916
1243
  * Plan-based keyed reconciliation (spec §15 — the optimised reactive-list path).
917
1244
  *
@@ -930,7 +1257,7 @@ declare function reconcileChildren(ctx: RenderContext, parentDom: Element, oldIn
930
1257
  * DOM reordering uses a longest-increasing-subsequence pass so the number of
931
1258
  * moves is minimal (e.g. a prepend into a 10k list moves 1 node, not 10k).
932
1259
  */
933
- declare function reconcileChildrenByPlan(ctx: RenderContext, parentDom: Element, oldInstances: NodeInstance[], plan: readonly PlanEntry[], mountFn: MountFn): ReconcileResult;
1260
+ declare function reconcileChildrenByPlan(ctx: RenderContext, parentDom: Element, oldInstances: NodeInstance[], plan: readonly PlanEntry[], mountFn: MountFn, hooks?: TransitionHooks): ReconcileResult;
934
1261
 
935
1262
  /**
936
1263
  * StreetUI Renderer — framework-owned DOM renderer.
@@ -1253,6 +1580,17 @@ interface MountRouterOptions {
1253
1580
  * subsequent client-side navigations mount normally. Defaults to false.
1254
1581
  */
1255
1582
  readonly hydrate?: boolean;
1583
+ /**
1584
+ * Optional enter/leave transition played on client-side navigations (§9). When
1585
+ * set, each navigation mounts the incoming route into its own host wrapper,
1586
+ * plays the enter animation on it, and defers the outgoing route's disposal
1587
+ * (route-scoped cleanup + DOM removal) until its leave animation ends — so
1588
+ * resources stay alive exactly as long as the departing DOM. History is
1589
+ * untouched (the router already navigated), and the initial mount/hydration is
1590
+ * NOT animated (§22-style: the first paint must match the server). Reuses the
1591
+ * single CSS-class transition engine — no second animation system.
1592
+ */
1593
+ readonly transition?: TransitionConfig;
1256
1594
  }
1257
1595
  interface MountedRouter {
1258
1596
  /** The element route content is rendered into. */
@@ -1490,6 +1828,58 @@ interface InspectedComponent {
1490
1828
  * fine-grained prop updates (which never rebuild the node).
1491
1829
  */
1492
1830
  declare function inspectComponents(graph: ApplicationGraph): InspectedComponent[];
1831
+ /** An overlay currently wired in the graph (dialog/popover/tooltip/…). */
1832
+ interface InspectedOverlay {
1833
+ /** The build-order node id of the overlay's portal host. */
1834
+ id: string;
1835
+ /** The stable author-provided key, if any. */
1836
+ key: string | undefined;
1837
+ /** Whether the overlay is open right now (peeked, no subscription). */
1838
+ open: boolean;
1839
+ /** Modal (focus-trapping) overlay? */
1840
+ modal: boolean;
1841
+ /** Does it move focus into itself on open? */
1842
+ takesFocus: boolean;
1843
+ /** Is it a roving-focus menu (role="menu")? */
1844
+ menu: boolean;
1845
+ /** Does Escape close it? */
1846
+ closeOnEscape: boolean;
1847
+ /** Does it restore focus to the opener on close? */
1848
+ restoreFocus: boolean;
1849
+ /** Depth of the portal host node in the graph. */
1850
+ depth: number;
1851
+ }
1852
+ /** A transition currently wired on a graph node. */
1853
+ interface InspectedTransition {
1854
+ /** The build-order node id the transition is attached to. */
1855
+ id: string;
1856
+ /** The stable author-provided key, if any. */
1857
+ key: string | undefined;
1858
+ /** The node type the transition animates (element/portal/list-item/…). */
1859
+ nodeType: string;
1860
+ /** Fallback completion timeout in ms (the resolved `duration`). */
1861
+ duration: number;
1862
+ /** Does it animate the very first appearance (initial mount)? */
1863
+ appear: boolean;
1864
+ /** Depth of the node in the graph. */
1865
+ depth: number;
1866
+ }
1867
+ /** A prod-safe snapshot of the graph's interaction wiring. */
1868
+ interface InspectedInteractions {
1869
+ overlays: InspectedOverlay[];
1870
+ transitions: InspectedTransition[];
1871
+ }
1872
+ /**
1873
+ * Snapshot every overlay and transition currently wired in the graph, in
1874
+ * document order. This is the interaction-aware companion to
1875
+ * {@link inspectComponents}: it reads only the `__overlay__<id>` /
1876
+ * `__transition__<id>` handler descriptors and public graph structure — it
1877
+ * peeks the `open` signal without subscribing, retains no DOM nodes, mutates
1878
+ * nothing, and is safe to call in production. Because a departing overlay's
1879
+ * handler is pruned on detach (`_unregisterNodeHandlers`), a closed-and-removed
1880
+ * overlay simply no longer appears here.
1881
+ */
1882
+ declare function inspectInteractions(graph: ApplicationGraph): InspectedInteractions;
1493
1883
 
1494
1884
  /**
1495
1885
  * DevTools foundation (v0.6, Phase 18). A single read-only entry point that
@@ -1794,12 +2184,35 @@ interface PerformancePanel {
1794
2184
  readonly snapshot: ApplicationInspection['perf'];
1795
2185
  readonly diagnostics: readonly PerfDiagnostic[];
1796
2186
  }
2187
+ /**
2188
+ * Compilation diagnostics for the DevTools "Diagnostics" panel (§8). These are
2189
+ * the compiler's own findings (errors + warnings), surfaced verbatim — not a
2190
+ * runtime error stream. Runtime/production error reports flow through the
2191
+ * separate `reportError`/`DiagnosticSink` seam in `streetui` (§7).
2192
+ */
2193
+ interface DiagnosticsPanel {
2194
+ readonly errors: number;
2195
+ readonly warnings: number;
2196
+ readonly messages: readonly string[];
2197
+ }
1797
2198
  /** All panels captured at one `refresh()`. */
1798
2199
  interface DevToolsSnapshot {
1799
2200
  readonly application: ApplicationPanel;
1800
2201
  readonly graph: InspectedNode;
1801
2202
  readonly signals: SignalsPanel;
1802
2203
  readonly performance: PerformancePanel;
2204
+ /**
2205
+ * Component tree (§9): every `component()` instance in document order, with
2206
+ * its stable key/name/location. Read structurally from the graph — the same
2207
+ * data an app/component-tree UI panel renders.
2208
+ */
2209
+ readonly components: readonly InspectedComponent[];
2210
+ /** Overlays currently wired (§13), in graph/containment order (dialog/popover/…). */
2211
+ readonly overlays: readonly InspectedOverlay[];
2212
+ /** Transitions currently wired on nodes (§14). Structural — never interferes with lifecycle. */
2213
+ readonly transitions: readonly InspectedTransition[];
2214
+ /** Compilation diagnostics (§8). */
2215
+ readonly diagnostics: DiagnosticsPanel;
1803
2216
  readonly router?: RouterInspection;
1804
2217
  readonly resources?: Readonly<Record<string, ResourceInspection>>;
1805
2218
  readonly forms?: Readonly<Record<string, FormInspection>>;
@@ -1869,6 +2282,44 @@ interface DevToolsSession {
1869
2282
  */
1870
2283
  declare function createDevTools(compiled: CompiledApplication, sources?: DevToolsSources, options?: DevToolsOptions): DevToolsSession;
1871
2284
 
2285
+ /**
2286
+ * DevTools view (2.0 §8) — a DOM-free HTML renderer for a {@link DevToolsSnapshot}.
2287
+ *
2288
+ * This is the "initial UI" layer: it turns the headless snapshot (already
2289
+ * composed from the existing read-only inspectors) into a single self-contained
2290
+ * HTML string a host can inject into a panel, an iframe, or a static report. It
2291
+ * is deliberately a *pure string builder*:
2292
+ *
2293
+ * - No DOM API is touched and nothing is mounted, so it runs anywhere (Node,
2294
+ * a worker, a test) and adds nothing to the running app.
2295
+ * - It reads ONLY the snapshot — no second graph, no reactive subscriptions,
2296
+ * no retained resource/DOM references.
2297
+ * - Every dynamic value is HTML-escaped, so an app's data (signal values that
2298
+ * the caller chose to expose, route paths, form labels) can never break out
2299
+ * of the markup.
2300
+ *
2301
+ * HONEST SCOPE: this produces markup. Whether it *renders* correctly in a real
2302
+ * browser, is accessible to a screen reader, or performs at 60fps is NOT claimed
2303
+ * here and has NOT been verified — no browser/AT is available in this
2304
+ * environment (the §24 browser gate remains BLOCKED). The value proven by tests
2305
+ * is that the string faithfully and safely reflects the snapshot.
2306
+ *
2307
+ * EFFECTS (§8): StreetUI keeps no global registry of effects (that would require
2308
+ * instrumenting the reactive runtime, which DevTools deliberately does not do).
2309
+ * The closest safe signal is each live signal's `observerCount` — the number of
2310
+ * effects/derivations currently depending on it — which the Signals section
2311
+ * shows. The view labels this honestly rather than inventing an effect list.
2312
+ */
2313
+
2314
+ /** Escape a string for safe interpolation into HTML text/attribute content. */
2315
+ declare function escapeHtml(value: unknown): string;
2316
+ /**
2317
+ * Render a snapshot to a complete, self-contained HTML document string. The
2318
+ * markup is static; call `renderDevToolsHTML(session.refresh())` again to
2319
+ * reflect new state (DevTools pulls — it is never pushed to).
2320
+ */
2321
+ declare function renderDevToolsHTML(s: DevToolsSnapshot): string;
2322
+
1872
2323
  /**
1873
2324
  * StreetUI project configuration (Phase 9). The config is intentionally tiny:
1874
2325
  * every field has a sensible default so `streetui.config.ts` is optional. A
@@ -1923,4 +2374,4 @@ interface ResolvedConfig {
1923
2374
  /** Identity helper that gives `streetui.config.ts` full type-checking + inference. */
1924
2375
  declare function defineConfig(config: StreetUIConfig): StreetUIConfig;
1925
2376
 
1926
- 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 InspectedComponent, 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 PlanEntry, 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, ServerRawHTML, 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, containFocus, createApplication, createBrowserHistory, createContext, createDevTools, createForm, createI18n, createMemoryHistory, createRenderContext, createRenderer, createRouter, createRuntime, createStore, createStreetEvent, defineConfig, diagnosePerformance, email, environment, escapeHtmlAttr, escapeHtmlText, flushSync, focusById, focusFirst, focusInitial, formatDiagnosticContext, frameworkError, getFocusable, globalEventBus, headingUpdate, hydrateGraph, inputUpdate, inspectApplication, inspectComponents, inspectContext, inspectForm, inspectGraph, inspectI18n, inspectResource, inspectRouter, inspectSignal, interpolate, matchPattern, matchRoutes, maxLength, minLength, mountGraph, mountNode, mountRouter, nodeTypeStats, normalizePath, onEscape, patchNode, patchProp, pattern, printDiagnostics, printGraph, reconcileChildren, reconcileChildrenByPlan, reportDiagnostic, required, resolveTag, resource, restoreFocus, routerOutlet, runValidators, saveFocus, scheduleImmediate, scheduleUpdate, scheduler, serializeChildren, serializeServerNode, splitTarget, textUpdate, toIdToken, transformGraph, trapFocus, validateGraph, wireComponentBehavior, wireEvents, wireOverlayBehavior, wireReactiveList, wireSignalBindings };
2377
+ export { type A11yIds, type Announcer, Application, ApplicationGraph, ApplicationId, type ApplicationIdentity, type ApplicationInspection, type ApplicationOptions, type ApplicationPanel, type AuthSession, type AuthSessionConfig, type AuthStatus, BrowserDOMAdapter, CleanupRegistry, type Client, type ClientConfig, 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 DiagnosticsPanel, type DiagnosticsSummary, type DomBinding, DomEventRegistry, Environment, type EnvironmentCapabilities, type EnvironmentKind, type ErrorReport, type ErrorReportOptions, EventBus, type EventHandler, FOCUSABLE_SELECTOR, type FetchLike, type Field, type Form, type FormConfig, type FormInspection, type FormLike, type FormValidators, type FormValues, GraphNode, HttpError, type HttpMethod, HydrationDiagnosticSink, type I18n, type I18nConfig, type I18nInspection, type I18nLike, type InspectFormOptions, type InspectI18nOptions, type InspectResourceOptions, type InspectSignalOptions, type InspectedComponent, type InspectedInteractions, type InspectedNode, type InspectedOverlay, type InspectedPage, type InspectedTransition, type InterpolationParams, type IsActiveOptions, type Job, Lifecycle, type LoadUser, type MatchResult, type MessageMap, type MountFn, type MountRouterOptions, type MountedApplication, type MountedRouter, type Mutation, type MutationOptions, type MutationStatus, type Mutator, type NavigateOptions, NodeInstance, type NodeInstanceOptions, PageDSL, type PerfDiagnostic, type PerfDiagnosticCode, type PerfSnapshot, type PerfThresholds, type PerformancePanel, type PlanEntry, type Priority, ROUTER_OUTLET_ID, ReadonlySignal, type ReconcileResult, RenderContext, RenderHandle, type RequestConfig, type ResolvedConfig, type ResolvedTransitionLike, Resource, type ResourceInspection, type ResourceLike, ResourceLoaderContext, ResourceOptions, 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, ServerRawHTML, 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, TransitionConfig, TransitionController, type TransitionHooks, type TransitionPhase, Unsubscribe, type Validator, a11yIds, applyNodeProps, applyProp, bindDomEvent, browserDOMAdapter, buttonUpdate, consoleDiagnosticSink, containFocus, createAnnouncer, createApplication, createAuthSession, createBrowserHistory, createClient, createContext, createDevTools, createForm, createI18n, createMemoryHistory, createRenderer, createRouter, createRuntime, createStore, createStreetEvent, defineConfig, describeError, diagnosePerformance, email, environment, escapeHtml, escapeHtmlAttr, escapeHtmlText, flushSync, focusById, focusFirst, focusInitial, formatDiagnosticContext, frameworkError, getFocusable, getResolvedTransition, globalEventBus, headingUpdate, hydrateGraph, inputUpdate, inspectApplication, inspectComponents, inspectContext, inspectForm, inspectGraph, inspectI18n, inspectInteractions, inspectResource, inspectRouter, inspectSignal, interpolate, linkUpdate, matchPattern, matchRoutes, maxLength, minLength, mountGraph, mountNode, mountRouter, mutation, nodeTypeStats, normalizePath, onEscape, patchNode, patchProp, pattern, printDiagnostics, printGraph, reconcileChildren, reconcileChildrenByPlan, renderDevToolsHTML, reportDiagnostic, reportError, required, resolveTag, restoreFocus, routerOutlet, rovingMenu, runElementTransition, runValidators, saveFocus, scheduleImmediate, scheduleUpdate, scheduler, serializeChildren, serializeServerNode, splitTarget, textUpdate, toIdToken, transformGraph, trapFocus, validateGraph, wireComponentBehavior, wireEvents, wireOverlayBehavior, wireReactiveList, wireSignalBindings };