@askrjs/askr 0.0.84 → 0.0.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/{access-ePqdQqkV.js → access-Dpfoi4cA.js} +7 -1
  2. package/dist/actions/index.d.ts +1 -1
  3. package/dist/actions/index.js +3 -3
  4. package/dist/{activity-pySy9t7Y.d.ts → activity-B1I5HJQI.d.ts} +1 -1
  5. package/dist/{activity-CZiPgZy0.js → activity-BvgvaMZA.js} +53 -16
  6. package/dist/benchmark.js +8 -8
  7. package/dist/boot/index.d.ts +3 -3
  8. package/dist/boot/index.js +1 -1
  9. package/dist/{boot-D28lesMw.js → boot-CViTpUiB.js} +22 -20
  10. package/dist/{cleanup-ARiXproi.js → cleanup-c837KVij.js} +16 -6
  11. package/dist/{component-cleanup-Bug5tSiG.js → component-cleanup-BUfUleGZ.js} +2 -2
  12. package/dist/{component-internal-Bw4Mf9fd.js → component-internal-DcrLymUJ.js} +5 -5
  13. package/dist/components/index.d.ts +1 -1
  14. package/dist/components/index.js +1 -1
  15. package/dist/control/index.d.ts +1 -1
  16. package/dist/control/index.js +1 -1
  17. package/dist/{control-Y8F73Uln.d.ts → control-DhUZU4_V.d.ts} +2 -1
  18. package/dist/{control-nrS65oG3.js → control-oLADku43.js} +4 -4
  19. package/dist/{csp-nonce-ZTlD_EnK.js → csp-nonce-Bc52OIVc.js} +19 -2
  20. package/dist/data/index.js +3 -3
  21. package/dist/{data-BRS5woEh.js → data-DGrD3-qA.js} +5 -5
  22. package/dist/{data-runtime-D68YOo9L.js → data-runtime-Ck3tJDDQ.js} +1 -1
  23. package/dist/default-portal-runtime-DvMvWJVp.js +20 -0
  24. package/dist/{authoring-DQPG0VAm.js → deferred-nleQtdfY.js} +85 -5
  25. package/dist/{effect-B487asyz.js → effect-lnxQIpJG.js} +2 -2
  26. package/dist/{fastlane-BOr4lSLl.js → fastlane-CrfB3QG0.js} +2 -2
  27. package/dist/{for-internal-DjpV8ioj.js → for-internal-B_mnQhan.js} +18 -6
  28. package/dist/foundations/index.d.ts +1 -1
  29. package/dist/foundations/index.js +1 -2
  30. package/dist/foundations/state/index.d.ts +1 -1
  31. package/dist/foundations/state/index.js +1 -1
  32. package/dist/foundations/structures/index.d.ts +2 -2
  33. package/dist/foundations/structures/index.js +2 -3
  34. package/dist/fx/index.js +1 -1
  35. package/dist/{index-BApH3laU.d.ts → index-DflhL1h8.d.ts} +20 -16
  36. package/dist/{index-_UN08Pg1.d.ts → index-pIgGaXvS.d.ts} +19 -3
  37. package/dist/{index-DUXXZ31H.d.ts → index-r-nt13FE.d.ts} +1 -1
  38. package/dist/index.d.ts +3 -3
  39. package/dist/index.js +12 -12
  40. package/dist/{lifecycle-batch-CFI4NKpJ.js → lifecycle-batch-Dfpid9-A.js} +39 -20
  41. package/dist/{lifecycle-operations-KYOIcM5R.js → lifecycle-operations-BrYrBsXY.js} +1 -1
  42. package/dist/manifest-D1P9uTlz.js +53 -0
  43. package/dist/{navigate-Dr3cjOSz.js → navigate-BwAPbpCb.js} +17 -16
  44. package/dist/{navigate-CgVIrrSs.d.ts → navigate-NyIjQUwI.d.ts} +1 -1
  45. package/dist/{query-registry-BL6rjGsO.js → query-registry-CPW_6nwF.js} +1 -1
  46. package/dist/{readable-_GQGe_St.js → readable-B5NtV7V0.js} +1 -1
  47. package/dist/render-context-D0Hoj9dW.js +161 -0
  48. package/dist/{renderer-B9j5IV1q.js → renderer-D9DL9R2D.js} +58 -17
  49. package/dist/{deferred-oqtAJ4iH.js → resolution-YHwi4Bav.js} +234 -222
  50. package/dist/resource-operation-ys6eDoRJ.js +304 -0
  51. package/dist/resources/index.d.ts +2 -2
  52. package/dist/resources/index.js +5 -5
  53. package/dist/{route-matching-IfQZmqkN.js → route-matching-DqULzZtC.js} +46 -277
  54. package/dist/router/index.d.ts +12 -10
  55. package/dist/router/index.js +10 -7
  56. package/dist/{selector-DJ1-N1K-.js → selector-Ck_Pp9av.js} +2 -2
  57. package/dist/ssg/index.d.ts +9 -3
  58. package/dist/ssg/index.js +31 -15
  59. package/dist/ssr/index.d.ts +7 -3
  60. package/dist/ssr/index.js +2 -2
  61. package/dist/{ssr-CtC4lChT.js → ssr-B98593qJ.js} +53 -27
  62. package/dist/ssr-DSHd_Q9K.js +25 -0
  63. package/dist/{state-BzIMC6-i.js → state-DCjONcHK.js} +4 -3
  64. package/dist/{portal-pHBoqwYd.js → structures-DsOm-clq.js} +236 -7
  65. package/dist/testing/index.d.ts +2 -2
  66. package/dist/testing/index.js +4 -4
  67. package/dist/{verify-hydration-BguOSco9.js → verify-hydration-ww1dgwea.js} +5 -4
  68. package/package.json +2 -2
  69. package/dist/render-context-D-DFk2WN.js +0 -73
  70. package/dist/ssr-CbQO5xd3.js +0 -12
  71. package/dist/structures-D3vO-WMN.js +0 -221
@@ -1,6 +1,6 @@
1
- import { f as DocumentRenderArgs, g as SSRStyleRegistration, h as SSRData, m as DocumentRenderer, p as DocumentRenderContext } from "../control-Y8F73Uln.js";
1
+ import { _ as SSRStyleRegistrationValidation, f as DocumentRenderArgs, g as SSRStyleRegistration, h as SSRData, m as DocumentRenderer, p as DocumentRenderContext } from "../control-DhUZU4_V.js";
2
2
  import { r as JSXElement, s as Props } from "../jsx-Dvr2SGRG.js";
3
- import { A as RouteHandler, C as Route, J as RouteRequestResult, V as RouteRecord, W as RouteRegistry, g as AccessRedirectDecision, l as DeferredBoundaryRegistration, m as AccessDenyDecision, u as SSRPortalState, w as RouteAuthOptions } from "../index-_UN08Pg1.js";
3
+ import { A as RouteHandler, C as Route, J as RouteRequestResult, V as RouteRecord, W as RouteRegistry, g as AccessRedirectDecision, l as DeferredBoundaryRegistration, m as AccessDenyDecision, u as SSRPortalState, w as RouteAuthOptions } from "../index-pIgGaXvS.js";
4
4
  import { D as QueryPrefetchContext, _ as CoreTelemetry, n as ServerQueryRegistry, v as DataRuntime } from "../index-5S-fi2xy.js";
5
5
  import { AuthContext } from "@askrjs/auth";
6
6
  //#region src/common/page-render-envelope.d.ts
@@ -29,6 +29,7 @@ interface RenderContext {
29
29
  params?: Record<string, string>;
30
30
  routes?: readonly Route[];
31
31
  routeAuth?: RouteAuthOptions;
32
+ basePath?: string;
32
33
  authContext?: AuthContext;
33
34
  signal?: AbortSignal;
34
35
  dataRuntime?: unknown;
@@ -51,6 +52,7 @@ declare function createRenderContext(seed?: number, opts?: {
51
52
  params?: Record<string, string>;
52
53
  routes?: readonly Route[];
53
54
  routeAuth?: RouteAuthOptions;
55
+ basePath?: string;
54
56
  signal?: AbortSignal;
55
57
  dataRuntime?: unknown;
56
58
  mode?: 'ssr' | 'spa';
@@ -92,6 +94,8 @@ type RouteRenderOptions = SSRRouteSource & {
92
94
  /** @internal Precomposed page state used by hydration verification. */
93
95
  envelope?: PageRenderEnvelope;
94
96
  cspNonce?: string;
97
+ /** Diagnose document renderers that omit request-local SSR style registrations. */
98
+ styleRegistrationValidation?: SSRStyleRegistrationValidation;
95
99
  };
96
100
  type RouteStreamOptions = RouteRenderOptions & {
97
101
  onChunk(html: string): void;
@@ -185,4 +189,4 @@ declare function renderToString(component: (props?: Record<string, unknown>) =>
185
189
  declare function renderToString(opts: RouteRenderOptions): string;
186
190
  declare function renderToStream(opts: RouteStreamOptions): void;
187
191
  //#endregion
188
- export { type DocumentRenderArgs, type DocumentRenderContext, type DocumentRenderer, type RenderRouteRequestOptions, type RenderRouteRequestResult, type SSRComponent, SSRDataMissingError, type SSRRoute, type SSRStyleRegistration, type VNode, createRenderContext, getRenderContext, renderResolvedToStringSync, renderRouteRequest, renderRouteRequestToString, renderToStream, renderToString, renderToStringSync, resolveRequest, withRenderContext, withRenderContextAsync };
192
+ export { type DocumentRenderArgs, type DocumentRenderContext, type DocumentRenderer, type RenderRouteRequestOptions, type RenderRouteRequestResult, type SSRComponent, SSRDataMissingError, type SSRRoute, type SSRStyleRegistration, type SSRStyleRegistrationValidation, type VNode, createRenderContext, getRenderContext, renderResolvedToStringSync, renderRouteRequest, renderRouteRequestToString, renderToStream, renderToString, renderToStringSync, resolveRequest, withRenderContext, withRenderContextAsync };
package/dist/ssr/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { d as SSRDataMissingError } from "../render-context-D-DFk2WN.js";
2
- import { a as renderResolvedToStringSync, c as createRenderContext, d as withRenderContextAsync, i as renderRouteRequestToString, l as getRenderContext, n as renderToString, o as renderToStringSync, r as renderRouteRequest, s as resolveRequest, t as renderToStream, u as withRenderContext } from "../ssr-CtC4lChT.js";
1
+ import { x as SSRDataMissingError } from "../render-context-D0Hoj9dW.js";
2
+ import { a as renderResolvedToStringSync, c as createRenderContext, d as withRenderContextAsync, i as renderRouteRequestToString, l as getRenderContext, n as renderToString, o as renderToStringSync, r as renderRouteRequest, s as resolveRequest, t as renderToStream, u as withRenderContext } from "../ssr-B98593qJ.js";
3
3
  export { SSRDataMissingError, createRenderContext, getRenderContext, renderResolvedToStringSync, renderRouteRequest, renderRouteRequestToString, renderToStream, renderToString, renderToStringSync, resolveRequest, withRenderContext, withRenderContextAsync };
@@ -1,23 +1,22 @@
1
1
  import { t as __CONTROL_BOUNDARY__ } from "./control-C-ApnFs1.js";
2
2
  import { t as __ERROR_BOUNDARY__ } from "./vnode-D24p6IW2.js";
3
- import { v as logger } from "./access-ePqdQqkV.js";
4
- import { i as isFragmentType, n as Fragment, t as ELEMENT_TYPE } from "./jsx-C3IECePB.js";
5
- import { f as getExecutionContextFrame, h as markVNodeTreeWithContextFrame, l as callWithContext, n as createComponentInstance, p as getVNodeContextFrame } from "./component-internal-Bw4Mf9fd.js";
3
+ import { v as logger } from "./access-Dpfoi4cA.js";
6
4
  import { f as getCurrentComponentInstance, w as setCurrentComponentInstance } from "./component-scope-DzJEbYGE.js";
7
- import { t as cleanupComponent } from "./component-cleanup-Bug5tSiG.js";
8
- import { p as isPromiseLike } from "./lifecycle-batch-CFI4NKpJ.js";
9
- import { _ as setControlBoundaryContextFrame, g as evaluateShowState, h as evaluateCaseState, i as evaluateForState, o as rollbackForStateTransaction, r as commitForStateTransaction } from "./for-internal-DjpV8ioj.js";
10
- import { _ as isPageRenderEnvelope, g as isEmptyPageRenderEnvelope, h as createPageRenderEnvelope, i as isDeferred, l as createRouteRegistry, m as resolveRouteRequest, t as DEFERRED_BOUNDARY } from "./deferred-oqtAJ4iH.js";
11
- import { d as SSRDataMissingError, l as stopRenderPhase$1, s as startRenderPhase$1, t as configureRenderContextProvider } from "./render-context-D-DFk2WN.js";
12
- import { a as resolveRouteFromRoutes, t as _resolveRouteMatchFromRoutes } from "./route-matching-IfQZmqkN.js";
13
- import { n as SSR_PORTAL_HOST, t as SSR_PORTAL_ANCHOR } from "./portal-w3oQ6oQU.js";
14
- import { a as disposeDefaultPortalScope, t as DefaultPortal } from "./portal-pHBoqwYd.js";
5
+ import { i as isFragmentType, n as Fragment, t as ELEMENT_TYPE } from "./jsx-C3IECePB.js";
6
+ import { f as getExecutionContextFrame, h as markVNodeTreeWithContextFrame, l as callWithContext, n as createComponentInstance, p as getVNodeContextFrame } from "./component-internal-DcrLymUJ.js";
7
+ import { t as cleanupComponent } from "./component-cleanup-BUfUleGZ.js";
8
+ import { p as isPromiseLike } from "./lifecycle-batch-Dfpid9-A.js";
9
+ import { _ as setControlBoundaryContextFrame, g as evaluateShowState, h as evaluateCaseState, i as evaluateForState, o as rollbackForStateTransaction, r as commitForStateTransaction } from "./for-internal-B_mnQhan.js";
10
+ import { _ as isPageRenderEnvelope, f as stopRenderPhase$1, g as isEmptyPageRenderEnvelope, h as createPageRenderEnvelope, t as configureRenderContextProvider, u as startRenderPhase$1, x as SSRDataMissingError } from "./render-context-D0Hoj9dW.js";
15
11
  import { n as getPublicAttributeName, t as sanitizeCssValue } from "./css-BuPaTOfe.js";
16
- import { a as isSafeHref, r as validateCspNonce, t as CspNonceScope } from "./csp-nonce-ZTlD_EnK.js";
17
- import { t as createDataRuntime } from "./data-runtime-D68YOo9L.js";
18
- import { c as withTelemetry, i as dehydrateDataRuntime, t as createQueryPrefetchContext } from "./query-registry-BL6rjGsO.js";
19
- import { a as route } from "./authoring-DQPG0VAm.js";
20
- import { n as renderDocument, t as SSR_RENDER_DATA_ATTR } from "./ssr-CbQO5xd3.js";
12
+ import { o as isUnsafeUrlAttribute, r as validateCspNonce, t as CspNonceScope } from "./csp-nonce-Bc52OIVc.js";
13
+ import { n as SSR_PORTAL_HOST, t as SSR_PORTAL_ANCHOR } from "./portal-w3oQ6oQU.js";
14
+ import { t as createDataRuntime } from "./data-runtime-Ck3tJDDQ.js";
15
+ import { c as withTelemetry, i as dehydrateDataRuntime, t as createQueryPrefetchContext } from "./query-registry-CPW_6nwF.js";
16
+ import { a as resolveRouteFromRoutes, c as removeRouteBasePath, s as normalizeRouteBasePath, t as _resolveRouteMatchFromRoutes } from "./route-matching-DqULzZtC.js";
17
+ import { a as resolveRouteRequest, d as DEFERRED_BOUNDARY, l as validateRouteHydrationData, o as getRouteHydrationMetadata, p as isDeferred } from "./resolution-YHwi4Bav.js";
18
+ import { n as disposeRegisteredDefaultPortalScope, r as getDefaultPortalHost } from "./default-portal-runtime-DvMvWJVp.js";
19
+ import { n as renderDocument, t as SSR_RENDER_DATA_ATTR } from "./ssr-DSHd_Q9K.js";
21
20
  //#region src/ssr/escape.ts
22
21
  /**
23
22
  * HTML escaping utilities for SSR
@@ -197,6 +196,7 @@ function createRenderContext(seed = 12345, opts = {}) {
197
196
  params: opts.params,
198
197
  routes: opts.routes,
199
198
  routeAuth: opts.routeAuth,
199
+ basePath: opts.basePath,
200
200
  signal: opts.signal,
201
201
  dataRuntime: opts.dataRuntime ?? createDataRuntime({ queryCache }),
202
202
  mode: opts.mode ?? "ssr",
@@ -307,7 +307,9 @@ function resolvePolicyAwareSSRRoute(opts, routeTable) {
307
307
  let href = opts.url;
308
308
  const visited = /* @__PURE__ */ new Set();
309
309
  for (let redirects = 0; redirects <= MAX_SSR_REDIRECTS; redirects += 1) {
310
- const matched = _resolveRouteMatchFromRoutes(new URL(href, "http://localhost").pathname, routeTable);
310
+ const logicalHref = removeRouteBasePath(href, normalizeRouteBasePath(manifest.basePath));
311
+ if (logicalHref === void 0) throw new Error(`SSR: no route found for url: ${href}`);
312
+ const matched = _resolveRouteMatchFromRoutes(new URL(logicalHref, "http://localhost").pathname, routeTable);
311
313
  if (!matched) throw new Error(`SSR: no route found for url: ${href}`);
312
314
  const resolved = getRouteRequestResultSync(resolveRouteRequest(href, {
313
315
  registry: opts.registry,
@@ -446,13 +448,14 @@ function resolveSSRRouteSource(source) {
446
448
  }));
447
449
  }
448
450
  function resolveSSRRouteRender(opts) {
449
- const { seed = 12345, data, document } = opts;
451
+ const { seed = 12345, data, document, styleRegistrationValidation = "warn" } = opts;
450
452
  const cspNonce = validateCspNonce(opts.cspNonce);
451
453
  const routeTable = resolveSSRRouteSource(opts);
452
454
  const resolvedRoute = resolvePolicyAwareSSRRoute(opts, routeTable);
453
455
  const requestUrl = new URL(resolvedRoute.url, "http://localhost");
454
456
  const ctx = createRenderContext(seed, {
455
457
  url: resolvedRoute.url,
458
+ basePath: opts.registry.manifest.basePath,
456
459
  data,
457
460
  params: resolvedRoute.params,
458
461
  routes: routeTable,
@@ -470,7 +473,8 @@ function resolveSSRRouteRender(opts) {
470
473
  data,
471
474
  ctx,
472
475
  document,
473
- cspNonce
476
+ cspNonce,
477
+ styleRegistrationValidation
474
478
  };
475
479
  }
476
480
  function buildDocumentRenderArgs(resolved, appHtml) {
@@ -514,7 +518,7 @@ function renderToSinkInternal(opts, host) {
514
518
  const appSink = new StringSink();
515
519
  renderResolvedRouteAppToSink(resolved, appSink, host);
516
520
  appSink.end();
517
- sink.write(renderDocument(resolved.document, buildDocumentRenderArgs(resolved, appSink.toString()), "renderToString()/renderToStream()"));
521
+ sink.write(renderDocument(resolved.document, buildDocumentRenderArgs(resolved, appSink.toString()), "renderToString()/renderToStream()", resolved.styleRegistrationValidation));
518
522
  });
519
523
  }
520
524
  function renderRouteToString(opts, host) {
@@ -551,7 +555,7 @@ function executeComponentSync(component, props, ctx, ownerFrame = null) {
551
555
  cleanupError = error;
552
556
  }
553
557
  try {
554
- disposeDefaultPortalScope(temp);
558
+ disposeRegisteredDefaultPortalScope(temp);
555
559
  } catch (error) {
556
560
  if (cleanupError) throw new AggregateError([cleanupError, error], "SSR temporary owner cleanup failed");
557
561
  throw error;
@@ -592,7 +596,7 @@ function wrapWithDefaultPortal(out) {
592
596
  if (isPromiseLike(out)) throwSSRDataMissing();
593
597
  const portalVNode = {
594
598
  $$typeof: ELEMENT_TYPE,
595
- type: DefaultPortal,
599
+ type: getDefaultPortalHost(),
596
600
  props: { __askrAutoDefaultPortal: true },
597
601
  key: "__default_portal"
598
602
  };
@@ -781,7 +785,7 @@ function renderAttrsDirect(props, sink) {
781
785
  }
782
786
  if (value === false || value === null || value === void 0) continue;
783
787
  const strValue = String(value);
784
- if (attrName.toLowerCase() === "href" && !isSafeHref(strValue)) continue;
788
+ if (isUnsafeUrlAttribute(attrName, strValue)) continue;
785
789
  sink.write(" ");
786
790
  sink.write(attrName);
787
791
  sink.write("=\"");
@@ -816,7 +820,7 @@ function renderAttrs(props, opts) {
816
820
  else if (value === false || value === null || value === void 0) continue;
817
821
  else {
818
822
  const strValue = String(value);
819
- if (attrName.toLowerCase() === "href" && !isSafeHref(strValue)) continue;
823
+ if (isUnsafeUrlAttribute(attrName, strValue)) continue;
820
824
  attrParts.push(` ${attrName}="${getEscapedAttrValue(strValue)}"`);
821
825
  }
822
826
  }
@@ -853,6 +857,8 @@ function serializeHydrationRenderData(data, runtime) {
853
857
  route: current.route,
854
858
  framework: current.framework
855
859
  });
860
+ const routeHydration = getRouteHydrationMetadata(payload.framework);
861
+ if (routeHydration) validateRouteHydrationData(payload.route, routeHydration.r);
856
862
  if (isEmptyPageRenderEnvelope(payload)) return "";
857
863
  return `<script type="application/json" ${SSR_RENDER_DATA_ATTR}="true">${JSON.stringify(payload, hydrationReplacer).replace(/</g, "\\u003C").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")}<\/script>`;
858
864
  }
@@ -1251,11 +1257,27 @@ function renderResolvedToStringSync(opts) {
1251
1257
  ...route,
1252
1258
  handler
1253
1259
  } : route);
1260
+ const matchedRoute = routes[matchedIndex];
1254
1261
  return renderToString({
1255
1262
  url,
1256
- registry: createRouteRegistry(() => {
1257
- for (const route$1 of effectiveRoutes) route(route$1.path, route$1.handler, { namespace: route$1.namespace });
1258
- }),
1263
+ registry: {
1264
+ manifest: {
1265
+ records: registry.manifest.records.map((record) => {
1266
+ const effectiveHandler = record.path === matchedRoute.path && record.handler === matchedRoute.handler && record.options.namespace === matchedRoute.namespace ? handler : record.handler;
1267
+ return {
1268
+ ...record,
1269
+ component: effectiveHandler,
1270
+ handler: effectiveHandler,
1271
+ layoutChain: [],
1272
+ pageChain: [],
1273
+ options: record.options.namespace ? { namespace: record.options.namespace } : {},
1274
+ metaChain: void 0
1275
+ };
1276
+ }),
1277
+ ...registry.manifest.basePath ? { basePath: registry.manifest.basePath } : {}
1278
+ },
1279
+ routes: effectiveRoutes
1280
+ },
1259
1281
  seed: options?.seed,
1260
1282
  data: options?.data,
1261
1283
  dataRuntime: options?.dataRuntime,
@@ -1401,6 +1423,9 @@ async function renderRouteRequestInternal(options) {
1401
1423
  });
1402
1424
  const context = createRenderContext(options.seed, {
1403
1425
  url: options.url,
1426
+ basePath: manifest.basePath,
1427
+ routes: options.registry.routes,
1428
+ routeAuth: options.auth ?? manifest.auth,
1404
1429
  data: options.data,
1405
1430
  framework: options.framework,
1406
1431
  signal,
@@ -1421,6 +1446,7 @@ async function renderRouteRequestInternal(options) {
1421
1446
  });
1422
1447
  if (!resolved) return { kind: "no-match" };
1423
1448
  if (resolved.kind !== "render") return resolved;
1449
+ context.params = resolved.params;
1424
1450
  const sink = new StringSink();
1425
1451
  renderSSRRouteAppToSink({
1426
1452
  route: {
@@ -0,0 +1,25 @@
1
+ //#region src/common/ssr.ts
2
+ function escapedStyleRawText(value) {
3
+ return value.replace(/<\//gi, "<\\/");
4
+ }
5
+ function validateDocumentStyleRegistrations(html, styles, apiName, validation) {
6
+ if (validation === "off" || !styles || styles.length === 0) return;
7
+ const dropped = styles.filter(({ cssText }) => !html.includes(cssText) && !html.includes(escapedStyleRawText(cssText)));
8
+ if (dropped.length === 0) return;
9
+ const registrations = dropped.map(({ id }) => JSON.stringify(id)).join(", ");
10
+ const message = `${apiName} document() dropped ${dropped.length} registered SSR style${dropped.length === 1 ? "" : "s"}: ${registrations}. Serialize context.styles into the returned document, or set styleRegistrationValidation to "off" when omission is intentional.`;
11
+ if (validation === "error") throw new Error(message);
12
+ console.warn(message);
13
+ }
14
+ function renderDocument(document, args, apiName, styleRegistrationValidation = "warn") {
15
+ const html = document(args);
16
+ if (typeof html !== "string") {
17
+ const received = typeof html === "object" && html !== null && "then" in html && typeof html.then === "function" ? "a Promise-like value" : html === null ? "null" : Array.isArray(html) ? "an array" : typeof html;
18
+ throw new Error(`${apiName} document() must synchronously return a string; received ${received}.`);
19
+ }
20
+ validateDocumentStyleRegistrations(html, args.context.styles, apiName, styleRegistrationValidation);
21
+ return html;
22
+ }
23
+ const SSR_RENDER_DATA_ATTR = "data-askr-render-data";
24
+ //#endregion
25
+ export { renderDocument as n, SSR_RENDER_DATA_ATTR as t };
@@ -1,7 +1,7 @@
1
- import { x as isProductionEnvironment } from "./access-ePqdQqkV.js";
2
- import { t as isBulkCommitActive } from "./fastlane-BOr4lSLl.js";
3
- import { l as notifyReadableReaders, o as markReactivePropsDirtySource, s as markReadableDerivedSubscribersDirty, u as recordReadableRead } from "./readable-_GQGe_St.js";
1
+ import { v as logger, x as isProductionEnvironment } from "./access-Dpfoi4cA.js";
2
+ import { t as isBulkCommitActive } from "./fastlane-CrfB3QG0.js";
4
3
  import { i as claimHookIndex, p as getCurrentInstance } from "./component-scope-DzJEbYGE.js";
4
+ import { l as notifyReadableReaders, o as markReactivePropsDirtySource, s as markReadableDerivedSubscribersDirty, u as recordReadableRead } from "./readable-B5NtV7V0.js";
5
5
  //#region src/runtime/state.ts
6
6
  /**
7
7
  * State primitive for Askr components
@@ -77,6 +77,7 @@ function createStateCell(initialValue, instance) {
77
77
  else newValue = newValueOrUpdater;
78
78
  if (Object.is(value, newValue)) return;
79
79
  if (isBulkCommitActive()) {
80
+ if (!isProductionEnvironment()) logger.warn("[Askr] state.set() was called while a bulk commit (fast-lane reorder) was in progress. The value updated, but subscribers were not notified, so this change will not be reflected in the UI until an unrelated update triggers a re-render. This usually happens from a ref callback or a native focus/blur event fired synchronously during a large keyed-list reorder.");
80
81
  value = newValue;
81
82
  return;
82
83
  }
@@ -1,11 +1,107 @@
1
- import { o as getRuntimeRenderer } from "./access-ePqdQqkV.js";
2
- import { t as ELEMENT_TYPE } from "./jsx-C3IECePB.js";
3
- import { l as notifyReadableReaders, o as markReactivePropsDirtySource, s as markReadableDerivedSubscribersDirty, u as recordReadableRead } from "./readable-_GQGe_St.js";
1
+ import { o as getRuntimeRenderer } from "./access-Dpfoi4cA.js";
4
2
  import { f as getCurrentComponentInstance, m as getCurrentPortalScope } from "./component-scope-DzJEbYGE.js";
5
- import { t as cleanupComponent } from "./component-cleanup-Bug5tSiG.js";
6
- import { c as getCurrentLifecycleCommitBatch, u as registerLifecycleTransaction } from "./lifecycle-batch-CFI4NKpJ.js";
7
- import { n as getActiveRenderContext } from "./render-context-D-DFk2WN.js";
3
+ import { n as Fragment, t as ELEMENT_TYPE } from "./jsx-C3IECePB.js";
4
+ import { l as notifyReadableReaders, o as markReactivePropsDirtySource, s as markReadableDerivedSubscribersDirty, u as recordReadableRead } from "./readable-B5NtV7V0.js";
5
+ import { t as cleanupComponent } from "./component-cleanup-BUfUleGZ.js";
6
+ import { c as getCurrentLifecycleCommitBatch, u as registerLifecycleTransaction } from "./lifecycle-batch-Dfpid9-A.js";
7
+ import { n as getActiveRenderContext } from "./render-context-D0Hoj9dW.js";
8
8
  import { i as createSSRPortalHostToken, n as SSR_PORTAL_HOST, r as createSSRPortalAnchorToken, t as SSR_PORTAL_ANCHOR } from "./portal-w3oQ6oQU.js";
9
+ import { i as registerDefaultPortalRuntime } from "./default-portal-runtime-DvMvWJVp.js";
10
+ //#region src/jsx/utils.ts
11
+ function isElement(value) {
12
+ return typeof value === "object" && value !== null && value.$$typeof === ELEMENT_TYPE;
13
+ }
14
+ function cloneElement(element, props) {
15
+ return {
16
+ $$typeof: element.$$typeof,
17
+ type: element.type,
18
+ key: element.key,
19
+ props: {
20
+ ...element.props,
21
+ ...props
22
+ }
23
+ };
24
+ }
25
+ //#endregion
26
+ //#region src/foundations/structures/layout.tsx
27
+ function layout(Layout) {
28
+ return (children, props) => {
29
+ return Layout({
30
+ ...props,
31
+ children
32
+ });
33
+ };
34
+ }
35
+ //#endregion
36
+ //#region src/foundations/structures/slot.tsx
37
+ /**
38
+ * Slot
39
+ *
40
+ * Structural primitive for prop forwarding patterns.
41
+ *
42
+ * POLICY DECISIONS (LOCKED):
43
+ *
44
+ * 1. asChild Pattern
45
+ * When asChild=true, merges props into the single child element.
46
+ * Child must be a valid JSXElement; non-element children return null.
47
+ * **Slot props override child props** (injection pattern).
48
+ *
49
+ * 2. Fallback Behavior
50
+ * When asChild=false, returns a Fragment (structural no-op).
51
+ * No DOM element is introduced.
52
+ *
53
+ * 3. Type Safety
54
+ * asChild=true requires exactly one JSXElement child (enforced by type).
55
+ * Runtime validates with isElement() check.
56
+ */
57
+ function Slot(props) {
58
+ if (props.asChild) {
59
+ const { children, asChild: _asChild, ...rest } = props;
60
+ if (isElement(children)) return cloneElement(children, rest);
61
+ return null;
62
+ }
63
+ return {
64
+ $$typeof: ELEMENT_TYPE,
65
+ type: Fragment,
66
+ props: { children: props.children },
67
+ key: null
68
+ };
69
+ }
70
+ //#endregion
71
+ //#region src/foundations/structures/presence.ts
72
+ /**
73
+ * Presence
74
+ *
75
+ * Structural policy primitive for conditional mount/unmount.
76
+ * - No timers
77
+ * - No animation coupling
78
+ * - No DOM side-effects
79
+ *
80
+ * POLICY DECISIONS (LOCKED):
81
+ *
82
+ * 1. Present as Function
83
+ * Accepts boolean OR function to support lazy evaluation patterns.
84
+ * Function is called once per render. Use boolean form for static values.
85
+ *
86
+ * 2. Children Type
87
+ * Presence forwards normal renderable child content only.
88
+ * Imperative DOM nodes are not part of the public contract.
89
+ *
90
+ * 3. Immediate Mount/Unmount
91
+ * No exit animations or transitions. When `present` becomes false,
92
+ * children are removed immediately. Animation must be layered above
93
+ * this primitive.
94
+ */
95
+ function Presence({ present, children }) {
96
+ if (!(typeof present === "function" ? present() : Boolean(present))) return null;
97
+ return {
98
+ $$typeof: ELEMENT_TYPE,
99
+ type: Fragment,
100
+ props: { children },
101
+ key: null
102
+ };
103
+ }
104
+ //#endregion
9
105
  //#region src/runtime/portal.ts
10
106
  const DEFAULT_SSR_PORTAL_KEY = {};
11
107
  function getSSRPortalSlot(key) {
@@ -391,5 +487,138 @@ function Portal(props) {
391
487
  writeDefaultPortal(props, owner);
392
488
  return null;
393
489
  }
490
+ registerDefaultPortalRuntime({
491
+ host: DefaultPortal,
492
+ clearForInstance(instance) {
493
+ clearDefaultPortalForInstance(instance);
494
+ },
495
+ disposeScope: disposeDefaultPortalScope
496
+ });
497
+ //#endregion
498
+ //#region src/foundations/structures/collection.ts
499
+ function createCollection() {
500
+ const registry = /* @__PURE__ */ new Map();
501
+ function register(node, metadata) {
502
+ const item = {
503
+ node,
504
+ metadata
505
+ };
506
+ registry.set(node, item);
507
+ return () => {
508
+ registry.delete(node);
509
+ };
510
+ }
511
+ function items() {
512
+ return Array.from(registry.values());
513
+ }
514
+ function clear() {
515
+ registry.clear();
516
+ }
517
+ function size() {
518
+ return registry.size;
519
+ }
520
+ return {
521
+ register,
522
+ items,
523
+ clear,
524
+ size
525
+ };
526
+ }
527
+ /**
528
+ * USAGE EXAMPLE:
529
+ *
530
+ * // Create a collection for menu items
531
+ * const menuItems = createCollection<HTMLElement, { disabled: boolean }>();
532
+ *
533
+ * // Register items
534
+ * const unregister1 = menuItems.register(element1, { disabled: false });
535
+ * const unregister2 = menuItems.register(element2, { disabled: true });
536
+ *
537
+ * // Query items
538
+ * const allItems = menuItems.items();
539
+ * const enabledItems = allItems.filter(item => !item.metadata.disabled);
540
+ *
541
+ * // Cleanup
542
+ * unregister1();
543
+ * unregister2();
544
+ */
545
+ //#endregion
546
+ //#region src/foundations/structures/layer.ts
547
+ function createLayer() {
548
+ const stack = [];
549
+ let nextId = 1;
550
+ function register(options) {
551
+ const id = nextId++;
552
+ const entry = {
553
+ id,
554
+ options
555
+ };
556
+ stack.push(entry);
557
+ function isTop() {
558
+ return stack[stack.length - 1]?.id === id;
559
+ }
560
+ function unregister() {
561
+ const index = stack.findIndex((e) => e.id === id);
562
+ if (index !== -1) stack.splice(index, 1);
563
+ }
564
+ return {
565
+ id,
566
+ isTop,
567
+ unregister
568
+ };
569
+ }
570
+ function layers() {
571
+ return stack.map((entry) => ({
572
+ id: entry.id,
573
+ isTop: () => stack[stack.length - 1]?.id === entry.id,
574
+ unregister: () => {
575
+ const index = stack.findIndex((e) => e.id === entry.id);
576
+ if (index !== -1) stack.splice(index, 1);
577
+ }
578
+ }));
579
+ }
580
+ function handleEscape() {
581
+ const top = stack[stack.length - 1];
582
+ if (top) top.options.onEscape?.();
583
+ }
584
+ function handleOutsidePointer(e) {
585
+ const top = stack[stack.length - 1];
586
+ if (!top) return;
587
+ const node = top.options.node;
588
+ if (node && e.target instanceof Node) {
589
+ if (!node.contains(e.target)) top.options.onOutsidePointer?.(e);
590
+ } else top.options.onOutsidePointer?.(e);
591
+ }
592
+ return {
593
+ register,
594
+ layers,
595
+ handleEscape,
596
+ handleOutsidePointer
597
+ };
598
+ }
599
+ /**
600
+ * USAGE EXAMPLE:
601
+ *
602
+ * const layerManager = createLayer();
603
+ *
604
+ * function Modal({ onClose }) {
605
+ * const modalRef = ref<HTMLDivElement>();
606
+ *
607
+ * const layer = layerManager.register({
608
+ * node: modalRef.current,
609
+ * onEscape: onClose,
610
+ * onOutsidePointer: onClose,
611
+ * });
612
+ *
613
+ * onUnmount(() => layer.unregister());
614
+ *
615
+ * return (
616
+ * <div ref={modalRef}>
617
+ * <h1>Modal</h1>
618
+ * {layer.isTop() && <p>I am on top!</p>}
619
+ * </div>
620
+ * );
621
+ * }
622
+ */
394
623
  //#endregion
395
- export { disposeDefaultPortalScope as a, definePortal as i, Portal as n, clearDefaultPortalForInstance as r, DefaultPortal as t };
624
+ export { definePortal as a, layout as c, Portal as i, cloneElement as l, createCollection as n, Presence as o, DefaultPortal as r, Slot as s, createLayer as t, isElement as u };
@@ -1,5 +1,5 @@
1
- import { l as ComponentFunction } from "../control-Y8F73Uln.js";
2
- import { M as RouteMatch, W as RouteRegistry, w as RouteAuthOptions } from "../index-_UN08Pg1.js";
1
+ import { l as ComponentFunction } from "../control-DhUZU4_V.js";
2
+ import { M as RouteMatch, W as RouteRegistry, w as RouteAuthOptions } from "../index-pIgGaXvS.js";
3
3
  import { C as Query, k as QueryStaleReason } from "../index-5S-fi2xy.js";
4
4
  //#region src/testing/render.d.ts
5
5
  interface RenderOptions {
@@ -1,7 +1,7 @@
1
- import { r as flushRuntimeScheduler } from "../access-ePqdQqkV.js";
2
- import { r as computeRouteActivityMatches } from "../route-matching-IfQZmqkN.js";
3
- import { l as addInvalidationListener } from "../data-runtime-D68YOo9L.js";
4
- import { a as cleanupApp, r as createSPA, t as createIsland } from "../boot-D28lesMw.js";
1
+ import { r as flushRuntimeScheduler } from "../access-Dpfoi4cA.js";
2
+ import { l as addInvalidationListener } from "../data-runtime-Ck3tJDDQ.js";
3
+ import { r as computeRouteActivityMatches } from "../route-matching-DqULzZtC.js";
4
+ import { a as cleanupApp, r as createSPA, t as createIsland } from "../boot-CViTpUiB.js";
5
5
  //#region src/testing/render.ts
6
6
  const renderStates = /* @__PURE__ */ new WeakMap();
7
7
  const eventConstructorNames = {
@@ -1,5 +1,6 @@
1
- import { t as SSR_RENDER_DATA_ATTR } from "./ssr-CbQO5xd3.js";
2
- import { a as renderResolvedToStringSync } from "./ssr-CtC4lChT.js";
1
+ import { m as withHydrationVerificationRender } from "./render-context-D0Hoj9dW.js";
2
+ import { t as SSR_RENDER_DATA_ATTR } from "./ssr-DSHd_Q9K.js";
3
+ import { a as renderResolvedToStringSync } from "./ssr-B98593qJ.js";
3
4
  //#region src/ssr/verify-hydration.ts
4
5
  function normalizeHydrationHtml(html) {
5
6
  const template = document.createElement("template");
@@ -9,13 +10,13 @@ function normalizeHydrationHtml(html) {
9
10
  }
10
11
  function verifyHydrationSyncForUrl(opts) {
11
12
  const { root, url, registry, resolved, options } = opts;
12
- const expected = renderResolvedToStringSync({
13
+ const expected = withHydrationVerificationRender(() => renderResolvedToStringSync({
13
14
  url,
14
15
  registry,
15
16
  handler: resolved.handler,
16
17
  params: resolved.params,
17
18
  options
18
- });
19
+ }));
19
20
  return normalizeHydrationHtml(root.innerHTML) === normalizeHydrationHtml(expected);
20
21
  }
21
22
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askrjs/askr",
3
- "version": "0.0.84",
3
+ "version": "0.0.86",
4
4
  "description": "Actor-backed deterministic UI framework",
5
5
  "keywords": [
6
6
  "askr",
@@ -170,7 +170,7 @@
170
170
  }
171
171
  },
172
172
  "engines": {
173
- "node": ">=24.15.0"
173
+ "node": ">=24.0.0"
174
174
  },
175
175
  "packageManager": "npm@12.0.1",
176
176
  "allowScripts": {