@ultimat3/cli 29.3.2 → 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 +13 -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 +28 -8
  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 +42 -138
  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 +17 -27
  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,247 @@
1
+ # The eight primitives
2
+
3
+ Everything in the framework is one of these. `x g resource <name>` scaffolds a whole feature slice — an entity + repo, a policy, an errors module, **two** actions (`create-*`, `archive-*`), a live query, a job, an app route, plus service, UI and i18n files, 25 in all — each with a test that **passes on the first run**. Not one of each: the slice emits no `mutator` and no `task`. Those have their own generators.
4
+
5
+ ```
6
+ entity — a table + its domain type + invariants
7
+ policy — an authz rule, evaluated in every surface
8
+ action — a mutation or command (server-authoritative)
9
+ mutator — an action with an optimistic local twin (offline/realtime)
10
+ query — a read; optionally live (subscribable)
11
+ job — durable background work, optionally multi-step
12
+ route — a URL + render mode + metadata + offline strategy
13
+ task — a scheduled trigger (cron) that enqueues jobs
14
+ ```
15
+
16
+ | Primitive | Declared in | Generator | Deep page |
17
+ |---|---|---|---|
18
+ | `entity` | `<feature>/entity.ts` | `x g entity <name>` | [Entities and migrations](Entities-And-Migrations) |
19
+ | `policy` | `<feature>/policy.ts` | `x g policy <name>` | [Policies and authz](Policies-And-Authz) |
20
+ | `action` | `api/` or `<feature>/actions/<name>.ts` | `x g action <name>` | [Actions](Actions) |
21
+ | `mutator` | `<feature>/actions/<name>.ts` | `x g mutator <name>` | [Realtime](Realtime) |
22
+ | `query` | `<feature>/queries/<name>.ts`, or `live/<name>.ts` with `--live` | `x g query <name> --feature <slice>` | [Queries and live queries](Queries-And-Live-Queries) |
23
+ | `job` | `<feature>/jobs/<name>.ts` | `x g job <name>` | [Jobs and workflows](Jobs-And-Workflows) |
24
+ | `route` | a route folder's `config` export | `x g route <path>` | [Routes and render modes](Routes-And-Render-Modes) |
25
+ | `task` | `<feature>/tasks/<name>.ts`, plus the job it enqueues | `x g task <name>` | [Scheduled tasks](Scheduled-Tasks) |
26
+
27
+ Every one is a **function returning a value**, so an app encodes a house rule by wrapping one and exporting its own factory — `tenantEntity`, `auditedMutator` — with the registry, the manifest, the projections, admin and MCP treating the result identically. That is how the framework extends itself too (`llm()` returns an `action`, `backfill()` returns a `job`), and it is why there is no plugin API → [Building your own base](Building-Your-Own-Base).
28
+
29
+ ## Every primitive projects itself
30
+
31
+ A primitive's surfaces are methods **on the primitive**, never free functions taking it. `publishPost.openapi()`, not `toOpenApiOperation(publishPost)`. (The MCP tool is `@ultimat3/mcp`'s one projection, `toolFrom(publishPost)`: a tier-3 primitive cannot hold a tier-4 projection.) Every declared field is lifted onto it, and the declaration object — the `handle`, the `sql`, the `server` — is not reachable from app code at all.
32
+
33
+ | Primitive | Its surface |
34
+ |---|---|
35
+ | `entity` | `$`-sigil members: `posts.$view([...])`, `posts.$parse(row)`, `posts.$assert(row)`. An entity *is* its columns, so the sigil keeps the namespace clear — `posts.name` is a column, `posts.$name` is the entity |
36
+ | `policy` | one signature, `({ actor, input, row, ctx })`, identical on every surface. `row` is required and nullable, never smuggled through `input` |
37
+ | `action` | `.as()` `.openapi()` `.client()` `.job()` `.contract()` `.describe()`, plus `.input` `.output` `.policy` `.mcp`. `handle` is not among them |
38
+ | `mutator` | everything `action` has, plus `.local()` `.server()` `.conflict` `.describeMutator()` |
39
+ | `query` | `.as()` `.live()` `.client()` `.describe()`, plus `.input` `.policy` `.cache` `.mcp` `.isLive`. `sql` is not among them |
40
+ | `job` | `.enqueue()` `.as()` `.describe()`, plus `.parse()` and `.idempotencyKeyFor()` |
41
+ | `route` | a normalized descriptor rather than methods — `meta()` always awaits, `budget` is always an object. A route declares no behaviour to project; `describePages()` is the one route list |
42
+ | `task` | `.entries()` `.enqueue()` `.describe()` |
43
+
44
+ The declaration lives in a private store inside the package that runs it, and nothing exports a reader for it. So there is one execution path and one authz path structurally, not by convention: a hand-rolled look-alike carrying the right `kind` is `X_ACTION_FOREIGN` / `X_QUERY_FOREIGN`, never a registered primitive. Registration stamps the export name **in place**, so `import { publishPost }` is the object that projects once the app has booted — there is no second, differently-named twin to remember.
45
+
46
+ Each primitive's deep page carries the full member table.
47
+
48
+ ## One import per authoring file
49
+
50
+ `t` is re-exported from every package that hosts a declaration factory. Import it from the package you declare in; never reach past that package to `@ultimat3/schema`. It is the **same object**, not a copy — `configureSchemaProvider()` still swaps the provider under it — and each package pins that with an identity assertion in its `index.test.ts`.
51
+
52
+ | Import `t` from | When the file declares |
53
+ |---|---|
54
+ | `@ultimat3/entity` | an `entity` |
55
+ | `@ultimat3/action` | an `action` or a `mutator` |
56
+ | `@ultimat3/query` | a `query` |
57
+ | `@ultimat3/jobs` | a `job` or a `task` |
58
+ | `@ultimat3/mail` | a `defineMail` template |
59
+ | `@ultimat3/mcp` | a `defineAppMcp` hand-written tool |
60
+ | `@ultimat3/ai` | an `llm` — a factory over `action`, so it carries `t` too |
61
+ | `@ultimat3/schema` | nothing — a plain view-schema module, where schema *is* the one import |
62
+
63
+ `policy` and `route` declare no schema, so neither package re-exports `t`: a policy decides from `({ actor, input, row, ctx })` and a route from its path.
64
+
65
+ ## `entity`
66
+
67
+ A table + its domain type + invariants. The single source of the DB schema, the TS type, and the parse boundary.
68
+
69
+ | Aspect | Rule |
70
+ |---|---|
71
+ | Projects to | Postgres table, domain type, migration, repo (`postgresRepo`), admin screen, seed factory |
72
+ | Owns | column types, defaults, invariants, tenant column |
73
+ | Never | business logic, I/O, HTTP awareness, policy decisions |
74
+
75
+ ## `policy`
76
+
77
+ An authz rule, evaluated in every surface.
78
+
79
+ ```ts
80
+ policy: can('post:publish', ({ input, actor }) => ownsPost(actor, input.postId))
81
+ ```
82
+
83
+ | Aspect | Rule |
84
+ |---|---|
85
+ | Projects to | HTTP guard, live-query row filter, job actor check, MCP tool gate, admin visibility |
86
+ | Owns | the yes/no and the denial reason |
87
+ | Never | mutate, query outside the declared repos, or return partial data (filter in the `query`) |
88
+
89
+ ## `action`
90
+
91
+ A mutation or command, server-authoritative. Declared once, projected six ways.
92
+
93
+ ```ts
94
+ export const publishPost = action({
95
+ input: t.object({ postId: t.uuid, notify: t.boolean.default(true) }),
96
+ output: PostView,
97
+ policy: can('post:publish', ({ input, actor }) => ownsPost(actor, input.postId)),
98
+ cache: { invalidates: [tag.post, tag.feed] },
99
+ mcp: { expose: true, description: 'Publish a draft post' },
100
+ async handle({ input, ctx }) {
101
+ const post = await ctx.posts.publish(input.postId);
102
+ if (input.notify) await notifySubscribers.enqueue({ postId: post.id, orgId: post.orgId });
103
+ return post;
104
+ },
105
+ });
106
+ ```
107
+
108
+ | Aspect | Rule |
109
+ |---|---|
110
+ | Projects to | HTTP route, OpenAPI operation, typed client function, job handle, MCP tool, contract test |
111
+ | Owns | input/output contract, its policy, what it invalidates, MCP exposure |
112
+ | Never | read headers or cookies, render, authorize inside `handle`, do slow work inline |
113
+
114
+ ## `mutator`
115
+
116
+ An action with an optimistic local twin. `local` runs client-side against the local store; `server` is authoritative. Same input, same name, both halves in one file.
117
+
118
+ ```ts
119
+ export const likePost = mutator({
120
+ // REQUIRED (`X_MUTATOR_NOT_IDEMPOTENT`): a replay under the same Idempotency-Key answers the
121
+ // first result instead of running `server` again.
122
+ idempotent: true,
123
+ // Convergent, not incremental: `local` replays on every rebase, so applying it N times has to
124
+ // equal applying it once — `likedByMe` is what makes the second application a no-op.
125
+ local(tx, { postId }) {
126
+ tx.posts.update(postId, (p) =>
127
+ p.likedByMe ? {} : { likedByMe: true, likeCount: p.likeCount + 1 });
128
+ },
129
+ async server(ctx, { postId }) { return ctx.posts.like(postId); },
130
+ conflict: 'server-wins', // | 'last-write-wins' | custom(merge)
131
+ });
132
+ ```
133
+
134
+ | Aspect | Rule |
135
+ |---|---|
136
+ | Projects to | everything `action` does, plus a local-store transaction and a rebase entry |
137
+ | Owns | conflict strategy |
138
+ | Never | let `local` do I/O, randomness, or `Date.now()` — it must be replayable |
139
+
140
+ ## `query`
141
+
142
+ A read; optionally live (subscribable).
143
+
144
+ ```ts
145
+ export const liveFeed = query({
146
+ input: t.object({ orgId: t.uuid }),
147
+ policy: can('feed:read'),
148
+ live: true,
149
+ sql: ({ orgId }) => db.posts.where({ orgId }).orderBy('createdAt').limit(50),
150
+ });
151
+ ```
152
+
153
+ | Aspect | Rule |
154
+ |---|---|
155
+ | Projects to | HTTP GET, typed client hook, live subscription (`live: true`), cache entry with tags, MCP read tool |
156
+ | Owns | shape + row-level filtering |
157
+ | Never | write, enqueue, or send mail. `live: true` requires a deterministic, bounded `sql` |
158
+
159
+ ## `job`
160
+
161
+ Durable background work, optionally multi-step. `idempotencyKey` is **required by the type**.
162
+
163
+ ```ts
164
+ export const onboardOrg = job({
165
+ input: t.object({ orgId: t.uuid }),
166
+ tenant: ({ orgId }) => orgId, // the org this run acts as
167
+ idempotencyKey: ({ orgId }) => `onboard:${orgId}`, // REQUIRED by the type
168
+ retry: { attempts: 5, backoff: 'exponential' },
169
+ async run({ input, step, ctx }) {
170
+ const org = await step.run('provision', () => ctx.orgs.provision(input.orgId));
171
+ await step.run('welcome-email', () => ctx.mail.send(welcomeEmail, org));
172
+ await step.sleep('3d');
173
+ await step.run('nudge', () => ctx.mail.send(nudgeEmail, org));
174
+ },
175
+ });
176
+ ```
177
+
178
+ | Aspect | Rule |
179
+ |---|---|
180
+ | Projects to | queue row, per-step persistence, retry schedule, dashboard entry, MCP `jobs.inspect` tool |
181
+ | Owns | retries, steps, concurrency class |
182
+ | Never | assume it runs once — assume at-least-once. Durable business state lives in your tables, never only in the payload |
183
+ | Factories over it | `backfill()`, `rollup()`, `refreshView()` ([Rollups](Jobs-And-Workflows#rollups-and-materialized-views)), `consumer()` (a `job` plus its `.supervisor` `task` — [Consumers](Jobs-And-Workflows#consumers--consumer)), `purge()`, `exportRows()`, `importRows()`, `notifier()`, `webhook()` (outbound) and `webhookReceiver()` (inbound: a `job` plus the plain route that verifies, enqueues, then acks) — [Jobs and workflows](Jobs-And-Workflows#inbound-webhooks) |
184
+
185
+ ## `route`
186
+
187
+ A URL + render mode + metadata + offline strategy.
188
+
189
+ ```ts
190
+ export const config = defineRoute({
191
+ render: 'isr', // static | isr | ssr | stream
192
+ revalidate: { tags: [tag.post] },
193
+ prerender: () => db.posts.slugs(),
194
+ offline: 'precache', // precache | runtime | network-only
195
+ hydrate: 'visible', // idle | visible | interaction | never
196
+ budget: { js: '40kb' },
197
+ meta: ({ post }) => ({ title: post.title, description: post.excerpt,
198
+ og: { image: post.cover }, ld: ld.Article(post) }),
199
+ });
200
+ ```
201
+
202
+ | Aspect | Rule |
203
+ |---|---|
204
+ | Projects to | router entry, prerender list, `sw.js` precache/runtime rule, sitemap + RSS row, `<head>` + JSON-LD, per-route budget check |
205
+ | Owns | render mode, hydration timing, metadata, offline strategy |
206
+ | Never | touch the DB directly, hold business logic, or omit `meta.description` in `site/` — that is a build error, `X_SEO_META_MISSING` — the same code for a missing title, with `cause` naming the field |
207
+
208
+ Render modes: `static` (built once), `isr` (static + background regen), `ssr` (per-request), `stream` (shell flushed instantly, holes streamed — **default for app pages**). Four, declared once in `@ultimat3/core` as `RENDER_MODES`; `spa` was the fifth until 6.0.0. Table in [Routes and render modes](Routes-And-Render-Modes).
209
+
210
+ ## `task`
211
+
212
+ A scheduled trigger. Enqueues jobs; never does work itself.
213
+
214
+ ```ts
215
+ export const nightlyDigest = task({
216
+ cron: '0 3 * * *',
217
+ tz: 'UTC',
218
+ enqueue: () => [[sendDigest, {}]],
219
+ });
220
+ ```
221
+
222
+ | Aspect | Rule |
223
+ |---|---|
224
+ | Projects to | scheduler entry (lease-row leader, `x_scheduler_leader`), next-run introspection, a `tasks` row in `x.manifest.json` |
225
+ | Owns | cron expression + explicit `tz` |
226
+ | Never | contain a handler body. If it does work, it is a `job` |
227
+
228
+ ## Composition
229
+
230
+ ```
231
+ entity ──> policy ──> action ──> job ──> task
232
+ │ │ │
233
+ │ └──> query (live) ──> route
234
+ └──> mutator (action + local twin)
235
+ ```
236
+
237
+ Feature slicing puts a feature's primitives in one folder, not one layer per app:
238
+
239
+ ```
240
+ apps/web/app/<feature>/{entity,repo,service,actions,live,jobs,policy,ui}.ts
241
+ ```
242
+
243
+ Every primitive emits its test beside it, and that test **passes on the first run** — a generator that emits a `TODO` has moved the work, not done it. What it pins is the distant invariant the primitive owns: a policy denial, an idempotency key, a budget. Extend it as the feature grows ([Testing](Testing)). Every primitive appears in `x.manifest.json`, so `x manifest --json` and the MCP `manifest.read` tool describe the whole app as data.
244
+
245
+ ## The rule
246
+
247
+ **If a feature doesn't fit a primitive, it doesn't ship.** No ninth primitive, no escape-hatch `mode:` option, no plugin API in 1.0. Removing an alternative is a feature — ambiguity is the tax agents pay. Source: [`docs/idea/02-primitives.md`](https://github.com/developerz-ai/ultimate/blob/main/docs/idea/02-primitives.md).
@@ -0,0 +1,362 @@
1
+ # Theming
2
+
3
+ SCSS modules + design tokens. No Tailwind, no CSS-in-JS, no second CSS system. Build-time only, zero runtime.
4
+
5
+ `As of 2026-08`. Stable API — semver from here ([Upgrading](Upgrading)).
6
+
7
+ Every colour is a semantic token. A raw hex in any component or stylesheet is a lint failure and fails `x verify`'s `lint` step.
8
+
9
+ **Source of truth:** [`packages/ui/src/tokens/_colors.scss`](https://github.com/developerz-ai/ultimate/blob/main/packages/ui/src/tokens/_colors.scss). [`tokens.ts`](https://github.com/developerz-ai/ultimate/blob/main/packages/ui/src/tokens/tokens.ts) is a hand-maintained typed mirror for consumers that cannot read CSS — charts, `<canvas>`, OG images, transactional mail — and `tokens.test.ts` fails the build if the two disagree. Values on this page are transcribed from those files; when they differ, the files win.
10
+
11
+ ## Colour roles
12
+
13
+ Named by **role**, not by value: `--color-bg`, never `--blue-500`. Stored as **space-separated RGB channels** so `rgb(var(--color-accent) / 0.5)` composites without a second token.
14
+
15
+ **24 roles**, in the order `COLOR_ROLES` declares them. A component that needs a 25th is asking for a design decision, not a variable.
16
+
17
+ | Role | Light | Dark | Used for |
18
+ |---|---|---|---|
19
+ | `bg` | `253 246 240` | `18 18 20` | page background |
20
+ | `bg-soft` | `245 237 230` | `28 28 32` | subtle zones, hovers |
21
+ | `surface` | `250 245 241` | `34 34 39` | cards, sheets |
22
+ | `surface-raised` | `255 255 255` | `44 44 50` | popovers, dialogs, inputs |
23
+ | `fg` | `38 34 31` | `228 226 222` | body text |
24
+ | `fg-strong` | `17 15 13` | `248 247 245` | headings, emphasis |
25
+ | `fg-muted` | `110 102 94` | `155 151 145` | captions, placeholders |
26
+ | `line` | `208 198 188` | `72 72 80` | borders, dividers |
27
+ | `scrim` | `17 15 13` | `0 0 0` | modal backdrops — the darkest role in each theme |
28
+ | `accent` | `31 110 178` | `96 170 240` | primary action, links |
29
+ | `accent-strong` | `21 92 152` | `130 190 248` | hover/active of accent |
30
+ | `accent-fg` | `255 255 255` | `16 20 26` | text **on** accent |
31
+ | `success` | `21 123 80` | `74 190 130` | the success tone |
32
+ | `success-soft` | `222 244 232` | `22 46 34` | its filled background |
33
+ | `success-fg` | `255 255 255` | `12 26 18` | text on `success` |
34
+ | `warning` | `155 93 7` | `226 170 66` | the warning tone |
35
+ | `warning-soft` | `253 240 213` | `52 42 20` | its filled background |
36
+ | `warning-fg` | `255 255 255` | `28 20 6` | text on `warning` |
37
+ | `danger` | `190 42 42` | `240 110 110` | the danger tone |
38
+ | `danger-soft` | `253 227 227` | `56 26 26` | its filled background |
39
+ | `danger-fg` | `255 255 255` | `30 12 12` | text on `danger` |
40
+ | `info` | `31 110 178` | `96 170 240` | the info tone |
41
+ | `info-soft` | `224 239 252` | `22 38 56` | its filled background |
42
+ | `info-fg` | `255 255 255` | `12 20 30` | text on `info` |
43
+
44
+ The six-name tone vocabulary components expose — `neutral`, `accent`, `success`, `warning`, `danger`, `info` — is `$tones` in `_colors.scss`, mirroring `TONES` in `components/variants.ts`; `variants.test.ts` fails on drift.
45
+
46
+ ### The 1.1.0 contrast retune
47
+
48
+ `As of 2026-08` seven channels moved, because eight pairings failed WCAG AA. The worst was `line` on `surface-raised` in dark at **1.16:1** — an input border nobody can see.
49
+
50
+ | Role | Theme | Was | Now |
51
+ |---|---|---|---|
52
+ | `line` | dark | `54 54 60` | `72 72 80` |
53
+ | `line` | light | `224 216 208` | `208 198 188` |
54
+ | `accent` | light | `34 122 197` | `31 110 178` |
55
+ | `fg-muted` | dark | `150 146 140` | `155 151 145` |
56
+ | `surface` | light | `255 255 255` | `250 245 241` |
57
+
58
+ Anything still carrying the old numbers is stale — including any copy of the palette outside `packages/ui/src/tokens/`. There is no gate that finds one, which is exactly how the `/_x` dashboard shipped the 1.16:1 border of its own.
59
+
60
+ ## Other token scales
61
+
62
+ Every scale is a SCSS map in `packages/ui/src/tokens/`, emitted as a custom property by [`theme.scss`](https://github.com/developerz-ai/ultimate/blob/main/packages/ui/src/tokens/theme.scss) — the **only** stylesheet that emits any — and read back through the function in the third column below. `t.tracking(wide)`, never a hand-written `var(--tracking-wide)` — the property name is spelled in one file, and a scale with no function is one every author has to spell for themselves.
63
+
64
+ Most scales carry a typed mirror in `tokens.ts` as well, for consumers that cannot read CSS. Two do not, and the table says which; `tokens.test.ts` fails the build on drift in the ones that do.
65
+
66
+ | Scale | Custom property | Read it with | Values |
67
+ |---|---|---|---|
68
+ | colour | `--color-accent` | `t.role('accent', $alpha)` | the 24 roles above, as RGB channels |
69
+ | space | `--space-4` | `t.space(4)` | `0 1 2 3 4 5 6 8 10 12 16` → `0` … `4rem` |
70
+ | radius | `--radius-md` | `t.radius(md)` | `none sm md lg xl pill full` |
71
+ | stroke | `--stroke-thick` | `t.stroke(thick)` | `hairline 1px`, `thick 2px`, `heavy 3px` — a border, an outline, a focus ring. px on purpose: a line weight does not grow with text size |
72
+ | z-index | `--z-dialog` | `t.z(dialog)` | `base raised sticky dropdown drawer dialog popover tooltip toast skip-nav` |
73
+ | duration | `--duration-fast` | `t.duration(fast)` | `instant 0ms`, `fast 120ms`, `base 220ms`, `slow 400ms`, `slower 640ms` |
74
+ | easing | `--easing-out` | `t.easing(out)` | `out in in-out spring` |
75
+ | shadow | `--shadow-md` | `t.shadow(md)` | `xs sm md lg xl` — **themed**, like colour: separate light and dark maps |
76
+ | font family | `--font-sans`, `--font-mono` | `var(--font-sans)` | the two slots `defineTheme()` overrides. The one scale with no function and no TS mirror: a stack is replaced whole, not picked off a rung, and a comma list is not one value per key |
77
+ | font size | `--text-md` | `t.text(md)` | `xs` … `3xl`, every one a `clamp()` |
78
+ | font weight | `--weight-semibold` | `t.weight(semibold)` | `normal medium semibold bold` |
79
+ | line height | `--leading-normal` | `t.leading(normal)` | `tight snug normal loose` |
80
+ | letter spacing | `--tracking-tight` | `t.tracking(tight)` | `tight normal wide` — SCSS only, no TS mirror |
81
+ | breakpoint | **none** | `@include t.respond-to(md)` · `t.respond-down(md)` · `t.respond-between(md, lg)` | `sm 480px` … `2xl 1536px`; never emitted as a custom property, because a media query cannot read one |
82
+
83
+ ### Breakpoints and computed lengths
84
+
85
+ Every length, breakpoint and fluid size comes from `@ultimat3/ui/tokens`. An app that needs `max-width` does not write its own mixin.
86
+
87
+ | Helper | Emits | Notes |
88
+ |---|---|---|
89
+ | `@include t.respond-to(md)` | `@media (min-width: 768px)` | the rung and wider |
90
+ | `@include t.respond-down(md)` | `@media (max-width: 767.98px)` | narrower than the rung. 0.02px under it: `max-width: 768px` and `min-width: 768px` both match at exactly 768px |
91
+ | `@include t.respond-between(md, lg)` | `@media (min-width: 768px) and (max-width: 1023.98px)` | `$from` up to, not including, `$to` |
92
+ | `t.rem(24px)` | `1.5rem` | px, rem or a unitless px count; `t.rem(20px, 10px)` states another root |
93
+ | `t.fluid(1rem, 2rem, 20rem, 80rem)` | `clamp(1rem, 0.6666666667rem + 1.6666666667vw, 2rem)` | first size at the `$from` viewport, second at `$to`, linear between; the range defaults to `20rem`–`80rem`; px accepted |
94
+
95
+ A rung is quoted or bare — `'2xl'` and `2xl` are the same rung. Prefer a container query (`t.container`, `t.container-query`) to all three mixins: reach for the viewport only when the layout depends on it.
96
+
97
+ Each refusal is a Sass `@error` — the stylesheet does not compile — in the same shape as every other error: code, cause, `fix:`.
98
+
99
+ | Failure | Code | `fix:` |
100
+ |---|---|---|
101
+ | a rung not in `$breakpoints` | `X_TOKEN_UNKNOWN` | `use one of sm, md, lg, xl, 2xl` |
102
+ | `respond-between(lg, md)` | `X_UI_INVALID_VALUE` | `write respond-between("md", "lg")` |
103
+ | `respond-between(md, md)` | `X_UI_INVALID_VALUE` | `write respond-to("md"), or name a higher rung as $to` |
104
+ | `t.rem(2em)`, `t.fluid(1vw, 2rem)` | `X_UI_INVALID_VALUE` | `pass a px length, e.g. rem(24px)` |
105
+ | `t.fluid(1rem, 2rem, 80rem, 20rem)` | `X_UI_INVALID_VALUE` | `pass the narrower viewport first, e.g. fluid(1rem, 2rem, 20rem, 80rem)` |
106
+
107
+ ### Layout mixins
108
+
109
+ **A flex row or column is `@include t.row(…)` / `@include t.column(…)`, never a hand-written
110
+ `display: flex` block.** Defined in [`packages/ui/src/tokens/_mixins.scss`](https://github.com/developerz-ai/ultimate/blob/main/packages/ui/src/tokens/_mixins.scss); every gap default is a spacing token.
111
+
112
+ | Write | Emits | Use for |
113
+ |---|---|---|
114
+ | `@include t.row` | `display: flex; flex-direction: row; align-items: center; justify-content: flex-start; gap: var(--space-3)` | a toolbar, a header, an icon beside a label |
115
+ | `@include t.row(t.space(2), baseline, space-between)` | the same, with your `$gap`, `$align`, `$justify` | a row whose items spread |
116
+ | `@include t.column` | `display: flex; flex-direction: column; align-items: stretch; gap: var(--space-3)` | a stack of fields, a card body |
117
+ | `@include t.column(t.space(5), flex-start)` | the same, with your `$gap`, `$align` | a stack whose items keep their own width |
118
+ | `@include t.container(<name>)` + `@include t.container-query(<name>, 30rem)` | a query container and a rule under it | layout that depends on the box, not the viewport |
119
+ | `@include t.margin-inline(…)`, `t.padding-block(…)`, `t.inset-inline(…)` | the logical property pair | spacing that flips correctly under RTL |
120
+ | `@include t.truncate` / `t.line-clamp(2)` | one-line ellipsis / a clamped block | a cell, a card title |
121
+
122
+ Measured on one downstream app, `As of 2026-10`: 691 hand-written `display: flex` blocks restated
123
+ these two mixins across 211 stylesheet modules. No guard refuses a hand-written flex block yet —
124
+ `row` and `column` are the convention, not a build error.
125
+
126
+ Note the naming: font size is `--text-*`, weight is `--weight-*`, line height is `--leading-*`, tracking is `--tracking-*` — not `--font-size-*`.
127
+
128
+ ## Defined once per theme
129
+
130
+ Light in `:root`, dark behind the media query, and **both** mirrored under `html[data-theme]` so an explicit user choice always beats the OS. `_colors.scss`'s `emit` mixin writes all four blocks from the same two maps, so a role cannot be defined in one block and forgotten in another.
131
+
132
+ ```scss
133
+ :root { color-scheme: light; /* $light */ }
134
+ @media (prefers-color-scheme: dark) { :root { color-scheme: dark; /* $dark */ } }
135
+ html[data-theme='dark'] { color-scheme: dark; /* $dark */ }
136
+ html[data-theme='light'] { color-scheme: light; /* $light */ }
137
+ ```
138
+
139
+ `color-scheme` rides along, so form controls, scrollbars, and the UA's own `::selection` follow the theme without a second declaration.
140
+
141
+ ## Consuming tokens
142
+
143
+ Channels, not `#rrggbb`, so any opacity is `rgb(var(--token) / a)`. Inside `@ultimat3/ui` the wrapper is `t.role('<name>', $alpha)`.
144
+
145
+ ```scss
146
+ /* apps/web/app/nav/toolbar.module.scss */
147
+ .toolbar {
148
+ background: rgb(var(--color-bg) / 0.8);
149
+ backdrop-filter: blur(12px);
150
+ color: rgb(var(--color-fg));
151
+ border-bottom: 1px solid rgb(var(--color-line));
152
+ }
153
+
154
+ .toolbar__action {
155
+ background: rgb(var(--color-accent));
156
+ color: rgb(var(--color-accent-fg));
157
+
158
+ &:hover { background: rgb(var(--color-accent-strong)); }
159
+ &[disabled] { color: rgb(var(--color-fg-muted) / 0.6); }
160
+ }
161
+ ```
162
+
163
+ Text on a filled surface takes that surface's `-fg` role — `accent-fg` on `accent`, `danger-fg` on `danger`. Hardcoding `accent-fg` for every tone is the bug `IconButton` shipped with before 1.1.0: a danger icon button wore accent's on-colour.
164
+
165
+ No `dark:` variants, no `@media` in a component. A component is `bg` + `fg` + `line`; the theme flip happens above it.
166
+
167
+ ### From TypeScript
168
+
169
+ For anything that cannot read a custom property — a chart, a `<canvas>`, an OG image, an email:
170
+
171
+ ```ts
172
+ import { color, colorRgb, colorVar } from '@ultimat3/ui';
173
+
174
+ colorVar('accent'); // 'var(--color-accent)' — the channel list
175
+ color('accent', 0.5); // 'rgb(var(--color-accent) / 0.5)'
176
+ colorRgb('dark', 'accent'); // 'rgb(96 170 240)' — resolved, no indirection
177
+ ```
178
+
179
+ An unknown role throws `X_TOKEN_UNKNOWN` naming every role that exists.
180
+
181
+ ## Brand overrides — `defineTheme()`
182
+
183
+ The **one** seam for restyling. Not a forked stylesheet, not an SCSS `@use ... with ()` override — there is no second path.
184
+
185
+ **Declare it once, in `apps/web/shared/theme.ts`, as a named export `brand`. The framework does the rest** (`As of 2026-10`):
186
+
187
+ ```ts
188
+ // apps/web/shared/theme.ts
189
+ import { defineTheme } from '@ultimat3/ui';
190
+
191
+ export const brand = defineTheme({ preset: 'scifi' });
192
+ ```
193
+
194
+ | Who | Does |
195
+ |---|---|
196
+ | the app | exports `brand` from `apps/web/shared/theme.ts`. Nothing else: no `<style>` in a layout, no CSP edit |
197
+ | `x dev`, the container (`ROLE=web`), `x build`'s static export | inline `brandStyleTag(brand)` in the `<head>` of every document — `ssr`, `stream`, `static`, `isr`, and every `/admin` screen — **after** the surface stylesheet `<link>`, so the brand wins the cascade at equal specificity |
198
+ | the served processes | admit the same body to `style-src` as a `sha256` source (`brandStyleCspSource(brand)`), so the enforced policy a container sends never blocks it |
199
+ | an app with no `shared/theme.ts`, or one whose brand renders no CSS | nothing: no tag, no hash, no bytes |
200
+
201
+ One file, one reader: `packages/cli/src/theme-brand.ts`. The `/_x` dev dashboard is framework chrome, not an app document, and keeps the shipped palette. No JavaScript is added: the brand is CSS, and the theme toggle and dark mode work as before — the brand answers `html[data-theme]` and `prefers-color-scheme` at every level `theme.scss` does, so the boot script and `ThemeToggle` flip between the brand's two palettes. A save to `theme.ts` is applied on the next `x dev`.
202
+
203
+ | Failure | Code | When |
204
+ |---|---|---|
205
+ | a palette below WCAG AA | `X_UI_CONTRAST_INSUFFICIENT` | at boot: `x dev`, `ROLE=web` and `x build` import the module, and `defineTheme()` refuses at import |
206
+ | an unknown role, rung or slot / an unusable value | `X_TOKEN_UNKNOWN` / `X_UI_INVALID_VALUE` | the same moment |
207
+ | `shared/theme.ts` exports no `brand`, or a `brand` that is not a `defineTheme()` result (or carries `<`) | `X_CONFIG_INVALID` | at boot; `fix:` is the export to write |
208
+
209
+ The demo app (`dummy/social-media-clone`) ships the `scifi` preset this way; `examples/dummy` keeps the default palette.
210
+
211
+ ```ts
212
+ import { brandStyleTag, defineTheme } from '@ultimat3/ui';
213
+
214
+ const brand = defineTheme({
215
+ colors: { light: { accent: '31 110 178' }, dark: { accent: '96 170 240' } },
216
+ radius: { md: '0.375rem' },
217
+ font: { sans: 'Inter, system-ui, sans-serif' },
218
+ });
219
+
220
+ brand.css; // the four CSS blocks, as a string
221
+ brandStyleTag(brand); // '<style>…</style>' — exactly what the framework inlines
222
+ ```
223
+
224
+ | Field | Shape | Notes |
225
+ |---|---|---|
226
+ | `preset` | `'scifi'` | a shipped palette every other slot layers onto, role by role |
227
+ | `colors` | `Partial<Record<'light' \| 'dark', Partial<Record<ColorRole, string>>>>` | any subset of the 24 roles, per theme |
228
+ | `shadows` | `Partial<Record<'light' \| 'dark', Partial<Record<ShadowName, string>>>>` | elevation is themed like colour |
229
+ | `radius` | `Partial<Record<RadiusName, string>>` | `none sm md lg xl pill full` |
230
+ | `font` | `Partial<Record<'sans' \| 'mono' \| 'data', string>>` | the font slots |
231
+
232
+ Returns a frozen `{ css: string }`. It emits `:root`, `html[data-theme='light']`, the `prefers-color-scheme: dark` block and `html[data-theme='dark']` — radius and font ride `:root` only. Output is ordered by the canonical scale arrays rather than by your object, so re-rendering the same input is byte-identical. Empty input gives `css: ''`, which the framework emits nothing for.
233
+
234
+ ### Values are validated, never escaped
235
+
236
+ The output lands in a `<style>` element, so a value that could close it is refused rather than sanitised.
237
+
238
+ | Slot | Accepted | Refused |
239
+ |---|---|---|
240
+ | colour | `^\d{1,3} \d{1,3} \d{1,3}$`, each channel ≤ 255 | `#1e6eb2`, `rgb(1,2,3)`, `1 1 1; } html { display: none }` |
241
+ | radius | `^(0\|\d+(\.\d+)?(px\|rem\|em\|ch\|%))$` | `calc(…)`, `var(…)`, `1` with no unit |
242
+ | font | `^[\w\s,'"-]{1,200}$` | anything with `;` `}` `<` `>` `(` `)` `/` `:` — so `Menlo</style><script>` cannot get through |
243
+
244
+ | Failure | Code | Means |
245
+ |---|---|---|
246
+ | unknown role, radius name or font slot | `X_TOKEN_UNKNOWN` | the key is not in the scale; `cause` lists every name that is |
247
+ | known key, unusable value | `X_UI_INVALID_VALUE` | `cause` names the slot and what was expected |
248
+
249
+ ## Resolution and first paint
250
+
251
+ Order: **explicit `localStorage` choice → `theme.defaultMode`**, where `'system'` (the default) means the OS preference and `'dark'` or `'light'` is the app's own opinion. Applied by a blocking inline `<head>` script, before first paint and before the render-blocking stylesheet, so there is no flash of the wrong theme.
252
+
253
+ **The boot writes the script; the app writes nothing** (`As of 20.2.0`). `x dev`, the container and `x build`'s static export all inline `themeScript({ fallback })` from `@ultimat3/render` with `theme.defaultMode` from `app.config.ts` as the fallback, and the served processes admit its `sha256` to `script-src` from the same string — `packages/cli/src/theme-boot.ts`. An app that wants to open dark sets `theme: { defaultMode: 'dark' }` and is done. The storage key is `ultimate.theme` on both sides: the boot reads it, `ThemeToggle` writes it, and a test pins the two literals equal. Before this release neither of the framework's two theme scripts was inlined by anything, both fell back to light, and they disagreed on the key.
254
+
255
+ `@ultimat3/ui`'s own inline script (`THEME_INLINE_SCRIPT`, `themeInlineScriptTag`, `themeInlineScriptHash`, `themeInlineScriptCspSource`) is **gone**: an app that still inlined it by hand deletes that code — the boot's script already runs first.
256
+
257
+ On the client, `resolveTheme()` reads the same order back: stored choice, then the `data-theme` the boot stamped (`ThemeEnv.current()`), then `defaultTheme(env)` — the app's own `defaultMode` as the boot stamped it in `data-theme-default` (`ThemeEnv.appDefault()`), and the OS only when that is `'system'` or absent — so with `defaultMode: 'dark'` on a light-OS machine the first toggle flips to light instead of writing the dark already on screen.
258
+
259
+ | Concern | Rule |
260
+ |---|---|
261
+ | Persist | only when the user explicitly picks. `clearTheme()` removes the key and returns to the app default — `theme.defaultMode` when it is `'light'` or `'dark'`, the OS for `'system'` |
262
+ | OS flip | a `matchMedia` change listener re-applies **only** when no explicit choice is stored **and** `theme.defaultMode` is `'system'`. The boot stamps the mode itself as `data-theme-default` (`ThemeEnv.appDefault()`), so a fixed `'dark'` is never flipped light by the OS |
263
+ | Determinism | `data-theme` beating the media query is what makes Playwright screenshots reproducible — set the attribute, don't emulate |
264
+ | SSR | the server never guesses a theme; it emits the boot script and neutral markup |
265
+ | No flash | the script is blocking and inline. An async or deferred theme script is a regression, not an optimization |
266
+ | Bad value | `X_THEME_INVALID` — `light` or `dark`, or clear the attribute to follow the OS |
267
+
268
+ ## Where tokens live
269
+
270
+ | Path | Contents |
271
+ |---|---|
272
+ | `packages/ui/src/tokens/_colors.scss` | the two colour maps, the tone list, the `emit` mixin — canonical |
273
+ | `packages/ui/src/tokens/tokens.ts` | the typed mirror, gated by `tokens.test.ts` |
274
+ | `packages/ui/src/tokens/theme.scss` | the only stylesheet emitting global custom properties |
275
+ | `packages/ui/src/tokens/contrast.ts` | WCAG ratios over the channel tokens |
276
+ | `packages/ui/src/theme/brand.ts` | `defineTheme()` — the one brand-override seam |
277
+ | `apps/web/shared/theme.ts` | the app's `brand` export — the one place an app declares its theme; read by `packages/cli/src/theme-brand.ts` |
278
+ | `packages/ui/src/tokens/_index.scss` | what `@use '@ultimat3/ui/tokens' as t` forwards: maps, `t.role()`, `t.space()`, the mixins. Emits no CSS |
279
+ | `apps/web/shared/tokens.scss` | the generated app's own layer. One line — `@forward '@ultimat3/ui/tokens'` — and it emits **zero bytes** of CSS by design: every module is its own Sass compilation, so a `:root` block here would be inlined once per stylesheet. Compiles as scaffolded, verified `As of 2026-08-19`; the bare specifier is resolved by `css-modules.ts`'s package importer, since `./tokens` is an `exports` entry only the module resolver can place |
280
+
281
+ `shared/` is importable by `site/`, `app/`, and `api/`. `site/` importing from `app/` stays a build error — see [Project layout](Project-Layout). The `/_x` dev dashboard reads its six channels from `colorTokens` at render time rather than keeping a copy → [Admin dashboard](Admin-Dashboard).
282
+
283
+ ## Derived surfaces
284
+
285
+ | Surface | Derived from |
286
+ |---|---|
287
+ | PWA manifest `theme_color` | `--color-bg` of the light theme, resolved to hex at build time |
288
+ | PWA manifest `background_color` | same token, so the splash screen matches the shell → [PWA and offline](PWA-And-Offline) |
289
+ | `<meta name="theme-color">` | emitted twice, one per `prefers-color-scheme` media attribute |
290
+ | Maskable icon background | `--color-surface` |
291
+ | OG image background | `--color-bg`, `--color-fg-strong` for text |
292
+
293
+ The manifest is generated. Hand-editing a colour there drifts from the tokens and fails `x verify`'s `manifest` step.
294
+
295
+ ## Accessibility
296
+
297
+ ```scss
298
+ :focus-visible {
299
+ outline: var(--stroke-thick) solid rgb(var(--color-accent));
300
+ outline-offset: var(--stroke-thick);
301
+ }
302
+
303
+ ::selection {
304
+ background: rgb(var(--color-accent) / 0.25);
305
+ color: rgb(var(--color-fg-strong));
306
+ }
307
+
308
+ @media (prefers-reduced-motion: reduce) {
309
+ *, *::before, *::after {
310
+ animation-duration: 0.01ms !important;
311
+ animation-iteration-count: 1 !important;
312
+ transition-duration: 0.01ms !important;
313
+ scroll-behavior: auto !important;
314
+ }
315
+ }
316
+ ```
317
+
318
+ ### What contrast is actually gated
319
+
320
+ `contrast.test.ts` measures every pairing below **in both themes**, over the four surfaces (`bg`, `bg-soft`, `surface`, `surface-raised`) and the four status tones. A failure reports the measured ratio, not just a boolean.
321
+
322
+ | Pairing | Threshold |
323
+ |---|---|
324
+ | `fg`, `fg-strong`, `fg-muted` on every surface | `AA_TEXT` **4.5:1** |
325
+ | `accent`, `accent-strong` on every surface | 4.5:1 |
326
+ | `accent-fg` on `accent` / `accent-strong`; each `<tone>-fg` on its tone | 4.5:1 |
327
+ | each `<tone>` on its own `-soft`; `fg-muted` on every `-soft` | 4.5:1 |
328
+ | each `<tone>` on every surface | 4.5:1 |
329
+ | `accent` (the focus ring) on every surface | `AA_LARGE` **3:1** |
330
+ | `line` on every surface | **1.4:1** — a framework floor, not a WCAG level: a border is not text, but 1.16 is invisible |
331
+ | `scrim` | must be the darkest role in its theme — a luminance ordering, not a ratio |
332
+
333
+ `shadow` is not contrast-gated.
334
+
335
+ | Check | Enforcement |
336
+ |---|---|
337
+ | Contrast, shipped pairings | the table above, run by `x verify`'s `unit` step |
338
+ | Contrast, a `defineTheme()` override | **refused when `defineTheme()` runs** — at import, since a brand is declared at module top level — before a single declaration renders. Every pair in `CONTRAST_PAIRS` (`packages/ui/src/tokens/contrast-pairs.ts`, the same list `contrast.test.ts` holds the shipped palette to) is measured on the resolved palette — your overrides on top of the shipped channels — at its floor: 4.5:1 text, 3:1 the focus ring, 1.4:1 `line`. Only pairs the brand changed, either side, are measured; the rest are the shipped palette's. A miss throws `X_UI_CONTRAST_INSUFFICIENT` naming the pair, the theme and the ratio. The `scrim` ordering is not measured for a brand |
339
+ | Focus ring | `:focus-visible` from `--color-accent` in `reset.scss`, and `@include t.focus-ring` per control. Taking one away without painting one back is refused in an **app** by `guards/focus-visible.ts`, on `x verify`'s `boundaries` step — **not by lint**, which ignores `.scss` entirely |
340
+ | Reduced motion | honored globally, not per component — and it *deletes* rather than reduces, see below |
341
+ | Lighthouse a11y | minimum threshold in `app.config.ts`, default 95 → [Testing](Testing) |
342
+
343
+ **The global guard collapses motion to `0.01ms !important` on `*`.** Substituting a cross-fade for
344
+ a movement therefore needs the component's own `prefers-reduced-motion` block, `!important`, on a
345
+ selector more specific than `*` — otherwise the feedback is gone rather than calmed. The rule and
346
+ the rest of the motion vocabulary: [Interface rules](Interface-Rules#motion).
347
+
348
+ ## Rules
349
+
350
+ - Semantic tokens everywhere. A raw hex — or an `rgb()`/`hsl()` called with numbers — in any `.scss` this package ships is a failing **test**, `packages/ui/src/tokens/tokens.test.ts`, on `x verify`'s `unit` step. Not lint: biome ignores `.scss`, and the rule said "lint" while nothing read it. Two files are exempt, because a literal is *supposed* to live in them: `tokens/_colors.scss` and `tokens/_shadow.scss`. In an app the same rule is `guards/raw-colour.ts`.
351
+ - Each token defined once per theme, by the `emit` mixin: `:root`, the media query, and both `data-theme` mirrors.
352
+ - `html[data-theme]` always beats `prefers-color-scheme`.
353
+ - Theme applied before first paint by a blocking inline script.
354
+ - 24 colour roles. Adding one is a design-system change, reviewed as one, and it lands in `_colors.scss` **and** `tokens.ts` in the same commit.
355
+ - Text on a filled surface uses that surface's `-fg` role, never `accent-fg` by default.
356
+ - Restyle through `defineTheme()`. A forked stylesheet is the wrong answer to every brand question.
357
+ - Components never contain a media query for theme. They read tokens.
358
+ - Contrast verified in light **and** dark; a token pair that passes in one theme only is a failure.
359
+ - Colours are themed; numbers, dates, and money are localized → [I18n](I18n), [Money](Money), [Timezones and dates](Timezones-And-Dates).
360
+
361
+ Component-by-component props and the token vocabulary each one accepts: [UI components](UI-Components).
362
+ What a screen has to do with those tokens — motion, focus, contrast, the four loading states: [Interface rules](Interface-Rules).