streetui 1.9.0 → 2.1.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.cjs CHANGED
@@ -37,6 +37,8 @@ __export(src_exports, {
37
37
  FOCUSABLE_SELECTOR: () => FOCUSABLE_SELECTOR,
38
38
  FormBuilderImpl: () => FormBuilderImpl,
39
39
  GraphNode: () => GraphNode,
40
+ HeadManager: () => HeadManager,
41
+ HttpError: () => HttpError,
40
42
  Lifecycle: () => Lifecycle,
41
43
  ListBuilderImpl: () => ListBuilderImpl,
42
44
  NodeInstance: () => NodeInstance,
@@ -77,7 +79,9 @@ __export(src_exports, {
77
79
  containFocus: () => containFocus,
78
80
  createAnnouncer: () => createAnnouncer,
79
81
  createApplication: () => createApplication,
82
+ createAuthSession: () => createAuthSession,
80
83
  createBrowserHistory: () => createBrowserHistory,
84
+ createClient: () => createClient,
81
85
  createContext: () => createContext,
82
86
  createDevTools: () => createDevTools,
83
87
  createForm: () => createForm,
@@ -93,10 +97,12 @@ __export(src_exports, {
93
97
  createStreetEvent: () => createStreetEvent,
94
98
  defineConfig: () => defineConfig,
95
99
  derived: () => derived,
100
+ describeError: () => describeError,
96
101
  diagnosePerformance: () => diagnosePerformance,
97
102
  effect: () => effect,
98
103
  email: () => email,
99
104
  environment: () => environment,
105
+ escapeHtml: () => escapeHtml,
100
106
  escapeHtmlAttr: () => escapeHtmlAttr,
101
107
  escapeHtmlText: () => escapeHtmlText,
102
108
  flushSync: () => flushSync,
@@ -128,7 +134,9 @@ __export(src_exports, {
128
134
  interpolate: () => interpolate,
129
135
  isBatching: () => isBatching,
130
136
  isComponentDefinition: () => isComponentDefinition,
137
+ isHeadContribution: () => isHeadContribution,
131
138
  isTransitionConfig: () => isTransitionConfig,
139
+ linkUpdate: () => linkUpdate,
132
140
  matchPattern: () => matchPattern,
133
141
  matchRoutes: () => matchRoutes,
134
142
  maxLength: () => maxLength,
@@ -136,6 +144,7 @@ __export(src_exports, {
136
144
  mountGraph: () => mountGraph,
137
145
  mountNode: () => mountNode,
138
146
  mountRouter: () => mountRouter,
147
+ mutation: () => mutation,
139
148
  nextId: () => nextId,
140
149
  nodeIdPrefix: () => nodeIdPrefix,
141
150
  nodeTypeStats: () => nodeTypeStats,
@@ -152,10 +161,15 @@ __export(src_exports, {
152
161
  readState: () => readState,
153
162
  reconcileChildren: () => reconcileChildren,
154
163
  reconcileChildrenByPlan: () => reconcileChildrenByPlan,
164
+ renderDevToolsHTML: () => renderDevToolsHTML,
165
+ renderDevToolsReport: () => renderDevToolsReport,
166
+ renderHead: () => renderHead,
155
167
  renderToString: () => renderToString,
156
168
  reportDiagnostic: () => reportDiagnostic,
169
+ reportError: () => reportError,
157
170
  required: () => required,
158
171
  resetIdCounter: () => resetIdCounter,
172
+ resolveHead: () => resolveHead,
159
173
  resolveTag: () => resolveTag,
160
174
  resolveTransition: () => resolveTransition,
161
175
  resource: () => resource,
@@ -183,6 +197,7 @@ __export(src_exports, {
183
197
  validateGraph: () => validateGraph,
184
198
  wireComponentBehavior: () => wireComponentBehavior,
185
199
  wireEvents: () => wireEvents,
200
+ wireHeadBehavior: () => wireHeadBehavior,
186
201
  wireOverlayBehavior: () => wireOverlayBehavior,
187
202
  wireReactiveList: () => wireReactiveList,
188
203
  wireSignalBindings: () => wireSignalBindings
@@ -190,7 +205,7 @@ __export(src_exports, {
190
205
  module.exports = __toCommonJS(src_exports);
191
206
 
192
207
  // src/version.ts
193
- var VERSION = "1.9.0";
208
+ var VERSION = "2.1.0";
194
209
 
195
210
  // ../state/src/signal.ts
196
211
  var _activeConsumer = null;
@@ -530,6 +545,198 @@ function resource(loader, options = {}) {
530
545
  };
531
546
  }
532
547
 
548
+ // ../state/src/mutation.ts
549
+ function mutation(mutator, options = {}) {
550
+ const status = signal("idle");
551
+ const data = signal(void 0);
552
+ const error = signal(void 0);
553
+ const pending = derived(() => status.get() === "loading");
554
+ let disposed = false;
555
+ let runId = 0;
556
+ const mutate = async (args) => {
557
+ if (disposed) {
558
+ return await mutator(args);
559
+ }
560
+ const myRun = ++runId;
561
+ batch(() => {
562
+ error.set(void 0);
563
+ status.set("loading");
564
+ });
565
+ try {
566
+ const result = await mutator(args);
567
+ if (!disposed && myRun === runId) {
568
+ batch(() => {
569
+ data.set(result);
570
+ error.set(void 0);
571
+ status.set("success");
572
+ });
573
+ }
574
+ if (!disposed && myRun === runId) {
575
+ await options.onSuccess?.(result, args);
576
+ await options.onSettled?.(args);
577
+ }
578
+ return result;
579
+ } catch (err) {
580
+ if (!disposed && myRun === runId) {
581
+ batch(() => {
582
+ error.set(err);
583
+ status.set("error");
584
+ });
585
+ await options.onError?.(err, args);
586
+ await options.onSettled?.(args);
587
+ }
588
+ throw err;
589
+ }
590
+ };
591
+ const reset = () => {
592
+ batch(() => {
593
+ status.set("idle");
594
+ data.set(void 0);
595
+ error.set(void 0);
596
+ });
597
+ };
598
+ const dispose = () => {
599
+ disposed = true;
600
+ };
601
+ if (options.onCleanup !== void 0) options.onCleanup(dispose);
602
+ return { status, data, error, pending, mutate, reset, dispose };
603
+ }
604
+
605
+ // ../state/src/client.ts
606
+ var HttpError = class extends Error {
607
+ status;
608
+ statusText;
609
+ url;
610
+ body;
611
+ constructor(status, statusText, url, body) {
612
+ super(`HTTP ${status} ${statusText} for ${url}`);
613
+ this.name = "HttpError";
614
+ this.status = status;
615
+ this.statusText = statusText;
616
+ this.url = url;
617
+ this.body = body;
618
+ }
619
+ };
620
+ function joinUrl(baseUrl, path) {
621
+ if (baseUrl === void 0 || baseUrl === "") return path;
622
+ const b = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
623
+ const p = path.startsWith("/") ? path : `/${path}`;
624
+ return `${b}${p}`;
625
+ }
626
+ function withQuery(url, query) {
627
+ if (query === void 0) return url;
628
+ const params = new URLSearchParams();
629
+ for (const [k, v] of Object.entries(query)) params.set(k, String(v));
630
+ const qs = params.toString();
631
+ if (qs === "") return url;
632
+ return url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
633
+ }
634
+ function createClient(config = {}) {
635
+ const doFetch = config.fetch ?? ((input, init) => {
636
+ if (typeof fetch === "undefined") {
637
+ throw new Error("createClient: no global fetch; pass { fetch } explicitly");
638
+ }
639
+ return fetch(input, init);
640
+ });
641
+ async function request(method, path, body, reqConfig = {}) {
642
+ const url = withQuery(joinUrl(config.baseUrl, path), reqConfig.query);
643
+ const headers = { ...config.headers, ...reqConfig.headers };
644
+ const init = { method, headers };
645
+ if (reqConfig.signal !== void 0) init.signal = reqConfig.signal;
646
+ if (body !== void 0) {
647
+ if (headers["Content-Type"] === void 0) headers["Content-Type"] = "application/json";
648
+ init.body = JSON.stringify(body);
649
+ }
650
+ const response = await doFetch(url, init);
651
+ const parsed = await parseBody(response);
652
+ if (!response.ok) {
653
+ throw new HttpError(response.status, response.statusText, url, parsed);
654
+ }
655
+ return parsed;
656
+ }
657
+ return {
658
+ request,
659
+ get: (path, c) => request("GET", path, void 0, c),
660
+ post: (path, b, c) => request("POST", path, b, c),
661
+ put: (path, b, c) => request("PUT", path, b, c),
662
+ patch: (path, b, c) => request("PATCH", path, b, c),
663
+ del: (path, c) => request("DELETE", path, void 0, c),
664
+ resource: (path, options = {}) => {
665
+ const { query, ...resourceOptions } = options;
666
+ return resource(
667
+ (ctx) => request("GET", path, void 0, { signal: ctx.signal, ...query !== void 0 ? { query } : {} }),
668
+ resourceOptions
669
+ );
670
+ },
671
+ mutation: (method, path, options) => mutation((args) => request(method, path, args), options ?? {})
672
+ };
673
+ }
674
+ async function parseBody(response) {
675
+ const text = await response.text();
676
+ if (text === "") return void 0;
677
+ const type = response.headers.get("content-type") ?? "";
678
+ if (type.includes("application/json")) {
679
+ try {
680
+ return JSON.parse(text);
681
+ } catch {
682
+ return text;
683
+ }
684
+ }
685
+ return text;
686
+ }
687
+
688
+ // ../state/src/auth.ts
689
+ function createAuthSession(config) {
690
+ const session = resource(
691
+ (ctx) => config.loadUser(ctx),
692
+ {
693
+ ...config.immediate !== void 0 ? { immediate: config.immediate } : {},
694
+ ...config.onCleanup !== void 0 ? { onCleanup: config.onCleanup } : {}
695
+ }
696
+ );
697
+ const status = derived(() => {
698
+ const s = session.status.get();
699
+ const d = session.data.get();
700
+ if (s === "error") return "error";
701
+ if (s === "idle" || s === "loading" && d === void 0) return "loading";
702
+ return d === null || d === void 0 ? "unauthenticated" : "authenticated";
703
+ });
704
+ const user = derived(() => {
705
+ const d = session.data.get();
706
+ return d === null ? void 0 : d;
707
+ });
708
+ const authenticated = derived(() => status.get() === "authenticated");
709
+ const unauthenticated = derived(() => status.get() === "unauthenticated");
710
+ const loading = derived(() => status.get() === "loading");
711
+ const logoutMutation = mutation(
712
+ async () => {
713
+ await config.logout?.();
714
+ },
715
+ { onSuccess: () => session.refetch() }
716
+ );
717
+ const dispose = () => {
718
+ session.dispose();
719
+ status.dispose();
720
+ user.dispose();
721
+ authenticated.dispose();
722
+ unauthenticated.dispose();
723
+ loading.dispose();
724
+ logoutMutation.dispose();
725
+ };
726
+ return {
727
+ status,
728
+ user,
729
+ error: session.error,
730
+ authenticated,
731
+ unauthenticated,
732
+ loading,
733
+ loggingOut: logoutMutation.pending,
734
+ refresh: () => session.refetch(),
735
+ logout: () => logoutMutation.mutate(void 0),
736
+ dispose
737
+ };
738
+ }
739
+
533
740
  // ../core/src/a11y-ids.ts
534
741
  var UNSAFE = /[^A-Za-z0-9_-]+/g;
535
742
  function toIdToken(base) {
@@ -796,9 +1003,13 @@ function formatDiagnosticContext(context) {
796
1003
  const parts = [];
797
1004
  if (context.package !== void 0) parts.push(`package=${context.package}`);
798
1005
  if (context.operation !== void 0) parts.push(`operation=${context.operation}`);
1006
+ if (context.component !== void 0) parts.push(`component=${context.component}`);
1007
+ if (context.phase !== void 0) parts.push(`phase=${context.phase}`);
799
1008
  if (context.nodeId !== void 0) parts.push(`node=${context.nodeId}`);
1009
+ if (context.element !== void 0) parts.push(`element=${context.element}`);
800
1010
  if (context.route !== void 0) parts.push(`route=${context.route}`);
801
1011
  if (context.resource !== void 0) parts.push(`resource=${context.resource}`);
1012
+ if (context.signal !== void 0) parts.push(`signal=${context.signal}`);
802
1013
  return parts.length > 0 ? ` [${parts.join(", ")}]` : "";
803
1014
  }
804
1015
  var StreetFrameworkError = class extends Error {
@@ -829,6 +1040,42 @@ function consoleDiagnosticSink(logger = console) {
829
1040
  error: (m, c) => logger.error?.(`${m}${formatDiagnosticContext(c)}`)
830
1041
  };
831
1042
  }
1043
+ function describeErrorAt(error, context, options, depth) {
1044
+ const isError = error instanceof Error;
1045
+ const name = isError ? error.name : "Error";
1046
+ const message = isError ? error.message : safeStringify(error);
1047
+ const report = { name, message, isError };
1048
+ if (context !== void 0) report.context = context;
1049
+ if (options.includeStack === true && isError && typeof error.stack === "string") {
1050
+ report.stack = error.stack;
1051
+ }
1052
+ if (options.includeCause === true && isError && depth < 4) {
1053
+ const cause = error.cause;
1054
+ if (cause !== void 0 && cause !== null) {
1055
+ report.cause = describeErrorAt(cause, void 0, options, depth + 1);
1056
+ }
1057
+ }
1058
+ return report;
1059
+ }
1060
+ function safeStringify(value) {
1061
+ if (typeof value === "string") return value;
1062
+ if (value === null) return "null";
1063
+ if (value === void 0) return "undefined";
1064
+ const t = typeof value;
1065
+ if (t === "number" || t === "boolean" || t === "bigint" || t === "symbol") {
1066
+ return String(value);
1067
+ }
1068
+ const ctor = t === "object" && value !== null ? value.constructor?.name ?? "Object" : t;
1069
+ return `[non-Error ${ctor}]`;
1070
+ }
1071
+ function describeError(error, context, options = {}) {
1072
+ return describeErrorAt(error, context, options, 0);
1073
+ }
1074
+ function reportError(sink, error, context, options = {}) {
1075
+ const report = describeError(error, context, options);
1076
+ reportDiagnostic(sink, "error", report.message, report.context);
1077
+ return report;
1078
+ }
832
1079
 
833
1080
  // ../graph/src/graph-node.ts
834
1081
  var GraphNode = class _GraphNode {
@@ -999,6 +1246,7 @@ var ApplicationGraph = class {
999
1246
  this.handlers.delete(`__overlay__${node.id}`);
1000
1247
  this.handlers.delete(`__component__${node.id}`);
1001
1248
  this.handlers.delete(`__transition__${node.id}`);
1249
+ this.handlers.delete(`__head__${node.id}`);
1002
1250
  }
1003
1251
  // ── Handler registry ──────────────────────────────────────────────────────
1004
1252
  registerHandler(key, fn) {
@@ -1128,6 +1376,121 @@ function isTransitionConfig(value) {
1128
1376
  return typeof o["name"] === "string" || typeof o["enter"] === "string" || typeof o["enterActive"] === "string" || typeof o["enterFrom"] === "string" || typeof o["leave"] === "string" || typeof o["leaveActive"] === "string" || typeof o["leaveFrom"] === "string";
1129
1377
  }
1130
1378
 
1379
+ // ../dsl/src/head.ts
1380
+ function metaDedupKey(m) {
1381
+ if (m.charset !== void 0) return "meta:charset";
1382
+ if (m.name !== void 0) return `meta:name=${m.name}`;
1383
+ if (m.property !== void 0) return `meta:property=${m.property}`;
1384
+ if (m.httpEquiv !== void 0) return `meta:http-equiv=${m.httpEquiv}`;
1385
+ return void 0;
1386
+ }
1387
+ function metaAttrs(m) {
1388
+ const attrs = {};
1389
+ if (m.charset !== void 0) attrs["charset"] = m.charset;
1390
+ if (m.name !== void 0) attrs["name"] = m.name;
1391
+ if (m.property !== void 0) attrs["property"] = m.property;
1392
+ if (m.httpEquiv !== void 0) attrs["http-equiv"] = m.httpEquiv;
1393
+ if (m.content !== void 0) attrs["content"] = m.content;
1394
+ return attrs;
1395
+ }
1396
+ function linkAttrs(l) {
1397
+ const attrs = { rel: l.rel, href: l.href };
1398
+ if (l.sizes !== void 0) attrs["sizes"] = l.sizes;
1399
+ if (l.type !== void 0) attrs["type"] = l.type;
1400
+ if (l.media !== void 0) attrs["media"] = l.media;
1401
+ if (l.as !== void 0) attrs["as"] = l.as;
1402
+ if (l.crossorigin !== void 0) attrs["crossorigin"] = l.crossorigin;
1403
+ if (l.hreflang !== void 0) attrs["hreflang"] = l.hreflang;
1404
+ return attrs;
1405
+ }
1406
+ function resolveHead(config) {
1407
+ const entries = [];
1408
+ if (config.charset !== void 0) {
1409
+ entries.push({ tag: "meta", dedupKey: "meta:charset", attrs: { charset: config.charset } });
1410
+ }
1411
+ if (config.base !== void 0) {
1412
+ entries.push({ tag: "base", dedupKey: "base", attrs: { href: config.base } });
1413
+ }
1414
+ if (config.title !== void 0) {
1415
+ entries.push({ tag: "title", dedupKey: "title", attrs: {}, text: config.title });
1416
+ }
1417
+ if (config.description !== void 0) {
1418
+ entries.push({
1419
+ tag: "meta",
1420
+ dedupKey: "meta:name=description",
1421
+ attrs: { name: "description", content: config.description }
1422
+ });
1423
+ }
1424
+ if (config.canonical !== void 0) {
1425
+ entries.push({
1426
+ tag: "link",
1427
+ dedupKey: "link:rel=canonical",
1428
+ attrs: { rel: "canonical", href: config.canonical }
1429
+ });
1430
+ }
1431
+ if (config.robots !== void 0) {
1432
+ entries.push({
1433
+ tag: "meta",
1434
+ dedupKey: "meta:name=robots",
1435
+ attrs: { name: "robots", content: config.robots }
1436
+ });
1437
+ }
1438
+ if (config.themeColor !== void 0) {
1439
+ entries.push({
1440
+ tag: "meta",
1441
+ dedupKey: "meta:name=theme-color",
1442
+ attrs: { name: "theme-color", content: config.themeColor }
1443
+ });
1444
+ }
1445
+ if (config.viewport !== void 0) {
1446
+ entries.push({
1447
+ tag: "meta",
1448
+ dedupKey: "meta:name=viewport",
1449
+ attrs: { name: "viewport", content: config.viewport }
1450
+ });
1451
+ }
1452
+ if (config.favicon !== void 0) {
1453
+ const l = typeof config.favicon === "string" ? { rel: "icon", href: config.favicon } : config.favicon;
1454
+ entries.push({ tag: "link", dedupKey: `link:rel=${l.rel}`, attrs: linkAttrs(l) });
1455
+ }
1456
+ if (config.openGraph !== void 0) {
1457
+ for (const key of Object.keys(config.openGraph)) {
1458
+ const property = `og:${key}`;
1459
+ entries.push({
1460
+ tag: "meta",
1461
+ dedupKey: `meta:property=${property}`,
1462
+ attrs: { property, content: config.openGraph[key] }
1463
+ });
1464
+ }
1465
+ }
1466
+ if (config.twitter !== void 0) {
1467
+ for (const key of Object.keys(config.twitter)) {
1468
+ const name = `twitter:${key}`;
1469
+ entries.push({
1470
+ tag: "meta",
1471
+ dedupKey: `meta:name=${name}`,
1472
+ attrs: { name, content: config.twitter[key] }
1473
+ });
1474
+ }
1475
+ }
1476
+ if (config.meta !== void 0) {
1477
+ for (const m of config.meta) {
1478
+ const key = metaDedupKey(m);
1479
+ if (key === void 0) continue;
1480
+ entries.push({ tag: "meta", dedupKey: key, attrs: metaAttrs(m) });
1481
+ }
1482
+ }
1483
+ if (config.link !== void 0) {
1484
+ for (const l of config.link) {
1485
+ entries.push({ tag: "link", dedupKey: `link:rel=${l.rel}:href=${l.href}`, attrs: linkAttrs(l) });
1486
+ }
1487
+ }
1488
+ return { entries };
1489
+ }
1490
+ function isHeadContribution(value) {
1491
+ return value !== null && typeof value === "object" && Array.isArray(value["entries"]);
1492
+ }
1493
+
1131
1494
  // ../dsl/src/builders.ts
1132
1495
  function isSignal(v) {
1133
1496
  return v !== null && typeof v === "object" && typeof v["get"] === "function" && typeof v["subscribe"] === "function";
@@ -1509,8 +1872,14 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
1509
1872
  this.container(id, (c) => {
1510
1873
  c.when(
1511
1874
  hasError,
1512
- // Error state → fallback.
1513
- (fb) => options.fallback(fb, readError(), retry),
1875
+ // Error state → fallback. The fallback branch mounts exactly when the
1876
+ // boundary enters its error state, so this is also where the optional
1877
+ // `onError` reporting hook fires (observe-only; §6/§7).
1878
+ (fb) => {
1879
+ const currentError = readError();
1880
+ options.onError?.(currentError);
1881
+ options.fallback(fb, currentError, retry);
1882
+ },
1514
1883
  // Healthy state → body, guarded against synchronous build throws.
1515
1884
  (body) => {
1516
1885
  try {
@@ -1522,6 +1891,24 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
1522
1891
  );
1523
1892
  }, { id });
1524
1893
  }
1894
+ asyncBoundary(key, res, branches) {
1895
+ const isError = derived(() => res.status.get() === "error");
1896
+ const showSuccess = derived(
1897
+ () => res.status.get() !== "error" && res.data.get() !== void 0
1898
+ );
1899
+ const showLoading = derived(
1900
+ () => res.status.get() !== "error" && res.data.get() === void 0
1901
+ );
1902
+ const dataSignal = derived(() => res.data.get());
1903
+ const retry = () => {
1904
+ void res.refetch();
1905
+ };
1906
+ this.container(key, (c) => {
1907
+ c.when(isError, (fb) => branches.error?.(fb, res.error.peek(), retry));
1908
+ c.when(showSuccess, (sb) => branches.success(sb, dataSignal));
1909
+ c.when(showLoading, (lb) => branches.loading?.(lb));
1910
+ }, { key });
1911
+ }
1525
1912
  // ── Portals & overlays ──────────────────────────────────────────────────────
1526
1913
  portal(key, builder, options = {}) {
1527
1914
  const node = this._graph.createNode("portal", {
@@ -1531,6 +1918,31 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
1531
1918
  });
1532
1919
  builder(new ContainerBuilderImpl(node, this._graph));
1533
1920
  }
1921
+ /**
1922
+ * Declare document metadata (2.0 §1–§3). Creates a `'head'` node — a neutral,
1923
+ * empty inline anchor at this position (one node / one element, so positional
1924
+ * hydration is preserved) — and registers a `__head__<id>` descriptor holding
1925
+ * this call's normalized, dedup-keyed {@link resolveHead} contribution. The
1926
+ * renderer applies it to `document.head` on the browser (adopting server tags
1927
+ * on hydration, cleaning up on unmount / route change) and emits the active
1928
+ * graph's merged metadata as a string on the server (`renderHead`).
1929
+ *
1930
+ * Multiple `head()` nodes may be live at once (app default + route + component)
1931
+ * — the renderer merges them and, per dedup key, the last in document order
1932
+ * wins (see head.ts). No new render path: this reuses the same graph-node +
1933
+ * handler-registry convention as overlays/transitions/components.
1934
+ */
1935
+ head(metadata) {
1936
+ const node = this._graph.createNode("head", {
1937
+ parent: this._node,
1938
+ props: { "data-streetui-head-anchor": "" }
1939
+ });
1940
+ const contribution = resolveHead(metadata);
1941
+ this._graph.registerHandler(
1942
+ `__head__${node.id}`,
1943
+ () => contribution
1944
+ );
1945
+ }
1534
1946
  /**
1535
1947
  * Shared assembly for every overlay kind: a `portal` node whose single child
1536
1948
  * is a `when(open, panel)` conditional. The panel container carries the
@@ -1902,8 +2314,8 @@ var RuntimeNodeInstance = class {
1902
2314
  this.children.push(instance);
1903
2315
  }
1904
2316
  /** Subscribe to a signal and register the unsubscribe for cleanup. */
1905
- trackSignal(signal2, handler) {
1906
- const unsub = signal2.subscribe(handler);
2317
+ trackSignal(signal3, handler) {
2318
+ const unsub = signal3.subscribe(handler);
1907
2319
  this.cleanup.add(unsub);
1908
2320
  }
1909
2321
  /** Register an arbitrary cleanup function (e.g. DOM event removal). */
@@ -2274,6 +2686,9 @@ var BrowserDOMAdapter = class {
2274
2686
  body() {
2275
2687
  return document.body ?? null;
2276
2688
  }
2689
+ head() {
2690
+ return document.head ?? null;
2691
+ }
2277
2692
  activeElement() {
2278
2693
  return document.activeElement ?? null;
2279
2694
  }
@@ -2653,6 +3068,9 @@ var ServerDOMAdapter = class {
2653
3068
  body() {
2654
3069
  return null;
2655
3070
  }
3071
+ head() {
3072
+ return null;
3073
+ }
2656
3074
  activeElement() {
2657
3075
  return null;
2658
3076
  }
@@ -3035,7 +3453,13 @@ var TAG_MAP = {
3035
3453
  // its children are relocated to a document.body container on the browser
3036
3454
  // (see the portal branch in mount.ts). On the server (no body) it renders
3037
3455
  // inline, so the anchor tag is what SSR/hydration positionally match on.
3038
- portal: "div"
3456
+ portal: "div",
3457
+ // A `head()` node renders as a neutral, empty inline anchor <div> at its
3458
+ // declaration site (like a portal anchor). Its actual contribution — title/
3459
+ // meta/link/etc. — is applied to `document.head` by `wireHeadBehavior` on the
3460
+ // browser, and emitted separately by `renderHead()` on the server. Keeping a
3461
+ // one-node/one-element anchor preserves positional hydration.
3462
+ head: "div"
3039
3463
  };
3040
3464
  function resolveTag(type) {
3041
3465
  return TAG_MAP[type] ?? "div";
@@ -3491,6 +3915,184 @@ var TransitionController = class {
3491
3915
  }
3492
3916
  };
3493
3917
 
3918
+ // ../renderer/src/head.ts
3919
+ var HEAD_MARKER = "data-streetui-head";
3920
+ var HEAD_KEY = "data-streetui-head-key";
3921
+ function isSignalLike(v) {
3922
+ return v !== null && typeof v === "object" && typeof v["subscribe"] === "function" && typeof v["peek"] === "function";
3923
+ }
3924
+ function readValue(v) {
3925
+ if (isSignalLike(v)) return String(v.peek() ?? "");
3926
+ return String(v ?? "");
3927
+ }
3928
+ function resolveEntry(entry) {
3929
+ const attrs = {};
3930
+ for (const key of Object.keys(entry.attrs)) {
3931
+ attrs[key] = readValue(entry.attrs[key]);
3932
+ }
3933
+ const resolved = { tag: entry.tag, attrs };
3934
+ if (entry.tag === "title") {
3935
+ return { ...resolved, text: readValue(entry.text) };
3936
+ }
3937
+ return resolved;
3938
+ }
3939
+ var HeadManager = class {
3940
+ _dom;
3941
+ _head;
3942
+ _contributions = /* @__PURE__ */ new Map();
3943
+ _applied = /* @__PURE__ */ new Map();
3944
+ _order = 0;
3945
+ _adopted = false;
3946
+ constructor(dom, head) {
3947
+ this._dom = dom;
3948
+ this._head = head;
3949
+ }
3950
+ /** Register (or replace) a node's contribution and re-apply the merged result. */
3951
+ register(nodeId, entries) {
3952
+ this._contributions.set(nodeId, { order: this._order++, entries });
3953
+ this.apply();
3954
+ }
3955
+ /** Withdraw a node's contribution (unmount / route change) and re-apply. */
3956
+ unregister(nodeId) {
3957
+ if (this._contributions.delete(nodeId)) this.apply();
3958
+ }
3959
+ /** Recompute the merged head and patch `document.head` to match. */
3960
+ apply() {
3961
+ if (!this._adopted) {
3962
+ this._adoptServerTags();
3963
+ this._adopted = true;
3964
+ }
3965
+ const ordered = [...this._contributions.values()].sort((a, b) => a.order - b.order);
3966
+ const merged = /* @__PURE__ */ new Map();
3967
+ for (const contribution of ordered) {
3968
+ for (const entry of contribution.entries) {
3969
+ merged.set(entry.dedupKey, resolveEntry(entry));
3970
+ }
3971
+ }
3972
+ for (const [key, desired] of merged) {
3973
+ const existing = this._applied.get(key);
3974
+ if (existing !== void 0 && existing.tag === desired.tag) {
3975
+ this._reconcileAttrs(existing, desired);
3976
+ } else {
3977
+ if (existing !== void 0) {
3978
+ this._dom.removeChild(this._head, existing.el);
3979
+ this._applied.delete(key);
3980
+ }
3981
+ const el = this._createTag(key, desired);
3982
+ this._dom.appendChild(this._head, el);
3983
+ this._applied.set(key, { el, attrKeys: new Set(Object.keys(desired.attrs)), tag: desired.tag });
3984
+ }
3985
+ }
3986
+ for (const [key, record] of [...this._applied]) {
3987
+ if (!merged.has(key)) {
3988
+ this._dom.removeChild(this._head, record.el);
3989
+ this._applied.delete(key);
3990
+ }
3991
+ }
3992
+ }
3993
+ _createTag(key, desired) {
3994
+ const el = this._dom.createElement(desired.tag);
3995
+ this._dom.setAttribute(el, HEAD_MARKER, "");
3996
+ this._dom.setAttribute(el, HEAD_KEY, key);
3997
+ for (const attr of Object.keys(desired.attrs)) {
3998
+ this._dom.setAttribute(el, attr, desired.attrs[attr]);
3999
+ }
4000
+ if (desired.tag === "title") this._dom.setTextContent(el, desired.text ?? "");
4001
+ return el;
4002
+ }
4003
+ _reconcileAttrs(record, desired) {
4004
+ const nextKeys = new Set(Object.keys(desired.attrs));
4005
+ for (const attr of record.attrKeys) {
4006
+ if (!nextKeys.has(attr)) this._dom.removeAttribute(record.el, attr);
4007
+ }
4008
+ for (const attr of nextKeys) {
4009
+ this._dom.setAttribute(record.el, attr, desired.attrs[attr]);
4010
+ }
4011
+ if (desired.tag === "title") this._dom.setTextContent(record.el, desired.text ?? "");
4012
+ record.attrKeys = nextKeys;
4013
+ }
4014
+ /**
4015
+ * Seed `_applied` from server-emitted `[data-streetui-head-key]` tags already
4016
+ * in `document.head`. The subsequent diff reuses these elements when the
4017
+ * client desires the same key (no duplicate), rewrites them if the value
4018
+ * changed, or removes them if the client graph no longer wants them.
4019
+ */
4020
+ _adoptServerTags() {
4021
+ for (const child of this._dom.childNodes(this._head)) {
4022
+ if (!this._dom.isElement(child)) continue;
4023
+ const el = child;
4024
+ const key = this._dom.getAttribute(el, HEAD_KEY);
4025
+ if (key === null) continue;
4026
+ this._applied.set(key, {
4027
+ el,
4028
+ attrKeys: new Set(this._attrNames(el)),
4029
+ tag: this._dom.tagName(el)
4030
+ });
4031
+ }
4032
+ }
4033
+ /** The framework-managed attribute names currently on a server tag. */
4034
+ _attrNames(el) {
4035
+ const names = [];
4036
+ if (this._dom.getAttribute(el, HEAD_MARKER) !== null) names.push(HEAD_MARKER);
4037
+ if (this._dom.getAttribute(el, HEAD_KEY) !== null) names.push(HEAD_KEY);
4038
+ return names;
4039
+ }
4040
+ };
4041
+ function getHeadManager(ctx) {
4042
+ const head = ctx.dom.head();
4043
+ if (head === null) return null;
4044
+ const mutable = ctx;
4045
+ if (mutable.head === void 0) mutable.head = new HeadManager(ctx.dom, head);
4046
+ return mutable.head;
4047
+ }
4048
+ function wireHeadBehavior(ctx, graphNode, instance) {
4049
+ const manager = getHeadManager(ctx);
4050
+ if (manager === null) return;
4051
+ const descFn = ctx.graph.getHandler(`__head__${graphNode.id}`);
4052
+ if (descFn === void 0) return;
4053
+ const contribution = descFn();
4054
+ const nodeId = graphNode.id;
4055
+ manager.register(nodeId, contribution.entries);
4056
+ for (const entry of contribution.entries) {
4057
+ for (const attrKey of Object.keys(entry.attrs)) {
4058
+ const v = entry.attrs[attrKey];
4059
+ if (isSignalLike(v)) {
4060
+ instance.trackCleanup(v.subscribe(() => manager.apply()));
4061
+ }
4062
+ }
4063
+ if (isSignalLike(entry.text)) {
4064
+ instance.trackCleanup(entry.text.subscribe(() => manager.apply()));
4065
+ }
4066
+ }
4067
+ instance.trackCleanup(() => manager.unregister(nodeId));
4068
+ }
4069
+ function renderHead(compiled) {
4070
+ const graph = compiled.graph;
4071
+ const merged = /* @__PURE__ */ new Map();
4072
+ graph.walk((node) => {
4073
+ if (node.type !== "head") return;
4074
+ const descFn = graph.getHandler(`__head__${node.id}`);
4075
+ if (descFn === void 0) return;
4076
+ for (const entry of descFn().entries) {
4077
+ merged.set(entry.dedupKey, resolveEntry(entry));
4078
+ }
4079
+ });
4080
+ if (merged.size === 0) return "";
4081
+ const dom = new ServerDOMAdapter();
4082
+ let out = "";
4083
+ for (const [key, desired] of merged) {
4084
+ const el = dom.createElement(desired.tag);
4085
+ dom.setAttribute(el, HEAD_MARKER, "");
4086
+ dom.setAttribute(el, HEAD_KEY, key);
4087
+ for (const attr of Object.keys(desired.attrs)) {
4088
+ dom.setAttribute(el, attr, desired.attrs[attr]);
4089
+ }
4090
+ if (desired.tag === "title") dom.setTextContent(el, desired.text ?? "");
4091
+ out += dom.serializeOuter(el);
4092
+ }
4093
+ return out;
4094
+ }
4095
+
3494
4096
  // ../renderer/src/mount.ts
3495
4097
  var SKIP_PROP_KEYS = /* @__PURE__ */ new Set([
3496
4098
  "text",
@@ -3613,6 +4215,9 @@ function mountNode(ctx, graphNode, parentDom) {
3613
4215
  const instance2 = new NodeInstance(graphNode, el2);
3614
4216
  ctx.instances.set(graphNode.id, instance2);
3615
4217
  wireEvents(dom, graph, graphNode, el2, instance2);
4218
+ if (graphNode.stateRefs.length !== 0) {
4219
+ wireSignalBindings(ctx, graphNode, instance2, linkUpdate(dom, el2));
4220
+ }
3616
4221
  dom.appendChild(parentDom, el2);
3617
4222
  return instance2;
3618
4223
  }
@@ -3668,6 +4273,16 @@ function mountNode(ctx, graphNode, parentDom) {
3668
4273
  wireOverlayBehavior(ctx, graphNode, instance2, target);
3669
4274
  return instance2;
3670
4275
  }
4276
+ if (graphNode.type === "head") {
4277
+ const anchor = dom.createElement(resolveTag("head"));
4278
+ dom.setAttribute(anchor, "data-streetui-head-anchor", "");
4279
+ applyNodeProps(ctx, graphNode, anchor);
4280
+ const instance2 = new NodeInstance(graphNode, anchor);
4281
+ ctx.instances.set(graphNode.id, instance2);
4282
+ dom.appendChild(parentDom, anchor);
4283
+ wireHeadBehavior(ctx, graphNode, instance2);
4284
+ return instance2;
4285
+ }
3671
4286
  const tag = resolveTag(graphNode.type);
3672
4287
  const el = dom.createElement(tag);
3673
4288
  applyNodeProps(ctx, graphNode, el);
@@ -3732,6 +4347,17 @@ function buttonUpdate(dom, el) {
3732
4347
  }
3733
4348
  };
3734
4349
  }
4350
+ function linkUpdate(dom, el) {
4351
+ return (propKey, value) => {
4352
+ if (propKey === "label") {
4353
+ dom.setTextContent(el, String(value ?? ""));
4354
+ } else if (propKey === "href") {
4355
+ dom.setAttribute(el, "href", String(value ?? ""));
4356
+ } else {
4357
+ applyProp(dom, el, propKey, value);
4358
+ }
4359
+ };
4360
+ }
3735
4361
  function applyNodeProps(ctx, graphNode, el) {
3736
4362
  const props = graphNode.props;
3737
4363
  for (const key in props) {
@@ -3966,7 +4592,12 @@ function hydrateNode(ctx, graphNode, domNode, path) {
3966
4592
  case "link": {
3967
4593
  const instance = new NodeInstance(graphNode, domNode);
3968
4594
  ctx.instances.set(graphNode.id, instance);
3969
- if (graphNode.type === "link") wireEvents(dom, graph, graphNode, domNode, instance);
4595
+ if (graphNode.type === "link") {
4596
+ wireEvents(dom, graph, graphNode, domNode, instance);
4597
+ if (graphNode.stateRefs.length !== 0) {
4598
+ wireSignalBindings(ctx, graphNode, instance, linkUpdate(dom, domNode));
4599
+ }
4600
+ }
3970
4601
  return instance;
3971
4602
  }
3972
4603
  case "reactive-list":
@@ -3996,6 +4627,12 @@ function hydrateNode(ctx, graphNode, domNode, path) {
3996
4627
  wireOverlayBehavior(ctx, graphNode, instance, target);
3997
4628
  return instance;
3998
4629
  }
4630
+ case "head": {
4631
+ const instance = new NodeInstance(graphNode, domNode);
4632
+ ctx.instances.set(graphNode.id, instance);
4633
+ wireHeadBehavior(ctx, graphNode, instance);
4634
+ return instance;
4635
+ }
3999
4636
  default: {
4000
4637
  const instance = new NodeInstance(graphNode, domNode);
4001
4638
  ctx.instances.set(graphNode.id, instance);
@@ -4242,7 +4879,8 @@ function analyzeGraph(graph) {
4242
4879
  const isConditional = node.type === "conditional";
4243
4880
  const isPortal = node.type === "portal";
4244
4881
  const isComponent = node.type === "component";
4245
- const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal && !isComponent;
4882
+ const isHead = node.type === "head";
4883
+ const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal && !isComponent && !isHead;
4246
4884
  const isStaticSubtree = isStatic && allChildrenStatic;
4247
4885
  nodes.set(node.id, {
4248
4886
  isStatic,
@@ -5304,11 +5942,22 @@ function capture(compiled, sources, options) {
5304
5942
  snapshot: app.perf,
5305
5943
  diagnostics: diagnosePerformance(compiled, options.perfThresholds)
5306
5944
  };
5945
+ const components = inspectComponents(compiled.graph);
5946
+ const interactions = inspectInteractions(compiled.graph);
5947
+ const diagnostics = {
5948
+ errors: app.diagnostics.errors,
5949
+ warnings: app.diagnostics.warnings,
5950
+ messages: app.diagnostics.messages
5951
+ };
5307
5952
  const snapshot = {
5308
5953
  application,
5309
5954
  graph: app.graph,
5310
5955
  signals,
5311
5956
  performance,
5957
+ components,
5958
+ overlays: interactions.overlays,
5959
+ transitions: interactions.transitions,
5960
+ diagnostics,
5312
5961
  ...sources.router !== void 0 ? { router: inspectRouter(sources.router) } : {},
5313
5962
  ...sources.resources !== void 0 ? { resources: mapInspect(sources.resources, (r) => inspectResource(r)) } : {},
5314
5963
  ...sources.forms !== void 0 ? { forms: mapInspect(sources.forms, (f) => inspectForm(f)) } : {},
@@ -5349,6 +5998,25 @@ function formatSnapshot(s) {
5349
5998
  for (const [label, sig] of Object.entries(s.signals.live)) {
5350
5999
  lines.push(` ${label} [${sig.kind}] = ${format(sig.value)} \xB7 observers ${sig.observerCount ?? "?"}`);
5351
6000
  }
6001
+ if (s.components.length > 0) {
6002
+ lines.push(`Components: ${s.components.length}`);
6003
+ for (const c of s.components) {
6004
+ lines.push(` ${" ".repeat(c.depth)}${c.name}${c.key !== void 0 ? ` (#${c.key})` : ""} \xB7 children ${c.childCount}`);
6005
+ }
6006
+ }
6007
+ if (s.overlays.length > 0) {
6008
+ lines.push(`Overlays: ${s.overlays.length}`);
6009
+ for (const o of s.overlays) {
6010
+ const kind = o.modal ? "modal" : o.menu ? "menu" : o.takesFocus ? "focusable" : "non-modal";
6011
+ lines.push(` ${o.key ?? o.id} [${kind}] ${o.open ? "open" : "closed"}`);
6012
+ }
6013
+ }
6014
+ if (s.transitions.length > 0) {
6015
+ lines.push(`Transitions: ${s.transitions.length}`);
6016
+ for (const t of s.transitions) {
6017
+ lines.push(` ${t.key ?? t.id} on <${t.nodeType}> \xB7 ${t.duration}ms${t.appear ? " \xB7 appear" : ""}`);
6018
+ }
6019
+ }
5352
6020
  if (s.router !== void 0) {
5353
6021
  lines.push(`Router: ${s.router.path} (${s.router.pattern})${s.router.isFallback ? " [fallback]" : ""}`);
5354
6022
  }
@@ -5382,6 +6050,12 @@ function formatSnapshot(s) {
5382
6050
  for (const d of s.performance.diagnostics) {
5383
6051
  lines.push(` ${d.code}: ${d.message}`);
5384
6052
  }
6053
+ lines.push(
6054
+ `Diagnostics: ${s.diagnostics.errors} error(s), ${s.diagnostics.warnings} warning(s)`
6055
+ );
6056
+ for (const m of s.diagnostics.messages) {
6057
+ lines.push(` ${m}`);
6058
+ }
5385
6059
  return lines.join("\n");
5386
6060
  }
5387
6061
  function format(value) {
@@ -5397,6 +6071,182 @@ function format(value) {
5397
6071
  return String(value);
5398
6072
  }
5399
6073
 
6074
+ // ../devtools/src/view.ts
6075
+ function escapeHtml(value) {
6076
+ const s = typeof value === "string" ? value : stringifyValue(value);
6077
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
6078
+ }
6079
+ function stringifyValue(value) {
6080
+ if (value === null || value === void 0) return String(value);
6081
+ if (typeof value === "object") {
6082
+ try {
6083
+ return JSON.stringify(value);
6084
+ } catch {
6085
+ return "[object]";
6086
+ }
6087
+ }
6088
+ return String(value);
6089
+ }
6090
+ function section(title, count, body) {
6091
+ const badge = count === void 0 ? "" : ` <span class="st-count">${count}</span>`;
6092
+ return `<section class="st-panel"><h2>${escapeHtml(title)}${badge}</h2>${body}</section>`;
6093
+ }
6094
+ function ul(items) {
6095
+ if (items.length === 0) return '<p class="st-empty">(none)</p>';
6096
+ return `<ul>${items.map((i) => `<li>${i}</li>`).join("")}</ul>`;
6097
+ }
6098
+ function renderDevToolsHTML(s) {
6099
+ const app = s.application;
6100
+ const parts = [];
6101
+ parts.push(
6102
+ section(
6103
+ "Application",
6104
+ void 0,
6105
+ `<p>${escapeHtml(app.identity.name)} <span class="st-dim">v${escapeHtml(app.identity.version)}</span></p><p class="st-dim">${app.nodeCount} nodes \xB7 depth ${app.maxDepth} \xB7 ${app.pages.length} page(s) \xB7 ${app.signalCount} signals \xB7 ${app.eventHandlers} handlers \xB7 ${app.stateBindings} bindings</p>`
6106
+ )
6107
+ );
6108
+ parts.push(
6109
+ section(
6110
+ "Components",
6111
+ s.components.length,
6112
+ ul(
6113
+ s.components.map(
6114
+ (c) => `<span class="st-depth" style="--d:${c.depth}"></span><code>${escapeHtml(c.name)}</code>` + (c.key !== void 0 ? ` <span class="st-key">#${escapeHtml(c.key)}</span>` : "") + ` <span class="st-dim">${c.childCount} child(ren)</span>`
6115
+ )
6116
+ )
6117
+ )
6118
+ );
6119
+ parts.push(section("Graph", void 0, `<pre class="st-tree">${escapeHtml(renderNodeTree(s.graph))}</pre>`));
6120
+ parts.push(
6121
+ section(
6122
+ "Signals",
6123
+ s.signals.boundSignalIds.length,
6124
+ `<p class="st-dim">${s.signals.boundSignalIds.length} bound in graph \xB7 effects shown as observer counts (no global effect registry)</p>` + ul(
6125
+ Object.entries(s.signals.live).map(
6126
+ ([label, sig]) => `<code>${escapeHtml(label)}</code> <span class="st-key">[${escapeHtml(sig.kind)}]</span> = <code>${escapeHtml(sig.value)}</code> <span class="st-dim">observers ${escapeHtml(sig.observerCount ?? "?")}</span>`
6127
+ )
6128
+ )
6129
+ )
6130
+ );
6131
+ if (s.router !== void 0) {
6132
+ parts.push(
6133
+ section(
6134
+ "Router",
6135
+ void 0,
6136
+ `<p><code>${escapeHtml(s.router.path)}</code> <span class="st-dim">(${escapeHtml(s.router.pattern)})${s.router.isFallback ? " \xB7 fallback" : ""}</span></p>`
6137
+ )
6138
+ );
6139
+ }
6140
+ if (s.resources !== void 0) {
6141
+ parts.push(
6142
+ section(
6143
+ "Resources",
6144
+ Object.keys(s.resources).length,
6145
+ ul(
6146
+ Object.entries(s.resources).map(
6147
+ ([label, r]) => `<code>${escapeHtml(label)}</code>: ${escapeHtml(r.status)}` + (r.loading ? ' <span class="st-dim">(loading)</span>' : "") + (r.hasError ? ` <span class="st-err">!${escapeHtml(r.errorName)}</span>` : "")
6148
+ )
6149
+ )
6150
+ )
6151
+ );
6152
+ }
6153
+ parts.push(
6154
+ section(
6155
+ "Overlays",
6156
+ s.overlays.length,
6157
+ ul(
6158
+ s.overlays.map((o) => {
6159
+ const kind = o.modal ? "modal" : o.menu ? "menu" : o.takesFocus ? "focusable" : "non-modal";
6160
+ return `<code>${escapeHtml(o.key ?? o.id)}</code> <span class="st-key">[${kind}]</span> <span class="st-dim">${o.open ? "open" : "closed"}${o.closeOnEscape ? " \xB7 esc" : ""}${o.restoreFocus ? " \xB7 restore" : ""}</span>`;
6161
+ })
6162
+ )
6163
+ )
6164
+ );
6165
+ parts.push(
6166
+ section(
6167
+ "Transitions",
6168
+ s.transitions.length,
6169
+ ul(
6170
+ s.transitions.map(
6171
+ (t) => `<code>${escapeHtml(t.key ?? t.id)}</code> on <code>&lt;${escapeHtml(t.nodeType)}&gt;</code> <span class="st-dim">${t.duration}ms${t.appear ? " \xB7 appear" : ""}</span>`
6172
+ )
6173
+ )
6174
+ )
6175
+ );
6176
+ if (s.forms !== void 0) {
6177
+ parts.push(
6178
+ section(
6179
+ "Forms",
6180
+ Object.keys(s.forms).length,
6181
+ ul(
6182
+ Object.entries(s.forms).map(
6183
+ ([label, f]) => `<code>${escapeHtml(label)}</code>: ${f.valid ? "valid" : "invalid"} \xB7 ${escapeHtml(f.status)} \xB7 ${f.fields.length} field(s)`
6184
+ )
6185
+ )
6186
+ )
6187
+ );
6188
+ }
6189
+ if (s.i18n !== void 0) {
6190
+ const missing = s.i18n.missingKeys;
6191
+ parts.push(
6192
+ section(
6193
+ "i18n",
6194
+ void 0,
6195
+ `<p><code>${escapeHtml(s.i18n.locale)}</code> of [${s.i18n.locales.map(escapeHtml).join(", ")}]${missing !== void 0 ? ` <span class="st-dim">\xB7 missing ${missing.length}</span>` : ""}</p>`
6196
+ )
6197
+ );
6198
+ }
6199
+ parts.push(
6200
+ section(
6201
+ "Performance",
6202
+ s.performance.diagnostics.length,
6203
+ `<p class="st-dim">Structural counts, not runtime timings \u2014 not a production profiler.</p>` + ul(s.performance.diagnostics.map((d) => `<code>${escapeHtml(d.code)}</code>: ${escapeHtml(d.message)}`))
6204
+ )
6205
+ );
6206
+ parts.push(
6207
+ section(
6208
+ "Diagnostics",
6209
+ s.diagnostics.errors + s.diagnostics.warnings,
6210
+ `<p class="st-dim">${s.diagnostics.errors} error(s) \xB7 ${s.diagnostics.warnings} warning(s)</p>` + ul(s.diagnostics.messages.map((m) => escapeHtml(m)))
6211
+ )
6212
+ );
6213
+ const title = `StreetUI DevTools \u2014 ${escapeHtml(app.identity.name)}`;
6214
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${title}</title><style>${DEVTOOLS_CSS}</style></head><body class="st-devtools"><header class="st-header"><h1>${title} <span class="st-dim">v${escapeHtml(app.identity.version)}</span></h1></header><main>${parts.join("")}</main></body></html>`;
6215
+ }
6216
+ function renderNodeTree(node) {
6217
+ const lines = [];
6218
+ const walk = (n) => {
6219
+ const indent = " ".repeat(n.depth);
6220
+ const key = n.key !== void 0 ? ` #${n.key}` : "";
6221
+ lines.push(`${indent}<${n.type}${key}> ${n.id}`);
6222
+ for (const c of n.children) walk(c);
6223
+ };
6224
+ walk(node);
6225
+ return lines.join("\n");
6226
+ }
6227
+ var DEVTOOLS_CSS = [
6228
+ ".st-devtools{font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;margin:0;color:#e6e6e6;background:#1e1e28}",
6229
+ ".st-header{padding:12px 16px;border-bottom:1px solid #333;background:#15151c}",
6230
+ ".st-header h1{font-size:14px;margin:0}",
6231
+ "main{padding:8px 16px}",
6232
+ ".st-panel{margin:12px 0;border:1px solid #2c2c38;border-radius:6px;overflow:hidden}",
6233
+ ".st-panel h2{font-size:12px;text-transform:uppercase;letter-spacing:.04em;margin:0;padding:6px 10px;background:#23232e}",
6234
+ ".st-panel ul{list-style:none;margin:0;padding:6px 10px}",
6235
+ ".st-panel li{padding:1px 0}",
6236
+ ".st-panel p{margin:6px 10px}",
6237
+ ".st-count{background:#3a3a4a;border-radius:10px;padding:0 7px;font-size:11px;float:right}",
6238
+ ".st-dim{color:#8a8a9a}.st-key{color:#7db4ff}.st-err{color:#ff8a8a}.st-empty{color:#6a6a7a}",
6239
+ ".st-tree{margin:6px 10px;white-space:pre;overflow:auto;color:#c8c8d4}",
6240
+ ".st-depth{display:inline-block}.st-depth{width:calc(var(--d,0)*12px)}",
6241
+ "code{color:#d7d7e0}"
6242
+ ].join("");
6243
+
6244
+ // ../devtools/src/devtools-report.ts
6245
+ function renderDevToolsReport(compiled, sources = {}, options = {}) {
6246
+ const session = createDevTools(compiled, sources, options);
6247
+ return renderDevToolsHTML(session.snapshot);
6248
+ }
6249
+
5400
6250
  // src/config.ts
5401
6251
  function defineConfig(config) {
5402
6252
  return config;
@@ -5420,6 +6270,8 @@ function defineConfig(config) {
5420
6270
  FOCUSABLE_SELECTOR,
5421
6271
  FormBuilderImpl,
5422
6272
  GraphNode,
6273
+ HeadManager,
6274
+ HttpError,
5423
6275
  Lifecycle,
5424
6276
  ListBuilderImpl,
5425
6277
  NodeInstance,
@@ -5460,7 +6312,9 @@ function defineConfig(config) {
5460
6312
  containFocus,
5461
6313
  createAnnouncer,
5462
6314
  createApplication,
6315
+ createAuthSession,
5463
6316
  createBrowserHistory,
6317
+ createClient,
5464
6318
  createContext,
5465
6319
  createDevTools,
5466
6320
  createForm,
@@ -5476,10 +6330,12 @@ function defineConfig(config) {
5476
6330
  createStreetEvent,
5477
6331
  defineConfig,
5478
6332
  derived,
6333
+ describeError,
5479
6334
  diagnosePerformance,
5480
6335
  effect,
5481
6336
  email,
5482
6337
  environment,
6338
+ escapeHtml,
5483
6339
  escapeHtmlAttr,
5484
6340
  escapeHtmlText,
5485
6341
  flushSync,
@@ -5511,7 +6367,9 @@ function defineConfig(config) {
5511
6367
  interpolate,
5512
6368
  isBatching,
5513
6369
  isComponentDefinition,
6370
+ isHeadContribution,
5514
6371
  isTransitionConfig,
6372
+ linkUpdate,
5515
6373
  matchPattern,
5516
6374
  matchRoutes,
5517
6375
  maxLength,
@@ -5519,6 +6377,7 @@ function defineConfig(config) {
5519
6377
  mountGraph,
5520
6378
  mountNode,
5521
6379
  mountRouter,
6380
+ mutation,
5522
6381
  nextId,
5523
6382
  nodeIdPrefix,
5524
6383
  nodeTypeStats,
@@ -5535,10 +6394,15 @@ function defineConfig(config) {
5535
6394
  readState,
5536
6395
  reconcileChildren,
5537
6396
  reconcileChildrenByPlan,
6397
+ renderDevToolsHTML,
6398
+ renderDevToolsReport,
6399
+ renderHead,
5538
6400
  renderToString,
5539
6401
  reportDiagnostic,
6402
+ reportError,
5540
6403
  required,
5541
6404
  resetIdCounter,
6405
+ resolveHead,
5542
6406
  resolveTag,
5543
6407
  resolveTransition,
5544
6408
  resource,
@@ -5566,6 +6430,7 @@ function defineConfig(config) {
5566
6430
  validateGraph,
5567
6431
  wireComponentBehavior,
5568
6432
  wireEvents,
6433
+ wireHeadBehavior,
5569
6434
  wireOverlayBehavior,
5570
6435
  wireReactiveList,
5571
6436
  wireSignalBindings