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