@ultimat3/cli 29.4.0 → 30.1.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 +1314 -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
@@ -1,6 +1,7 @@
1
- // `x g resource <name>`'s page: the plural route a slice is reached at. It READS — `load` calls the
2
- // slice's own query as the request's actor — renders the rows through the slice's list component
3
- // inside `AsyncRegion` (ready, empty, failed), and mounts the slice's form island. `x g route`
1
+ // `x g resource <name>`'s page: the plural route a slice is reached at. It READS — a `<Hole>` calls
2
+ // the slice's own query a page at a time as the request's actor, after the shell left — renders the
3
+ // rows in a link-mode `DataTable` (loading, failed, empty, ready; paged by URL, no script), and
4
+ // mounts the slice's form island. `x g route`
4
5
  // writes a page with a heading and nothing to read; a resource has rows and a form, and a page
5
6
  // that showed neither was a slice nobody could reach without wiring it by hand.
6
7
 
@@ -8,7 +9,8 @@ import { sortedImports } from './imports';
8
9
  import type { GeneratedFile, NameSet } from './naming';
9
10
  import { titleKey } from './naming';
10
11
  import { formIslandSpecifier } from './resource-form-island';
11
- import { LINE_WIDTH } from './wrap';
12
+ import { pageDbTest } from './resource-page-db';
13
+ import { LINE_WIDTH, wrapImport } from './wrap';
12
14
 
13
15
  /**
14
16
  * The page's JS budget — MEASURED, never a ceiling picked to pass. `x build --target static` on a
@@ -20,9 +22,18 @@ import { LINE_WIDTH } from './wrap';
20
22
  * byte-identical since 1.4.1 (issue #354), so no headroom is held for run-to-run movement. An app
21
23
  * that adds per-document scripts (`navigation: { client: [...] }` charges its router, ~18 kB, to
22
24
  * every `app/` page) is over it on its first build, and `nextSteps` sends the author there.
25
+ *
26
+ * Raised to 68kb (69,632) on 2026-11-06, measured on Bun 1.4.4 the same way: 66,285 B — the island
27
+ * 65,430 + the runtime 855, over 64kb by 749 B. why: `@ultimat3/ui` grew a wave of components
28
+ * (pickers, lists, viewers, drag, charts) and the island carries ui's whole catalog-key list
29
+ * (`i18n-keys.ts`) beside the Form/Input/Button it renders. The same 3.3 kB of headroom.
30
+ *
31
+ * Lowered to 66kb (67,584) the same day, measured the same way: 64,167 B — the island 63,312 +
32
+ * the runtime 855. why: `UI_KEYS` became a namespace over `ui-keys.ts`, so the island keeps the
33
+ * three keys its components read, not all of them (−2,118 B). The same ~3.4 kB of headroom.
23
34
  */
24
- export const RESOURCE_PAGE_BUDGET = '64kb';
25
- export const RESOURCE_PAGE_MEASURED = '59,679 B';
35
+ export const RESOURCE_PAGE_BUDGET = '66kb';
36
+ export const RESOURCE_PAGE_MEASURED = '64,167 B';
26
37
 
27
38
  /** How the page reaches a string — the same rule `route.ts` states, for the same issue (#249). */
28
39
  const catalogImport = (module: string | undefined): string =>
@@ -36,11 +47,12 @@ const i18nImports = (module: string | undefined): readonly string[] =>
36
47
  ? ["import { currentLocale, t } from '@ultimat3/i18n';"]
37
48
  : [catalogImport(module), "import { currentLocale } from '@ultimat3/i18n';"];
38
49
 
39
- /** `const load = … => rowsState(() => <read>(input()));`, as Biome prints it at any name length. */
40
- const loadLine = (read: string): string => {
41
- const head = 'const load = (): Promise<PageData> =>';
42
- const body = `rowsState(() => ${read}(input()));`;
43
- return `${head} ${body}`.length <= LINE_WIDTH ? `${head} ${body}` : `${head}\n ${body}`;
50
+ /** The read in `rows()`, as Biome prints it at any name length. */
51
+ const stateLine = (read: string): string => {
52
+ const line = ` const state = await rowsState(() => ${read}.page(inputOf(list), windowOf(list)));`;
53
+ return line.length <= LINE_WIDTH
54
+ ? line
55
+ : ` const state = await rowsState(() =>\n ${read}.page(inputOf(list), windowOf(list)),\n );`;
44
56
  };
45
57
 
46
58
  export interface ResourcePageOptions {
@@ -59,6 +71,7 @@ const pageSource = (options: ResourcePageOptions): string => {
59
71
  const { feature } = options;
60
72
  const path = feature.pluralKebab;
61
73
  const list = `${feature.camel}List`;
74
+ const translator = options.catalogModule === undefined ? '' : '\n const t = useT();\n';
62
75
  const open = options.shell
63
76
  ? '<Shell>\n <div class={styles.page}>'
64
77
  : '<main class={styles.page}>';
@@ -69,24 +82,25 @@ const pageSource = (options: ResourcePageOptions): string => {
69
82
  .split('\n')
70
83
  .map((line) => (line === '' ? line : `${indent}${line}`))
71
84
  .join('\n');
72
- return `// /${path}: the org's ${feature.pluralKebab}, newest first, and the form that adds one. \`load\`
73
- // reads through the slice's own query, so the policy, the tenancy and the order are the query's;
74
- // the form is the slice's island, the one module of this page a browser downloads.
85
+ return `// /${path}: the org's ${feature.pluralKebab}, newest first, and the form that adds one.
86
+ // The rows are read inside a \`<Hole>\`, through the slice's own query, so the shell and the form
87
+ // are on screen before the read answers; the policy, the tenancy and the order are the query's.
88
+ // The pager is links (\`listUrl\`), so paging needs no script; the form is the slice's island, the
89
+ // one module of this page a browser downloads.
75
90
 
76
91
  ${sortedImports([
77
92
  ...i18nImports(options.catalogModule),
78
93
  "import { derivePath } from '@ultimat3/action';",
79
- "import type { AsyncState } from '@ultimat3/core';",
94
+ "import type { AsyncState, Page as RowPage } from '@ultimat3/core';",
80
95
  "import type { KnownPermission } from '@ultimat3/policy';",
81
- "import { defineRoute, island } from '@ultimat3/render';",
82
- "import { AsyncRegion, EmptyState, PageHeader, Section, Skeleton } from '@ultimat3/ui';",
96
+ "import { defineRoute, Hole, island } from '@ultimat3/render';",
97
+ "import {\n DataTable,\n type ListUrl,\n listUrl,\n Money,\n PageHeader,\n Section,\n Skeleton,\n} from '@ultimat3/ui';",
83
98
  ])}
84
99
  ${options.shell ? "import { Shell } from '../../shared/shell';\n" : ''}import type { ${feature.pascal} } from '../${feature.kebab}/entity';
85
- import { ${list} } from '../${feature.kebab}/live/${feature.kebab}-list';
86
- import { ${feature.pascal}List } from '../${feature.kebab}/ui';
100
+ ${wrapImport([list, `${list}Sorts`], `../${feature.kebab}/live/${feature.kebab}-list`)}
87
101
  import styles from './page.module.scss';
88
102
 
89
- /** One page of rows: the read's own bound. Past it, page with a cursor (\`${list}.page\`). */
103
+ /** The most rows one page shows; \`?limit=\` may ask for fewer, never more. */
90
104
  const PAGE_ROWS = 50;
91
105
 
92
106
  /** The currency a price is typed in; the form sends integer minor units of it. */
@@ -94,14 +108,14 @@ const CURRENCY = 'USD';
94
108
 
95
109
  type Row = ${feature.pascal};
96
110
 
97
- /** What \`load\` answers: the read's outcome as DATA, so the page renders each of its states. */
98
- export type PageData = AsyncState<readonly Row[]>;
111
+ /** The read's outcome as DATA, so the table renders each of its states. */
112
+ export type PageData = AsyncState<RowPage<Row>>;
99
113
 
100
114
  /**
101
- * The read, as a state. A refusal or a failed read is the region's error branch — code, cause and
102
- * fix on screen, the form still there — never a 500 for the whole document.
115
+ * The read, as a state. A refusal, a failed read or a stale cursor is the table's error branch —
116
+ * code, cause and fix on screen, the form still there — never a 500 for the whole document.
103
117
  */
104
- export async function rowsState(read: () => Promise<readonly Row[]>): Promise<PageData> {
118
+ export async function rowsState(read: () => Promise<RowPage<Row>>): Promise<PageData> {
105
119
  try {
106
120
  return { status: 'ready', data: await read() };
107
121
  } catch (error) {
@@ -109,10 +123,62 @@ export async function rowsState(read: () => Promise<readonly Row[]>): Promise<Pa
109
123
  }
110
124
  }
111
125
 
112
- /** No org: the query reads the ACTOR's, so a read can only ever be for the caller's own org. */
113
- const input = () => ({ limit: PAGE_ROWS });
126
+ /** The list's state in the URL: a sort among the query's own columns, a cursor, a page size. */
127
+ type ListState = ListUrl<(typeof ${list}Sorts)[number]>;
114
128
 
115
- ${loadLine(list)}
129
+ const listOf = (url: string | undefined): ListState =>
130
+ listUrl(url ?? '/${path}', {
131
+ sorts: ${list}Sorts,
132
+ limit: { default: PAGE_ROWS, max: PAGE_ROWS },
133
+ });
134
+
135
+ /**
136
+ * No org: the query reads the ACTOR's, so a read can only ever be for the caller's own org. No
137
+ * sort in the URL is the query's own order, newest first; a cursor is minted for one sort alone,
138
+ * and \`listUrl\` drops it whenever the sort changes.
139
+ */
140
+ const inputOf = (list: ListState) =>
141
+ list.sort === undefined
142
+ ? { limit: PAGE_ROWS }
143
+ : { limit: PAGE_ROWS, sort: list.sort.key, dir: list.sort.direction };
144
+
145
+ /** The page the URL names: its size, and the cursor it reads after when it is not the first. */
146
+ const windowOf = (list: ListState) =>
147
+ list.cursor === undefined ? { first: list.limit } : { first: list.limit, after: list.cursor };
148
+
149
+ /** The rows, once they are read. Called by the \`<Hole>\`: it starts after the first flush. */
150
+ async function rows(list: ListState) {${translator}
151
+ ${stateLine(list)}
152
+ const page = state.status === 'ready' ? state.data : undefined;
153
+ return (
154
+ <DataTable
155
+ caption={t('${titleKey(path)}')}
156
+ rows={page?.rows ?? []}
157
+ rowKey={(row: Row) => row.id}
158
+ error={state.status === 'failed' ? state.error : undefined}
159
+ emptyTitle={t('app.${feature.kebab}.empty')}
160
+ columns={[
161
+ {
162
+ key: 'title',
163
+ header: t('app.${feature.kebab}.titleLabel'),
164
+ sortable: true,
165
+ cell: (row) => row.title,
166
+ },
167
+ {
168
+ key: 'price',
169
+ header: t('app.${feature.kebab}.priceLabel'),
170
+ numeric: true,
171
+ cell: (row) => <Money value={row.price} />,
172
+ },
173
+ ]}
174
+ sort={list.sort}
175
+ sortHrefFor={list.sortHrefFor}
176
+ nextCursor={page?.nextCursor}
177
+ hrefFor={list.hrefFor}
178
+ firstHref={list.firstHref}
179
+ />
180
+ );
181
+ }
116
182
 
117
183
  // Declared ABOVE \`defineRoute\`: the route drains the islands declared before it. Named by
118
184
  // SPECIFIER, never imported — a string has no import edge, so the page's bundle graph stays the
@@ -123,28 +189,24 @@ const ${feature.pascal}Form = island({
123
189
  });
124
190
 
125
191
  export const config = defineRoute({
126
- render: 'ssr',
192
+ render: 'stream',
127
193
  hydrate: 'visible',
128
194
  offline: 'runtime',
129
195
  // An org's rows are not public: a route with no policy registers \`auth: 'public'\` and its
130
196
  // response carries no \`vary: cookie\`. Which ORG is the query's decision, on every read.
131
197
  policy: { permission: '${feature.kebab}:read' satisfies KnownPermission },
132
- // measured: ${RESOURCE_PAGE_MEASURED} (\`x build --target static\`, Bun 1.4.0) — the form island and the
198
+ // measured: ${RESOURCE_PAGE_MEASURED} (\`x build --target static\`, Bun 1.4.4) — the form island and the
133
199
  // \`visible\` runtime. why: the form is Solid, @ultimat3/ui's Form/Input/Button and the transport.
134
200
  // A raise states its own measured bytes and why here, in the same diff (\`X_BUDGET_EXCEEDED\`).
135
201
  budget: { js: '${RESOURCE_PAGE_BUDGET}' },
136
- load,
137
202
  meta: ({ t }) => ({
138
203
  title: t('${titleKey(path)}'),
139
204
  description: t('${titleKey(path).replace('.title', '.description')}'),
140
205
  }),
141
206
  });
142
207
 
143
- export interface PageProps {
144
- readonly data: PageData;
145
- }
146
-
147
- export function Page(props: PageProps) {${options.catalogModule === undefined ? '' : '\n const t = useT();\n'}
208
+ export function Page(props: { readonly url?: string | undefined }) {${options.catalogModule === undefined ? '' : '\n const t = useT();\n'}
209
+ const list = listOf(props.url);
148
210
  return (
149
211
  ${open}
150
212
  ${inner(`<PageHeader title={t('${titleKey(path)}')} />
@@ -164,12 +226,7 @@ ${inner(`<PageHeader title={t('${titleKey(path)}')} />
164
226
  <Skeleton lines={3} />
165
227
  </${feature.pascal}Form>
166
228
  </Section>
167
- <AsyncRegion
168
- state={props.data}
169
- reserve={{ lines: 3 }}
170
- empty={() => <EmptyState title={t('app.${feature.kebab}.empty')} />}
171
- ready={(rows) => <${feature.pascal}List rows={rows} />}
172
- />`)}
229
+ <Hole fallback={<Skeleton lines={3} />}>{() => rows(list)}</Hole>`)}
173
230
  ${close}
174
231
  );
175
232
  }
@@ -182,9 +239,9 @@ const OTHER_ORG = '00000000-0000-4000-8000-000000000009';
182
239
  const pageTest = (options: ResourcePageOptions): string => {
183
240
  const { feature } = options;
184
241
  const t = options.catalogModule === undefined ? '' : '\n const t = useT();';
185
- return `// /${feature.pluralKebab}, rendered as a request renders it: \`load\` reads the actor's org through the
186
- // slice's query — the in-memory driver under test — and the page shows the rows, the empty state
187
- // or the refusal, with the form island beside them in every one.
242
+ return `// /${feature.pluralKebab}, rendered as a request renders it, with the \`<Hole>\` awaited in line: the
243
+ // read names the actor's org through the slice's query — the in-memory driver under test — and the
244
+ // page shows the rows, the empty state or the refusal, with the form island beside them in each.
188
245
  ${sortedImports([
189
246
  catalogImport(options.catalogModule),
190
247
  `import { driver } from '${options.dbModule}';`,
@@ -224,7 +281,7 @@ unitTest('the page lists the org’s rows, newest first, beside the form', async
224
281
  clock.advance(1000);
225
282
  await store('newer');
226
283
  const view = await renderRoute(page, { url, actor: member });
227
- expect(view.data.status).toBe('ready');
284
+ expect(view.text).toContain('newer');
228
285
  expect(view.text.indexOf('newer')).toBeLessThan(view.text.indexOf('older'));
229
286
  // The form is the one island, and it wakes when it is visible.
230
287
  expect(view.islands.map((island) => island.strategy)).toEqual(['visible']);
@@ -233,30 +290,78 @@ unitTest('the page lists the org’s rows, newest first, beside the form', async
233
290
 
234
291
  unitTest('an org with no rows reads the empty state, not an empty list', async () => {${t}
235
292
  const view = await renderRoute(page, { url, actor: member });
236
- expect(view.data).toEqual({ status: 'ready', data: [] });
237
293
  expect(view.text).toContain(t('app.${feature.kebab}.empty'));
238
294
  expect(view.meta.title).toBe(t('${titleKey(feature.pluralKebab)}'));
239
295
  });
240
296
 
241
- unitTest('a member of another org reads none of this org’s rows', async () => {
297
+ unitTest('a member of another org reads none of this org’s rows', async () => {${t}
242
298
  await store('ours');
243
299
  // The same page, the same URL: which org it reads is the actor's, never a parameter.
244
300
  const outsider = testActor('outsider', { orgId: otherOrg, permissions: [read] }).actor;
245
301
  const view = await renderRoute(page, { url, actor: outsider });
246
- expect(view.data).toEqual({ status: 'ready', data: [] });
302
+ expect(view.text).not.toContain('ours');
303
+ expect(view.text).toContain(t('app.${feature.kebab}.empty'));
247
304
  });
248
305
 
249
306
  unitTest('a caller the read refuses sees the refusal, and the page around it', async () => {
250
307
  // In the org, without the grant: the query's policy refuses before any row is read.
251
308
  const stranger = testActor('stranger', { orgId }).actor;
252
309
  const view = await renderRoute(page, { url, actor: stranger });
253
- expect(view.data.status).toBe('failed');
254
310
  expect(view.html).toContain('X_FORBIDDEN');
255
311
  expect(view.islands).toHaveLength(1);
256
312
  });
257
313
 
314
+ /** The anchor a pager side renders, read off the HTML: \`rel\` names the side. */
315
+ const linkTo = (html: string, rel: string): string | undefined => {
316
+ const anchor = new RegExp(\`<a\\\\b[^>]*\\\\brel="\${rel}"[^>]*>\`).exec(html)?.[0];
317
+ return /href="([^"]*)"/.exec(anchor ?? '')?.[1]?.replaceAll('&amp;', '&');
318
+ };
319
+
320
+ unitTest('the list pages by link: a cursor in the URL, no script', async () => {
321
+ await store('older');
322
+ clock.advance(1000);
323
+ await store('newer');
324
+ const first = await renderRoute(page, { url: \`\${url}?limit=1\`, actor: member });
325
+ expect(first.text).toContain('newer');
326
+ expect(first.text).not.toContain('older');
327
+ // The first page has nowhere back to, and a keyset page has no Previous.
328
+ expect(first.html).not.toContain('href="/${feature.pluralKebab}?limit=1"');
329
+ expect(linkTo(first.html, 'prev')).toBeUndefined();
330
+ const next = linkTo(first.html, 'next');
331
+ expect(next).toContain('after=');
332
+
333
+ const second = await renderRoute(page, { url: new URL(next ?? '', url).href, actor: member });
334
+ expect(second.text).toContain('older');
335
+ expect(second.text).not.toContain('newer');
336
+ // The last page links back to the first and on to nothing, never to \`after=null\`.
337
+ expect(second.html).toContain('href="/${feature.pluralKebab}?limit=1"');
338
+ expect(linkTo(second.html, 'next')).toBeUndefined();
339
+ expect(second.html).not.toContain('after=null');
340
+ });
341
+
342
+ unitTest('the list sorts by link, and a sorted list pages in that order', async () => {
343
+ // Written so that newest-first and by-title disagree: a sort applied after the bound would show
344
+ // the two newest re-sorted, never the first two titles.
345
+ for (const title of ['beta', 'gamma', 'alpha']) {
346
+ await store(title);
347
+ clock.advance(1000);
348
+ }
349
+ const sorted = \`\${url}?sort=title&dir=asc&limit=2\`;
350
+ const first = await renderRoute(page, { url: sorted, actor: member });
351
+ expect(first.text.indexOf('alpha')).toBeLessThan(first.text.indexOf('beta'));
352
+ expect(first.text).not.toContain('gamma');
353
+ // The header links on to the next state of its cycle, the cursor dropped with the old order.
354
+ const descending = 'href="/${feature.pluralKebab}?sort=title&amp;dir=desc&amp;limit=2"';
355
+ expect(first.html).toContain(descending);
356
+ const next = linkTo(first.html, 'next');
357
+ expect(next).toContain('sort=title');
358
+ const second = await renderRoute(page, { url: new URL(next ?? '', url).href, actor: member });
359
+ expect(second.text).toContain('gamma');
360
+ expect(second.text).not.toContain('alpha');
361
+ });
362
+
258
363
  unitTest('it is gated, rendered per request, and budgeted', () => {
259
- expect(page.config.render).toBe('ssr');
364
+ expect(page.config.render).toBe('stream');
260
365
  expect(page.config.policy?.permission).toBe(read);
261
366
  expect(page.config.offline).toBe('runtime');
262
367
  expect(page.config.budget.js).toBe('${RESOURCE_PAGE_BUDGET}');
@@ -264,8 +369,12 @@ unitTest('it is gated, rendered per request, and budgeted', () => {
264
369
  `;
265
370
  };
266
371
 
267
- /** The resource's `page.tsx` and its unit test. The stylesheet, e2e test and catalog are `x g route`'s. */
372
+ /**
373
+ * The resource's `page.tsx`, its unit test, and the same read over the migrated database. The
374
+ * stylesheet, e2e test and catalog are `x g route`'s.
375
+ */
268
376
  export const resourcePageFiles = (options: ResourcePageOptions): readonly GeneratedFile[] => [
269
377
  { path: `${options.pageDir}/page.tsx`, contents: pageSource(options) },
270
378
  { path: `${options.pageDir}/page.test.ts`, contents: pageTest(options) },
379
+ { path: `${options.pageDir}/page-db.test.ts`, contents: pageDbTest(options) },
271
380
  ];
@@ -13,13 +13,11 @@ import { kebab, pascal, titleKey } from './naming';
13
13
  export type Surface = 'site' | 'app';
14
14
 
15
15
  /**
16
- * `ssr` on `app/`, not `stream`, for the reason `scaffold-app.ts`'s dashboard already states:
17
- * `stream` sets `needsSuspense`, the framework ships no hole marker, and `defineRoute` therefore
18
- * throws `X_ROUTE_MODE_INVALID` at import. That is not a failing page — it is a page that registers
19
- * NO route at all, so every `x g route --surface app` and every `x g resource` scaffolded a URL
20
- * that was absent from `x routes`, from the manifest and from `budgets`. Ship the mode that works.
16
+ * `stream` on `app/`: the surface's default (`surfaces.ts`), and a stream is always
17
+ * `private, no-store` where an ungated `ssr` page may be public. A page with no `<Hole>` pays
18
+ * nothing for it (`renderStreamHtml`), and the first read an author moves into one streams.
21
19
  */
22
- const RENDER: Record<Surface, string> = { site: 'isr', app: 'ssr' };
20
+ const RENDER: Record<Surface, string> = { site: 'isr', app: 'stream' };
23
21
  /**
24
22
  * `app` is `'visible'` on a page the generator writes with no island, and that is not an oversight.
25
23
  * A declared strategy costs nothing when there is nothing to hydrate — `hydrateRuntime` is emitted
@@ -10,6 +10,7 @@ import { dashboardFiles } from './scaffold-dashboard';
10
10
  import { demoOrgFiles } from './scaffold-demo-org';
11
11
  import { entryFiles } from './scaffold-entries';
12
12
  import { errorPageFiles } from './scaffold-errors';
13
+ import { exerciseFiles } from './scaffold-exercise';
13
14
  import { httpFiles } from './scaffold-http';
14
15
  import { icon } from './scaffold-icon';
15
16
  import { rolesFiles } from './scaffold-roles';
@@ -407,6 +408,8 @@ export function appFiles(app: NameSet, example: boolean): readonly GeneratedFile
407
408
  { path: 'apps/web/tsconfig.json', contents: tsconfig() },
408
409
  // The process a container starts and the artifact a CDN is handed — `scaffold-entries.ts`.
409
410
  ...entryFiles(),
411
+ // What `x exercise` cannot guess, empty until a finding says what to paste — `scaffold-exercise.ts`.
412
+ ...exerciseFiles(),
410
413
  { path: 'apps/web/site/icon.png', contents: icon() },
411
414
  // The landing page: hero, two calls to action, three feature cards — `scaffold-site.ts`.
412
415
  ...siteFiles(app),
@@ -98,7 +98,8 @@ export const devAuthenticate = (request: HeaderRequest): Actor | null => {
98
98
  };
99
99
  `;
100
100
 
101
- const authModule =
101
+ /** `apps/web/app/auth/auth.ts`: also `x g sign-in`'s, written only where an older app lacks it. */
102
+ export const authModule =
102
103
  (): string => `// This app's one \`@ultimat3/auth\` instance: sessions, passwords and the lockout, over the
103
104
  // framework's own \`x_users\` and \`x_sessions\` tables — \`x dev\` and \`ROLE=migrate\` apply them.
104
105
  //
@@ -1,14 +1,13 @@
1
- // The two scripts a scaffolded app is driven by — `bun run setup` and `bun run check` — written as
2
- // TypeScript that Bun runs, never as shell. A bash `bin/setup` was the first thing a PowerShell or
3
- // cmd user hit after `x new`, and it failed there; a `.ts` file run by the Bun the app already
4
- // requires is the same script on every OS. `bun run dev` needs no file: it is `x dev`.
1
+ // The script a scaffolded app is set up by — `bun run setup` — written as TypeScript that Bun runs,
2
+ // never as shell. A bash `bin/setup` was the first thing a PowerShell or cmd user hit after `x new`,
3
+ // and it failed there. `bun run dev` and `bun run verify` need no file: they are `x dev` and
4
+ // `x verify`, and `x verify` builds what its budgets step weighs (no `bin/check`, plan 101 21).
5
5
 
6
6
  import type { GeneratedFile } from './naming';
7
7
 
8
8
  /** Where each script lands, and what the root `package.json`'s `scripts` run — named once. */
9
9
  export const BIN_SCRIPTS = {
10
10
  setup: 'bin/setup.ts',
11
- check: 'bin/check.ts',
12
11
  } as const;
13
12
 
14
13
  /** The `package.json` script line that runs a bin file: Bun itself, so no shell interprets it. */
@@ -70,53 +69,7 @@ await x('manifest');
70
69
  console.log('setup complete — next: bun run dev');
71
70
  `;
72
71
 
73
- const binCheck =
74
- (): string => `// The gate. Same steps as CI, because a check that lives only in CI cannot be run locally.
75
- // \`bun run check\` — Bun runs this file, so it is the same gate under PowerShell, cmd or bash.
76
- import { join } from 'node:path';
77
-
78
- const root = join(import.meta.dir, '..');
79
- const args = process.argv.slice(2);
80
-
81
- /** The app's own CLI, as \`bunx x\` resolves it, from the app root; resolves to its exit code. */
82
- const x = async (...argv: string[]): Promise<number> => {
83
- const child = Bun.spawn([process.execPath, 'x', 'x', ...argv], {
84
- cwd: root,
85
- stdio: ['inherit', 'inherit', 'inherit'],
86
- });
87
- // The terminal already hands Ctrl-C to the child; this process only waits for its verdict. A
88
- // SIGTERM is forwarded, so a CI timeout stops the gate instead of orphaning it mid-step.
89
- const wait = (): void => {};
90
- const stop = (): void => {
91
- child.kill('SIGTERM');
92
- };
93
- process.on('SIGINT', wait);
94
- process.on('SIGTERM', stop);
95
- const code = await child.exited;
96
- process.off('SIGINT', wait);
97
- process.off('SIGTERM', stop);
98
- return code;
99
- };
100
-
101
- // The build FIRST, and not as a convenience: \`x verify\`'s budgets step compares declared limits
102
- // against measured bytes in .x/build-stats.json, so with no build it reports X_BUDGET_UNMEASURED
103
- // and the very first gate anyone runs on a brand-new app is red for a reason that has nothing to
104
- // do with their code.
105
- //
106
- // \`--json\` is forwarded to BOTH, or the contract breaks: the build's human renderer followed by
107
- // the gate's JSON is neither on stdout. Both commands emit one object; a reader takes the last line.
108
- // \`--no-preflight\`: the build's own preflight is x verify's first six steps (typecheck, lint,
109
- // boundaries, filesize, package-shape, errors), and the gate below runs them anyway.
110
- const json = args.some((arg) => arg === '--json' || arg === '-j') ? ['--json'] : [];
111
- const built = await x('build', '--target', 'static', '--no-preflight', ...json);
112
- if (built !== 0) process.exit(built);
113
- process.exit(await x('verify', ...args));
114
- `;
115
-
116
72
  /** The bin scripts, in the order a newcomer runs them. */
117
73
  export function binFiles(): readonly GeneratedFile[] {
118
- return [
119
- { path: BIN_SCRIPTS.setup, contents: binSetup() },
120
- { path: BIN_SCRIPTS.check, contents: binCheck() },
121
- ];
74
+ return [{ path: BIN_SCRIPTS.setup, contents: binSetup() }];
122
75
  }
@@ -7,7 +7,7 @@ import type { GeneratedFile, NameSet } from './naming';
7
7
 
8
8
  const shape = (app: NameSet): string => `---
9
9
  name: shape
10
- description: Use BEFORE writing any code, at the idea stage — turns a product request into the one decision that governs everything after it: which of the eight primitives each piece is, which slice and surface it lives in, and the exact \`x g\` invocations. Read-only; it decides, it never builds. Also use when a request seems to need something the framework does not have.
10
+ description: Use BEFORE writing any code, at the idea stage — turns a product request into the one decision that governs everything after it: which of the eight primitives each piece is, which slice and surface it lives in, and the exact \`bunx x g\` invocations. Read-only; it decides, it never builds. Also use when a request seems to need something the framework does not have.
11
11
  tools: Read, Glob, Grep, Bash
12
12
  ---
13
13
 
@@ -45,8 +45,8 @@ stop; that answer is worth more than a plan built on a shape the app cannot hold
45
45
  | \`packages/db/\` | schema, migrations, seed |
46
46
  | \`packages/ui/\` | components with no feature knowledge |
47
47
 
48
- Read the tree before you place anything. \`x routes\`, \`x actions\`, \`x queries\`, \`x entities\`,
49
- \`x jobs\`, \`x tasks\` and \`x policy list\` are the registries — check whether the thing already
48
+ Read the tree before you place anything. \`bunx x routes\`, \`bunx x actions\`, \`bunx x queries\`, \`bunx x entities\`,
49
+ \`bunx x jobs\`, \`bunx x tasks\` and \`bunx x policy list\` are the registries — check whether the thing already
50
50
  exists before proposing it, and add \`--json\` to any of them.
51
51
 
52
52
  ## Report
@@ -54,7 +54,7 @@ exists before proposing it, and add \`--json\` to any of them.
54
54
  \`\`\`
55
55
  Build: yes | no — <one line>
56
56
  Pieces: <primitive> <name> → <dir> (one line each)
57
- Generate: x g <kind> <name> --feature <slice> (one line each, in dependency order)
57
+ Generate: bunx x g <kind> <name> --feature <slice> (one line each, in dependency order)
58
58
  Existing: <what already covers part of this>
59
59
  Refused: <anything that fits no primitive, and why>
60
60
  \`\`\`
@@ -72,17 +72,17 @@ feature slice. Nothing else. A change outside that set is a collision — report
72
72
  | Rule | Detail |
73
73
  |---|---|
74
74
  | The entity is the schema | columns, invariants, indexes and tenancy are declared there, never in SQL |
75
- | Migrations are generated | edit \`entity.ts\`, then \`x db gen "what changed"\`. Never hand-write a file into \`packages/db/migrations/\` |
75
+ | Migrations are generated | edit \`entity.ts\`, then \`bunx x db gen "what changed"\`. Never hand-write a file into \`packages/db/migrations/\` |
76
76
  | Destructive is declared | a migration that drops, truncates or retypes carries \`-- destructive: true\`, or the gate refuses it |
77
77
  | \`repo.ts\` is the only door | every read and write goes through it; a raw statement anywhere else is authz bypassed |
78
- | Branch before you break things | \`x db branch create <name>\`, never the shared dev database |
78
+ | Branch before you break things | \`bunx x db branch create <name>\`, never the shared dev database |
79
79
  | Money | \`{ minor, currency }\` — integer minor units and an ISO code, both, always. Never a float |
80
80
  | Time | store UTC; a formatted date always names an explicit IANA time zone |
81
81
 
82
- Inspect before you change: \`x entities list\`, \`x entities show <name>\`, both with \`--json\`.
82
+ Inspect before you change: \`bunx x entities list\`, \`bunx x entities show <name>\`, both with \`--json\`.
83
83
 
84
84
  Checks: \`bun test <path>/entity.test.ts\`, \`bunx biome check --write <paths>\`, and \`bun run typecheck\`
85
- once when you are otherwise done. Never \`x verify\` — that belongs to whoever coordinates you.
85
+ once when you are otherwise done. Never \`bunx x verify\` — that belongs to whoever coordinates you.
86
86
  `;
87
87
 
88
88
  const server = (app: NameSet): string => `---
@@ -97,19 +97,19 @@ renders. A change outside that set is a collision — report it, do not make it.
97
97
 
98
98
  | Rule | Detail |
99
99
  |---|---|
100
- | Generate, then edit | \`x g <kind> <name> --feature <slice>\` for every one of them — a hand-written primitive is missing from every registry that projects it |
100
+ | Generate, then edit | \`bunx x g <kind> <name> --feature <slice>\` for every one of them — a hand-written primitive is missing from every registry that projects it |
101
101
  | One authz object | the policy decides; a route or a UI that re-checks is a second answer that will drift |
102
102
  | Data through the repo | an action calls \`repo.ts\`, never the database |
103
- | Errors are instructions | never \`throw new Error\` — subclass \`UltimateError\` with a stable \`X_SCREAMING_SNAKE\` code, a cause and a \`fix:\` a caller can actually run |
103
+ | Errors are instructions | never \`throw new Error\` — declare each code once with \`defineError({ code, title, status, cause, fix })\` from \`@ultimat3/core\`: a stable \`X_SCREAMING_SNAKE\` code, a cause and a \`fix:\` a caller can actually run. \`guards/error-declared.ts\` refuses a handwritten \`UltimateError\` subclass |
104
104
  | Jobs run at least once | a handler must be idempotent — an upsert or a statement whose second run changes nothing, never \`count + 1\` |
105
105
  | Tasks name a time zone | a cron schedule with an ambient zone is a different time twice a year |
106
106
  | No \`any\` | \`unknown\` plus a schema parse |
107
107
 
108
- Inspect before you change: \`x actions show <name>\`, \`x queries show <name>\`, \`x jobs list\`,
109
- \`x tasks list\`, \`x policy explain <subject>\` — every one takes \`--json\`.
108
+ Inspect before you change: \`bunx x actions show <name>\`, \`bunx x queries show <name>\`, \`bunx x jobs list\`,
109
+ \`bunx x tasks list\`, \`bunx x policy explain <subject>\` — every one takes \`--json\`.
110
110
 
111
111
  Checks: \`bun test <path>/<file>.test.ts\`, \`bunx biome check --write <paths>\`, and \`bun run typecheck\`
112
- once when you are otherwise done. Never \`x verify\` — that belongs to whoever coordinates you.
112
+ once when you are otherwise done. Never \`bunx x verify\` — that belongs to whoever coordinates you.
113
113
  `;
114
114
 
115
115
  const web = (app: NameSet): string => `---
@@ -124,7 +124,7 @@ calls. A change outside that set is a collision — report it, do not make it.
124
124
 
125
125
  | Rule | Detail |
126
126
  |---|---|
127
- | \`site/\` is 0kb JS | and may not import from \`app/\`. One interactive control on a static page is an island: \`x g island <name> --at <dir>\` |
127
+ | \`site/\` is 0kb JS | and may not import from \`app/\`. One interactive control on a static page is an island: \`bunx x g island <name> --at <dir>\` |
128
128
  | \`shared/\` is a leaf | tokens, primitives, the actor type. It imports nothing of yours |
129
129
  | The directory is the URL | \`page.tsx\` under \`site/\`/\`app/\`; \`api/\` holds no route file, only \`defineApi()\`'s actions and queries. The filename is never the path |
130
130
  | Every string through \`t()\` | a literal in a component is a string no locale can ever translate |
@@ -135,16 +135,16 @@ calls. A change outside that set is a collision — report it, do not make it.
135
135
  | The focus ring is replaced | \`outline: none\` on its own leaves a keyboard user with nothing. \`@include tokens.focus-ring\`, or a \`:focus-visible\` box-shadow beside it |
136
136
  | Every image has a box | width + height, or an aspect-ratio. An unsized image moves everything under it when its bytes land, and the priority one is never \`loading="lazy"\` |
137
137
  | Animate transform and opacity | nothing else composites: a \`top\`, a \`width\` or a \`box-shadow\` costs a layout pass every frame, and \`transition: all\` animates properties nobody chose. \`tokens.duration()\` / \`tokens.easing()\` are the values |
138
- | An island declares its states | a sibling \`<name>.island.states.ts\`, so \`x shot --island <name> --json\` can photograph the failures nobody can click to |
138
+ | An island declares its states | a sibling \`<name>.island.states.ts\`, so \`bunx x shot --island <name> --json\` can photograph the failures nobody can click to |
139
139
 
140
- Every one of the last five is a build error, not a preference — \`guards/\` holds them and \`x verify\`'s
140
+ Every one of the last five is a build error, not a preference — \`guards/\` holds them and \`bunx x verify\`'s
141
141
  \`boundaries\` step runs them. Read the guard rather than guessing at the rule.
142
142
 
143
- Inspect before you change: \`x routes --json\`, \`x i18n check\` for catalog gaps, and
144
- \`x shot <route> --json\` when you need to see what you built.
143
+ Inspect before you change: \`bunx x routes --json\`, \`bunx x i18n check\` for catalog gaps, and
144
+ \`bunx x shot <route> --json\` when you need to see what you built.
145
145
 
146
146
  Checks: \`bun test <path>/page.test.ts\`, \`bunx biome check --write <paths>\`, and \`bun run typecheck\`
147
- once when you are otherwise done. Never \`x verify\` — that belongs to whoever coordinates you.
147
+ once when you are otherwise done. Never \`bunx x verify\` — that belongs to whoever coordinates you.
148
148
  `;
149
149
 
150
150
  /** One agent per boundary, plus the read-only one that runs before there is a boundary to hold. */