@warlock.js/web 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/LICENSE +21 -0
  3. package/README.md +125 -0
  4. package/esm/build/contribution.d.mts +55 -0
  5. package/esm/build/contribution.mjs +132 -0
  6. package/esm/build/contribution.mjs.map +1 -0
  7. package/esm/build/discover-pages.mjs +294 -0
  8. package/esm/build/discover-pages.mjs.map +1 -0
  9. package/esm/build/generate-client-registry.mjs +123 -0
  10. package/esm/build/generate-client-registry.mjs.map +1 -0
  11. package/esm/build/generate-pages-barrel.mjs +330 -0
  12. package/esm/build/generate-pages-barrel.mjs.map +1 -0
  13. package/esm/build/read-route-exports.mjs +212 -0
  14. package/esm/build/read-route-exports.mjs.map +1 -0
  15. package/esm/client/build-hydrated-tree.mjs +113 -0
  16. package/esm/client/build-hydrated-tree.mjs.map +1 -0
  17. package/esm/client/hydrate-page.mjs +73 -0
  18. package/esm/client/hydrate-page.mjs.map +1 -0
  19. package/esm/client/navigation/current-route.d.mts +91 -0
  20. package/esm/client/navigation/current-route.mjs +66 -0
  21. package/esm/client/navigation/current-route.mjs.map +1 -0
  22. package/esm/client/navigation/fetch-page-data.mjs +97 -0
  23. package/esm/client/navigation/fetch-page-data.mjs.map +1 -0
  24. package/esm/client/navigation/navigation-root.mjs +196 -0
  25. package/esm/client/navigation/navigation-root.mjs.map +1 -0
  26. package/esm/client/navigation/prefetch.mjs +110 -0
  27. package/esm/client/navigation/prefetch.mjs.map +1 -0
  28. package/esm/client/navigation/refresh.d.mts +40 -0
  29. package/esm/client/navigation/refresh.mjs +146 -0
  30. package/esm/client/navigation/refresh.mjs.map +1 -0
  31. package/esm/client/navigation/verbs.d.mts +55 -0
  32. package/esm/client/navigation/verbs.mjs +66 -0
  33. package/esm/client/navigation/verbs.mjs.map +1 -0
  34. package/esm/client/runtime/index.d.mts +4 -0
  35. package/esm/client/runtime/index.mjs +4 -0
  36. package/esm/client/runtime/manifest.d.mts +8 -0
  37. package/esm/client/runtime/manifest.mjs +77 -0
  38. package/esm/client/runtime/manifest.mjs.map +1 -0
  39. package/esm/client/runtime/matcher.d.mts +7 -0
  40. package/esm/client/runtime/matcher.mjs +169 -0
  41. package/esm/client/runtime/matcher.mjs.map +1 -0
  42. package/esm/client/runtime/types.d.mts +22 -0
  43. package/esm/components/default-app.mjs +32 -0
  44. package/esm/components/default-app.mjs.map +1 -0
  45. package/esm/components/document-context.d.mts +1 -0
  46. package/esm/components/document-context.mjs +24 -0
  47. package/esm/components/document-context.mjs.map +1 -0
  48. package/esm/components/head.d.mts +14 -0
  49. package/esm/components/head.mjs +63 -0
  50. package/esm/components/head.mjs.map +1 -0
  51. package/esm/components/link.d.mts +157 -0
  52. package/esm/components/link.mjs +194 -0
  53. package/esm/components/link.mjs.map +1 -0
  54. package/esm/components/scripts.d.mts +14 -0
  55. package/esm/components/scripts.mjs +21 -0
  56. package/esm/components/scripts.mjs.map +1 -0
  57. package/esm/connector/index.d.mts +6 -0
  58. package/esm/connector/index.mjs +7 -0
  59. package/esm/context.d.mts +107 -0
  60. package/esm/hydration/index.d.mts +1 -0
  61. package/esm/hydration/index.mjs +33 -0
  62. package/esm/hydration/index.mjs.map +1 -0
  63. package/esm/hydration-payload.d.mts +1 -0
  64. package/esm/hydration-payload.mjs +72 -0
  65. package/esm/hydration-payload.mjs.map +1 -0
  66. package/esm/index.d.mts +35 -0
  67. package/esm/index.mjs +12 -0
  68. package/esm/loaders.d.mts +47 -0
  69. package/esm/metadata.d.mts +54 -0
  70. package/esm/props.d.mts +42 -0
  71. package/esm/route.d.mts +30 -0
  72. package/esm/routing/compose-route-path.mjs +33 -0
  73. package/esm/routing/compose-route-path.mjs.map +1 -0
  74. package/esm/routing/data-request.mjs +52 -0
  75. package/esm/routing/data-request.mjs.map +1 -0
  76. package/esm/routing/layout-policy.mjs +78 -0
  77. package/esm/routing/layout-policy.mjs.map +1 -0
  78. package/esm/routing/navigator.mjs +21 -0
  79. package/esm/routing/navigator.mjs.map +1 -0
  80. package/esm/routing/query-string.d.mts +250 -0
  81. package/esm/routing/query-string.mjs +340 -0
  82. package/esm/routing/query-string.mjs.map +1 -0
  83. package/esm/routing/route-identity.mjs +68 -0
  84. package/esm/routing/route-identity.mjs.map +1 -0
  85. package/esm/routing/route-table.d.mts +34 -0
  86. package/esm/routing/route-table.mjs +182 -0
  87. package/esm/routing/route-table.mjs.map +1 -0
  88. package/esm/routing/router-events.d.mts +159 -0
  89. package/esm/routing/router-events.mjs +83 -0
  90. package/esm/routing/router-events.mjs.map +1 -0
  91. package/esm/server/buffered-response.mjs +115 -0
  92. package/esm/server/buffered-response.mjs.map +1 -0
  93. package/esm/server/build-hydration-payload.mjs +27 -0
  94. package/esm/server/build-hydration-payload.mjs.map +1 -0
  95. package/esm/server/client-asset-url-prefix.d.mts +22 -0
  96. package/esm/server/client-asset-url-prefix.mjs +23 -0
  97. package/esm/server/client-asset-url-prefix.mjs.map +1 -0
  98. package/esm/server/create-page-module-loader.mjs +49 -0
  99. package/esm/server/create-page-module-loader.mjs.map +1 -0
  100. package/esm/server/create-page-route-handler.mjs +107 -0
  101. package/esm/server/create-page-route-handler.mjs.map +1 -0
  102. package/esm/server/dev-server.mjs +167 -0
  103. package/esm/server/dev-server.mjs.map +1 -0
  104. package/esm/server/execute-page-request.d.mts +1 -0
  105. package/esm/server/execute-page-request.mjs +166 -0
  106. package/esm/server/execute-page-request.mjs.map +1 -0
  107. package/esm/server/execute-page-request.types.d.mts +1 -0
  108. package/esm/server/hydration-client-url.d.mts +37 -0
  109. package/esm/server/hydration-client-url.mjs +101 -0
  110. package/esm/server/hydration-client-url.mjs.map +1 -0
  111. package/esm/server/index.mjs +13 -0
  112. package/esm/server/install-page-routes-from-manifest.mjs +133 -0
  113. package/esm/server/install-page-routes-from-manifest.mjs.map +1 -0
  114. package/esm/server/install-page-routes.d.mts +1 -0
  115. package/esm/server/install-page-routes.mjs +6 -0
  116. package/esm/server/install-production-page-routes.mjs +55 -0
  117. package/esm/server/install-production-page-routes.mjs.map +1 -0
  118. package/esm/server/match-page-route.mjs +48 -0
  119. package/esm/server/match-page-route.mjs.map +1 -0
  120. package/esm/server/page-context.d.mts +1 -0
  121. package/esm/server/page-context.mjs +33 -0
  122. package/esm/server/page-context.mjs.map +1 -0
  123. package/esm/server/page-manifest.d.mts +102 -0
  124. package/esm/server/page-manifest.mjs +31 -0
  125. package/esm/server/page-manifest.mjs.map +1 -0
  126. package/esm/server/render-page.mjs +214 -0
  127. package/esm/server/render-page.mjs.map +1 -0
  128. package/esm/server/resolve-page-metadata.mjs +33 -0
  129. package/esm/server/resolve-page-metadata.mjs.map +1 -0
  130. package/esm/server/resolve-validation-data.mjs +27 -0
  131. package/esm/server/resolve-validation-data.mjs.map +1 -0
  132. package/esm/server/settle-page-response.d.mts +1 -0
  133. package/esm/server/settle-page-response.mjs +90 -0
  134. package/esm/server/settle-page-response.mjs.map +1 -0
  135. package/esm/server/stylesheet-urls.mjs +65 -0
  136. package/esm/server/stylesheet-urls.mjs.map +1 -0
  137. package/esm/server/web-connector-factory.d.mts +25 -0
  138. package/esm/server/web-connector-factory.mjs +121 -0
  139. package/esm/server/web-connector-factory.mjs.map +1 -0
  140. package/esm/server/web-connector.d.mts +34 -0
  141. package/esm/server/web-connector.mjs +516 -0
  142. package/esm/server/web-connector.mjs.map +1 -0
  143. package/esm/shared.d.mts +22 -0
  144. package/esm/shared.mjs +223 -0
  145. package/esm/shared.mjs.map +1 -0
  146. package/esm/validation.d.mts +31 -0
  147. package/esm/vite/app-convention-aliases.mjs +43 -0
  148. package/esm/vite/app-convention-aliases.mjs.map +1 -0
  149. package/esm/vite/build-client.d.mts +38 -0
  150. package/esm/vite/build-client.mjs +72 -0
  151. package/esm/vite/build-client.mjs.map +1 -0
  152. package/esm/vite/gate-a-resolve.d.mts +57 -0
  153. package/esm/vite/gate-a-resolve.mjs +834 -0
  154. package/esm/vite/gate-a-resolve.mjs.map +1 -0
  155. package/esm/vite/gate-b-secrets.d.mts +57 -0
  156. package/esm/vite/gate-b-secrets.mjs +299 -0
  157. package/esm/vite/gate-b-secrets.mjs.map +1 -0
  158. package/esm/vite/gate-c-verify.d.mts +118 -0
  159. package/esm/vite/gate-c-verify.mjs +263 -0
  160. package/esm/vite/gate-c-verify.mjs.map +1 -0
  161. package/esm/vite/hydration-entries.d.mts +17 -0
  162. package/esm/vite/hydration-entries.mjs +45 -0
  163. package/esm/vite/hydration-entries.mjs.map +1 -0
  164. package/esm/vite/index.d.mts +126 -0
  165. package/esm/vite/index.mjs +146 -0
  166. package/esm/vite/index.mjs.map +1 -0
  167. package/esm/vite/page-registry-plugin.d.mts +43 -0
  168. package/esm/vite/page-registry-plugin.mjs +138 -0
  169. package/esm/vite/page-registry-plugin.mjs.map +1 -0
  170. package/esm/vite/projection.d.mts +31 -0
  171. package/esm/vite/projection.mjs +384 -0
  172. package/esm/vite/projection.mjs.map +1 -0
  173. package/llms-full.txt +1041 -0
  174. package/llms.txt +21 -0
  175. package/package.json +77 -0
  176. package/skills/add-web-to-an-app/SKILL.md +103 -0
  177. package/skills/create-a-page/SKILL.md +141 -0
  178. package/skills/load-page-data/SKILL.md +183 -0
  179. package/skills/navigate-on-the-client/SKILL.md +182 -0
  180. package/skills/serve-styles/SKILL.md +145 -0
  181. package/skills/use-layouts/SKILL.md +121 -0
  182. package/skills/write-the-root/SKILL.md +131 -0
@@ -0,0 +1,66 @@
1
+ import { currentNavigator } from "../../routing/navigator.mjs";
2
+
3
+ //#region ../web/src/client/navigation/verbs.ts
4
+ /**
5
+ * The history verbs, under the names MRR spells them
6
+ * (`@mongez/react-router` — `src/utilities.tsx`).
7
+ *
8
+ * A developer moving between a Mongez CSR app and a Warlock SSR app should not
9
+ * relearn "go there" and "go back", so the names and call shapes are MRR's.
10
+ * What is NOT MRR's is the mechanism: MRR's `navigateTo` reaches its router
11
+ * singleton, and that singleton is the client-side matcher Warlock
12
+ * deliberately does not have — the SERVER router is the only matcher
13
+ * (canon `9c8f878b`). So `navigateTo` here delegates to the navigator the
14
+ * client runtime registered, and knows nothing about paths beyond passing one
15
+ * along.
16
+ *
17
+ * ## Why nothing in this file throws
18
+ *
19
+ * All three are importable from universal modules, which means all three can
20
+ * be CALLED with no browser and no runtime: during the server render, and in
21
+ * the gap between first paint and hydration. Each therefore has a defined
22
+ * "did nothing" answer — `false`, a no-op, `""` — instead of an exception.
23
+ * Throwing here would cost the page rather than the navigation.
24
+ */
25
+ /**
26
+ * Navigate to a path client-side.
27
+ *
28
+ * @param path where to go — passed through untouched; matching is the server's
29
+ * job, so this is not parsed, resolved or prefixed here.
30
+ * @param options `replace: true` swaps the current history entry instead of
31
+ * pushing a new one, so Back skips it.
32
+ * @returns `true` if the client runtime accepted the navigation. `false` means
33
+ * nothing handled it — either no runtime is connected yet (server render, or
34
+ * pre-hydration) or the runtime declined the URL. A `false` caller that needs
35
+ * the user to arrive anyway should fall back to a real browser navigation.
36
+ */
37
+ function navigateTo(path, options) {
38
+ const navigator = currentNavigator();
39
+ if (!navigator) return false;
40
+ return navigator(path, options);
41
+ }
42
+ /**
43
+ * Go back one entry in browser history.
44
+ *
45
+ * This is the browser's own Back, not a re-navigation to a remembered URL, so
46
+ * it restores scroll and forward history the way the button does. No-op
47
+ * without a `window`.
48
+ */
49
+ function navigateBack() {
50
+ if (typeof window === "undefined") return;
51
+ window.history.back();
52
+ }
53
+ /**
54
+ * @returns the current url's hash WITHOUT its leading `#` — MRR's shape, so
55
+ * `getHash() === "reviews"` for `/products#reviews`. `""` when there is no
56
+ * hash, and `""` rather than `undefined` when there is no `window`, so callers
57
+ * can treat the result as a string unconditionally.
58
+ */
59
+ function getHash() {
60
+ if (typeof window === "undefined") return "";
61
+ return window.location.hash.replace("#", "");
62
+ }
63
+
64
+ //#endregion
65
+ export { getHash, navigateBack, navigateTo };
66
+ //# sourceMappingURL=verbs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verbs.mjs","names":[],"sources":["../../../../../../../../web/src/client/navigation/verbs.ts"],"sourcesContent":["import { currentNavigator } from \"../../routing/navigator\";\n\n/**\n * The history verbs, under the names MRR spells them\n * (`@mongez/react-router` — `src/utilities.tsx`).\n *\n * A developer moving between a Mongez CSR app and a Warlock SSR app should not\n * relearn \"go there\" and \"go back\", so the names and call shapes are MRR's.\n * What is NOT MRR's is the mechanism: MRR's `navigateTo` reaches its router\n * singleton, and that singleton is the client-side matcher Warlock\n * deliberately does not have — the SERVER router is the only matcher\n * (canon `9c8f878b`). So `navigateTo` here delegates to the navigator the\n * client runtime registered, and knows nothing about paths beyond passing one\n * along.\n *\n * ## Why nothing in this file throws\n *\n * All three are importable from universal modules, which means all three can\n * be CALLED with no browser and no runtime: during the server render, and in\n * the gap between first paint and hydration. Each therefore has a defined\n * \"did nothing\" answer — `false`, a no-op, `\"\"` — instead of an exception.\n * Throwing here would cost the page rather than the navigation.\n */\n\n/**\n * Navigate to a path client-side.\n *\n * @param path where to go — passed through untouched; matching is the server's\n * job, so this is not parsed, resolved or prefixed here.\n * @param options `replace: true` swaps the current history entry instead of\n * pushing a new one, so Back skips it.\n * @returns `true` if the client runtime accepted the navigation. `false` means\n * nothing handled it — either no runtime is connected yet (server render, or\n * pre-hydration) or the runtime declined the URL. A `false` caller that needs\n * the user to arrive anyway should fall back to a real browser navigation.\n */\nexport function navigateTo(path: string, options?: { replace?: boolean }): boolean {\n const navigator = currentNavigator();\n\n if (!navigator) return false;\n\n return navigator(path, options);\n}\n\n/**\n * Go back one entry in browser history.\n *\n * This is the browser's own Back, not a re-navigation to a remembered URL, so\n * it restores scroll and forward history the way the button does. No-op\n * without a `window`.\n */\nexport function navigateBack(): void {\n if (typeof window === \"undefined\") return;\n\n window.history.back();\n}\n\n/**\n * @returns the current url's hash WITHOUT its leading `#` — MRR's shape, so\n * `getHash() === \"reviews\"` for `/products#reviews`. `\"\"` when there is no\n * hash, and `\"\"` rather than `undefined` when there is no `window`, so callers\n * can treat the result as a string unconditionally.\n */\nexport function getHash(): string {\n if (typeof window === \"undefined\") return \"\";\n\n return window.location.hash.replace(\"#\", \"\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,WAAW,MAAc,SAA0C;CACjF,MAAM,YAAY,iBAAiB;CAEnC,IAAI,CAAC,WAAW,OAAO;CAEvB,OAAO,UAAU,MAAM,OAAO;AAChC;;;;;;;;AASA,SAAgB,eAAqB;CACnC,IAAI,OAAO,WAAW,aAAa;CAEnC,OAAO,QAAQ,KAAK;AACtB;;;;;;;AAQA,SAAgB,UAAkB;CAChC,IAAI,OAAO,WAAW,aAAa,OAAO;CAE1C,OAAO,OAAO,SAAS,KAAK,QAAQ,KAAK,EAAE;AAC7C"}
@@ -0,0 +1,4 @@
1
+ import { ClientPageEntry, ClientProjectedModule, ClientRouteComposition, ClientRouteLoad, ClientRouteMatch, ClientRouteParams } from "./types.mjs";
2
+ import { loadClientRouteComposition, validateClientRouteManifest } from "./manifest.mjs";
3
+ import { matchClientRoute } from "./matcher.mjs";
4
+ export { type ClientPageEntry, type ClientProjectedModule, type ClientRouteComposition, type ClientRouteLoad, type ClientRouteMatch, type ClientRouteParams, loadClientRouteComposition, matchClientRoute, validateClientRouteManifest };
@@ -0,0 +1,4 @@
1
+ import { loadClientRouteComposition, validateClientRouteManifest } from "./manifest.mjs";
2
+ import { matchClientRoute } from "./matcher.mjs";
3
+
4
+ export { loadClientRouteComposition, matchClientRoute, validateClientRouteManifest };
@@ -0,0 +1,8 @@
1
+ import { ClientPageEntry, ClientRouteComposition } from "./types.mjs";
2
+
3
+ //#region ../web/src/client/runtime/manifest.d.ts
4
+ declare function validateClientRouteManifest(input: unknown): readonly ClientPageEntry[];
5
+ declare function loadClientRouteComposition(entry: ClientPageEntry): Promise<ClientRouteComposition>;
6
+ //#endregion
7
+ export { loadClientRouteComposition, validateClientRouteManifest };
8
+ //# sourceMappingURL=manifest.d.mts.map
@@ -0,0 +1,77 @@
1
+ //#region ../web/src/client/runtime/manifest.ts
2
+ const ENTRY_KEYS = [
3
+ "type",
4
+ "name",
5
+ "path",
6
+ "load"
7
+ ];
8
+ const COMPOSITION_REQUIRED_KEYS = ["Page", "layouts"];
9
+ const COMPOSITION_OPTIONAL_KEYS = ["App"];
10
+ function isNonArrayObject(value) {
11
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12
+ }
13
+ function printableValue(value) {
14
+ if (typeof value === "string") return JSON.stringify(value);
15
+ try {
16
+ return String(value);
17
+ } catch {
18
+ return "<unprintable>";
19
+ }
20
+ }
21
+ function assertExactDataKeys(value, requiredKeys, optionalKeys, label) {
22
+ const allowedKeys = new Set([...requiredKeys, ...optionalKeys]);
23
+ const descriptors = Object.getOwnPropertyDescriptors(value);
24
+ const ownKeys = Reflect.ownKeys(value);
25
+ for (const key of ownKeys) if (typeof key !== "string" || !allowedKeys.has(key)) throw new TypeError(`${label} has unexpected own key ${JSON.stringify(String(key))}.`);
26
+ for (const key of requiredKeys) if (!Object.prototype.hasOwnProperty.call(descriptors, key)) throw new TypeError(`${label} is missing own key ${JSON.stringify(key)}.`);
27
+ for (const key of ownKeys) {
28
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
29
+ if (descriptor && !("value" in descriptor)) throw new TypeError(`${label} key ${JSON.stringify(String(key))} must be an own data property.`);
30
+ }
31
+ }
32
+ function validateEntry(input, index) {
33
+ const label = `Client route manifest entry at index ${index}`;
34
+ if (!isNonArrayObject(input)) throw new TypeError(`${label} must be a non-array object.`);
35
+ assertExactDataKeys(input, ENTRY_KEYS, [], label);
36
+ if (input.type !== "page") throw new TypeError(`${label} has unknown type ${printableValue(input.type)}; expected "page".`);
37
+ if (typeof input.name !== "string" || input.name.trim().length === 0) throw new TypeError(`${label} name must be a non-empty string.`);
38
+ if (typeof input.path !== "string" || input.path.trim().length === 0) throw new TypeError(`${label} path must be a non-empty string.`);
39
+ if (typeof input.load !== "function") throw new TypeError(`${label} load must be callable.`);
40
+ return input;
41
+ }
42
+ function validateProjectedModule(input, label) {
43
+ if (!isNonArrayObject(input)) throw new TypeError(`${label} must be a non-array module object.`);
44
+ return input;
45
+ }
46
+ function validateComposition(input) {
47
+ const label = "Loaded client route composition";
48
+ if (!isNonArrayObject(input)) throw new TypeError(`${label} must be a non-array object.`);
49
+ assertExactDataKeys(input, COMPOSITION_REQUIRED_KEYS, COMPOSITION_OPTIONAL_KEYS, label);
50
+ validateProjectedModule(input.Page, `${label} Page`);
51
+ if (!Array.isArray(input.layouts)) throw new TypeError(`${label} layouts must be an array.`);
52
+ input.layouts.forEach((layout, index) => {
53
+ validateProjectedModule(layout, `${label} layout at index ${index}`);
54
+ });
55
+ if (Object.prototype.hasOwnProperty.call(input, "App")) validateProjectedModule(input.App, `${label} App`);
56
+ return input;
57
+ }
58
+ function validateClientRouteManifest(input) {
59
+ if (!Array.isArray(input)) throw new TypeError("Client route manifest must be an array.");
60
+ const names = /* @__PURE__ */ new Set();
61
+ const paths = /* @__PURE__ */ new Set();
62
+ return input.map((candidate, index) => {
63
+ const entry = validateEntry(candidate, index);
64
+ if (names.has(entry.name)) throw new TypeError(`Client route manifest has duplicate name ${JSON.stringify(entry.name)}.`);
65
+ if (paths.has(entry.path)) throw new TypeError(`Client route manifest has duplicate path ${JSON.stringify(entry.path)}.`);
66
+ names.add(entry.name);
67
+ paths.add(entry.path);
68
+ return entry;
69
+ });
70
+ }
71
+ async function loadClientRouteComposition(entry) {
72
+ return validateComposition(await entry.load());
73
+ }
74
+
75
+ //#endregion
76
+ export { loadClientRouteComposition, validateClientRouteManifest };
77
+ //# sourceMappingURL=manifest.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.mjs","names":[],"sources":["../../../../../../../../web/src/client/runtime/manifest.ts"],"sourcesContent":["import type {\n ClientPageEntry,\n ClientProjectedModule,\n ClientRouteComposition,\n} from \"./types\";\n\nconst ENTRY_KEYS = [\"type\", \"name\", \"path\", \"load\"] as const;\nconst COMPOSITION_REQUIRED_KEYS = [\"Page\", \"layouts\"] as const;\nconst COMPOSITION_OPTIONAL_KEYS = [\"App\"] as const;\n\ntype DataRecord = Record<PropertyKey, unknown>;\n\nfunction isNonArrayObject(value: unknown): value is DataRecord {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction printableValue(value: unknown): string {\n if (typeof value === \"string\") return JSON.stringify(value);\n\n try {\n return String(value);\n } catch {\n return \"<unprintable>\";\n }\n}\n\nfunction assertExactDataKeys(\n value: DataRecord,\n requiredKeys: readonly string[],\n optionalKeys: readonly string[],\n label: string,\n): void {\n const allowedKeys = new Set([...requiredKeys, ...optionalKeys]);\n const descriptors = Object.getOwnPropertyDescriptors(value);\n const ownKeys = Reflect.ownKeys(value);\n\n for (const key of ownKeys) {\n if (typeof key !== \"string\" || !allowedKeys.has(key)) {\n throw new TypeError(`${label} has unexpected own key ${JSON.stringify(String(key))}.`);\n }\n }\n\n for (const key of requiredKeys) {\n if (!Object.prototype.hasOwnProperty.call(descriptors, key)) {\n throw new TypeError(`${label} is missing own key ${JSON.stringify(key)}.`);\n }\n }\n\n for (const key of ownKeys) {\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n if (descriptor && !(\"value\" in descriptor)) {\n throw new TypeError(\n `${label} key ${JSON.stringify(String(key))} must be an own data property.`,\n );\n }\n }\n}\n\nfunction validateEntry(input: unknown, index: number): ClientPageEntry {\n const label = `Client route manifest entry at index ${index}`;\n\n if (!isNonArrayObject(input)) {\n throw new TypeError(`${label} must be a non-array object.`);\n }\n\n assertExactDataKeys(input, ENTRY_KEYS, [], label);\n\n if (input.type !== \"page\") {\n throw new TypeError(\n `${label} has unknown type ${printableValue(input.type)}; expected \"page\".`,\n );\n }\n\n if (typeof input.name !== \"string\" || input.name.trim().length === 0) {\n throw new TypeError(`${label} name must be a non-empty string.`);\n }\n\n if (typeof input.path !== \"string\" || input.path.trim().length === 0) {\n throw new TypeError(`${label} path must be a non-empty string.`);\n }\n\n if (typeof input.load !== \"function\") {\n throw new TypeError(`${label} load must be callable.`);\n }\n\n return input as ClientPageEntry;\n}\n\nfunction validateProjectedModule(input: unknown, label: string): ClientProjectedModule {\n if (!isNonArrayObject(input)) {\n throw new TypeError(`${label} must be a non-array module object.`);\n }\n\n return input;\n}\n\nfunction validateComposition(input: unknown): ClientRouteComposition {\n const label = \"Loaded client route composition\";\n\n if (!isNonArrayObject(input)) {\n throw new TypeError(`${label} must be a non-array object.`);\n }\n\n assertExactDataKeys(\n input,\n COMPOSITION_REQUIRED_KEYS,\n COMPOSITION_OPTIONAL_KEYS,\n label,\n );\n validateProjectedModule(input.Page, `${label} Page`);\n\n if (!Array.isArray(input.layouts)) {\n throw new TypeError(`${label} layouts must be an array.`);\n }\n\n input.layouts.forEach((layout, index) => {\n validateProjectedModule(layout, `${label} layout at index ${index}`);\n });\n\n if (Object.prototype.hasOwnProperty.call(input, \"App\")) {\n validateProjectedModule(input.App, `${label} App`);\n }\n\n return input as ClientRouteComposition;\n}\n\nexport function validateClientRouteManifest(input: unknown): readonly ClientPageEntry[] {\n if (!Array.isArray(input)) {\n throw new TypeError(\"Client route manifest must be an array.\");\n }\n\n const names = new Set<string>();\n const paths = new Set<string>();\n\n return input.map((candidate, index) => {\n const entry = validateEntry(candidate, index);\n\n if (names.has(entry.name)) {\n throw new TypeError(\n `Client route manifest has duplicate name ${JSON.stringify(entry.name)}.`,\n );\n }\n\n if (paths.has(entry.path)) {\n throw new TypeError(\n `Client route manifest has duplicate path ${JSON.stringify(entry.path)}.`,\n );\n }\n\n names.add(entry.name);\n paths.add(entry.path);\n return entry;\n });\n}\n\nexport async function loadClientRouteComposition(\n entry: ClientPageEntry,\n): Promise<ClientRouteComposition> {\n const loaded = await entry.load();\n return validateComposition(loaded);\n}\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { ClientPageEntry, ClientRouteMatch } from "./types.mjs";
2
+
3
+ //#region ../web/src/client/runtime/matcher.d.ts
4
+ declare function matchClientRoute(entries: readonly ClientPageEntry[], pathname: string): ClientRouteMatch | null;
5
+ //#endregion
6
+ export { matchClientRoute };
7
+ //# sourceMappingURL=matcher.d.mts.map
@@ -0,0 +1,169 @@
1
+ //#region ../web/src/client/runtime/matcher.ts
2
+ const PARAMETER_NAME = /^[A-Za-z0-9_]+$/;
3
+ const REGEXP_SPECIAL = /[.*+?^${}()|[\]\\]/g;
4
+ function escapeRegExp(value) {
5
+ return value.replace(REGEXP_SPECIAL, "\\$&");
6
+ }
7
+ function decodeReservedCharacter(high, low) {
8
+ return {
9
+ "23": "#",
10
+ "24": "$",
11
+ "25": "%",
12
+ "26": "&",
13
+ "2B": "+",
14
+ "2C": ",",
15
+ "2F": "/",
16
+ "3A": ":",
17
+ "3B": ";",
18
+ "3D": "=",
19
+ "3F": "?",
20
+ "40": "@"
21
+ }[`${high}${low}`.toUpperCase()] ?? null;
22
+ }
23
+ function sanitizePathname(pathname) {
24
+ if (typeof pathname !== "string" || !pathname.startsWith("/")) throw new Error("Client route pathname must start with '/'");
25
+ let path = pathname;
26
+ let shouldDecode = false;
27
+ let shouldDecodeParameters = false;
28
+ for (let index = 1; index < path.length; index++) {
29
+ if (path[index] !== "%") continue;
30
+ const reserved = decodeReservedCharacter(path[index + 1] ?? "", path[index + 2] ?? "");
31
+ if (reserved === null) {
32
+ shouldDecode = true;
33
+ continue;
34
+ }
35
+ shouldDecodeParameters = true;
36
+ if (reserved === "%") {
37
+ path = `${path.slice(0, index + 1)}25${path.slice(index + 1)}`;
38
+ shouldDecode = true;
39
+ index += 2;
40
+ }
41
+ index += 2;
42
+ }
43
+ try {
44
+ if (shouldDecode) path = decodeURI(path);
45
+ } catch {
46
+ return null;
47
+ }
48
+ if (path.length > 1 && path.endsWith("/")) path = path.slice(0, -1);
49
+ return {
50
+ path,
51
+ shouldDecodeParameters
52
+ };
53
+ }
54
+ function decodeParameter(value) {
55
+ let decoded = "";
56
+ for (let index = 0; index < value.length; index++) {
57
+ if (value[index] !== "%") {
58
+ decoded += value[index];
59
+ continue;
60
+ }
61
+ const reserved = decodeReservedCharacter(value[index + 1] ?? "", value[index + 2] ?? "");
62
+ if (reserved === null) return value;
63
+ decoded += reserved;
64
+ index += 2;
65
+ }
66
+ return decoded;
67
+ }
68
+ function parsePattern(entry) {
69
+ const original = entry.path;
70
+ const isExactRootCatchAll = original === "*";
71
+ if (!isExactRootCatchAll && !original.startsWith("/")) throw new Error(`Client route pattern '${original}' must start with '/'`);
72
+ const pattern = original.length > 1 && original.endsWith("/") ? original.slice(0, -1) : original;
73
+ const segments = isExactRootCatchAll ? ["*"] : pattern === "/" ? [] : pattern.slice(1).split("/");
74
+ const tokens = [];
75
+ const parameterNames = [];
76
+ for (let index = 0; index < segments.length; index++) {
77
+ const segment = segments[index];
78
+ if (!segment) throw new Error(`Client route pattern '${original}' contains an empty segment`);
79
+ if (segment === "*") {
80
+ if (index !== segments.length - 1) throw new Error(`Client route pattern '${original}' has a non-terminal catch-all`);
81
+ tokens.push({ type: "catch-all" });
82
+ parameterNames.push("*");
83
+ continue;
84
+ }
85
+ if (segment.startsWith(":")) {
86
+ const name = segment.slice(1);
87
+ if (!PARAMETER_NAME.test(name)) throw new Error(`Client route pattern '${original}' has an unsupported parameter segment`);
88
+ if (parameterNames.includes(name)) throw new Error(`Client route pattern '${original}' repeats parameter '${name}'`);
89
+ tokens.push({
90
+ type: "parameter",
91
+ name
92
+ });
93
+ parameterNames.push(name);
94
+ continue;
95
+ }
96
+ if (segment.includes(":") || segment.includes("*") || segment.includes("?") || segment.includes("%")) throw new Error(`Client route pattern '${original}' contains unsupported syntax`);
97
+ tokens.push({
98
+ type: "static",
99
+ value: segment
100
+ });
101
+ }
102
+ let source = "^";
103
+ for (let index = 0; index < tokens.length; index++) {
104
+ const token = tokens[index];
105
+ if (token.type === "static") source += `/${escapeRegExp(token.value)}`;
106
+ if (token.type === "parameter") source += "/([^/]{1,100})";
107
+ if (token.type === "catch-all") source += isExactRootCatchAll ? "(.*)" : index === 0 ? "/(.*)" : "/(.+)";
108
+ }
109
+ if (tokens.length === 0) source += "/";
110
+ source += "$";
111
+ const collisionKey = tokens.map((token) => {
112
+ if (token.type === "static") return `s:${token.value.toLowerCase()}`;
113
+ if (token.type === "parameter") return "p";
114
+ return "w";
115
+ }).join("/");
116
+ return {
117
+ entry,
118
+ tokens,
119
+ parameterNames,
120
+ expression: new RegExp(source, "i"),
121
+ collisionKey
122
+ };
123
+ }
124
+ function compareSpecificity(left, right) {
125
+ const rank = (token) => {
126
+ if (!token || token.type === "static") return 3;
127
+ if (token.type === "parameter") return 2;
128
+ return 1;
129
+ };
130
+ const length = Math.max(left.tokens.length, right.tokens.length);
131
+ for (let index = 0; index < length; index++) {
132
+ const difference = rank(right.tokens[index]) - rank(left.tokens[index]);
133
+ if (difference !== 0) return difference;
134
+ }
135
+ return 0;
136
+ }
137
+ function compileRoutes(entries) {
138
+ const collisions = /* @__PURE__ */ new Map();
139
+ return entries.map((entry) => {
140
+ const route = parsePattern(entry);
141
+ const existing = collisions.get(route.collisionKey);
142
+ if (existing) throw new Error(`Client route patterns '${existing.path}' and '${entry.path}' collide under server matching`);
143
+ collisions.set(route.collisionKey, entry);
144
+ return route;
145
+ }).sort(compareSpecificity);
146
+ }
147
+ function matchClientRoute(entries, pathname) {
148
+ const routes = compileRoutes(entries);
149
+ const sanitized = sanitizePathname(pathname);
150
+ if (!sanitized) return null;
151
+ for (const route of routes) {
152
+ const match = route.expression.exec(sanitized.path);
153
+ if (!match) continue;
154
+ const params = {};
155
+ for (let index = 0; index < route.parameterNames.length; index++) {
156
+ const value = match[index + 1];
157
+ params[route.parameterNames[index]] = sanitized.shouldDecodeParameters ? decodeParameter(value) : value;
158
+ }
159
+ return {
160
+ entry: route.entry,
161
+ params
162
+ };
163
+ }
164
+ return null;
165
+ }
166
+
167
+ //#endregion
168
+ export { matchClientRoute };
169
+ //# sourceMappingURL=matcher.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcher.mjs","names":[],"sources":["../../../../../../../../web/src/client/runtime/matcher.ts"],"sourcesContent":["import type { ClientPageEntry, ClientRouteMatch } from \"./types\";\n\ntype RouteToken =\n | { readonly type: \"static\"; readonly value: string }\n | { readonly type: \"parameter\"; readonly name: string }\n | { readonly type: \"catch-all\" };\n\ntype CompiledRoute = {\n readonly entry: ClientPageEntry;\n readonly tokens: readonly RouteToken[];\n readonly parameterNames: readonly string[];\n readonly expression: RegExp;\n readonly collisionKey: string;\n};\n\ntype SanitizedPath = {\n readonly path: string;\n readonly shouldDecodeParameters: boolean;\n};\n\nconst PARAMETER_NAME = /^[A-Za-z0-9_]+$/;\nconst REGEXP_SPECIAL = /[.*+?^${}()|[\\]\\\\]/g;\n\nfunction escapeRegExp(value: string): string {\n return value.replace(REGEXP_SPECIAL, \"\\\\$&\");\n}\n\nfunction decodeReservedCharacter(high: string, low: string): string | null {\n const pair = `${high}${low}`.toUpperCase();\n const reserved: Readonly<Record<string, string>> = {\n \"23\": \"#\",\n \"24\": \"$\",\n \"25\": \"%\",\n \"26\": \"&\",\n \"2B\": \"+\",\n \"2C\": \",\",\n \"2F\": \"/\",\n \"3A\": \":\",\n \"3B\": \";\",\n \"3D\": \"=\",\n \"3F\": \"?\",\n \"40\": \"@\",\n };\n\n return reserved[pair] ?? null;\n}\n\nfunction sanitizePathname(pathname: string): SanitizedPath | null {\n if (typeof pathname !== \"string\" || !pathname.startsWith(\"/\")) {\n throw new Error(\"Client route pathname must start with '/'\");\n }\n\n let path = pathname;\n let shouldDecode = false;\n let shouldDecodeParameters = false;\n\n for (let index = 1; index < path.length; index++) {\n if (path[index] !== \"%\") continue;\n\n const high = path[index + 1] ?? \"\";\n const low = path[index + 2] ?? \"\";\n const reserved = decodeReservedCharacter(high, low);\n\n if (reserved === null) {\n shouldDecode = true;\n continue;\n }\n\n shouldDecodeParameters = true;\n if (reserved === \"%\") {\n path = `${path.slice(0, index + 1)}25${path.slice(index + 1)}`;\n shouldDecode = true;\n index += 2;\n }\n index += 2;\n }\n\n try {\n if (shouldDecode) path = decodeURI(path);\n } catch {\n return null;\n }\n\n if (path.length > 1 && path.endsWith(\"/\")) path = path.slice(0, -1);\n\n return { path, shouldDecodeParameters };\n}\n\nfunction decodeParameter(value: string): string {\n let decoded = \"\";\n\n for (let index = 0; index < value.length; index++) {\n if (value[index] !== \"%\") {\n decoded += value[index];\n continue;\n }\n\n const reserved = decodeReservedCharacter(value[index + 1] ?? \"\", value[index + 2] ?? \"\");\n if (reserved === null) return value;\n\n decoded += reserved;\n index += 2;\n }\n\n return decoded;\n}\n\nfunction parsePattern(entry: ClientPageEntry): CompiledRoute {\n const original = entry.path;\n const isExactRootCatchAll = original === \"*\";\n if (!isExactRootCatchAll && !original.startsWith(\"/\")) {\n throw new Error(`Client route pattern '${original}' must start with '/'`);\n }\n\n const pattern = original.length > 1 && original.endsWith(\"/\")\n ? original.slice(0, -1)\n : original;\n const segments = isExactRootCatchAll\n ? [\"*\"]\n : pattern === \"/\"\n ? []\n : pattern.slice(1).split(\"/\");\n const tokens: RouteToken[] = [];\n const parameterNames: string[] = [];\n\n for (let index = 0; index < segments.length; index++) {\n const segment = segments[index];\n if (!segment) {\n throw new Error(`Client route pattern '${original}' contains an empty segment`);\n }\n\n if (segment === \"*\") {\n if (index !== segments.length - 1) {\n throw new Error(`Client route pattern '${original}' has a non-terminal catch-all`);\n }\n tokens.push({ type: \"catch-all\" });\n parameterNames.push(\"*\");\n continue;\n }\n\n if (segment.startsWith(\":\")) {\n const name = segment.slice(1);\n if (!PARAMETER_NAME.test(name)) {\n throw new Error(`Client route pattern '${original}' has an unsupported parameter segment`);\n }\n if (parameterNames.includes(name)) {\n throw new Error(`Client route pattern '${original}' repeats parameter '${name}'`);\n }\n tokens.push({ type: \"parameter\", name });\n parameterNames.push(name);\n continue;\n }\n\n if (segment.includes(\":\") || segment.includes(\"*\") || segment.includes(\"?\") || segment.includes(\"%\")) {\n throw new Error(`Client route pattern '${original}' contains unsupported syntax`);\n }\n tokens.push({ type: \"static\", value: segment });\n }\n\n let source = \"^\";\n for (let index = 0; index < tokens.length; index++) {\n const token = tokens[index];\n if (token.type === \"static\") source += `/${escapeRegExp(token.value)}`;\n if (token.type === \"parameter\") source += \"/([^/]{1,100})\";\n if (token.type === \"catch-all\") {\n source += isExactRootCatchAll ? \"(.*)\" : index === 0 ? \"/(.*)\" : \"/(.+)\";\n }\n }\n if (tokens.length === 0) source += \"/\";\n source += \"$\";\n\n const collisionKey = tokens\n .map((token) => {\n if (token.type === \"static\") return `s:${token.value.toLowerCase()}`;\n if (token.type === \"parameter\") return \"p\";\n return \"w\";\n })\n .join(\"/\");\n\n return {\n entry,\n tokens,\n parameterNames,\n expression: new RegExp(source, \"i\"),\n collisionKey,\n };\n}\n\nfunction compareSpecificity(left: CompiledRoute, right: CompiledRoute): number {\n const rank = (token: RouteToken | undefined): number => {\n if (!token || token.type === \"static\") return 3;\n if (token.type === \"parameter\") return 2;\n return 1;\n };\n\n const length = Math.max(left.tokens.length, right.tokens.length);\n for (let index = 0; index < length; index++) {\n const difference = rank(right.tokens[index]) - rank(left.tokens[index]);\n if (difference !== 0) return difference;\n }\n return 0;\n}\n\nfunction compileRoutes(entries: readonly ClientPageEntry[]): readonly CompiledRoute[] {\n const collisions = new Map<string, ClientPageEntry>();\n const routes = entries.map((entry) => {\n const route = parsePattern(entry);\n const existing = collisions.get(route.collisionKey);\n if (existing) {\n throw new Error(\n `Client route patterns '${existing.path}' and '${entry.path}' collide under server matching`,\n );\n }\n collisions.set(route.collisionKey, entry);\n return route;\n });\n\n return routes.sort(compareSpecificity);\n}\n\nexport function matchClientRoute(\n entries: readonly ClientPageEntry[],\n pathname: string,\n): ClientRouteMatch | null {\n const routes = compileRoutes(entries);\n const sanitized = sanitizePathname(pathname);\n if (!sanitized) return null;\n\n for (const route of routes) {\n const match = route.expression.exec(sanitized.path);\n if (!match) continue;\n\n const params: Record<string, string> = {};\n for (let index = 0; index < route.parameterNames.length; index++) {\n const value = match[index + 1];\n params[route.parameterNames[index]] = sanitized.shouldDecodeParameters\n ? decodeParameter(value)\n : value;\n }\n return { entry: route.entry, params };\n }\n\n return null;\n}\n"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ //#region ../web/src/client/runtime/types.d.ts
2
+ type ClientProjectedModule = Readonly<Record<string, unknown>>;
3
+ type ClientRouteComposition = {
4
+ readonly Page: ClientProjectedModule;
5
+ readonly layouts: readonly ClientProjectedModule[];
6
+ readonly App?: ClientProjectedModule;
7
+ };
8
+ type ClientRouteLoad = () => ClientRouteComposition | Promise<ClientRouteComposition>;
9
+ type ClientPageEntry = {
10
+ readonly type: "page";
11
+ readonly name: string;
12
+ readonly path: string;
13
+ readonly load: ClientRouteLoad;
14
+ };
15
+ type ClientRouteParams = Readonly<Record<string, string>>;
16
+ type ClientRouteMatch = {
17
+ readonly entry: ClientPageEntry;
18
+ readonly params: ClientRouteParams;
19
+ };
20
+ //#endregion
21
+ export { ClientPageEntry, ClientProjectedModule, ClientRouteComposition, ClientRouteLoad, ClientRouteMatch, ClientRouteParams };
22
+ //# sourceMappingURL=types.d.mts.map
@@ -0,0 +1,32 @@
1
+ import { useDocumentContext } from "./document-context.mjs";
2
+ import { Head } from "./head.mjs";
3
+ import { Scripts } from "./scripts.mjs";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+
6
+ //#region ../web/src/components/default-app.tsx
7
+ /**
8
+ * The framework's own root, used whenever an app defines no `root.tsx` (or
9
+ * omits a default export). The framework's default App is ALSO a full
10
+ * document — so every page renders a complete `<html>` regardless of
11
+ * whether the app customized its root. The app owns the document: when a
12
+ * custom `root.tsx` exists (`v5/app/src/web/root.tsx` is that example), it is
13
+ * fully responsible for `lang`/`dir`/`<Scripts nonce>`/providers/its own
14
+ * `<head>` tags. Only when no custom App exists does this fallback supply
15
+ * `lang`/`dir`/nonce itself, sourced from the framework's document-context
16
+ * slots rather than any app-owned data.
17
+ */
18
+ function DefaultApp({ children }) {
19
+ const { lang, dir } = useDocumentContext("DefaultApp");
20
+ return /* @__PURE__ */ jsxs("html", {
21
+ lang,
22
+ dir,
23
+ children: [/* @__PURE__ */ jsx("head", { children: /* @__PURE__ */ jsx(Head, {}) }), /* @__PURE__ */ jsxs("body", { children: [/* @__PURE__ */ jsx("div", {
24
+ id: "root",
25
+ children
26
+ }), /* @__PURE__ */ jsx(Scripts, {})] })]
27
+ });
28
+ }
29
+
30
+ //#endregion
31
+ export { DefaultApp as default };
32
+ //# sourceMappingURL=default-app.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-app.mjs","names":[],"sources":["../../../../../../../web/src/components/default-app.tsx"],"sourcesContent":["import type { ReactElement, ReactNode } from \"react\";\nimport { useDocumentContext } from \"./document-context\";\nimport { Head } from \"./head\";\nimport { Scripts } from \"./scripts\";\n\nexport type DefaultAppProps = {\n children: ReactNode;\n};\n\n/**\n * The framework's own root, used whenever an app defines no `root.tsx` (or\n * omits a default export). The framework's default App is ALSO a full\n * document — so every page renders a complete `<html>` regardless of\n * whether the app customized its root. The app owns the document: when a\n * custom `root.tsx` exists (`v5/app/src/web/root.tsx` is that example), it is\n * fully responsible for `lang`/`dir`/`<Scripts nonce>`/providers/its own\n * `<head>` tags. Only when no custom App exists does this fallback supply\n * `lang`/`dir`/nonce itself, sourced from the framework's document-context\n * slots rather than any app-owned data.\n */\nexport default function DefaultApp({ children }: DefaultAppProps): ReactElement {\n const { lang, dir } = useDocumentContext(\"DefaultApp\");\n\n return (\n <html lang={lang} dir={dir}>\n <head>\n <Head />\n </head>\n <body>\n {/*\n The hydration mount point (documented previously as\n `render-page.ts`'s own hardcoded shell, now moved here since the\n root supplies the whole document itself). An app writing its own\n custom `root.tsx` is responsible for its own equivalent if it wants\n a stable hydration target — this default only covers the\n no-custom-root case.\n */}\n <div id=\"root\">{children}</div>\n {/* Prop-less: inherits the nonce slot via Scripts' own fallback\n (scripts.ts) rather than reading document-context twice here. */}\n <Scripts />\n </body>\n </html>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAoBA,SAAwB,WAAW,EAAE,YAA2C;CAC9E,MAAM,EAAE,MAAM,QAAQ,mBAAmB,YAAY;CAErD,OACE,qBAAC,QAAD;EAAY;EAAW;YAAvB,CACE,oBAAC,QAAD,YACE,oBAAC,MAAD,CAAO,GACH,IACN,qBAAC,QAAD,aASE,oBAAC,OAAD;GAAK,IAAG;GAAQ;EAAc,IAG9B,oBAAC,SAAD,CAAU,EACN,IACF;;AAEV"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,24 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ //#region ../web/src/components/document-context.ts
4
+ const PAYLOAD_SCRIPT_ID = "__WARLOCK_DATA__";
5
+ const LINE_SEPARATOR = String.fromCharCode(8232);
6
+ const PARAGRAPH_SEPARATOR = String.fromCharCode(8233);
7
+ /** Escape JSON text for raw insertion into an application/json script. */
8
+ function escapePayload(json) {
9
+ return json.split("<").join("\\u003c").split(">").join("\\u003e").split(LINE_SEPARATOR).join("\\u2028").split(PARAGRAPH_SEPARATOR).join("\\u2029");
10
+ }
11
+ const DocumentContext = createContext(void 0);
12
+ /**
13
+ * Require the page pipeline's universal document state. The payload id and
14
+ * escaping helpers remain exported above for the existing server seam.
15
+ */
16
+ function useDocumentContext(componentName) {
17
+ const value = useContext(DocumentContext);
18
+ if (!value) throw new Error(`<${componentName}/> was rendered outside the page pipeline's document context (web/src/components/document-context.ts). Fix: only render it inside an App/Layout/Page component tree the pipeline itself renders.`);
19
+ return value;
20
+ }
21
+
22
+ //#endregion
23
+ export { DocumentContext, PAYLOAD_SCRIPT_ID, escapePayload, useDocumentContext };
24
+ //# sourceMappingURL=document-context.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-context.mjs","names":[],"sources":["../../../../../../../web/src/components/document-context.ts"],"sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { MetadataOutput } from \"../metadata\";\n\nexport type HydrationDocumentPayloadSource = {\n readonly appData: unknown;\n readonly layoutData: unknown;\n readonly pageData: unknown;\n readonly shared: unknown;\n /**\n * The matched page manifest entry's stable `name` — the same field the\n * manifest entry contract `{ type, name, path, load }` declares. It is on\n * the wire so the browser can look up WHICH page the server rendered\n * instead of re-matching `location.pathname` itself: re-matching is a\n * second implementation of route semantics, and it can disagree with the\n * server on the very request it is hydrating.\n */\n readonly name: string;\n /**\n * The params the SERVER matched for this request — `bundle.route.params`\n * (`server/execute-page-request.ts:288`), carried untransformed. Same reason\n * `name` is here: the browser must not re-derive them from\n * `location.pathname`, because deriving them IS a second matcher.\n *\n * OPTIONAL, and ungated on purpose — see {@link metadata} below for the rule\n * both new keys share. The server always emits it (`{}` for a route with no\n * dynamic segments), so absence means the payload came from a producer that\n * predates this key; `currentRoute()` then reports `{}` rather than failing a\n * page over an accessor.\n */\n readonly params?: Readonly<Record<string, string>>;\n /**\n * The page metadata the server resolved at stage 8, carried WHOLE — the same\n * `MetadataOutput` `<Head/>` rendered into the document on the first request.\n *\n * Why it has to be on the wire at all: `<Head/>` renders inside the App\n * level, and the App level is not part of the hydrated tree (the client\n * mounts at `#root`, which App contains). So on a client navigation there is\n * no React render that can reach `<head>` — without this key the browser\n * never learns the new page's title and the tab keeps the old one.\n *\n * OPTIONAL, deliberately: `bundle.metadata` is itself optional\n * (`server/execute-page-request.ts:296`) — a page that exports no `metadata`\n * produces none, and a loader short-circuit skips stage 8 entirely. Gating a\n * key the server is right not to produce would make `readHydrationPayload`\n * throw on a valid page. Present-but-not-an-object is still MALFORMED and\n * still throws; only ABSENT is accepted.\n */\n readonly metadata?: MetadataOutput;\n};\n\nexport const PAYLOAD_SCRIPT_ID = \"__WARLOCK_DATA__\";\n\nconst LINE_SEPARATOR = String.fromCharCode(0x2028);\nconst PARAGRAPH_SEPARATOR = String.fromCharCode(0x2029);\n\n/** Escape JSON text for raw insertion into an application/json script. */\nexport function escapePayload(json: string): string {\n return json\n .split(\"<\")\n .join(\"\\\\u003c\")\n .split(\">\")\n .join(\"\\\\u003e\")\n .split(LINE_SEPARATOR)\n .join(\"\\\\u2028\")\n .split(PARAGRAPH_SEPARATOR)\n .join(\"\\\\u2029\");\n}\n\n/**\n * What `<Head/>`/`<Scripts/>` need to render real elements instead of the\n * framework injecting them by string surgery post-render (Suki, room seq\n * 1205): the resolved page metadata and the exact payload the hydration\n * script will read back. Provided once, around the root element, before\n * `renderToString` runs (`render-page.ts`'s stage 9 — the bundle is already\n * complete by then). Universal: no server-only imports, so the client's\n * hydration entry (a later slice) can provide the same shape from the parsed\n * payload script.\n */\nexport type DocumentContextValue = {\n metadata: MetadataOutput | undefined;\n payload: HydrationDocumentPayloadSource;\n /**\n * The nonce/lang/dir SLOTS: fed by the render provider from CORE request\n * fields — request nonce, request locale — never from app-owned `shared`\n * keys, which an app can overwrite. The provider-side\n * wiring is a separate slice, so these are absent at runtime until it\n * lands; every reader must treat them as optional.\n */\n nonce?: string;\n lang?: string;\n dir?: string;\n};\n\nexport const DocumentContext = createContext<DocumentContextValue | undefined>(undefined);\n\n/**\n * Require the page pipeline's universal document state. The payload id and\n * escaping helpers remain exported above for the existing server seam.\n */\nexport function useDocumentContext(componentName: string): DocumentContextValue {\n const value = useContext(DocumentContext);\n\n if (!value) {\n throw new Error(\n `<${componentName}/> was rendered outside the page pipeline's document context ` +\n \"(web/src/components/document-context.ts). Fix: only render it inside \" +\n \"an App/Layout/Page component tree the pipeline itself renders.\",\n );\n }\n\n return value;\n}\n"],"mappings":";;;AAkDA,MAAa,oBAAoB;AAEjC,MAAM,iBAAiB,OAAO,aAAa,IAAM;AACjD,MAAM,sBAAsB,OAAO,aAAa,IAAM;;AAGtD,SAAgB,cAAc,MAAsB;CAClD,OAAO,KACJ,MAAM,GAAG,CAAC,CACV,KAAK,SAAS,CAAC,CACf,MAAM,GAAG,CAAC,CACV,KAAK,SAAS,CAAC,CACf,MAAM,cAAc,CAAC,CACrB,KAAK,SAAS,CAAC,CACf,MAAM,mBAAmB,CAAC,CAC1B,KAAK,SAAS;AACnB;AA2BA,MAAa,kBAAkB,cAAgD,MAAS;;;;;AAMxF,SAAgB,mBAAmB,eAA6C;CAC9E,MAAM,QAAQ,WAAW,eAAe;CAExC,IAAI,CAAC,OACH,MAAM,IAAI,MACR,IAAI,cAAc,iMAGpB;CAGF,OAAO;AACT"}
@@ -0,0 +1,14 @@
1
+ import { ReactElement } from "react";
2
+
3
+ //#region ../web/src/components/head.d.ts
4
+ /**
5
+ * OPTIONAL placement override for the framework's `<head>` injection — the
6
+ * page's `metadata` output, the stylesheet/preload tags and the
7
+ * canonical/alternate links land here when it is present, and at the default
8
+ * position when it is not (root.tsx:85-92). Takes no props: it says WHERE, and
9
+ * only that.
10
+ */
11
+ declare function Head(): ReactElement;
12
+ //#endregion
13
+ export { Head };
14
+ //# sourceMappingURL=head.d.mts.map
@@ -0,0 +1,63 @@
1
+ import { useDocumentContext } from "./document-context.mjs";
2
+ import { Fragment, createElement } from "react";
3
+
4
+ //#region ../web/src/components/head.ts
5
+ /**
6
+ * OPTIONAL placement override for the framework's `<head>` injection — the
7
+ * page's `metadata` output, the stylesheet/preload tags and the
8
+ * canonical/alternate links land here when it is present, and at the default
9
+ * position when it is not (root.tsx:85-92). Takes no props: it says WHERE, and
10
+ * only that.
11
+ */
12
+ function Head() {
13
+ const { metadata } = useDocumentContext("Head");
14
+ const keywords = metadata?.keywords === void 0 ? void 0 : Array.isArray(metadata.keywords) ? metadata.keywords.join(", ") : metadata.keywords;
15
+ const openGraph = metadata?.openGraph;
16
+ const ogTitle = openGraph?.title ?? metadata?.title;
17
+ const ogDescription = openGraph?.description ?? metadata?.description;
18
+ const twitter = metadata?.twitter;
19
+ return createElement(Fragment, null, createElement("meta", { charSet: "utf-8" }), metadata?.title !== void 0 ? createElement("title", null, metadata.title) : null, metadata?.description !== void 0 ? createElement("meta", {
20
+ name: "description",
21
+ content: metadata.description
22
+ }) : null, keywords !== void 0 ? createElement("meta", {
23
+ name: "keywords",
24
+ content: keywords
25
+ }) : null, metadata?.canonical !== void 0 ? createElement("link", {
26
+ rel: "canonical",
27
+ href: metadata.canonical
28
+ }) : null, metadata?.robots !== void 0 ? createElement("meta", {
29
+ name: "robots",
30
+ content: metadata.robots
31
+ }) : null, openGraph !== void 0 && ogTitle !== void 0 ? createElement("meta", {
32
+ property: "og:title",
33
+ content: ogTitle
34
+ }) : null, openGraph !== void 0 && ogDescription !== void 0 ? createElement("meta", {
35
+ property: "og:description",
36
+ content: ogDescription
37
+ }) : null, openGraph?.image !== void 0 ? createElement("meta", {
38
+ property: "og:image",
39
+ content: openGraph.image
40
+ }) : null, openGraph?.url !== void 0 ? createElement("meta", {
41
+ property: "og:url",
42
+ content: openGraph.url
43
+ }) : null, openGraph?.type !== void 0 ? createElement("meta", {
44
+ property: "og:type",
45
+ content: openGraph.type
46
+ }) : null, twitter?.card !== void 0 ? createElement("meta", {
47
+ name: "twitter:card",
48
+ content: twitter.card
49
+ }) : null, twitter?.title !== void 0 ? createElement("meta", {
50
+ name: "twitter:title",
51
+ content: twitter.title
52
+ }) : null, twitter?.description !== void 0 ? createElement("meta", {
53
+ name: "twitter:description",
54
+ content: twitter.description
55
+ }) : null, twitter?.image !== void 0 ? createElement("meta", {
56
+ name: "twitter:image",
57
+ content: twitter.image
58
+ }) : null);
59
+ }
60
+
61
+ //#endregion
62
+ export { Head };
63
+ //# sourceMappingURL=head.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"head.mjs","names":[],"sources":["../../../../../../../web/src/components/head.ts"],"sourcesContent":["import { createElement, Fragment, type ReactElement } from \"react\";\nimport { useDocumentContext } from \"./document-context\";\n\n/**\n * OPTIONAL placement override for the framework's `<head>` injection — the\n * page's `metadata` output, the stylesheet/preload tags and the\n * canonical/alternate links land here when it is present, and at the default\n * position when it is not (root.tsx:85-92). Takes no props: it says WHERE, and\n * only that.\n */\nexport function Head(): ReactElement {\n const { metadata } = useDocumentContext(\"Head\");\n\n const keywords =\n metadata?.keywords === undefined\n ? undefined\n : Array.isArray(metadata.keywords)\n ? metadata.keywords.join(\", \")\n : metadata.keywords;\n\n const openGraph = metadata?.openGraph;\n // Falls back to the top-level fields — the only two fallbacks the type declares.\n const ogTitle = openGraph?.title ?? metadata?.title;\n const ogDescription = openGraph?.description ?? metadata?.description;\n\n const twitter = metadata?.twitter;\n\n return createElement(\n Fragment,\n null,\n createElement(\"meta\", { charSet: \"utf-8\" }),\n metadata?.title !== undefined ? createElement(\"title\", null, metadata.title) : null,\n metadata?.description !== undefined\n ? createElement(\"meta\", { name: \"description\", content: metadata.description })\n : null,\n keywords !== undefined ? createElement(\"meta\", { name: \"keywords\", content: keywords }) : null,\n metadata?.canonical !== undefined\n ? createElement(\"link\", { rel: \"canonical\", href: metadata.canonical })\n : null,\n metadata?.robots !== undefined\n ? createElement(\"meta\", { name: \"robots\", content: metadata.robots })\n : null,\n openGraph !== undefined && ogTitle !== undefined\n ? createElement(\"meta\", { property: \"og:title\", content: ogTitle })\n : null,\n openGraph !== undefined && ogDescription !== undefined\n ? createElement(\"meta\", { property: \"og:description\", content: ogDescription })\n : null,\n openGraph?.image !== undefined\n ? createElement(\"meta\", { property: \"og:image\", content: openGraph.image })\n : null,\n openGraph?.url !== undefined\n ? createElement(\"meta\", { property: \"og:url\", content: openGraph.url })\n : null,\n openGraph?.type !== undefined\n ? createElement(\"meta\", { property: \"og:type\", content: openGraph.type })\n : null,\n twitter?.card !== undefined\n ? createElement(\"meta\", { name: \"twitter:card\", content: twitter.card })\n : null,\n twitter?.title !== undefined\n ? createElement(\"meta\", { name: \"twitter:title\", content: twitter.title })\n : null,\n twitter?.description !== undefined\n ? createElement(\"meta\", { name: \"twitter:description\", content: twitter.description })\n : null,\n twitter?.image !== undefined\n ? createElement(\"meta\", { name: \"twitter:image\", content: twitter.image })\n : null,\n );\n}\n"],"mappings":";;;;;;;;;;;AAUA,SAAgB,OAAqB;CACnC,MAAM,EAAE,aAAa,mBAAmB,MAAM;CAE9C,MAAM,WACJ,UAAU,aAAa,SACnB,SACA,MAAM,QAAQ,SAAS,QAAQ,IAC7B,SAAS,SAAS,KAAK,IAAI,IAC3B,SAAS;CAEjB,MAAM,YAAY,UAAU;CAE5B,MAAM,UAAU,WAAW,SAAS,UAAU;CAC9C,MAAM,gBAAgB,WAAW,eAAe,UAAU;CAE1D,MAAM,UAAU,UAAU;CAE1B,OAAO,cACL,UACA,MACA,cAAc,QAAQ,EAAE,SAAS,QAAQ,CAAC,GAC1C,UAAU,UAAU,SAAY,cAAc,SAAS,MAAM,SAAS,KAAK,IAAI,MAC/E,UAAU,gBAAgB,SACtB,cAAc,QAAQ;EAAE,MAAM;EAAe,SAAS,SAAS;CAAY,CAAC,IAC5E,MACJ,aAAa,SAAY,cAAc,QAAQ;EAAE,MAAM;EAAY,SAAS;CAAS,CAAC,IAAI,MAC1F,UAAU,cAAc,SACpB,cAAc,QAAQ;EAAE,KAAK;EAAa,MAAM,SAAS;CAAU,CAAC,IACpE,MACJ,UAAU,WAAW,SACjB,cAAc,QAAQ;EAAE,MAAM;EAAU,SAAS,SAAS;CAAO,CAAC,IAClE,MACJ,cAAc,UAAa,YAAY,SACnC,cAAc,QAAQ;EAAE,UAAU;EAAY,SAAS;CAAQ,CAAC,IAChE,MACJ,cAAc,UAAa,kBAAkB,SACzC,cAAc,QAAQ;EAAE,UAAU;EAAkB,SAAS;CAAc,CAAC,IAC5E,MACJ,WAAW,UAAU,SACjB,cAAc,QAAQ;EAAE,UAAU;EAAY,SAAS,UAAU;CAAM,CAAC,IACxE,MACJ,WAAW,QAAQ,SACf,cAAc,QAAQ;EAAE,UAAU;EAAU,SAAS,UAAU;CAAI,CAAC,IACpE,MACJ,WAAW,SAAS,SAChB,cAAc,QAAQ;EAAE,UAAU;EAAW,SAAS,UAAU;CAAK,CAAC,IACtE,MACJ,SAAS,SAAS,SACd,cAAc,QAAQ;EAAE,MAAM;EAAgB,SAAS,QAAQ;CAAK,CAAC,IACrE,MACJ,SAAS,UAAU,SACf,cAAc,QAAQ;EAAE,MAAM;EAAiB,SAAS,QAAQ;CAAM,CAAC,IACvE,MACJ,SAAS,gBAAgB,SACrB,cAAc,QAAQ;EAAE,MAAM;EAAuB,SAAS,QAAQ;CAAY,CAAC,IACnF,MACJ,SAAS,UAAU,SACf,cAAc,QAAQ;EAAE,MAAM;EAAiB,SAAS,QAAQ;CAAM,CAAC,IACvE,IACN;AACF"}