@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,231 @@
1
+ # Tutorial 5 — deploy on a free tier
2
+
3
+ The framework ships **no** platform primitives — no buildpack, no `app.json`, no `fly.toml`, no adapter. It does not need to: every PaaS builds a Dockerfile, and every one of them wants the same three behaviours the scaffolded image already has.
4
+
5
+ `As of 2026-11-03`. Every command on this page down to [When one instance is not enough](#when-one-instance-is-not-enough) was re-run on a fresh `x new myapp --no-example` app carrying the tutorial 2–4 slices, with Docker 29 on Linux, its framework packed from this tree into `vendor/` the way `docker/deploy-proof/run.sh` packs it. `ROLE=migrate` ran against a real Postgres. **Not run: anything on a platform** — no host was used, so the platform table, the free-tier behaviour and the `x deploy` without `--dry-run` are still read, not measured. `scripts/tutorial-run.ts` does not run this page.
6
+
7
+ Series: [1 — first app](Tutorial-01-First-App) · [2 — first feature](Tutorial-02-First-Feature) · [3 — auth and admin](Tutorial-03-Auth-And-Admin) · [4 — jobs and realtime](Tutorial-04-Jobs-And-Realtime) · **5** · [6 — growing up](Tutorial-06-Growing-Up)
8
+
9
+ ## The whole integration
10
+
11
+ | The platform does | The image does |
12
+ |---|---|
13
+ | injects `PORT`, routes traffic to it | `apps/web/server.ts` binds exactly `$PORT`, refusing a non-port value with `X_PORT_INVALID` rather than defaulting past it |
14
+ | requires a bind on `0.0.0.0` | binds every interface — loopback is unreachable through a port mapping |
15
+ | polls a health path | `/readyz` for *may I have traffic*, `/healthz` for *am I alive* |
16
+ | sends `SIGTERM`, then `SIGKILL` | drains in three phases: stop accepting, finish in-flight, close |
17
+ | wants one artifact per release | one image, `ROLE` selects the process |
18
+
19
+ Set `DATABASE_URL` and the three signing secrets below. Point the platform at `docker/Dockerfile`. That is the integration.
20
+
21
+ ## Build
22
+
23
+ ```bash
24
+ bunx x build --target docker --tag myapp:dev
25
+ ```
26
+
27
+ ```text
28
+ ✓ built docker
29
+ ```
30
+
31
+ ```text
32
+ myapp:dev 300MB
33
+ ```
34
+
35
+ `300MB` is a 29.4.0 scaffold's image, measured 2026-11-03 (the 1.1.0 image was 194MB).
36
+
37
+ `x build` runs the static gate steps first — `typecheck`, `lint`, `boundaries`, `filesize`, `package-shape`, `errors` — and exits non-zero without building if any fail. A build that would fail `x verify` produces no artifact.
38
+
39
+ The scaffolded image is `oven/bun:1.4-alpine`, pinned by digest, in three stages (`manifests`, `deps` with `bun install --frozen-lockfile --production`, `runtime`). It runs as `USER 1000:1000`, and its `ENTRYPOINT` is `["bun", "apps/web/server.ts"]`. There is no compile stage and no second gate inside it: re-running `tsc` and `biome` there would need the devDependencies the `--production` install deliberately omits.
40
+
41
+ > `docs/ops/README.md` also describes a distroless, single-binary image. That is **this repo's own** CLI image (`docker/Dockerfile` in the framework repository), not what `x new` writes — your app's `docker/Dockerfile` is the authority for your image.
42
+
43
+ ## Run it, locally, exactly as the platform will
44
+
45
+ ```bash
46
+ docker run -d --name myapp-web -e ROLE=web -e PORT=8080 -p 8085:8080 \
47
+ -e SESSION_SECRET=$(openssl rand -hex 32) -e ULTIMATE_CURSOR_SECRET=$(openssl rand -hex 32) \
48
+ -e STORAGE_SIGNING_SECRET=$(openssl rand -hex 32) myapp:dev
49
+ ```
50
+
51
+ ```text
52
+ {"ts":"2026-11-03T16:23:42.394Z","level":"info","msg":"ultimate metrics listening","url":"http://0.0.0.0:9090/metrics"}
53
+ {"ts":"2026-11-03T16:23:46.918Z","level":"info","msg":"app mcp mounted","method":"POST","path":"/mcp"}
54
+ {"ts":"2026-11-03T16:23:46.961Z","level":"info","msg":"ultimate web listening on http://0.0.0.0:8080"}
55
+ {"ts":"2026-11-03T16:23:46.962Z","level":"info","msg":"ultimate started","role":"web","url":"http://0.0.0.0:8080","buildId":"edca46484ed2b43f","bus":"in-process"}
56
+ ```
57
+
58
+ Without the secrets it exits at boot, one refusal at a time: `X_CURSOR_SECRET_DEV` names `ULTIMATE_CURSOR_SECRET`, then `X_ENV_MISSING` names `STORAGE_SIGNING_SECRET` (an image with no `S3_ENDPOINT` stores uploads on its own disk). `ULTIMATE_ENV=production bunx x env check` lists them all before you build.
59
+
60
+ ```bash
61
+ curl http://127.0.0.1:8085/readyz
62
+ ```
63
+
64
+ ```json
65
+ {"state":"ready","ready":true,"role":"web"}
66
+ ```
67
+
68
+ The verdict, and nothing else: a published port reaches the container from the bridge gateway,
69
+ not from loopback, and only a peer `healthDetailPeers` lists is told the build id, the in-flight
70
+ count and the check names. From inside the container it is loopback, and they are printed:
71
+
72
+ ```bash
73
+ docker exec myapp-web bun --eval "fetch('http://127.0.0.1:8080/readyz').then(r=>r.text()).then(console.log)"
74
+ ```
75
+
76
+ ```json
77
+ {"state":"ready","ready":true,"uptimeMs":5236,"inflight":0,"buildId":"edca46484ed2b43f","checks":{"database":"ok"},"registered":1,"role":"web"}
78
+ ```
79
+
80
+ The image's own `HEALTHCHECK` (`/readyz` every 10s, a 30s start period) reported `healthy` 5s after the start. Prove that locally before you debug it on a platform.
81
+
82
+ ### The env vars that matter
83
+
84
+ | Key | Unset means | Set it to |
85
+ |---|---|---|
86
+ | `ROLE` | `web` | `web` for the service, `migrate` for the release phase |
87
+ | `PORT` | 3000 | whatever the platform injects — leave it to the platform |
88
+ | `DATABASE_URL` | **embedded PGlite** — never in production | the managed Postgres connection string |
89
+ | `SESSION_SECRET`, `ULTIMATE_CURSOR_SECRET`, `STORAGE_SIGNING_SECRET` | the shipped development keys, which a production boot refuses | `openssl rand -hex 32` each, in the platform's secret store |
90
+
91
+ A real `DATABASE_URL` env var wins over anything baked into the image; verified by pointing a container at an unreachable host and getting `X_DB_UNAVAILABLE` rather than a silent PGlite fallback.
92
+
93
+ **No `.env` file reaches the image**, `As of 2026-08-19`. `docker/Dockerfile.dockerignore` excludes `**/.env` and `**/.env.*`, keeping only `!**/.env.example`. It did not: the old patterns were `.env` and `.env.*.local`, which match neither `.env.development` **nor `.env.production`** — the file `docker/docker-compose.prod.yml`'s `env_file:` tells you to create. Both shipped inside the layer, proven by a real `docker build`, and this tutorial called that harmless.
94
+
95
+ On 3.0.0 and below, add these three lines to `docker/Dockerfile.dockerignore` before your first build — and rebuild, because an image already built still carries them:
96
+
97
+ ```text
98
+ **/.env
99
+ **/.env.*
100
+ !**/.env.example
101
+ ```
102
+
103
+ ## Release-phase migrations — the one way
104
+
105
+ Run **the same image** with `ROLE=migrate` before the new release serves traffic.
106
+
107
+ ```bash
108
+ docker run --rm -e ROLE=migrate -e DATABASE_URL=postgres://… myapp:dev
109
+ ```
110
+
111
+ ```json
112
+ {"ts":"2026-11-03T16:24:07.231Z","level":"info","msg":"ultimate migrate applied","applied":2,"available":2,"appVersion":"dev"}
113
+ ```
114
+
115
+ It applies every pending migration in `packages/db/migrations` under a Postgres advisory lock, records each in the `x_migrations` ledger with its checksum, and exits 0. Concurrent migrators serialise — the second waits up to 60s for the lock, then exits non-zero with `X_MIGRATE_CONCURRENT` rather than hanging the release; a checksum that no longer matches an applied migration stops the release rather than corrupting it.
116
+
117
+ Every setting each host asks for, and which were executed: [Deploy recipes](Deploy-Recipes).
118
+
119
+ | Platform | Where the command goes |
120
+ |---|---|
121
+ | Heroku | `release:` in `Procfile`, with `ROLE=migrate` on the release dyno |
122
+ | Render | a pre-deploy command running `ROLE=migrate bun apps/web/server.ts` |
123
+ | Fly.io | `[deploy] release_command` with `ROLE=migrate` |
124
+ | Railway | a pre-deploy command running the same |
125
+ | Kubernetes | an `initContainer` or a `Job` on the same image |
126
+ | Compose | the `migrate` service; every other role waits on `service_completed_successfully` |
127
+
128
+ **No release phase on your free tier?** Several platforms gate pre-deploy commands behind a paid plan. Run the one-off yourself against the same image and the same `DATABASE_URL` before promoting the release — that is what every row above ultimately is.
129
+
130
+ `x db migrate` is deliberately absent from that table: it is the developer's command, and it needs the toolchain ([tutorial 2](Tutorial-02-First-Feature#migrations)). The release phase runs the shipped image and nothing else.
131
+
132
+ ## What a free tier costs you
133
+
134
+ Free instances **sleep**. That is not a performance note; it changes which roles can exist.
135
+
136
+ | Role | On a single sleeping free instance |
137
+ |---|---|
138
+ | `web` | works. First request after a sleep pays a cold start, then serves normally |
139
+ | `worker` | **does not run reliably.** No inbound traffic means nothing wakes the instance, so queued jobs sit until the next visitor |
140
+ | `scheduler` | **does not run reliably.** A cron whose process is asleep at 03:00 does not fire then; `catchUp: 'skip'` is the generated default, so whenever something next wakes the instance a week of missed occurrences collapses into one late fire, never a replay of each |
141
+ | `sync` | works while awake; every sleep disconnects every subscriber |
142
+ | `replicator` | needs a Postgres with `wal_level=logical`, which free managed tiers generally do not offer |
143
+
144
+ Two honest options on rung 0:
145
+
146
+ | Option | Trade |
147
+ |---|---|
148
+ | ship `ROLE=web` only, do the work inline in the action | no durability, no retries — acceptable for work that is cheap and idempotent |
149
+ | ship `ROLE=web` and keep the jobs, accepting late execution | correct results, unpredictable latency. The queue is Postgres, so nothing is lost — only delayed |
150
+
151
+ Do not paper over it with an external pinger: keeping a free instance awake around the clock is what the free tier is not. The moment jobs must run on time, that is the signal to climb — [tutorial 6](Tutorial-06-Growing-Up).
152
+
153
+ ## Drain
154
+
155
+ `docker stop -t 20 myapp-web`:
156
+
157
+ ```text
158
+ {"ts":"2026-11-03T16:23:52.649Z","level":"info","msg":"draining","signal":"SIGTERM","deadlineMs":25000,"readinessGraceMs":5000,"inflight":0}
159
+ {"ts":"2026-11-03T16:23:57.656Z","level":"info","msg":"stopped","signal":"SIGTERM"}
160
+ ```
161
+
162
+ `/readyz` flips to 503 **before** the socket closes, and stays 503 for `readinessGraceMs` (5s) with the listener still open, so a load balancer polling it stops sending traffic before the socket goes — that is what makes a rolling restart drain instead of drop. An idle process stops at the end of the grace: 5s here.
163
+
164
+ Full role-by-role handoff table: [Deployment](Deployment).
165
+
166
+ ## The other two build targets
167
+
168
+ ```bash
169
+ bunx x build --target static --out .x/static
170
+ ```
171
+
172
+ ```text
173
+ skipped /todos app/ surface — server-rendered, not prerendered; app/ allows stream | ssr, so no route on it can ever reach the artifact
174
+ ✓ built static
175
+ ```
176
+
177
+ One HTML file per `render: 'static'` route — with this scaffold, `.x/static/index.html` beside `404.html`, the `offline` page, the manifest, icons, islands and styles. Every other render mode needs a running app and is listed as `skipped` (one line per route), never emitted. Serve it from a CDN or an object store with no process behind it; set `SITE_ORIGIN` so `canonical` and `og:url` are built against the real host.
178
+
179
+ `apps/web/prerender.ts` also writes **`sitemap.xml` and `robots.txt`** into the same directory, because a static export has no process to answer them at request time. `robots.txt` **fails closed**: anything that is not `ULTIMATE_ENV=production` emits `Disallow: /` and advertises no sitemap, so a preview build cannot outrank the real site.
180
+
181
+ ```bash
182
+ bunx x build --target binary --out .x/app
183
+ ```
184
+
185
+ ```text
186
+ ✓ built binary
187
+ ```
188
+
189
+ It boots `As of 2026-10-06`: the required `windows` CI job starts a scaffold's binary and gets `200` from `/healthz`, and a Linux e2e test runs a compiled probe tree. The framework is **not** compiled in: `x build` keeps `@ultimat3/*` external, so `@ultimat3/core` loads from the app's `node_modules` and `frameworkVersion()` reads its version from core's own `package.json` there (`resolveVersion`, `packages/core/src/version.ts`). `x build` still passes `--define ULTIMATE_FRAMEWORK_VERSION="<version>"`, but nothing in this binary reads it — the define answers only for a build that bundles the framework, such as the image's self-contained `x`. Such a build compiled without the define, and with no `package.json` beside core, exits `X_INVARIANT` (fix `x build --target binary`) rather than reporting a version it does not have.
190
+
191
+ Use `--target docker` anyway. The image is the self-contained artifact; the binary is a launcher for an app tree, and must be started from the app root with the source beside it — and no scaffolded app has yet been served from a bare VM this way.
192
+
193
+ ## When one instance is not enough
194
+
195
+ ```bash
196
+ bunx x deploy --image myapp:dev --dry-run
197
+ ```
198
+
199
+ ```text
200
+ migrate IMAGE=myapp:dev docker compose --env-file …/.env.production -f …/docker/docker-compose.prod.yml run --rm migrate
201
+ web IMAGE=myapp:dev docker compose --env-file …/.env.production -f …/docker/docker-compose.prod.yml up -d web
202
+ sync IMAGE=myapp:dev docker compose --env-file …/.env.production -f …/docker/docker-compose.prod.yml up -d sync
203
+ worker IMAGE=myapp:dev docker compose --env-file …/.env.production -f …/docker/docker-compose.prod.yml up -d --no-deps --no-recreate --wait --wait-timeout 300 --scale worker=2 worker
204
+ scheduler IMAGE=myapp:dev docker compose --env-file …/.env.production -f …/docker/docker-compose.prod.yml up -d --no-deps --no-recreate --wait --wait-timeout 300 --scale scheduler=2 scheduler
205
+ backfill IMAGE=myapp:dev docker compose --env-file …/.env.production -f …/docker/docker-compose.prod.yml run --rm backfill
206
+ ✓ containers only: 1 image, roles migrate,web,sync,worker,scheduler,backfill
207
+ ```
208
+
209
+ Migrate to completion, then the serving roles, then the one-shot `backfill`. `--dry-run` prints the plan and runs nothing. `…` is the app root, printed absolute.
210
+
211
+ **`--env-file` is what fills the `${VAR:?…}` guards.** Compose interpolates `SYNC_URL`, `APP_URL` and `POSTGRES_PASSWORD` from the shell and `--env-file` only, never from a service's `env_file:`. So all three go in `.env.production` — `APP_URL=http://<host>:3000` is the origin your pages are served on, and the only one besides its own that `sync` admits a socket from, and every step reads that file. A variable set in the shell still wins over it.
212
+
213
+ **At most one replica each for `web` and `sync`, and the file says so** `As of 2026-11-03`. Both publish a host port, and one host port has exactly one binder: `web` is `replicas: 1`, and `sync` is `replicas: 0` — off — until the app turns realtime on with a replicator and NATS (the comment above it in `docker/docker-compose.prod.yml` is the recipe), then `1`. Leave them there — this tutorial's rung is one box. `worker` publishes no port and scales freely, so `deploy: { replicas: 4 }` on it is the knob you actually have here; the plan rolls `worker` and `scheduler` by starting a second copy beside the first (`--scale <role>=2 --wait`).
214
+
215
+ Scaling the two serving roles is the next rung, and it is not an edit to this file: put a reverse proxy of your own on the compose network and drop their `ports:` lines, or move to the chart's per-role HPA → [6 · Growing up](Tutorial-06-Growing-Up).
216
+
217
+ ## What is not there yet
218
+
219
+ | Expected | Reality `As of 2026-08` |
220
+ |---|---|
221
+ | `/metrics` on the **app** port | `X_ROUTE_NOT_FOUND`, and that is deliberate. Every role — `web` included — serves `/metrics` on `METRICS_PORT`, default **9090**, because the Helm ingress routes `/` with no path exclusion and metrics on 3000 would be public. Scrape `http://<host>:9090/metrics`, not `:3000/metrics` |
222
+ | `x logs tail` | planned — `X_NOT_IMPLEMENTED`, with `x dev` → the `/_x` timeline panel as its fix |
223
+ | `x status` | planned — `x doctor --json` is the shipped answer |
224
+ | OTLP export, on by default | the exporter **ships** — `otlpSpanExporter()` / `otlpMetricExporter()`, OTLP/HTTP JSON — but the default is still the no-op, so nothing leaves the process until you register one: `configureTelemetry({ exporter: otlpSpanExporter() })` with `OTEL_EXPORTER_OTLP_ENDPOINT` set to a collector's HTTP receiver (`:4318` — `:4317` is gRPC and is refused) |
225
+ | a Helm chart in your app | **`x new` writes one** `As of 2026-11-03` — `docker/helm`, 10 files — so `x deploy --method helm` works here. On 3.0.0 and below it writes none: copy [`docker/helm`](https://github.com/developerz-ai/ultimate/tree/main/docker/helm) from the framework repo, or stay on `--method compose` |
226
+
227
+ ## Next
228
+
229
+ [Tutorial 6 — growing up](Tutorial-06-Growing-Up): managed Postgres, a shared cache, one service per role, then Compose and Kubernetes — with the app code unchanged at every rung.
230
+
231
+ Related: [Deployment](Deployment) · [Configuration](Configuration) · [CLI reference](CLI-Reference) · [Known gaps](Known-Gaps) · [Troubleshooting](Troubleshooting)
@@ -0,0 +1,158 @@
1
+ # Tutorial 6 — growing up
2
+
3
+ **The app code is identical at every rung.** Climbing is an env var, a driver swap and someone else's infrastructure — never a rewrite. A small idea reaches a big output by changing where the rows and the messages live, not by changing what an `entity`, `policy`, `action`, `mutator`, `query`, `job`, `route` or `task` says.
4
+
5
+ `As of 2026-08`. The full ladder, with costs, seams and the places the invariant breaks, is [`docs/idea/17-scale-ladder.md`](https://github.com/developerz-ai/ultimate/blob/main/docs/idea/17-scale-ladder.md); running it for real is [`docs/ops/`](https://github.com/developerz-ai/ultimate/tree/main/docs/ops). This page is the decision procedure, not a second copy of either.
6
+
7
+ Series: [1 — first app](Tutorial-01-First-App) · [2 — first feature](Tutorial-02-First-Feature) · [3 — auth and admin](Tutorial-03-Auth-And-Admin) · [4 — jobs and realtime](Tutorial-04-Jobs-And-Realtime) · [5 — deploy free](Tutorial-05-Deploy-Free) · **6**
8
+
9
+ ## Climb on a signal, never on a feeling
10
+
11
+ | Rung | You run | What forces the next rung | App code change |
12
+ |---|---|---|---|
13
+ | **0** — PaaS, one process | one web service, managed Postgres | a job that must not run in a request, or a cron that must fire on time | **none** |
14
+ | **1** — PaaS, roles split | one service per `ROLE`, managed Postgres, managed Redis-protocol cache | more than one `sync` replica, platform socket limits, or per-service pricing above a box | **none, plus config** |
15
+ | **2** — one box, Compose | one host: Postgres, cache, NATS, every role | one host is a single point of failure you can no longer accept, or cannot hold the load | **none, plus config** |
16
+ | **3** — Kubernetes | per-role Deployments and HPAs, JetStream fanout, logical replication | Postgres write throughput, a region you must serve from, a zone you must survive | **none, plus config** |
17
+ | **4** — distributed SQL | multi-AZ, YugabyteDB, metrics and traces wired end to end | nothing here is forced by scale — geography and availability targets force it | **none for the datastore swap** |
18
+
19
+ Skipping a rung to look serious buys a second product to maintain. A Kubernetes cluster running one app is that.
20
+
21
+ ## What is allowed to change
22
+
23
+ | May change | Never changes |
24
+ |---|---|
25
+ | `DATABASE_URL`, `REDIS_URL`, `NATS_URL`, `S3_ENDPOINT` | any file under `apps/` or `packages/` |
26
+ | `ROLE` — which process this container is | the eight primitives, their shapes, their authz |
27
+ | driver constructors at boot (`setJobDriver`, `setDbClient`, `configureTelemetry`) | call sites: `db()`, `ctx.jobs.enqueue()`, `query()`, `live()` |
28
+ | replica counts, HPA targets | the manifest, the OpenAPI, the typed client, the MCP tools |
29
+ | `app.config.ts` declarations | migrations already applied |
30
+
31
+ ## Rung 0 → 1: split the roles
32
+
33
+ Same platform, same image. One service per `ROLE`. This is the rung a free tier's sleeping instance forces whether or not traffic did — see [tutorial 5](Tutorial-05-Deploy-Free#what-a-free-tier-costs-you).
34
+
35
+ | Role | Why it leaves the web service |
36
+ |---|---|
37
+ | `worker` | a job must survive the request that queued it and must not compete with it for CPU |
38
+ | `scheduler` | fixed at one, and **must be always-on** — a plan that sleeps it drops the cron |
39
+ | `migrate` | run-once, before anything serves the new schema |
40
+
41
+ `scheduler` leadership is an expiring lease row in `x_scheduler_leader`, never an advisory lock — that grant belongs to the **session**, not to the process: it outlives every transaction on the connection and is released only by an explicit unlock, the pool's reset on release, or the connection dying — so a node can neither renew it nor prove it still holds one — so a second instance is an idle standby rather than a duplicate. It has no `/readyz` at all: `ROLE=scheduler` binds the metrics port only, and nothing routes request traffic to it.
42
+
43
+ ### The shared cache tier
44
+
45
+ Add it when there is more than one web replica **and** a measured cross-replica miss. Not before.
46
+
47
+ ```ts
48
+ // app.config.ts
49
+ cache: { tiers: ['request-memo', 'lru', 'redis'] },
50
+ ```
51
+
52
+ Read order is request-memo → LRU → redis → origin, and a tier is never consulted for a request whose `policy` has not already passed. Adding one changes where a value is found, never how it is asked for.
53
+
54
+ > **Any Redis-protocol server, `As of 2026-08`.** The tier-3 invalidation script used to `DEL` keys it never declared in `KEYS`, which Dragonfly and Redis Cluster reject. Fixed: the script touches only the buckets it was handed and returns the value keys for the client to drop one at a time, the buckets carry an `{entity}` hash tag, and `invalidateTags` issues one call per tag — so every key of a call co-slots. Redis, Valkey and Dragonfly all work. Clustered deployments are **unmeasured, not unsupported**: no test in the repo runs against a real cluster node.
55
+
56
+ Detail: [Caching and invalidation](Caching-And-Invalidation).
57
+
58
+ ## Rung 1 → 2: one box, Compose
59
+
60
+ `docker/docker-compose.prod.yml` **is** the production topology — one service per role, one image, differing only by `ROLE`. `migrate` runs to completion and every serving role waits on `service_completed_successfully`.
61
+
62
+ ```bash
63
+ bunx x deploy --image ghcr.io/you/myapp:1.2.3 --method compose
64
+ ```
65
+
66
+ Start Postgres with `wal_level=logical`, `max_replication_slots=8` and `max_wal_senders=8` so the `replicator` role can preflight rather than fail on its first change.
67
+
68
+ > **The compose ceiling is one replica of each serving role, declared** `As of 2026-08`. The framework's file and the scaffolded one both publish a host port on `web` and `sync` and both set `replicas: 1` — one host port has exactly one binder. `worker` publishes no port and scales freely.
69
+ >
70
+ > Two ways past it: delete their `ports:` lines and put a reverse proxy of your own on the compose network, which the service names resolve across every replica; or take the next rung, where the chart's per-role HPA does it. The framework ships neither proxy — one in `docker-compose.prod.yml` is a dependency every app inherits and a second answer to "how does traffic reach a role" beside the chart's Ingress.
71
+
72
+ ## Rung 2 → 3: Kubernetes
73
+
74
+ **`x new` writes the chart**, `As of 2026-08-19` — `docker/helm`, 8 files — a `Deployment` for each of the four roles enabled by default (`web`, `sync`, `worker`, `scheduler`), `replicator` behind `enabled: false`, and `migrate` as a `Job` — so `x deploy --method helm` runs `helm upgrade --install` against it with nothing to copy in. On 3.0.0 and below `x new` writes none and the command exits `X_NOT_IMPLEMENTED`: copy [`docker/helm`](https://github.com/developerz-ai/ultimate/tree/main/docker/helm) from the framework repo.
75
+
76
+ The alternative is the flat manifests in [`docs/ops/01-kubernetes.md`](https://github.com/developerz-ai/ultimate/blob/main/docs/ops/01-kubernetes.md). Carry one, never both — a repo with a chart *and* a manifest tree has two sources of truth for the same pod. Deleting `docker/helm` is how you choose the manifests: the command then fails with helm's own error, not the framework's.
77
+
78
+ Three things turn on at this rung, all by env, none by a code path:
79
+
80
+ | Turn on | Set | Effect |
81
+ |---|---|---|
82
+ | cross-node fanout | `NATS_URL` | `selectTransport` builds `NatsTransport` instead of `InProcessTransport` |
83
+ | presence across nodes | `NATS_KV_BUCKET` (default `x_presence`) | presence becomes a JetStream KV bucket; its age limit and the presence TTL are one number |
84
+ | live queries off a real WAL | `REPLICATION_URL` · `REPLICATION_SLOT` · `REPLICATION_PUBLICATION` | `selectChangeFeed` builds `postgresChangeFeed()`, under an advisory lock so exactly one replicator exists |
85
+
86
+ **Do not enable `replicator` until live queries are in use.** The chart ships it `enabled: false` for that reason.
87
+
88
+ Per-role HPAs target `rps`, `connections` and `queue_demand` — CPU is a lagging proxy for all three serving roles. Every role serves `/metrics` on `METRICS_PORT` (default 9090) and `http`/`realtime`/`jobs` emit the series. The chart still cannot scrape them: no role declares a metrics container port and no scrape target ships, so an HPA sits at `<unknown>` until you add both plus a custom metrics adapter.
89
+
90
+ ## Rung 3 → 4: three separable moves
91
+
92
+ | Move | The only good reasons | The bad reason |
93
+ |---|---|---|
94
+ | dedicated servers over cloud instances | steady predictable load; egress cost; hardware sized to the working set | "cloud is expensive", with no bill that says so |
95
+ | custom Kubernetes over a managed chart | node pools with real taints, affinity to node-local storage, an ingress you control | wanting the chart to look impressive |
96
+ | distributed SQL over one Postgres | multi-region **write** locality; surviving a zone loss with no manual failover | write throughput one Postgres has not actually failed to deliver |
97
+
98
+ Observability is the part that is not optional here, and the framework's half is done `As of 2026-08` — but **the default exporter is still the no-op**, so it emits nothing until you say so. Two lines at boot:
99
+
100
+ ```ts
101
+ configureTelemetry({ exporter: otlpSpanExporter() }); // OTEL_EXPORTER_OTLP_ENDPOINT
102
+ configureMetrics({ exporter: otlpMetricExporter() }); // same endpoint, metrics signal
103
+ ```
104
+
105
+ Point `OTEL_EXPORTER_OTLP_ENDPOINT` at your collector's **HTTP** receiver, `:4318` — `:4317` is gRPC and is refused with `X_OTLP_PROTOCOL_UNSUPPORTED`, permanently, because HTTP/2 plus protobuf is a dependency and a second wire format. Metrics also stay scrapeable at `METRICS_PORT` if you prefer an agent. [`docs/ops/03-observability.md`](https://github.com/developerz-ai/ultimate/blob/main/docs/ops/03-observability.md) says what to scrape, what to alert on, and what is still missing — no logs signal, and a rejected export is dropped rather than retried.
106
+
107
+ ## The seams, so nothing surprises you
108
+
109
+ | Concern | Interface | Decided by |
110
+ |---|---|---|
111
+ | rows | `@ultimat3/db` · `DbClient` | `DATABASE_URL` — unset is PGlite, and PGlite is `x dev` only |
112
+ | job queue | `@ultimat3/jobs` · `JobDriver` | `setJobDriver(postgresJobDriver({ executor }))` — there is no `jobs.driver` config line, and 5.0.0 deleted the one that never worked. Redis is an interface-complete stub that throws `X_NOT_IMPLEMENTED`; there is no NATS job driver since 25.0.0 |
113
+ | realtime fanout | `@ultimat3/realtime` · `Transport` | `NATS_URL` |
114
+ | change feed | `@ultimat3/realtime` · `ChangeFeed` | `REPLICATION_*` |
115
+ | cache | `@ultimat3/cache` · `CacheTier` | `cache.tiers` + `REDIS_URL` |
116
+ | object storage | `@ultimat3/storage` · `Driver` | `S3_ENDPOINT` |
117
+ | mail | `@ultimat3/mail` · `Driver` | mail env |
118
+ | tracing | `@ultimat3/core` · `SpanExporter` | `configureTelemetry()` — default no-op |
119
+
120
+ Every one keys on env or a boot-time constructor. One image resolves `x dev`, a Compose host and a cluster identically.
121
+
122
+ ## Rehearse the climb before you need it
123
+
124
+ | Step | Command |
125
+ |---|---|
126
+ | point the app at a real Postgres | `DATABASE_URL=… ROLE=migrate bun apps/web/server.ts`, then `ROLE=web` |
127
+ | prove the plan without running it | `bunx x deploy --image … --dry-run --json` |
128
+ | prove the image before the platform does | `docker run -e ROLE=web -e PORT=8080 -p 8085:8080 …` and poll `/readyz` |
129
+ | prove the gate still holds | `bunx x verify` — the same 21 steps, at every rung |
130
+
131
+ ## Where the invariant genuinely breaks
132
+
133
+ Named rather than left to be discovered:
134
+
135
+ | Break | Rung |
136
+ |---|---|
137
+ | tier-3 cache invalidation unmeasured against a real clustered cache node | 1 |
138
+ | the two roles that publish a host port run at one replica under the shipped compose file | 2 |
139
+ | no Helm chart in a scaffolded app | 3 |
140
+ | no OTLP **logs** signal, and a rejected export is dropped with a `warn` rather than retried — traces and metrics do export | 3–4 |
141
+ | a custom-metrics adapter, which the chart's HPAs need and the framework never ships | 3–4 |
142
+ | `x logs` planned — `X_NOT_IMPLEMENTED`, with `x dev` → the `/_x` timeline panel as its fix | any |
143
+ | the Redis **job** driver throws `X_NOT_IMPLEMENTED` (no NATS job driver since 25.0.0) | any |
144
+ | realtime tier 3 (local-first, `persist: true`), the plugin API, multi-region replication | **not shipped**, `As of 2026-08` |
145
+
146
+ Each sits behind an interface that ships today and fails loudly, rather than pretending to work. The full list, with a workaround per row: [Known gaps](Known-Gaps).
147
+
148
+ ## Read next
149
+
150
+ | Doc | Answers |
151
+ |---|---|
152
+ | [`docs/idea/17-scale-ladder.md`](https://github.com/developerz-ai/ultimate/blob/main/docs/idea/17-scale-ladder.md) | the full rung table, costs, the seam table, YugabyteDB honestly |
153
+ | [`docs/ops/`](https://github.com/developerz-ai/ultimate/tree/main/docs/ops) | Kubernetes manifests, secrets, observability, datastore sizing, disaster recovery, runbooks |
154
+ | [Deployment](Deployment) | roles, health endpoints, the drain contract, build targets |
155
+ | [Configuration](Configuration) | every `app.config.ts` field and every env var |
156
+ | [Upgrading](Upgrading) | moving every `@ultimat3/*` in lockstep |
157
+
158
+ Related: [Caching and invalidation](Caching-And-Invalidation) · [Realtime](Realtime) · [Jobs and workflows](Jobs-And-Workflows) · [Troubleshooting](Troubleshooting)
@@ -0,0 +1,156 @@
1
+ # UI components
2
+
3
+ `@ultimat3/ui` — SolidJS components on the semantic tokens in [Theming](Theming). SCSS modules, logical properties, no Tailwind, no CSS-in-JS.
4
+
5
+ `As of 2026-09`. Stable API — semver from here ([Upgrading](Upgrading)).
6
+
7
+ ## The catalog is generated — read it, don't ask
8
+
9
+ **[`packages/ui/CATALOG.md`](https://github.com/developerz-ai/ultimate/blob/main/packages/ui/CATALOG.md)** documents every component with every prop, every type, and the token scales, parsed straight out of `packages/ui/src/components/*.tsx`. It ships inside the npm tarball, so it is on disk at `node_modules/@ultimat3/ui/CATALOG.md` in any app.
10
+
11
+ **How many there are is a question for the file, never for this page.** It carried `52` for two majors:
12
+
13
+ ```bash
14
+ head -7 node_modules/@ultimat3/ui/CATALOG.md # the count and every name, on one line
15
+ ```
16
+
17
+ | Concern | Answer |
18
+ |---|---|
19
+ | Regenerate | `bun run --filter @ultimat3/ui catalog` |
20
+ | Drift | `catalog.test.ts` compares the committed file byte-for-byte against a fresh build; a prop change with no regen is a red `x verify` |
21
+ | Hand edits | refused by the same test — the file carries a `GENERATED` banner |
22
+
23
+ This page does **not** restate the catalog. It covers the four page composites, because they are the ones an agent picks wrong.
24
+
25
+ **Which component to reach for is not the same question as how it must behave.** The four-state
26
+ decision, skeleton-versus-spinner, response tiers, optimistic writes, forms, motion, focus, toasts
27
+ and images are [Interface rules](Interface-Rules) — one page, every rule marked either with the
28
+ guard, type or gate step that refuses it, or as judgement nothing checks.
29
+
30
+ **Form errors are translated on `FormIssue.rule`.** `formBinding`'s `messageFor` receives the same
31
+ `rule` and `params` (`string.min`, `{ min: 3 }`) from the local parse and from the server's
32
+ `X_INPUT_INVALID`, whose problem document carries the structured `issues` list —
33
+ ``messageFor: (issue) => t(`form.${issue.rule}`, issue.params)``. See [I18n](I18n).
34
+
35
+ ## The four page composites
36
+
37
+ A screen that hand-rolls a header grid is the bug these exist to prevent. All four hold no state — the route owns it.
38
+
39
+ ### `AppShell`
40
+
41
+ The frame: skip link, landmarks, optional sidebar. Off-canvas navigation is `Drawer`, not a prop here.
42
+
43
+ | Prop | Type | Default |
44
+ |---|---|---|
45
+ | `children` | `JSX.Element` | required |
46
+ | `header` | `JSX.Element` | omitted → no `<header>` |
47
+ | `sidebar` | `JSX.Element` | omitted → no `<nav>`, and the grid collapses to one column |
48
+ | `footer` | `JSX.Element` | omitted → no `<footer>` |
49
+ | `bottomNav` | `JSX.Element` — a `<BottomNav>` | omitted → none; given, shown below `md` only, last, sticky to the bottom edge and in flow, so it reserves its own height |
50
+ | `sidebarLabel` | `string` | `t('ui.navigation')` |
51
+ | `skipLabel` | `string` | `t('ui.skip')` |
52
+ | `sidebarWidth` | `string` | `'16rem'` |
53
+ | `stickyHeader` | `boolean` | **sticky unless explicitly `false`** — `undefined` is sticky |
54
+ | `class` | `string` | — |
55
+
56
+ Landmark order in the DOM: `banner` → `navigation` → `main` → `contentinfo` → the bottom bar's `navigation`. `<main>` is unconditional.
57
+
58
+ The sidebar's content is `NavList` and the bottom bar's is `BottomNav` (five links at most); both mark the current page by one rule, `currentOf(items, href)` — the longest covering href wins, so `/runs/7` marks `/runs`. The bar's padding clears the home indicator through `t.safe-area-padding(block-end)` (`viewport-fit=cover` on the page).
59
+
60
+ The skip link is real, not decorative: `<main>` carries `tabindex={-1}` so focus actually moves there, and the link's `href` and the target's `id` are derived from one `shellIds()` call, so they cannot drift apart. It sits off-screen at `translate: 0 -400%` and slides in on `:focus-visible`.
61
+
62
+ ### `PageHeader`
63
+
64
+ | Prop | Type | Default |
65
+ |---|---|---|
66
+ | `title` | `string` | required |
67
+ | `description` | `string` | omitted |
68
+ | `actions` | `JSX.Element` | omitted |
69
+ | `breadcrumbs` | `readonly BreadcrumbItem[]` | omitted → no `Breadcrumb` |
70
+ | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | **`1`** |
71
+ | `media` | `JSX.Element` | omitted |
72
+ | `class` | `string` | — |
73
+
74
+ ### `Section`
75
+
76
+ | Prop | Type | Default |
77
+ |---|---|---|
78
+ | `children` | `JSX.Element` | required |
79
+ | `title` | `string` | omitted → an unlabelled grouping, no `aria-labelledby` |
80
+ | `description` | `string` | omitted |
81
+ | `actions` | `JSX.Element` | omitted |
82
+ | `level` | `1` … `6` | **`2`** |
83
+ | `as` | `'section' \| 'article' \| 'aside'` | **`'section'`** |
84
+ | `class` | `string` | — |
85
+
86
+ A `title` wires `aria-labelledby` to the heading's generated id automatically. The head block disappears entirely when both `title` and `actions` are absent, so an untitled `Section` costs no markup.
87
+
88
+ A `level` outside 1–6 throws `X_UI_INVALID_VALUE` rather than emitting an `<h7>`.
89
+
90
+ ### `Toolbar`
91
+
92
+ | Prop | Type | Default |
93
+ |---|---|---|
94
+ | `children` | `JSX.Element` | required |
95
+ | `actions` | `JSX.Element` | omitted → no trailing group |
96
+ | `label` | `string` | **required** |
97
+ | `surface` | `boolean` | falsey |
98
+ | `class` | `string` | — |
99
+
100
+ `label` is required because `role="toolbar"` with no accessible name is an unnamed group. Arrow keys move through the focusable children via a roving tabindex, horizontal, direction-aware, **not** looping.
101
+
102
+ ## A control that navigates is a `Link`
103
+
104
+ `Button` has no `href`. A link that looks like a button is `<Link appearance="button">`; a pager on a server-rendered list is `<Pagination hrefFor>`. Neither needs an island.
105
+
106
+ ```tsx
107
+ <Link appearance="button" variant="secondary" size="sm" href="/posts/new">{t('posts.new')}</Link>
108
+
109
+ // `page` is a framework `Page` (a query's `.page()`): `nextCursor` is `string | null`, handed as is.
110
+ const list = listUrl(props.url, { sorts: ['title', 'createdAt'] });
111
+ <Pagination nextCursor={page.nextCursor} hrefFor={list.hrefFor} firstHref={list.firstHref} />
112
+ ```
113
+
114
+ `listUrl(url, { sorts, ns?, limit? })` reads a list's state out of its URL — `sort`, `cursor`, `limit`, `q`, never throwing: an unknown sort or a hostile limit is the default — and writes it back as hrefs: `hrefFor` (the cursor, everything else kept), `sortHrefFor` and `href(change)` (the change, the cursor dropped), `firstHref` (`undefined` on the first page). Param names are `sort`, `dir`, `after`, `limit`, `q`; `ns: 'runs'` prefixes them all (`runs.after`) for two lists on a page, and then any change but a cursor drops every cursor on the page, since a cursor is bound to its filter and sort (`X_CURSOR_INVALID`). The same object feeds a `DataTable`'s `hrefFor`, `sortHrefFor` and `firstHref`.
115
+
116
+ | Component | Mode | Props | Renders |
117
+ |---|---|---|---|
118
+ | `Link` | text (default) | `underline`, `tone: 'accent' \| 'inherit'` | `<a>` with the link classes |
119
+ | `Link` | `appearance="button"` | `variant`, `tone` (any `Tone`), `size`, `fullWidth`, `iconStart`, `iconEnd` | `<a>` with `Button`'s classes — one set of button styles, two elements |
120
+ | `Pagination` | callbacks | `onCursor`, or `page` + `totalPages` + `onPage`; `onFirst` | `<button>`s; needs an island |
121
+ | `Pagination` | links | `hrefFor(cursor, direction)`; `firstHref` | `<a rel="prev">` / `<a rel="next">`; Next with no cursor is a disabled button |
122
+ | `DataTable` | callbacks / links | `onCursor` + `onFirst`, or `hrefFor(cursor, direction)` + `firstHref` | its pager, in that mode — the props are handed straight to `Pagination` |
123
+
124
+ A cursor that is `null` or absent is one state: no page that way. Previous is drawn only when a `prevCursor` is given or the pager is numbered — a keyset `Page` has no way back, so its pager draws Next and, given `firstHref` / `onFirst`, First. Next holds the trailing edge either way.
125
+
126
+ One mode per use, and the type holds it: `hrefFor` beside `onCursor`, `onPage`, `page` or `totalPages` does not compile, and neither does `underline` on a button-link or `variant` on a text link. Link mode pages by cursor only. `Link` also takes `rel?: 'next' | 'prev'`, which joins the `noopener noreferrer` an external link always carries.
127
+
128
+ ## Rules an agent must not break
129
+
130
+ - **SCSS modules only.** `Foo.tsx` + `Foo.module.scss`, always paired. The only inline `style` allowed is a CSS custom property.
131
+ - **Logical properties.** `margin-inline`, `inset-inline-start`, `text-align: start`. A `left`/`right` in a stylesheet is a bug.
132
+ - **No hardcoded strings.** Label props, or the built-in keys in `src/i18n-keys.ts` → [I18n](I18n).
133
+ - **No raw colours.** `t.role('<role>')` or `var(--color-*)` → [Theming](Theming).
134
+ - **No prop spreading.** Components declare explicit props; there is no `{...rest}`.
135
+ - **`solid-js` is a type-only import** inside the package — reactive access goes through one registered adapter, so the design system does not pin a renderer version.
136
+ - Formatting logic lives in a pure `*-view.ts` beside the component (`money-view.ts`, `date-time-view.ts`), testable with no renderer.
137
+ - **Choosing from a list is three controls, never one with modes.** `Select` (short, closed), `SearchableSelect` (long, closed, value ≠ label — no-script it is the native `<select>`), `Combobox` (free text). There is no `<Select searchable>`: every `Select` island would carry the listbox.
138
+ - **A destructive question is `ConfirmDialog`, never `confirm(`.** Cancel is focused, the backdrop never dismisses, `action` posts with scripting off, `onConfirm` stays open on a refusal; the confirm label is a verb with no default. `guards/native-confirm.ts` refuses `confirm(` in app code.
139
+ - **Shortcuts are one table per island, merged per page.** `useHotkeys([{ keys, label, run }])`; `ShortcutSheet` lists the merged table on `?` and `useCommandPalette` adds `mod+k` to it — so the legend cannot drift from the bindings. The framework reserves no key.
140
+ - **The bottom sheet is `Drawer side="block-end" grabber`.** There is no `Sheet`. Link tabs are `Tabs` with `hrefFor` — a `<nav>`, never `role="tab"`.
141
+ - **Charts: options on the shipped charts, not new charts.** Signed bars and sparklines, `null` gaps, `at` for true x, `references`, `stacked` areas, `tone` per series, `Gauge` `thresholds`/`target`, `Meter` `target`. The two new names are `HeatStrip` and `LiveIndicator`, which no chart's shape covers. A stat grid is `Grid minColumn`.
142
+ - **Which list, when.** Records with columns: `DataTable`. Thousands of one-shape rows: `VirtualList` (no script, every row renders; with script, under 100 mounted). Things inside things: `Tree` — `hrefFor` is `<details>` and links at 0 B, `onSelect` is the island's `role="tree"`. What happened: `Timeline` (+ `InfiniteScroll` + `RelativeTime` is the activity feed; there is no feed component). Facts about one thing: `DescriptionList`, never a two-column `Table`.
143
+ - **Dates are the native input first.** `DatePicker`/`DateRangePicker` are `<input type="date">` with a `Calendar` popover only on a fine pointer with script. A date is a `PlainDate`; a window (`TimeRangePicker`, `timeRangeUrl`) is instants and takes a REQUIRED `timeZone`. Presets are the app's. `Slider` is `<input type="range">`; a segmented control is `Radio appearance="segmented"`; there is no number stepper.
144
+ - **Reordering is `SortableList` and `Board`, a split is `SplitPane`** — one Pointer Events gesture, never HTML5 drag. A drop calls `onMove` once and is a request: the row holds until it settles, a rejection restores it and says so. `canDrop(item, group)` is the one seam for a WIP limit or a status machine. With `action`, each row is a real `<form>` (Up/Down, or a target `select` per card). The handle alone has `touch-action: none`, so a phone still scrolls from a row's body. `SplitPane` is one column below `md`; `onResize` + `value` is how a split persists — storage is the app's.
145
+ - **What a model wrote is `Markdown`; code is `CodeBlock`; never `innerHTML`.** Raw HTML prints as text, links only http/https/mailto/relative, a remote image prints its alt as a link unless `resolveImage` vouches for it, `baseLevel` defaults to 2. Fences go through `CodeBlock`, highlighted only by the `languages` the caller passes (`allLanguages` on a server page; one named language in an island). Structured payloads are `JsonView`, changes `DiffView` (two texts or a unified patch), structured logs `LogViewer`, terminal output `TerminalView` (SGR colours as roles). A rich-text editor is `Textarea` plus a `Markdown` preview.
146
+
147
+ ## Error codes
148
+
149
+ | Code | Means |
150
+ |---|---|
151
+ | `X_TOKEN_UNKNOWN` | a design token role that does not exist; `cause` lists the ones that do |
152
+ | `X_THEME_INVALID` | a theme that is not `light` or `dark` |
153
+ | `X_UI_RUNTIME_MISSING` | a host capability the component needs is absent (`IntersectionObserver`, `localStorage`) |
154
+ | `X_UI_INVALID_VALUE` | a formatting component got an unrenderable value — `NaN` money, an invalid date, a heading level off the scale |
155
+
156
+ Full rows: [Error codes](Error-Codes).