@ultimat3/cli 29.4.0 → 30.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 (448) hide show
  1. package/CLAUDE.md +64 -40
  2. package/README.md +61 -11
  3. package/package.json +42 -28
  4. package/src/affected-tests.ts +97 -0
  5. package/src/affected-unowned.ts +43 -0
  6. package/src/affected.ts +25 -6
  7. package/src/app-boundaries.ts +29 -1
  8. package/src/app-evals.ts +12 -4
  9. package/src/app-load-unresolved.ts +62 -0
  10. package/src/app-load.ts +30 -37
  11. package/src/app-mcp.ts +14 -2
  12. package/src/app-module-graph.ts +232 -0
  13. package/src/app-reload-graph.ts +2 -2
  14. package/src/app-root.ts +37 -1
  15. package/src/bin.ts +1 -0
  16. package/src/boundary-findings.ts +6 -0
  17. package/src/budgets-freshness.ts +32 -0
  18. package/src/budgets.ts +20 -1
  19. package/src/build-capture-env.ts +9 -0
  20. package/src/cdp-shot-a11y.ts +2 -39
  21. package/src/ci-tested.ts +243 -0
  22. package/src/client-emit-customer-runtime.ts +139 -0
  23. package/src/client-emit-customer.ts +108 -0
  24. package/src/client-emit-drift.ts +72 -0
  25. package/src/client-emit-header.ts +67 -0
  26. package/src/client-emit-schemas.ts +175 -0
  27. package/src/client-emit-types.ts +121 -0
  28. package/src/client-emit-vendor.ts +146 -0
  29. package/src/client-errors-routes.ts +229 -0
  30. package/src/client-errors-script.ts +74 -0
  31. package/src/client-vitals-route.ts +197 -0
  32. package/src/client-vitals-script.ts +56 -0
  33. package/src/cmd-affected.ts +23 -0
  34. package/src/cmd-build-spec.ts +7 -1
  35. package/src/cmd-build.ts +35 -9
  36. package/src/cmd-ci-spec.ts +42 -6
  37. package/src/cmd-ci.ts +99 -2
  38. package/src/cmd-client-customer.ts +134 -0
  39. package/src/cmd-client-source.ts +81 -0
  40. package/src/cmd-client-spec.ts +48 -0
  41. package/src/cmd-client-write.ts +41 -0
  42. package/src/cmd-client.ts +111 -0
  43. package/src/cmd-db-recount.ts +120 -0
  44. package/src/cmd-db-spec.ts +36 -6
  45. package/src/cmd-db.ts +11 -3
  46. package/src/cmd-deploy-compose.ts +61 -3
  47. package/src/cmd-deploy-drain.ts +10 -2
  48. package/src/cmd-deploy-helm.ts +11 -0
  49. package/src/cmd-deploy-preflight.ts +118 -0
  50. package/src/cmd-deploy-spec.ts +58 -6
  51. package/src/cmd-deploy-verify.ts +380 -0
  52. package/src/cmd-deploy.ts +42 -9
  53. package/src/cmd-docs.ts +34 -4
  54. package/src/cmd-doctor.ts +66 -9
  55. package/src/cmd-drill-spec.ts +25 -0
  56. package/src/cmd-drill.ts +176 -0
  57. package/src/cmd-env-init.ts +102 -0
  58. package/src/cmd-env-spec.ts +10 -3
  59. package/src/cmd-env.ts +15 -10
  60. package/src/cmd-errors-recent.ts +60 -0
  61. package/src/cmd-errors-spec.ts +9 -1
  62. package/src/cmd-errors.ts +4 -0
  63. package/src/cmd-exercise-spec.ts +19 -0
  64. package/src/cmd-exercise.ts +129 -0
  65. package/src/cmd-generate-spec.ts +9 -1
  66. package/src/cmd-generate.ts +32 -27
  67. package/src/cmd-graph-load.ts +97 -0
  68. package/src/cmd-graph-spec.ts +16 -0
  69. package/src/cmd-graph.ts +73 -0
  70. package/src/cmd-i18n.ts +4 -1
  71. package/src/cmd-impact-spec.ts +13 -0
  72. package/src/cmd-impact.ts +64 -0
  73. package/src/cmd-jobs-resolve.ts +76 -0
  74. package/src/cmd-jobs-spec.ts +21 -2
  75. package/src/cmd-jobs.ts +10 -6
  76. package/src/cmd-mail-spec.ts +28 -0
  77. package/src/cmd-mail.ts +70 -0
  78. package/src/cmd-mcp-lock.ts +69 -0
  79. package/src/cmd-mcp-spec.ts +23 -4
  80. package/src/cmd-mcp.ts +17 -3
  81. package/src/cmd-new-spec.ts +8 -1
  82. package/src/cmd-new.ts +61 -9
  83. package/src/cmd-planned.ts +0 -6
  84. package/src/cmd-repro-spec.ts +31 -0
  85. package/src/cmd-repro.ts +174 -0
  86. package/src/cmd-stop-spec.ts +21 -0
  87. package/src/cmd-stop.ts +166 -0
  88. package/src/cmd-test-spec.ts +17 -4
  89. package/src/cmd-test.ts +70 -14
  90. package/src/cmd-upgrade-spec.ts +26 -0
  91. package/src/cmd-upgrade.ts +274 -0
  92. package/src/cmd-verify-spec.ts +3 -3
  93. package/src/db-backfill.ts +6 -0
  94. package/src/db-diagnostics.ts +106 -0
  95. package/src/db-generate.ts +9 -0
  96. package/src/db-reset-template.ts +1 -1
  97. package/src/db-rolling.ts +45 -0
  98. package/src/db-seed-generate.ts +188 -0
  99. package/src/deploy-verify-findings.ts +98 -0
  100. package/src/deploy-verify-flags.ts +109 -0
  101. package/src/deploy-verify-grade.ts +185 -0
  102. package/src/deploy-verify-queues.ts +53 -0
  103. package/src/dev-boot.ts +35 -15
  104. package/src/dev-child-watch.ts +16 -4
  105. package/src/dev-dashboard-guard.ts +8 -1
  106. package/src/dev-dashboard.ts +7 -0
  107. package/src/dev-lock-identity.ts +61 -0
  108. package/src/dev-lock-port-holder.ts +80 -0
  109. package/src/dev-lock-steal.ts +124 -0
  110. package/src/dev-lock.ts +92 -120
  111. package/src/dev-mail-templates.ts +51 -0
  112. package/src/dev-rebuild.ts +26 -2
  113. package/src/dev-route-table.ts +17 -0
  114. package/src/dev-supervisor.ts +5 -2
  115. package/src/dev-watch-tree.ts +11 -10
  116. package/src/dev-watch.ts +1 -1
  117. package/src/dispatch.ts +29 -6
  118. package/src/docs-section.ts +69 -0
  119. package/src/docs-sources.ts +100 -0
  120. package/src/doctor-auth.ts +11 -2
  121. package/src/doctor-chart-alerts.ts +55 -0
  122. package/src/doctor-pooler.ts +107 -0
  123. package/src/doctor-rolling.ts +35 -0
  124. package/src/doctor-row-security.ts +40 -0
  125. package/src/document-options.ts +13 -0
  126. package/src/drill-sync-lane.ts +25 -0
  127. package/src/drill-targets.ts +39 -0
  128. package/src/duplicate-packages.ts +1 -1
  129. package/src/env-production.ts +93 -0
  130. package/src/error-codes-shot.ts +59 -0
  131. package/src/error-codes-tooling.ts +29 -0
  132. package/src/error-codes.ts +100 -54
  133. package/src/error-contract.ts +2 -2
  134. package/src/errors.ts +22 -3
  135. package/src/exercise-database.ts +99 -0
  136. package/src/exercise-findings.ts +159 -0
  137. package/src/exercise-overrides.ts +42 -0
  138. package/src/exercise-registration.ts +58 -0
  139. package/src/exercise-static.ts +88 -0
  140. package/src/exercise-subject.ts +69 -0
  141. package/src/failure-capture-boot.ts +30 -0
  142. package/src/framework-env.ts +7 -1
  143. package/src/framework-schema.ts +72 -5
  144. package/src/generate-catalog-locales.ts +19 -0
  145. package/src/generate-chart-alerts.ts +156 -0
  146. package/src/generate-feature-required.ts +65 -0
  147. package/src/generate-feature.ts +83 -6
  148. package/src/generate-files.ts +24 -0
  149. package/src/generate-format-written.ts +32 -0
  150. package/src/generate-kinds.ts +6 -0
  151. package/src/generate-sign-in-config.ts +100 -0
  152. package/src/generate-sign-in-manifest.ts +28 -0
  153. package/src/generate-sign-in-mode.ts +101 -0
  154. package/src/generate-sign-in.ts +250 -0
  155. package/src/generate-write.ts +44 -7
  156. package/src/hand-rolled-socket.ts +72 -0
  157. package/src/i18n-app-key-shapes.ts +29 -0
  158. package/src/i18n-audit.ts +10 -3
  159. package/src/i18n-registration.ts +18 -3
  160. package/src/index.ts +2 -0
  161. package/src/island-bundle.ts +4 -3
  162. package/src/island-disposer.ts +116 -0
  163. package/src/island-realtime-state.ts +34 -6
  164. package/src/island-realtime.ts +6 -2
  165. package/src/island-routes.ts +3 -2
  166. package/src/isr-query-advice.ts +6 -11
  167. package/src/isr-query-guard.ts +4 -3
  168. package/src/job-budget.ts +95 -0
  169. package/src/jobs-json.ts +11 -0
  170. package/src/lint-findings.ts +104 -0
  171. package/src/local-cli.ts +34 -4
  172. package/src/loopback-host.ts +2 -2
  173. package/src/mail-preview-findings.ts +24 -0
  174. package/src/mcp-errors.ts +82 -2
  175. package/src/mcp-host.ts +11 -1
  176. package/src/mcp-lock-findings.ts +57 -0
  177. package/src/measure-paths.ts +12 -4
  178. package/src/measure-scope.ts +27 -13
  179. package/src/messages-client.ts +23 -0
  180. package/src/messages-db.ts +56 -0
  181. package/src/messages-gate.ts +107 -0
  182. package/src/messages-repro.ts +11 -0
  183. package/src/messages-shot.ts +51 -0
  184. package/src/messages-upgrade.ts +45 -0
  185. package/src/messages.ts +53 -178
  186. package/src/metrics-endpoint.ts +23 -5
  187. package/src/new-shapes-generate.ts +71 -0
  188. package/src/new-shapes.ts +63 -0
  189. package/src/openapi-compile-ir.ts +68 -0
  190. package/src/openapi-compile-schema.ts +284 -0
  191. package/src/openapi-compile.ts +273 -0
  192. package/src/openapi-refuse.ts +45 -0
  193. package/src/otlp-export.ts +2 -1
  194. package/src/output-findings.ts +123 -0
  195. package/src/output.ts +32 -8
  196. package/src/package-boundaries.ts +110 -0
  197. package/src/page-pins.ts +23 -0
  198. package/src/plain-routes.ts +13 -0
  199. package/src/prerender-entries.ts +59 -0
  200. package/src/prerender-heaviest.ts +27 -0
  201. package/src/prerender-preload.ts +6 -0
  202. package/src/prerender-report.ts +12 -0
  203. package/src/prerender-seo-reads.ts +38 -0
  204. package/src/prerender-served.ts +22 -7
  205. package/src/prerender-vitals.ts +42 -0
  206. package/src/prerender.ts +61 -32
  207. package/src/pwa-artifacts.ts +4 -3
  208. package/src/pwa-manifest-locales.ts +1 -1
  209. package/src/registry.ts +18 -0
  210. package/src/repro-bundle.ts +65 -0
  211. package/src/repro-errors.ts +47 -0
  212. package/src/repro-job.ts +72 -0
  213. package/src/repro-literal.ts +41 -0
  214. package/src/repro-request.ts +48 -0
  215. package/src/repro-write.ts +100 -0
  216. package/src/retain-sweep.ts +27 -0
  217. package/src/retained-assets-boot.ts +113 -0
  218. package/src/retained-assets-layout.ts +110 -0
  219. package/src/retained-assets-read.ts +142 -0
  220. package/src/retained-assets-sweep.ts +70 -0
  221. package/src/retained-assets.ts +171 -0
  222. package/src/revalidated-response.ts +8 -5
  223. package/src/role-replicator.ts +7 -1
  224. package/src/role-start-devices.ts +119 -0
  225. package/src/role-start-types.ts +6 -0
  226. package/src/role-start.ts +22 -5
  227. package/src/role-sync.ts +7 -1
  228. package/src/route-islands.ts +42 -0
  229. package/src/route-table.ts +2 -1
  230. package/src/row-security-boot.ts +58 -0
  231. package/src/row-security-wanted.ts +32 -0
  232. package/src/runtime-admin.ts +0 -1
  233. package/src/runtime-assets-site-cache.ts +55 -0
  234. package/src/runtime-assets-site.ts +136 -0
  235. package/src/runtime-assets.ts +99 -55
  236. package/src/runtime-bindings.ts +19 -3
  237. package/src/runtime-cache.ts +23 -14
  238. package/src/runtime-isr-outcome.ts +114 -60
  239. package/src/runtime-purge.ts +14 -2
  240. package/src/runtime-queue.ts +31 -4
  241. package/src/runtime-render-document.ts +204 -0
  242. package/src/runtime-render.ts +48 -198
  243. package/src/runtime-replica-position.ts +62 -0
  244. package/src/runtime-replica.ts +18 -3
  245. package/src/runtime-s3-path-style.ts +17 -0
  246. package/src/runtime-services.ts +19 -6
  247. package/src/runtime-statement-bound.ts +22 -0
  248. package/src/runtime-storage-upload.ts +9 -0
  249. package/src/runtime-storage.ts +41 -81
  250. package/src/schema-diff-views.ts +54 -0
  251. package/src/schema-diff.ts +21 -3
  252. package/src/schema-drift.ts +28 -9
  253. package/src/schema-dump-drift.ts +91 -7
  254. package/src/script-csp.ts +1 -1
  255. package/src/seo-cards.ts +26 -0
  256. package/src/seo-routes.ts +22 -8
  257. package/src/serve-boot.ts +54 -21
  258. package/src/serve-drain.ts +1 -0
  259. package/src/serve-env.ts +26 -8
  260. package/src/serve-retire.ts +12 -6
  261. package/src/serve-web.ts +42 -4
  262. package/src/serve.ts +9 -7
  263. package/src/site-asset-routes.ts +7 -1
  264. package/src/site-config.ts +4 -0
  265. package/src/site-llms-pages.ts +106 -0
  266. package/src/site-llms.ts +64 -0
  267. package/src/site-seo-write.ts +4 -2
  268. package/src/site-seo.ts +55 -6
  269. package/src/source-mtime.ts +50 -0
  270. package/src/static-capture-errors.ts +41 -0
  271. package/src/static-capture.ts +56 -0
  272. package/src/static-cards-serve.ts +39 -0
  273. package/src/static-cards.ts +93 -0
  274. package/src/static-document.ts +14 -13
  275. package/src/static-images.ts +115 -0
  276. package/src/stream-hole-scope.ts +55 -0
  277. package/src/style-bundle.ts +1 -1
  278. package/src/style-routes.ts +3 -2
  279. package/src/sw-precache-plan.ts +2 -2
  280. package/src/sw-routes.ts +2 -2
  281. package/src/templates/action-tests.ts +306 -0
  282. package/src/templates/action.ts +81 -269
  283. package/src/templates/backfill.ts +1 -1
  284. package/src/templates/entity.ts +21 -5
  285. package/src/templates/github/ci.yml.ts +8 -9
  286. package/src/templates/guard-bare-error.ts +2 -2
  287. package/src/templates/guard-error-declared.ts +263 -0
  288. package/src/templates/guard-native-confirm.ts +132 -0
  289. package/src/templates/guard-raw-fetch.ts +151 -0
  290. package/src/templates/guard-raw-interval.ts +121 -0
  291. package/src/templates/guard-repo-raw-sql.ts +71 -2
  292. package/src/templates/job.ts +1 -1
  293. package/src/templates/naming.ts +5 -0
  294. package/src/templates/pinned/docker-compose.yml +6 -0
  295. package/src/templates/pinned-images.ts +8 -0
  296. package/src/templates/policy.ts +85 -2
  297. package/src/templates/query.ts +70 -13
  298. package/src/templates/resource-create.ts +3 -1
  299. package/src/templates/resource-form-island.ts +15 -4
  300. package/src/templates/resource-page-db.ts +83 -0
  301. package/src/templates/resource-page.ts +161 -52
  302. package/src/templates/route.ts +4 -6
  303. package/src/templates/scaffold-app.ts +3 -0
  304. package/src/templates/scaffold-auth.ts +2 -1
  305. package/src/templates/scaffold-bin.ts +5 -52
  306. package/src/templates/scaffold-claude-agents.ts +19 -19
  307. package/src/templates/scaffold-claude-commands.ts +28 -31
  308. package/src/templates/scaffold-claude.ts +26 -23
  309. package/src/templates/scaffold-container-compose.ts +6 -1
  310. package/src/templates/scaffold-container.ts +24 -4
  311. package/src/templates/scaffold-dashboard-bare.ts +3 -3
  312. package/src/templates/scaffold-dashboard-example-tests.ts +49 -9
  313. package/src/templates/scaffold-dashboard-example-view.ts +156 -0
  314. package/src/templates/scaffold-dashboard-example.ts +43 -131
  315. package/src/templates/scaffold-dashboard-shared.ts +3 -7
  316. package/src/templates/scaffold-db-client.ts +20 -6
  317. package/src/templates/scaffold-db-package.ts +5 -14
  318. package/src/templates/scaffold-docs.ts +73 -57
  319. package/src/templates/scaffold-domain-package.ts +18 -14
  320. package/src/templates/scaffold-entries.ts +7 -2
  321. package/src/templates/scaffold-exercise.ts +37 -0
  322. package/src/templates/scaffold-guards.ts +13 -1
  323. package/src/templates/scaffold-helm-alerts.ts +234 -0
  324. package/src/templates/scaffold-helm-templates.ts +7 -5
  325. package/src/templates/scaffold-helm.ts +46 -12
  326. package/src/templates/scaffold-i18n.ts +6 -2
  327. package/src/templates/scaffold-mcp-endpoint.ts +182 -0
  328. package/src/templates/scaffold-mcp-json.ts +31 -0
  329. package/src/templates/scaffold-mcp-package.ts +59 -56
  330. package/src/templates/scaffold-repo.ts +7 -2
  331. package/src/templates/scaffold-shell.ts +45 -79
  332. package/src/templates/scaffold-test-services.ts +13 -5
  333. package/src/templates/shapes/api-keys.ts +170 -0
  334. package/src/templates/shapes/api.ts +101 -0
  335. package/src/templates/shapes/blog-site.ts +340 -0
  336. package/src/templates/shapes/blog-slice.ts +265 -0
  337. package/src/templates/shapes/blog.ts +114 -0
  338. package/src/templates/shapes/dashboard-detail.ts +242 -0
  339. package/src/templates/shapes/dashboard.ts +53 -0
  340. package/src/templates/shapes/index.ts +17 -0
  341. package/src/templates/shapes/overlay.ts +99 -0
  342. package/src/templates/shapes/shared-edits.ts +46 -0
  343. package/src/templates/sign-in-action-tests.ts +237 -0
  344. package/src/templates/sign-in-authenticator.ts +112 -0
  345. package/src/templates/sign-in-oidc.ts +189 -0
  346. package/src/templates/sign-in-pages.ts +148 -0
  347. package/src/templates/sign-in-password.ts +216 -0
  348. package/src/templates/sign-in-shared.ts +268 -0
  349. package/src/templates/slice-foundation.ts +70 -15
  350. package/src/test-batches.ts +1 -1
  351. package/src/test-database.ts +1 -1
  352. package/src/test-shards.ts +7 -1
  353. package/src/test-workers.ts +18 -12
  354. package/src/tool-gate-names.ts +34 -0
  355. package/src/upgrade-agents-link.ts +49 -0
  356. package/src/upgrade-bun-floor.ts +225 -0
  357. package/src/upgrade-gate.ts +228 -0
  358. package/src/upgrade-pins.ts +63 -0
  359. package/src/upgrade-plan.ts +214 -0
  360. package/src/upgrade-registry.ts +53 -0
  361. package/src/upgrade-report.ts +139 -0
  362. package/src/upgrade-root.ts +74 -0
  363. package/src/verify-budgets-build.ts +118 -0
  364. package/src/verify-checks.ts +67 -24
  365. package/src/verify-coverage-run.ts +1 -1
  366. package/src/verify-e2e-lane.ts +41 -0
  367. package/src/verify-exercise.ts +64 -0
  368. package/src/verify-floor.ts +41 -0
  369. package/src/verify-merge.ts +19 -4
  370. package/src/verify-role-load.ts +2 -0
  371. package/src/verify-run.ts +28 -8
  372. package/src/verify-shard.ts +12 -2
  373. package/src/verify-skip-ceiling.ts +87 -0
  374. package/src/verify-step.ts +5 -0
  375. package/src/verify-tests.ts +25 -5
  376. package/src/view-drift.ts +55 -0
  377. package/src/worker-bundle.ts +3 -2
  378. package/src/workspace-graph.ts +38 -19
  379. package/src/write-generated.ts +43 -0
  380. package/src/zone-boot.ts +20 -0
  381. package/wiki/Actions.md +342 -0
  382. package/wiki/Admin-Dashboard.md +432 -0
  383. package/wiki/Agents.md +958 -0
  384. package/wiki/Auth.md +195 -0
  385. package/wiki/Bare-VM.md +111 -0
  386. package/wiki/Batching-And-Preloading.md +136 -0
  387. package/wiki/Building-Your-Own-Base.md +203 -0
  388. package/wiki/CI-Parallel-Gate.md +220 -0
  389. package/wiki/CLI-Reference.md +1894 -0
  390. package/wiki/Caching-And-Invalidation.md +255 -0
  391. package/wiki/Client-Data.md +194 -0
  392. package/wiki/Client-Navigation.md +513 -0
  393. package/wiki/Compared.md +93 -0
  394. package/wiki/Configuration.md +868 -0
  395. package/wiki/Contributing.md +12 -0
  396. package/wiki/Deploy-Recipes.md +122 -0
  397. package/wiki/Deployment.md +508 -0
  398. package/wiki/Entities-And-Migrations.md +1116 -0
  399. package/wiki/FAQ.md +175 -0
  400. package/wiki/Feature-Flags.md +65 -0
  401. package/wiki/Getting-Started.md +221 -0
  402. package/wiki/Home.md +160 -0
  403. package/wiki/I18n.md +239 -0
  404. package/wiki/Installation.md +206 -0
  405. package/wiki/Interface-Rules.md +339 -0
  406. package/wiki/Jobs-And-Workflows.md +783 -0
  407. package/wiki/Known-Gaps.md +185 -0
  408. package/wiki/MCP-And-AI.md +753 -0
  409. package/wiki/Mail.md +138 -0
  410. package/wiki/Migrating-An-Existing-App.md +594 -0
  411. package/wiki/Migrating-From-Django.md +54 -0
  412. package/wiki/Migrating-From-Laravel.md +53 -0
  413. package/wiki/Migrating-From-Node-Drizzle.md +53 -0
  414. package/wiki/Migrating-From-Rails.md +53 -0
  415. package/wiki/Migrations-And-Backfills.md +552 -0
  416. package/wiki/Money.md +338 -0
  417. package/wiki/N-Plus-One-Detection.md +197 -0
  418. package/wiki/Notify.md +208 -0
  419. package/wiki/Observability.md +245 -0
  420. package/wiki/Outbound-HTTP.md +109 -0
  421. package/wiki/PWA-And-Offline.md +343 -0
  422. package/wiki/Policies-And-Authz.md +251 -0
  423. package/wiki/Project-Layout.md +150 -0
  424. package/wiki/Queries-And-Live-Queries.md +429 -0
  425. package/wiki/Realtime.md +734 -0
  426. package/wiki/Resource-Management.md +121 -0
  427. package/wiki/Routes-And-Render-Modes.md +521 -0
  428. package/wiki/SEO.md +288 -0
  429. package/wiki/Scheduled-Tasks.md +175 -0
  430. package/wiki/Scraping.md +493 -0
  431. package/wiki/Shapes.md +63 -0
  432. package/wiki/Stability.md +97 -0
  433. package/wiki/Static-Assets.md +166 -0
  434. package/wiki/Storage-And-Uploads.md +404 -0
  435. package/wiki/Testing.md +1313 -0
  436. package/wiki/The-Eight-Primitives.md +247 -0
  437. package/wiki/Theming.md +362 -0
  438. package/wiki/Timezones-And-Dates.md +194 -0
  439. package/wiki/Troubleshooting.md +163 -0
  440. package/wiki/Tutorial-01-First-App.md +289 -0
  441. package/wiki/Tutorial-02-First-Feature.md +339 -0
  442. package/wiki/Tutorial-03-Auth-And-Admin.md +379 -0
  443. package/wiki/Tutorial-04-Jobs-And-Realtime.md +263 -0
  444. package/wiki/Tutorial-05-Deploy-Free.md +231 -0
  445. package/wiki/Tutorial-06-Growing-Up.md +158 -0
  446. package/wiki/UI-Components.md +156 -0
  447. package/wiki/Upgrading.md +3667 -0
  448. package/src/stored-object-headers.ts +0 -43
@@ -0,0 +1,521 @@
1
+ # Routes and render modes
2
+
3
+ A `route` is a URL + render mode + metadata + offline strategy. Render mode is a per-route property. SEO is enforced by the build, not described in a guide.
4
+
5
+ `As of 2026-08`. Stable API — semver from here ([Upgrading](Upgrading)).
6
+
7
+ ## The canonical shape
8
+
9
+ ```ts
10
+ export const config = defineRoute({
11
+ render: 'isr', // static | isr | ssr | stream
12
+ revalidate: { tags: [tag.post] },
13
+ prerender: () => db.posts.slugs(),
14
+ offline: 'precache', // precache | runtime | network-only
15
+ hydrate: 'visible', // idle | visible | interaction | never
16
+ budget: { js: '40kb' },
17
+ meta: ({ post }) => ({ title: post.title, description: post.excerpt,
18
+ og: { image: post.cover }, ld: ld.Article(post) }),
19
+ });
20
+ ```
21
+
22
+ | Aspect | Rule |
23
+ |---|---|
24
+ | Projects to | router entry, prerender list, `sw.js` precache/runtime rule, sitemap + RSS row, `<head>` + JSON-LD, per-route budget check |
25
+ | Owns | render mode, hydration timing, metadata, offline strategy |
26
+ | Never | touch the DB directly, hold business logic, or omit `meta.description` in `site/` — that is a build error |
27
+
28
+ ### `post` — a page URL that also answers `POST`
29
+
30
+ `defineRoute({ render: 'ssr', …, post: 'unsubscribeOnboarding' })` (`As of 22.6.0`) mounts `POST`
31
+ at the page's own path, answered by that action — its policy, idempotency and redirect — with the
32
+ URL's query merged over the posted fields (the query wins a shared name: it is the URL the server
33
+ minted). The RFC 8058 case: the mail client POSTs `List-Unsubscribe=One-Click` to `/correos/baja?t=…`
34
+ and the action receives `{ t, 'List-Unsubscribe': 'One-Click' }`; the page's `GET` stays the confirm
35
+ page, and its form can post to itself with no JavaScript. Declare optional input keys for what the
36
+ client posts. Not an export name, or on `render: 'static'`: `X_ROUTE_POST_INVALID` at declaration;
37
+ naming no registered action: the same code at boot.
38
+
39
+ ### `navigation` — how the client router treats this page
40
+
41
+ Only on a surface listed in `app.config.ts` `navigation.client` ([Client navigation](Client-Navigation));
42
+ anywhere else `X_ROUTE_NAVIGATION_INVALID` at boot, and any value but these two at declaration.
43
+
44
+ | Value | The router | The server, before `load` |
45
+ |---|---|---|
46
+ | absent | swaps it in on a click; never fetches it early | answers a prefetch `204`, runs nothing |
47
+ | `'prefetch'` | also fetches it on hover/focus | answers a prefetch like a visit. Only for a GET that does nothing but render |
48
+ | `'document'` | always a real document load, and the page carries no router (no script, no metas, no bytes charged) | answers a soft visit `204` + `x-ultimate-location`, runs nothing; the browser's load runs it once. For a GET that records something: an open, a download, a token consumed — and for a `0b` page |
49
+
50
+ ## The descriptor
51
+
52
+ `route` is the one primitive whose façade is a **normalized descriptor** rather than a set of projection methods, because a route declares no behaviour to project. It is read by the router, the prerenderer, the sitemap and the budget check — and is designed to be read by `sw.js` generation, which no build calls yet ([#362](https://github.com/developerz-ai/ultimate/issues/362)). The descriptor's job is to hand all of them **one shape** so none of them branches. There is no `.describe()` on a route either — `describePages()` is the one route list.
53
+
54
+ | Member | Is | Rule |
55
+ |---|---|---|
56
+ | `kind: 'route'` | the brand | lets the registry reject a non-route export. `isRouteConfig(value)` is the guard |
57
+ | `meta(data)` | the `<head>` producer | **always returns a promise.** A synchronous declared `meta` is wrapped, and a `meta` that throws synchronously becomes a rejection — so `await config.meta(data)` is the one way to fail as well as the one way to succeed, and no consumer branches on a thenable |
58
+ | `budget` | the per-route limits | **always an object**, `{}` when undeclared, rather than a second undefined-check at every call site |
59
+ | `render` `revalidate` `prerender` `offline` `hydrate` `policy` `cache` | the declaration, carried through | unchanged. Optional keys are omitted, never set to `undefined` |
60
+ | the whole object | `Object.freeze`d | no consumer can mutate the route another consumer is about to read |
61
+
62
+ **The always-present `budget` does not weaken the `site/` JS-budget check.** `budget` is always an object, but its *fields* stay optional — so `budget.js === undefined` still means "this route declared no JS budget", and a `site/` route that opts into any `hydrate` other than `never` without one is `X_ROUTE_MODE_INVALID`, exactly as before the normalization. Measuring the declared numbers against the built output is the separate concern in **Budgets**, below.
63
+
64
+ Validation runs at **module evaluation**. `defineRoute` checks the shape and the mode-local invariants immediately, so a bad route fails at build time rather than on the first request in production.
65
+
66
+ | Checked | Where | Code |
67
+ |---|---|---|
68
+ | `offline` present and a known strategy | `defineRoute` | `X_ROUTE_OFFLINE_MISSING` |
69
+ | `meta` is a function | `defineRoute` | `X_ROUTE_META_MISSING` |
70
+ | mode-local: known `render` and `hydrate`; `static` with a `policy` or a `revalidate`; `isr` with a `policy` or with no trigger; `ssr` with a `prerender`; `cache` on any mode but `ssr`, or a `public`/`immutable` `cache` on a gated route | `defineRoute` | `X_ROUTE_MODE_INVALID` |
71
+ | surface-dependent: mode allowed on the surface; `site/` hydration without `budget.js`; `prerender` on a non-prerenderable mode | `registerRoute` | `X_ROUTE_MODE_INVALID` |
72
+ | the config came from `defineRoute` and not straight from the author | `registerRoute` | `X_ROUTE_UNNORMALIZED` |
73
+ | two files claiming one URL | `registerRoute` | `X_ROUTE_DUPLICATE` |
74
+
75
+ The split is about what is knowable, not about strictness: everything decidable from the config alone is decided at import; the rest needs the file's surface, which only the route table knows.
76
+
77
+ ## Four render modes
78
+
79
+ | Mode | Behavior | Use |
80
+ |---|---|---|
81
+ | `static` | built once, served as a file | marketing, docs |
82
+ | `isr` | static + background regen on tag/TTL | catalogs, profiles |
83
+ | `ssr` | per-request full render | fresh SEO pages |
84
+ | `stream` | static shell flushed instantly, holes streamed | **default for app pages** |
85
+
86
+ **A served `static` page**, `As of 2026-09`: a matching `If-None-Match` is a bodiless `304`, and
87
+ the container renders each page once per locale and answers from memory after — no `load`, no
88
+ render — for a page with no dynamic segment, asked for with no query string, that answered 200.
89
+ `x dev` renders per request. No other mode is kept this way. What is kept is bounded twice —
90
+ 1,024 entries and 32 MiB of bodies (`STATIC_MEMO_MAX_BYTES`) — because the request origin is in the
91
+ key when the app declares none; past either bound a page is rendered per request.
92
+
93
+ | Surface | Default | Allowed |
94
+ |---|---|---|
95
+ | `site/` | `static` | `static`, `isr`, `ssr` |
96
+ | `app/` | `stream` | `stream`, `ssr` |
97
+ | `api/` | n/a | no rendering at all |
98
+
99
+ A mode outside a surface's allowed set is a build error, not a runtime fallback. Surfaces and their boundaries: [Project layout](Project-Layout).
100
+
101
+ **`spa` was the fifth mode and was deleted in 6.0.0.** It served `<body><div id="x-root"></div></body>` for the framework's whole history — 200, correct headers, blank page — because `renderSpa` preloaded a `chunks` array no build ever produced and never read the route's component. `render: 'spa'` is now `X_ROUTE_MODE_INVALID` at `defineRoute` time; the migration is one line, `render: 'ssr'`, in [Upgrading](Upgrading).
102
+
103
+ ## What a mode's headers are, and who has the last word
104
+
105
+ A render mode states the mode's **intent**; `@ultimat3/http`'s `cache-headers` stage makes the final answer. `As of 2026-08-23` that stage REVIEWS a `cache-control` the mode already wrote instead of standing down, which is what made the rule below reachable for a page at all.
106
+
107
+ | The request | What it gets, whatever the mode wrote |
108
+ |---|---|
109
+ | anonymous | the mode's own intent, plus the key dimensions it forgot: `vary: accept-language, cookie, x-timezone` |
110
+ | carrying an identity | `private, max-age=0` — never `public`, never `s-maxage`, and never bare freshness: `max-age`, `must-revalidate` or `proxy-revalidate` without `private`/`no-store` is an offer a shared cache may take (RFC 9111 §3), so it is rewritten too. `meta.auth` is only `'public' \| 'required'`, so the page that greets a signed-in visitor by name is a `'public'` route whose own header offered it to a CDN for 30 seconds |
111
+ | a content-addressed URL (`immutable`) | left alone. `immutable` asserts the body is a function of the URL, which is what an island chunk is |
112
+
113
+ ### A route's own `cache`, on `ssr`
114
+
115
+ `As of 2026-09-24`. An ungated `ssr` page is offered to a CDN for 30 seconds
116
+ (`public, max-age=0, s-maxage=30, stale-while-revalidate=300`). A page can be public and still
117
+ personal: a recipient landing addressed by a capability token in its path, or a verification page
118
+ that must never answer with an old result. `cache` replaces that default:
119
+
120
+ ```ts
121
+ export const config = defineRoute({
122
+ render: 'ssr',
123
+ offline: 'network-only',
124
+ cache: 'no-store', // or a CacheHint: { mode: 'public', sMaxAgeSeconds: 5 }
125
+ meta: ({ t }) => ({ title: t('verify.title'), description: t('verify.description') }),
126
+ });
127
+ ```
128
+
129
+ | | |
130
+ |---|---|
131
+ | `'no-store'` | `private, no-store`, the answer a gated page already gets. It carries the principal scope like every private document |
132
+ | a `CacheHint` | `@ultimat3/http`'s own type, without `tags`, emitted by `cacheControl()`. Its `vary` joins the defaults. The router checks its ages at mount (`X_CONFIG_INVALID`) |
133
+ | which modes | `ssr` only. `static` and `isr` are hashed or TTL'd documents whose headers are the mode itself. A `stream` is always `private, no-store`. On any of them `cache` is `X_ROUTE_MODE_INVALID` |
134
+ | a gated route | may narrow its cache (`'no-store'`, `{ mode: 'private', … }`), never widen it. `public` or `immutable` is `X_ROUTE_MODE_INVALID` |
135
+ | a signed-in visitor | the table above still applies. A declared `public` becomes `private, max-age=0` for them |
136
+
137
+ **A shared document is rendered as nobody.** `As of 2026-11-02`. `isr` and `static` run `load`, `meta` and the page with the anonymous actor, services rebuilt for it, the app's default zone, `ctx.url` at the document's own address, and no inbound headers at all, `ctx.ip` and `ctx.peer` `null` (`withAnonymousRequest`, `@ultimat3/http`). Whatever a stored page holds is answered to every later visitor, with public cache headers, so it varies on what its key carries: the URL and the locale. `useRequestHeader()` answers `null`; `setRedirect()` still works. A page that varies per visitor is `ssr`.
138
+
139
+ **No declared origin: an `isr` entry is keyed by the request's origin too.** `canonical` and `og:url` are absolute against the declared origin (`APP_URL`, `SITE_ORIGIN`, `site.origin`) or, with none, the request's own, so without this a first request with a foreign `Host` wrote that host into a page later visitors were answered. The origin rides the reserved `__x_origin` parameter. With a declared origin (http or https) there is no such parameter: one entry, and `load` is handed `url` at the declared origin. Without one, a process stores pages for 16 request origins (`ISR_MAX_REQUEST_ORIGINS`); any other `Host` is answered uncached, `private, no-store`, and evicts nothing (`isr.origin.over_cap`, logged once: declare `APP_URL`).
140
+
141
+ **An `isr` entry is keyed by the negotiated locale.** The store key is `isrKey(url, locale, origin?)` — the path in its one spelling (below), the reserved `__x_locale` parameter, then the query the route declared (`revalidate.query`, below) with its params sorted. Without it, an app shipping two locales served visitor 2 the document rendered for visitor 1, for the whole TTL, and told the CDN to do the same. The time zone is deliberately **not** a dimension — a locale set is declared and bounded, a zone list is not — so a date on an `isr` page belongs in a zone the page itself names, or the page belongs in `ssr`.
142
+
143
+ **An `isr` page is one entry however its URL is spelled.** The router answers `/blog//abc/`, `/blog/%61bc` and `/blog/abc` with the same route and the same `slug`, so the store keys on the address it **matched**, never on the pathname as written: empty segments and the trailing slash dropped, each segment decoded and re-encoded one way (`@ultimat3/http`'s `canonicalPath`, built from the functions the router itself segments and decodes with). `As of 2026-10-11`.
144
+
145
+ | Request | Stored under | Why |
146
+ |---|---|---|
147
+ | `/blog/abc`, `/blog/abc/`, `/blog//abc`, `//blog/abc`, `/blog/%61bc`, `/blog/./abc` | `/blog/abc` | one route, one `slug` |
148
+ | `/blog/caf%c3%a9`, `/blog/caf%C3%A9`, `/blog/café` | `/blog/caf%C3%A9` | hex case and raw non-ASCII are spellings |
149
+ | `/blog/a%2fb`, `/blog/a%2Fb` | `/blog/a%2Fb` | an encoded slash is part of the segment (`slug` is `a/b`), never a separator: `/blog/a/b` is another address, and a 404 for `/blog/[slug]` |
150
+ | `/en/blog/abc`, `/en//blog/%61bc/` | `/en/blog/abc` | the locale prefix is kept; a locale's home is `/en/` |
151
+ | `/shop/blog/abc` under `basePath: '/shop'` | `/blog/abc` | the route table has never heard of the mount |
152
+
153
+ - **`load`, `meta` and the page are handed that same address** as `url` (base path included), so the document's `canonical` is `/blog/abc` whichever spelling arrived first. A `static` page is rendered at its address too, as the export writes it; `ssr` and `stream` keep the URL as the visitor wrote it.
154
+ - **Nothing is redirected**, and no status changes: a spelling the router answered with a 200 is still a 200, with the canonical page's `cache-control`, purge keys and `canonical` link. Redirect at the ingress if you want one URL in a browser's address bar.
155
+ - **A key no route owns is never stored.** An entry takes its `ttl` and its tags from its route, so stored without one it was fresh for ever and on no tag edge. `isrController().serve()` renders it for the request that asked, `private, no-store`, and logs `isr.key.unowned` once. The same holds for a request the router matched as one route while its address reads back as another (`//en/x` is the `/[lang]/x` route; `/en/x` is the `/x` route in English): answered, never stored.
156
+ - Releases through 27.8.0 keyed on the raw pathname ([Known Gaps](Known-Gaps)).
157
+
158
+ **A locale-prefixed `isr` page is its route's page.** The key keeps the prefix — `/en/blog/a?__x_locale=en` is the document rendered for that URL — and the route it belongs to is found on the path with the routed prefix removed (`unlocalizedPath`), as the router matches it. So `/en/blog/a` takes `/blog/:slug`'s `revalidate.ttl` (`s-maxage` included), joins its `revalidate.tags`, and is regenerated in its own locale: a tag bust or the TTL expires every locale's copy of a page, stored 404s included. A page whose truth changes with the clock (`publishedAt <= now`) is written by nothing when the instant passes, so no tag is busted — it needs a `ttl`, and the `ttl` alone is enough in every locale. Releases through 27.6.1 matched no route for a prefixed page ([Known Gaps](Known-Gaps)): tag-only `s-maxage=60`, no tag edge, fresh for the life of the process.
159
+
160
+ **An `isr` regeneration holds its page for at most 30 s** (`DEFAULT_ISR_REGENERATE_DEADLINE_MS`; `isrController({ regenerateDeadlineMs })`, a whole number ≥ 1) `As of 2026-10-02`. A render that never settles used to pin its page for the life of the process: a missed page hung every later request, a stale one was served stale forever. Past the deadline the next request renders again; the hung render is not cancelled, and its late result is dropped if a newer one has started. A stored path takes its TTL and tags from the **most specific** route that matches it — segment by segment, static beats `:param` beats `*catch-all`, as the request router resolves it — so `/docs/7` is `/docs/:id`'s, never `/docs/*path`'s. A zero-length `ttl` (`'0s'`, `0`) is no trigger at all: `X_ROUTE_MODE_INVALID`.
161
+
162
+ ### `revalidate` — what an invalidation does, how old a copy may be, what the page varies on
163
+
164
+ `As of 2026-10-10`. Three optional keys beside `tags` and `ttl`, each refused at `defineRoute` when the controller could not act on it (`X_ROUTE_MODE_INVALID`).
165
+
166
+ ```ts
167
+ import { tag } from '@ultimat3/cache';
168
+ import { defineRoute } from '@ultimat3/render';
169
+
170
+ declare const posts: { page(n: number): Promise<{ readonly titles: readonly string[] }> };
171
+
172
+ export const config = defineRoute({
173
+ render: 'isr',
174
+ revalidate: {
175
+ tags: [tag('post')],
176
+ ttl: '10m',
177
+ onInvalidate: 'purge', // 'stale' (default) | 'purge'
178
+ maxStale: '1h', // past ttl + 1h a copy is not an answer; 0 (purge's default) is never
179
+ query: ['page'], // the only query parameters in the key; [] for none
180
+ },
181
+ offline: 'runtime',
182
+ load: ({ url }) => posts.page(Number(new URL(url).searchParams.get('page') ?? '1')),
183
+ meta: ({ url }) => ({ title: 'Blog', description: 'Every post, newest first.', canonical: url }),
184
+ });
185
+ ```
186
+
187
+ | Key | Default | What it decides |
188
+ |---|---|---|
189
+ | `onInvalidate: 'stale'` | yes | a tag bust marks the stored page stale; the next request is answered **that copy, however old**, while a fresh one renders behind it (`x-ultimate-isr: stale`). Right for a price list |
190
+ | `onInvalidate: 'purge'` | — | a tag bust **deletes** every stored page of the route carrying the tag — each locale, each keyed query, stored 404s included — and evicts any render of them in flight, so the next request renders and waits. For content that must come **down** (a withdrawn article, a corrected legal text). Needs `tags` **and `ttl`** (`X_ROUTE_MODE_INVALID` without either). The response carries no `stale-while-revalidate`, and its `s-maxage` is what is **left** of the `ttl` |
191
+ | `maxStale` | unbounded; **`0` under `'purge'`** | how long **past its `ttl`** a stored page may still be answered stale: a duration, or `0` for never. Older, it is dropped and the request waits for a render. Also the `stale-while-revalidate` a shared cache is told (a day when omitted, absent at `0`). Needs `ttl` |
192
+ | `query` | `[]`, none | the query parameters the page varies on. Only these are in the stored page's key **and** in the `url` / `query` that `load`, `meta` and the page receive; one value each (the first), sorted |
193
+
194
+ **An `isr` route keys on no query unless it says so.** `As of 2026-11-02`. `/blog?x=1`, `/blog?x=2` and `/blog?utm_source=mail` are one render and one stored page, so no visitor can spend the origin's renders or push real pages out of the bounded store (it holds 1,000), and `utm_*` / `fbclid` / `gclid` do not split a page. An `isr` document is shared: what it varies on is the route's to declare.
195
+
196
+ **Declare every parameter the page reads.** A page that reads `?currency=` and does not list it is ONE stored document, the first visitor's, answered to every other currency. Two nets, both best-effort: `x verify` reads each `isr` route module for `query.<name>`, `query['<name>']` and `searchParams.get('<name>')` and warns for a name the declaration omits (a read made through a helper in another file is not seen); and outside production, the `query` a page component receives warns once (`isr.query.unkeyed-read`) when the page reads a parameter the route did not declare. `load` and `meta` read the narrowed `url`, where an undeclared parameter is simply absent.
197
+
198
+ **Nothing is redirected.** `/blog?utm_source=mail` is answered with the `/blog` page: `load` and `meta` are given the narrowed URL, so `canonical: url` is `/blog` and a parameter outside the key cannot change the document stored under it. A declared parameter's **values** are still the visitor's — `?page=999999` is a key — so `load` answers `withStatus(404, …)` for one that names nothing, and the bounded store evicts the rest.
199
+
200
+ #### How fast a purge takes a page down
201
+
202
+ "The bust" below is the moment `invalidateTags` reaches its ISR step — after the read tiers (`redis`, `lru`, `request-memo`) cleared, so the re-render reads fresh rows. An action's `cache.invalidates` runs after its commit and is **not awaited by the action's response**, so the response can precede the bust by those tiers' latency.
203
+
204
+ | Deployment | A request after the bust gets | Bound |
205
+ |---|---|---|
206
+ | one replica | a fresh render — never the stored page, never a render that began before the bust (its flight is evicted; a request that had joined it renders again) | the next request |
207
+ | several replicas, per-replica stores, bus healthy | the same, on each replica **once it has received the broadcast** | broadcast delivery, typically milliseconds; inside it a peer still answers its copy as a hit |
208
+ | several replicas, per-replica stores, **broadcast lost** | a replica that never heard the bust answers its copy as a fresh hit until that copy's `ttl`, then renders (nothing stale past it: `maxStale` is `0` under `'purge'`) | the route's `ttl`, at the origin **and** at an edge in front (`s-maxage` is the ttl's remainder) |
209
+ | several replicas over one `runtime.isrStore` | a fresh render on every replica: the page is deleted from the one store, and a replica that began a render before the bust cannot write it back (`IsrStore.tagFence`) | the next request, bus or no bus |
210
+
211
+ Order inside one bust: read tiers (farthest first) → this origin's ISR pages → the broadcast to peers → the `cdn` tier **last**. Each replica that receives the broadcast purges the edge again after its own delete, so a peer that answered the old page inside the broadcast window is cleared from the edge too. A render that read its rows before the bust is answered to its own request `private, no-store`, with no purge key, and is never stored.
212
+
213
+ **Finish a rollout before relying on `'purge'`.** A replica still on a release before this one reads `onInvalidate` as nothing: it marks the page stale and answers it once more, with `stale-while-revalidate=86400`.
214
+
215
+ **Past `maxStale` the copy is dropped before the re-render**, so a render that fails there answers its 5xx rather than the old page — that is what the bound means. Ages are measured by each replica's own clock against the entry's `generatedAt`; over a shared store, skew between replicas moves `ttl` and `maxStale` by that skew.
216
+
217
+ **A shared store must be able to refuse a write.** `runtime.isrStore` under `'purge'` needs `IsrStore.tagFence` — `sample(entities)` before a render reads its rows, `bump(entities)` on a bust, and an **atomic** `setIfCurrent(entry, entities, sampled)` (a transaction, a script, a conditional put) that refuses a write whose reading moved. The generation is per tag *entity* (`post` for `post:1`), so an unrelated row's bust costs one extra render and never a stale page. `memoryIsrStore` implements it; a store without it is refused at boot when any route purges (`X_ROUTE_MODE_INVALID`, naming the route). A bust also asks the store which keys it holds under the tags, by the route table, as well as the invalidation graph — the graph only knows the pages this process rendered. It matches on the **route's** tags: a `post:1` bust reaches every stored page of a route tagged `post`.
218
+
219
+ **What `isr` never stores.** What it does store was rendered as nobody (above); a per-visitor answer has no place in one, so that page is `ssr`.
220
+
221
+ | Render | Stored | The visitor gets |
222
+ |---|---|---|
223
+ | a 5xx (`withStatus(503, …)` — "the read failed") | never; never over a good copy | the last good page, stale, while one exists (`isr.render.unstored` is logged); otherwise the 5xx itself, `private, no-store`. For one second (`DEFAULT_ISR_FAILURE_COOLDOWN_MS`; `isrController({ failureCooldownMs })`) requests for that page are answered the same failure, or the same stale page, instead of each rendering again |
224
+ | `noStore(data)` from `load` | never; a stored copy of the page is dropped | that render, `private, no-store` |
225
+ | a `load` or page that throws | never | the last good page, stale (`isr.regenerate.failed`); with none, the error page |
226
+ | a render invalidated while it ran | never | the request that started it: that render, `private, no-store`; any other request: a render of its own |
227
+ | a redirect (`setRedirect()`) | never | the redirect |
228
+ | a 2xx or 4xx | yes — a 404 is a stored page | it, for the `ttl`. A 4xx is never offered to a shared cache (`no-store`, no purge key): the pipeline's rule for every refusal |
229
+
230
+ `noStore` is `@ultimat3/render`'s, a mark on the data like `withStatus` and composable with it (`noStore(withStatus(404, data))`). `isr` only — an `ssr` page's cache is its route's `cache` key.
231
+
232
+
233
+ **A page with an island is admitted to its own CSP by hash.** The hydration runtime is an inline `<script type="module">`, and `script-src` admits it as one of the seven bodies `HYDRATE_RUNTIME_BODIES` enumerates, hashed at boot. Hashes and not a nonce because a `render: 'static'` page is a file on disk. The theme boot script is the second body the boot hashes, from `theme.defaultMode` (`As of 20.2.0`; an app writes neither the tag nor the hash). The speculation rules are the third (`As of 2026-09`, [Client navigation](Client-Navigation)). An app that adds its own inline script beyond those has to admit it the same way, at module scope in a file under `apps/*/`: `configureHttp({ security: { csp: { extend: { 'script-src': [cspHashSource(body)] } } } })`. The boot's own hashes are **merged per directive**, never replaced, so admitting a CDN source does not evict the hydration runtime's hash and lock every island out of the page.
234
+
235
+ ## One declaration of the vocabulary, at tier 0
236
+
237
+ `As of 2026-08` the three closed sets a route is declared in — `RENDER_MODES`, `OFFLINE_STRATEGIES`, `HYDRATE_STRATEGIES` — are declared **once**, in `@ultimat3/core`, with each union derived from its array so the pair cannot disagree.
238
+
239
+ | Import it from | When |
240
+ |---|---|
241
+ | `@ultimat3/core` | anywhere. It is tier 0, so no package is below it |
242
+ | `@ultimat3/render` | you are already importing `defineRoute` — it re-exports all three, types and arrays |
243
+ | `@ultimat3/http` · `@ultimat3/seo` · `@ultimat3/manifest` · `@ultimat3/pwa` | that package's own signatures take one, so it re-exports what it takes |
244
+
245
+ **Re-export, never restate.** Six packages each kept a hand-written copy until 2026-08 — 14 declarations in all — because imports only go down tiers and copying was the move available. `'spa'` was then deleted from one of them and five went on admitting it under a green project-wide typecheck; `@ultimat3/pwa`'s copy mapped it to `cache-first`, the one strategy that gives an `app/` route a **shared** cache entry. `bun run scripts/render-modes.ts --json` refuses a second declaration, matching on the literal set rather than the name — the copy that did the damage was called `PwaRenderMode`.
246
+
247
+ ## Why `stream` is the app default
248
+
249
+ An authed page needs fresh data and fast first paint. `ssr` gives freshness and a slow TTFB — the whole page waits for the slowest query. `stream` gives both halves: shell now, data as it resolves. A page whose body belongs in the browser declares an `island({ src })`, budgeted in real bytes; there is no client-side-only mode and no client router.
250
+
251
+ `x g route`, `x g resource` and `x new`'s dashboard write `render: 'stream'` on `app/`. A `stream` page with no hole registers and pays nothing: it is one flush.
252
+
253
+ ```tsx
254
+ import { Hole } from '@ultimat3/render';
255
+ import type { JSX } from 'solid-js';
256
+
257
+ // Yours: the components and the two reads.
258
+ type View = (props: { children?: JSX.Element; rows?: readonly unknown[] }) => JSX.Element;
259
+ declare const Layout: View, Header: View, Stats: View, Feed: View;
260
+ declare const StatsSkeleton: View, StatsUnavailable: View, FeedSkeleton: View;
261
+ declare const statsQuery: (input: object) => Promise<readonly unknown[]>;
262
+ declare const feedQuery: (input: object) => Promise<readonly unknown[]>;
263
+
264
+ // A thunk: it starts after the first flush, in the request's context. `useContext()`, `useT()` and
265
+ // the actor all work in it. A promise written straight into the JSX has started already.
266
+ const readStats = async () => <Stats rows={await statsQuery({})} />;
267
+ const readFeed = async () => <Feed rows={await feedQuery({})} />;
268
+
269
+ export function Page() {
270
+ return (
271
+ <Layout>
272
+ <Header /> {/* in the first flush */}
273
+ <Hole fallback={<StatsSkeleton />} error={<StatsUnavailable />}>
274
+ {readStats} {/* streamed when the query resolves */}
275
+ </Hole>
276
+ <Hole fallback={<FeedSkeleton />}>
277
+ {readFeed} {/* independent hole, independent flush */}
278
+ </Hole>
279
+ </Layout>
280
+ );
281
+ }
282
+ ```
283
+
284
+ Solid's fine-grained reactivity is what makes this cheap:
285
+
286
+ | Property | Consequence |
287
+ |---|---|
288
+ | No VDOM | streamed HTML is patched into place; nothing re-renders to reconcile |
289
+ | Signals, not component re-execution | a resolved hole updates the exact DOM nodes bound to it |
290
+ | **No hydration pass over the shell** | the static shell costs 0 hydration work — a `<Hole>` hydrates only its own island, only when its `hydrate` timing fires |
291
+ | Compile-time templates | the shell's JS is markup-shaped, not a component tree to replay |
292
+
293
+ In a VDOM framework a streaming shell still pays for hydrating the whole tree, which is why streaming there buys TTFB but not TBT. Here it buys both.
294
+
295
+ ### What a hole does
296
+
297
+ | Fact | Rule |
298
+ |---|---|
299
+ | `fallback` | markup in the first flush. Rendered at shell time; it is not weighed by `budgets`, so keep islands out of it |
300
+ | `error` | shown when the hole's children throw or miss the 15 s deadline (`DEFAULT_HOLE_TIMEOUT_MS`). Rendered at shell time. Omitted: an empty hidden element. Either way the document closes, `render.stream.hole_rejected` or `render.stream.hole_deadline` is logged and the error monitor hears it |
301
+ | Other modes, a test, `budgets` | no second flush exists, so `<Hole>` is its children, awaited inline, and a throw is the render's throw. `renderRoute` shows the filled page, and a hole's island is weighed |
302
+ | Nested | the inner hole renders inline inside the outer one: the set of holes is fixed at the first flush |
303
+ | Status, redirects, cookies | decided before the first flush. `withStatus`, `setRedirect` and `setCookie` stay `load`'s: inside a hole the last two are `X_STREAM_ALREADY_FLUSHED`, shown as the hole's error markup and reported. A throw in a hole is its error markup |
304
+ | Cancellation | `useContext().signal` inside a hole is the hole's own: aborted at its deadline and when the client leaves. Pass it on: `fetch(url, { signal: useContext().signal })` |
305
+ | A long wait | an empty comment every second while a hole is open keeps the socket's idle timer (`requestTimeoutMs` + 2 s) from cutting the stream |
306
+ | Async component | `X_ROUTE_ASYNC_PAGE` applies to `Page`, never to what renders under a hole |
307
+ | A hole's island | hydrates from its chunk: the chunk carries the hydration runtime after its reveal, and the realtime boot if this is the first island that needs it |
308
+ | A shell island while a hole is open | live: the runtime is `<script type="module" async>`, so it does not wait for parsing to end. With a realtime boot in the document it stays deferred ([Known gaps](Known-Gaps)) |
309
+ | Client router | a soft visit reads the whole answer before it swaps ([Known gaps](Known-Gaps)); every reveal still runs |
310
+ | CSP | every chunk's inline script is a body `inlineScriptSources()` already hashes |
311
+
312
+ Solid's own boundary component is not this: it throws outside a Solid renderer, and the server JSX factory is inert by design.
313
+
314
+ Independent holes each resolve their own [queries](Queries-And-Live-Queries); the tier-1 request memo means three holes reading the same query hit Postgres once.
315
+
316
+ ## `hydrate` timings
317
+
318
+ | Value | Wakes when | Use |
319
+ |---|---|---|
320
+ | `idle` | after first paint, on `requestIdleCallback`. A click made before the island mounts is captured and replayed on the same element, found by its path, as `interaction` does (21.0.0; the click was lost before). A keyboard or scripted click (`detail: 0`) is not hit-tested at `(0, 0)` | default in `app/` — above-the-fold interactivity |
321
+ | `visible` | the island intersects the viewport | below-the-fold lists, comment threads, charts |
322
+ | `interaction` | first pointer/focus/key event on the island | menus, modals, popovers, dropdowns |
323
+ | `never` | not at all — server HTML is final | static islands, rendered markdown, badges |
324
+
325
+ Hydration is per-island, never per-page. A blown `budget.js` is a build failure, so islands cannot quietly accumulate.
326
+
327
+ **One kind of island is hidden until it mounts, whatever the strategy** (`As of 2026-10`): on a signed-in page with a sync node, an island that reads realtime's record store or outbox is held (`data-x-hold`, `visibility:hidden`, its box kept), booted at once, and revealed when it mounts or after 3 s. An island that only follows a channel's events, a roster or the connection is not, and with scripting off none is. The rule and the list of names: [Realtime](Realtime#which-islands-wait-for-the-pages-restore).
328
+
329
+ ## Budgets
330
+
331
+ Per route, and one key: `budget.js`, in bytes. `budget.lcp` is deleted — nothing in the build observes a paint, so it was declared and never weighed.
332
+
333
+ **`budget.js` counts raw minified bytes on disk — uncompressed**, `As of 2026-09-05`: what the
334
+ browser parses and executes, never the gzip or brotli size it transfers. A 350 kB library is 350 kB
335
+ against the budget however well it compresses, because the number bounds execution cost, not
336
+ bandwidth. `measureDocumentJs` in `packages/cli/src/budgets.ts` is the one reader, and the
337
+ `X_BUDGET_EXCEEDED` line says `(minified, uncompressed)` so the unit is never inferred.
338
+
339
+ | Check | Source of truth |
340
+ |---|---|
341
+ | Per-route JS bytes | `budget.js` on the route; measured from the real bundle graph |
342
+ | Lighthouse SEO + a11y scores | Score thresholds are not implemented or enforced (`As of 2026-10`) |
343
+ | Precache size | total `sw.js` precache set, see [PWA and offline](PWA-And-Offline) |
344
+ | Regression | budgets ratchet — the recorded baseline can tighten, never loosen silently |
345
+
346
+ A blown budget is a **build failure**, not a warning:
347
+
348
+ ```
349
+ x verify
350
+ ✓ typecheck ✓ import boundaries ✓ migration drift
351
+ ✓ lint ✓ tests (6 types) ✓ contract diff
352
+ ✗ budgets
353
+ site/pricing js 61kb > 40kb (chart.js via shared/ui/button.tsx)
354
+ ```
355
+
356
+ Failures name the *cause* — the transitive import that added the bytes — because "bundle too big" without a chain is not an instruction. `x verify --json` emits the same content machine-readably.
357
+
358
+ ## SEO enforcement
359
+
360
+ | Concern | Enforcement |
361
+ |---|---|
362
+ | `meta.title` missing on an indexable route | build error `X_SEO_META_MISSING`, `cause` naming `title` and the file |
363
+ | `meta.description` missing on an indexable route | the **same** code, `X_SEO_META_MISSING`, naming `description` — one code for both fields, with `fix: add description to meta in <file>` |
364
+ | Title over 60 chars, description over 160 | build error `X_SEO_META_TOO_LONG`, with the measured length. A **too-short** description is not checked and no minimum is exported `As of 2026-08`: a 10-character description passes the gate. `DESCRIPTION_MIN_LENGTH` used to be exported and read by no validator, which is the shape a length bound must not ship in — `validateMeta` enforces every bound `@ultimat3/seo` exports, pinned by a test |
365
+ | Duplicate title/description across routes | build error — duplicate meta is a ranking bug, not a style issue |
366
+ | `og.image` missing on a shareable route | build error; the generated fallback OG image must be opted into explicitly |
367
+ | Broken internal link | build error, resolved against the route table |
368
+ | Missing `alt` on an `<Image>` | build error |
369
+ | Canonical URL | emitted for every route from the route table; never hand-written |
370
+ | `robots` / `noindex` | a route-level field, so "we shipped staging to Google" is impossible without editing the route |
371
+
372
+ A convention that isn't a build error doesn't exist. SEO is the archetype — every SEO regression in history was a documented convention someone forgot.
373
+
374
+ ## Typed JSON-LD
375
+
376
+ ```ts
377
+ meta: ({ post }) => ({ /* ... */ ld: ld.Article(post) })
378
+ ```
379
+
380
+ Helpers, typed against schema.org shapes:
381
+
382
+ | Helper | For |
383
+ |---|---|
384
+ | `ld.Article` | posts, news, docs pages |
385
+ | `ld.Product` | catalog and detail pages with price + availability |
386
+ | `ld.Organization` | the site's own identity, emitted once |
387
+ | `ld.BreadcrumbList` | derived from the route's path segments |
388
+ | `ld.FAQPage` | Q&A blocks |
389
+ | `ld.SoftwareApplication` | app landing pages |
390
+ | `ld.Event` | dated, located events |
391
+ | `ld.Recipe` | ingredient + step content |
392
+
393
+ A missing required property is a **type error**, not a Rich Results Test failure discovered next week. Output is a single `<script type="application/ld+json">`, deduped per page.
394
+
395
+ ## Generated from the route table
396
+
397
+ | Artifact | Derived from | Notes |
398
+ |---|---|---|
399
+ | `sitemap.xml` | all indexable routes + `prerender()` results | one `<url>` per page per locale with `xhtml:link` alternates; `lastmod` and the locales a page is listed in from its `prerender()` entry (`{ params, lastmod: row.updatedAt, locales }`); an index of named sections by `seo.sitemap.sections`, else auto-split at 50k URLs — [SEO](SEO#robotstxt-and-sitemapxml) |
400
+ | `robots.txt` | `seo.robots.disallow` and `seo.robots.groups` in `app.config.ts` | sitemap reference included; anything but `ULTIMATE_ENV=production` is `Disallow: /` |
401
+ | `rss.xml` / `atom.xml` / `feed.json` | routes tagged as feed items | one declaration, three formats |
402
+ | `llms.txt` | `site/` route titles + descriptions | machine-readable site summary for agents: `GET /llms.txt` and the static export, `seo.llms: false` to opt out ([SEO](SEO)) |
403
+ | `404` / `500` | required routes | missing one is a build error |
404
+
405
+ Nothing here is a plugin. Deleting a route removes it from the sitemap in the same build.
406
+
407
+ ## i18n routing
408
+
409
+ | Feature | Behavior |
410
+ |---|---|
411
+ | Locale routing | `/`, `/en/`, `/de/` from the locales `defineCatalogs()` declared — no per-route wiring. The prefix is stripped before matching; `/<default>/x` 301s to `/x`; an unrouted prefix is a plain 404 |
412
+ | `site/` locale | the URL alone: unprefixed is always the default locale, never negotiated; no `Vary: accept-language` |
413
+ | `hreflang` | full reciprocal set emitted per route, BCP 47 region form, including `x-default`; absolute against the public origin |
414
+ | Per-locale static output | each `site/` page is prerendered once per locale (`index.html`, `en/index.html`); no client-side locale swap on `site/` |
415
+ | Missing key | renders `⟦key⟧` in dev, **fails `x verify`** in CI |
416
+ | Numbers / dates / money | `Intl.*` with an explicit IANA `timeZone` and ISO currency; never a hand-rolled format |
417
+ | Localized metadata | `meta` receives `locale`, `localizedPath(locale)` and `alternates` |
418
+
419
+ See [I18n](I18n), [Timezones and dates](Timezones-And-Dates), [Money](Money).
420
+
421
+ ## Image pipeline
422
+
423
+ ```tsx
424
+ <Image src={post.cover} alt={post.title} sizes="(max-width: 700px) 100vw, 700px" priority />
425
+ ```
426
+
427
+ One `<Image>` shape, one capability contract, stated once — in [`docs/idea/07-rendering-seo.md` → Image pipeline](https://github.com/developerz-ai/ultimate/blob/main/docs/idea/07-rendering-seo.md#image-pipeline): variants, `srcset`, inlined dimensions, the blur placeholder, the `@ultimat3/core` runtime, and the `ImageTransformDriver` seam AVIF/WebP variants come from. Restating it here would let the two copies drift.
428
+
429
+ ## A route that answers a status
430
+
431
+ `As of 2026-09-07`. A loader hands its data through `withStatus(status, data)` and the page
432
+ renders as it always did — the route's own component, inside the app's own shell — with that
433
+ status on the response.
434
+
435
+ ```ts
436
+ import { defineRoute, withStatus } from '@ultimat3/render';
437
+
438
+ export const config = defineRoute({
439
+ render: 'ssr',
440
+ offline: 'runtime',
441
+ load: async ({ params }) => {
442
+ const host = await hostList({ id: params.host });
443
+ return host === undefined
444
+ ? withStatus(404, { kind: 'missing' as const, hostId: params.host })
445
+ : { kind: 'page' as const, host };
446
+ },
447
+ meta: ({ data, t }) => ({ title: data.kind === 'missing' ? t('notFound') : data.host.label }),
448
+ });
449
+ ```
450
+
451
+ | | |
452
+ |---|---|
453
+ | what it does | records the status against **that object**; hands the same object back, so `load`'s return type, `meta`'s `data` and the page's `props.data` are untouched |
454
+ | which statuses | any 2xx, 4xx or 5xx. A 3xx is `X_ROUTE_STATUS_INVALID` — a redirect is a `Location` and no body, which is `setRedirect()` from `load` (below). Out of 200–599, or not whole, is refused where it is written, never as a `RangeError` at `new Response` |
455
+ | SEO | a 4xx or 5xx is `<meta name="robots" content="noindex">` **by construction** — the descriptor's `meta` applies it after the route's own `meta` ran, so a page that does not exist is never indexed however `meta` was written. `follow` and the rest stay the author's; a 200 hands `meta`'s object back untouched |
456
+ | every mode | `ssr`, `stream`, `static`-served and `isr` all answer it; an `isr` entry stores a 2xx or 4xx status beside the HTML and serves it on every hit, stale copies included; a 5xx is answered and never stored |
457
+ | the static export | a file has no status. `x build --target static` writes the document whatever the loader said, and the build's measurer — which renders an `app/` route with `params: {}` to weigh it — never fails on a loader answering 404: the status is a fact about the data, not a throw |
458
+ | an untouched app | reads 200 everywhere it did, byte for byte — nothing asks unless a loader answered |
459
+
460
+ **A redirect is `setRedirect()`, from `load`.** `As of 2026-09-24`. It is the same call an action's
461
+ handler uses, from `@ultimat3/http`. On the page path, the request answers with that `Location`
462
+ and status (default 303, and 302/307/308 when named) and no document is rendered:
463
+
464
+ ```ts
465
+ import { setRedirect } from '@ultimat3/http';
466
+
467
+ load: async ({ params }) => {
468
+ await recordOpen(params.token); // a tracking pixel, a download link
469
+ setRedirect('/pixel.gif', 302);
470
+ return { params }; // never rendered
471
+ },
472
+ ```
473
+
474
+ With no declared `cache`, the redirect is `private, no-store`: the loader decided it for this one
475
+ request, and a cached copy would answer later requests without running the loader. A route with a
476
+ declared `cache` gets that instead. `withStatus` still refuses a 3xx, because a rendered document
477
+ has no `Location`.
478
+
479
+ **A refused page is `withStatus(403, data)`.** The same call answers a denial rendered in the
480
+ shell — an `@ultimat3/admin` mount whose `guardedPage()` returned `AdminPageDenied` asks the same
481
+ `decideAll` in its `load` and returns `withStatus(403, data)` (the admin README shows the load).
482
+
483
+ **Not a throw, deliberately.** Throwing is the other 404 and it is still there: a route the table
484
+ does not have, or a loader that throws `X_NOT_FOUND`, gets the framework's error page —
485
+ outside the app's shell, no sidebar, no session list. Measured in one adopter's app on 2026-09-07,
486
+ `/fleet/nope` rendered the right page inside the shell and answered 200, because the only way to
487
+ the status was the way that lost the shell. The two answers stay distinct: **the table lacks the
488
+ route** → throw → error page; **the route exists and says "not found"** → `withStatus` → its own page.
489
+
490
+ ## Error pages
491
+
492
+ A browser that hits a failure in a production process gets the framework's error page — the
493
+ status, the code, the request id, and nothing off the throwable. Until `As of 2026-08-23` a
494
+ production browser got `problem+json` with the internal `cause` and the author-facing `fix:` in it.
495
+
496
+ | status | `x dev` | production |
497
+ |---|---|---|
498
+ | 4xx, app file present | the app's file | the app's file |
499
+ | 4xx, no app file | the overlay — cause, fix, stack | the framework's page |
500
+ | 5xx | the overlay, always | the app's file, else the framework's page |
501
+
502
+ A 4xx in dev is the page a visitor will see, so it is the one the author sees; a 5xx is a defect,
503
+ and the overlay is how dev debugs one. An agent (`accept: application/json`) gets `problem+json`
504
+ in both.
505
+
506
+ | | |
507
+ |---|---|
508
+ | a page that exists and answers 404 | not this page at all — `withStatus(404, data)` in the loader renders the route's own component with the status ([above](#a-route-that-answers-a-status)); the error page is for a **throw** |
509
+ | override, one per status | `apps/web/site/errors/<status>.html` — served byte for byte, exact status match only; read per request under `x dev` (drop a file into the running server), once per status in a container |
510
+ | static export | `404.html` is written by `x build --target static`, the app's file if present |
511
+ | copy | the `errors.*` keys of the i18n catalog — an app translates or overrides by declaring them |
512
+ | footer | links to the Ultimate repository and developerz.ai; an override replaces the whole page |
513
+
514
+ ## Rules
515
+
516
+ - `render` and `offline` are declared on the route or defaulted by surface. Never inferred at runtime.
517
+ - `revalidate.tags` are typed handles from the tag graph — an unknown tag is a compile error ([Caching and invalidation](Caching-And-Invalidation)).
518
+ - Routes never touch the DB directly. Data comes from a `query`.
519
+ - A route holds no business logic. That is a service, behind an [action](Actions).
520
+ - `prerender()` must be deterministic and bounded — it is a build input.
521
+ - Never hand-write a canonical URL, a sitemap entry, or `sw.js`.