@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,339 @@
1
+ # Interface rules
2
+
3
+ What a screen must do, as rules an agent can apply without judgement — and, where judgement is
4
+ unavoidable, marked as judgement so it is not mistaken for a check.
5
+
6
+ `As of 2026-09`.
7
+
8
+ **Read the second column before the first.** Every rule is one of two kinds:
9
+
10
+ | Column says | Means |
11
+ |---|---|
12
+ | a mechanism | a build error. The named guard, type or gate step refuses it. Break it and `x verify` is red |
13
+ | **judgement** | nothing checks it. It is here because an agent still has to follow it, and because axiom 3 says an unenforced convention does not exist — so this page says which ones do not |
14
+
15
+ The mechanisms are of three shapes, and knowing which one you hit tells you where the fix goes:
16
+
17
+ | Shape | Where it fires | Example |
18
+ |---|---|---|
19
+ | a **type** in `@ultimat3/ui` | `x verify`'s `typecheck` step | `AsyncRegionProps.empty` is required |
20
+ | a **guard** in your app's `guards/` | `x verify`'s `boundaries` step | `guards/focus-visible.ts` |
21
+ | a **test** in the framework | `x verify`'s `unit` step | `packages/ui/src/tokens/contrast.test.ts` |
22
+
23
+ Guards are yours: `x new` writes every name in `SHIPPED_GUARD_NAMES`
24
+ (`packages/cli/src/templates/scaffold-guards.ts`), the directory is the registration, and deleting
25
+ a file deletes the rule. Add one with `x g guard <name>` — a name the framework ships a guard under writes
26
+ that guard, any other name writes a blank one — and `x doctor` lists the shipped ones an app does
27
+ not hold. Each raises an app-owned `X_*` code derived from
28
+ its own filename — `guards/focus-visible.ts` raises `X_FOCUS_VISIBLE` — so a finding says which
29
+ convention broke, not which framework subsystem noticed.
30
+
31
+ ## Loading, empty, error
32
+
33
+ | Rule | Refused by |
34
+ |---|---|
35
+ | Every region that waits on data has four branches: pending, failed, empty, ready | `AsyncRegion` renders all four; a hand-rolled region is **judgement** |
36
+ | `empty` is unreachable while pending | the `AsyncBranch` union, `packages/ui/src/components/async-branch.ts` — `pending` carries no data, so nothing can be found empty in it |
37
+ | Declare what "nothing here" looks like | `AsyncRegionProps.empty` is a required prop with no default |
38
+ | A refetch keeps the previous data on screen | `asyncStateOf` — `loading` beside a defined `data` is `refreshing`, never `pending` |
39
+ | A failure beats stale data: report the error, do not re-render rows the failed request did not answer | `asyncBranch` tests `failed` first |
40
+ | Placeholder, error, empty and content land in the same box | `reserveBlockSize` is emitted as `--async-reserve` on the region in every branch |
41
+ | The region carries `aria-busy` while in flight | `AsyncRegion` derives it from `isBusyBranch` |
42
+ | `data: undefined` means nothing has arrived. An empty array is data | `asyncStateOf` |
43
+ | A `{ total: 0, items: [] }` envelope passes its own `isEmpty` — `isEmptyData` does not guess at an envelope | `AsyncRegionProps.isEmpty` |
44
+
45
+ Rendering "No results" for one frame before the first page arrives is the most common
46
+ agent-authored UX bug in a list screen. The union above is why it is unconstructible rather than
47
+ discouraged.
48
+
49
+ ## Skeletons and spinners
50
+
51
+ | Rule | Refused by |
52
+ |---|---|
53
+ | A skeleton only where the shape is known, stable, and the wait is 1–3 seconds | **judgement** |
54
+ | The placeholder box equals the loaded box | `AsyncRegion` feeds one `reserve` to `Skeleton` and to the content box |
55
+ | A skeleton is never announced | `Skeleton` is `aria-hidden="true"`; the region's `aria-busy` is the announcement |
56
+ | A spinner has an accessible name | `Spinner` is `role="status"` with `aria-label`, defaulting to the `ui.loading` catalog key |
57
+ | A decorative spinner inside an already-announced region is hidden | `SpinnerProps.decorative` |
58
+ | Never a skeleton for an unknown-length list, a variable-height card, or content whose shape depends on the response | **judgement** |
59
+
60
+ **The evidence runs against skeletons, not for them.** A controlled study (Viget, n = 136,
61
+ durations matched between conditions) found a skeleton screen lost to a plain spinner on every
62
+ measure taken, perceived wait included — **2.82s** perceived under the skeleton against **2.41s**
63
+ under the spinner. Treat "use a skeleton" as a claim needing a reason, not a default. A skeleton
64
+ whose box does not match the loaded content is strictly worse than a spinner: it converts a
65
+ perception problem into a layout-shift problem, and layout shift is measured.
66
+
67
+ ## Response tiers
68
+
69
+ Nielsen's three limits. All four rows are **judgement** — nothing in the gate times a response.
70
+
71
+ | Elapsed | Show |
72
+ |---|---|
73
+ | under 100ms | nothing. The result is the feedback. A spinner here reads as a stutter |
74
+ | under 1s | still nothing. Thought is unbroken; a spinner that appears and vanishes is noise |
75
+ | 1–10s | an indeterminate busy indicator. Keep the trigger visible and disabled, not replaced |
76
+ | over 10s | determinate progress **and** a cancel. Attention is gone at 10s; a bar with no cancel is a hostage |
77
+
78
+ Never a spinner on a control whose action completes locally. Never a determinate bar over a
79
+ percentage you are inventing.
80
+
81
+ ## Optimistic UI
82
+
83
+ Correct only when **all three** hold: the operation is idempotent, its result is predictable from
84
+ the input alone, and it targets the same URL.
85
+
86
+ | Rule | Refused by |
87
+ |---|---|
88
+ | Never optimistic on money, a balance, stock, a quota, or a permission grant | **judgement** |
89
+ | The optimistic twin is a pure function of `(tx, input)` — no I/O, no `Date.now()`, no `Math.random()` | **judgement** in an app. `local` is replayed on every rebase, so an impure one diverges silently |
90
+ | The twin is convergent, not incremental: applying it N times equals applying it once | **judgement**. `likedByMe ? {} : { likeCount: n + 1 }` is the shape; `likeCount + 1` is the bug |
91
+ | Rollback is written, never assumed | the local store journals the first write per key and undoes newest-first — but only when a `LocalStore` is wired. Without one, nothing rolls back |
92
+ | A refused mutation is rolled back, not retried | `@ultimat3/realtime` drops the refused intent from the rebase log |
93
+
94
+ Full shape and the conflict strategies: [Realtime](Realtime#same-mutator-at-every-rung).
95
+
96
+ ## Forms
97
+
98
+ | Rule | Refused by |
99
+ |---|---|
100
+ | A label is a real `<label for>`, never a placeholder and never an adjacent `<span>` | `FieldProps.label` is required, and `Field` renders `<label for={id}>` |
101
+ | `aria-describedby` and `aria-invalid` cannot drift from what is rendered | `Field` owns the ids and hands them to the control through `FieldControl` |
102
+ | Never block paste | `InputProps` declares no `onPaste` and `@ultimat3/ui` does no prop spreading, so there is no way to attach one through `Input` |
103
+ | No `type="number"` | the `InputType` union has no `'number'` member — a numeric field is `inputmode` plus a text type, so a locale decimal separator survives |
104
+ | `type`, `inputmode` and `autocomplete` on every input that has a right answer | **judgement** — the props exist, nothing requires them |
105
+ | One field per row. Two side by side only when they are one value (expiry month + year) | **judgement** |
106
+ | An error is an instruction: what to type, not what is wrong | **judgement** |
107
+ | The error text is announced with the control it belongs to | `Field` wires `error` into `aria-describedby` |
108
+ | Choosing one of a list: `Select` for a short closed list, `SearchableSelect` for a long one whose label is not its value, `Combobox` for free text with suggestions | one control per question; `SearchableSelect` posts hidden inputs, so typed text never reaches the server, and without script it is the native `<select>` |
109
+ | A chart's colour says what the caller said it means: `StatTile goodWhen="down"` for an error rate | `deltaMood` — the arrow carries the direction, the colour only a stated meaning; `goodWhen="none"` colours neither |
110
+ | A gap is a gap: never zero-fill a missing value in a chart | `ChartPoint.value` and every series value take `null`; a chart draws nothing there and the table reads `ui.chart.none` |
111
+
112
+ **16px is the floor for a touch-reachable text input.** iOS Safari zooms the viewport on focus
113
+ below it, and the zoom does not undo itself. The default control is safe — the `control` mixin sets
114
+ `font-size: var(--text-md)`, whose `clamp()` floor is `1rem` — but **`size="sm"` is not**:
115
+ `--text-sm` floors at `0.875rem` = **14px**, and `.input` is `font: inherit`. Nothing checks this.
116
+ Treat `<Input size="sm">` as desktop-only.
117
+
118
+ ## Motion
119
+
120
+ | Rule | Refused by |
121
+ |---|---|
122
+ | Animate `transform` and `opacity`. Nothing else | `guards/animated-layout-property.ts` |
123
+ | Never `transition: all` | same guard |
124
+ | A `transition` naming no property **is** `all` — name the properties | same guard, which reports the omission as the implicit `all` it is |
125
+ | No layout property inside `@keyframes` | same guard |
126
+ | Reach for `t.duration()` / `t.easing()`, or `@include t.transition(<props>)`, never a literal `220ms` or a raw `cubic-bezier` | **judgement** — the scales are in `packages/ui/src/tokens/_motion.scss` and nothing refuses a literal |
127
+ | Reduced motion is honoured globally, not per component | `tokens/reset.scss` |
128
+
129
+ **Reduce means reduce — but the shipped global rule deletes.** `reset.scss`'s
130
+ `@media (prefers-reduced-motion: reduce)` sets `animation-duration` and `transition-duration` to
131
+ `0.01ms !important` on `*`, so a component that wants a cross-fade in place of movement has to win
132
+ that cascade itself: its own `prefers-reduced-motion` block, with `!important`, on a selector more
133
+ specific than `*`. Replacing movement with a cross-fade is the rule; getting it past the global
134
+ guard is work, and if you skip it the feedback is gone rather than calmed.
135
+
136
+ Never remove the feedback. A state change with no motion still needs a state change the reader
137
+ can see.
138
+
139
+ ## Accessibility
140
+
141
+ | Rule | Refused by |
142
+ |---|---|
143
+ | A click is answered by a control, never by a `div` with a handler | `guards/semantic-interactive.ts` — `onClick`/`onMouseDown`/`onKeyDown` on an inert tag |
144
+ | Never `role="button"`, `role="link"` or `role="checkbox"` where the native element exists | same guard, which names the element to write instead |
145
+ | A role plus `tabindex` plus a key handler is the complete set the ARIA practices guide asks for, and is the one shape the guard passes over | same guard |
146
+ | The focus ring is replaced, never only removed | `guards/focus-visible.ts` — `outline: none\|0` with no indicator in the rule or within 600 characters of it |
147
+ | `@include t.focus-ring` is the one form | the mixin emits `:focus-visible` and the mouse-only `:focus:not(:focus-visible) { outline: none }` together; the guard recognises the include and never reads inside it |
148
+ | `:focus:not(:focus-visible) { outline: none }` is the correct idiom, not the defect | the guard never reports it |
149
+ | An icon-only control has a name | `IconButtonProps.label` is required — there is no unlabelled icon button |
150
+ | An icon with no `label` is decorative and hidden | `Icon` emits `aria-hidden="true"` unless given one |
151
+ | A live region exists before the message does | `AppShell` renders both `liveRegionAttrs` regions into the server response |
152
+ | One announcement path per message: `announce()` for a state change with no surface, a toast for a notification | **judgement** — two paths make a screen reader read it twice |
153
+ | Text meets 4.5:1 and the focus ring 3:1, in **both** themes | `packages/ui/src/tokens/contrast.test.ts`, `x verify`'s `unit` step. Shipped pairings only |
154
+
155
+ **No ARIA is better than bad ARIA** — the WAI-ARIA Authoring Practices Guide's own line, and the
156
+ measurement behind it: WebAIM's annual survey of a million home pages finds pages using ARIA
157
+ average **~41% more** detected errors than pages using none. `role="button"` is a promise the
158
+ element then owes — Space, Enter, focus order, a disabled state — and only the native element keeps
159
+ it for free.
160
+
161
+ **`announce()`'s first message can be silent.** A live region created and filled in the same frame
162
+ is not announced by most screen readers. `AppShell` renders the regions server-side so the first
163
+ message is safe; the create-if-absent fallback in a tree with no shell is right from its second
164
+ call onwards. Render `AppShell`.
165
+
166
+ **A brand override is contrast-checked where it is declared.** `defineTheme()` measures every pair
167
+ in `CONTRAST_PAIRS` that the brand changed, either side, on the resolved palette — 4.5:1 text, 3:1
168
+ the focus ring, 1.4:1 `line` — and throws `X_UI_CONTRAST_INSUFFICIENT` before rendering anything.
169
+ Declared at module top level, a failing brand stops the app at import, not in review. Measure a
170
+ candidate first with `contrastRatio` or `roleContrast` from `@ultimat3/ui` → [Theming](Theming).
171
+
172
+ ## Toasts
173
+
174
+ | Rule | Refused by |
175
+ |---|---|
176
+ | Confirmations only. Never the only copy of information | **judgement** |
177
+ | Never the only affordance for an action | **judgement**, plus the shape below |
178
+ | At most one control, and it is undo-shaped | `ToastAction` is a single optional object, not a list — not taking the offer is the status quo |
179
+ | Dwell is a token, never a number at the call site | the `ToastDwell` union — `'short' \| 'long' \| 'sticky'`; a call site cannot pass `6500` |
180
+ | A `sticky` toast still carries a dismiss control | **judgement** |
181
+ | Never take focus | `ToastRegion` sets no `tabindex` and moves focus nowhere; it pauses the dwell on `focusin` instead |
182
+ | The dwell pauses on pointer, on focus, and on a hidden tab — three independent reasons, never one boolean | `ToastHold` |
183
+ | At most three on screen; the rest queue and start their dwell when they surface | `TOAST_MAX_VISIBLE` |
184
+ | The list announces the arrival, not the whole list | `ToastRegion` is `aria-atomic="false"` on the `<ol>`, and the `<ol>` — not each toast — owns `aria-live` |
185
+
186
+ A toast disappears on a timer. A keyboard user who was typing when it arrived never reached it.
187
+
188
+ ## Images
189
+
190
+ | Rule | Refused by |
191
+ |---|---|
192
+ | Every image declares its box: `width` **and** `height`, or an `aspect-ratio` | `guards/image-dimensions.ts` |
193
+ | One dimension alone is a refusal, not a half-measure | `boxFor` throws `X_UI_INVALID_VALUE` — one alone reserves no aspect ratio |
194
+ | The LCP image is never lazy | `guards/image-dimensions.ts` reports `loading="lazy"` on a `priority` / `fetchpriority="high"` image; `<Image>` derives `loading` from `priority` and does not take it as a prop |
195
+ | `alt` is required. `alt=""` is a decision, made deliberately | `ImageProps.alt` has no default |
196
+ | At most one `priority` image per route | **judgement** — nothing counts them |
197
+ | `<Image>` fabricates nothing: no rendition it was not given, no dimension it did not measure | the component emits exactly what it is handed |
198
+
199
+ An unsized `<img>` is the largest single contributor to Cumulative Layout Shift: the page reflows
200
+ under the reader's finger the moment the bytes land.
201
+
202
+ ## Islands
203
+
204
+ | Rule | Refused by |
205
+ |---|---|
206
+ | Every `*.island.tsx` has a sibling `*.island.states.ts` | `guards/island-without-states.ts` |
207
+ | A states file is pure data — no sibling import, no framework import | `assertIslandStatesPure`, at `x shot` load time |
208
+ | An island polls with `visibleInterval()` from `@ultimat3/core/page`, never a bare `setInterval(`: a hidden tab and a lost session stop it ([Client data](Client-Data)). A `// why:` on the line, or the line above, admits a tick that is not a read | `guards/raw-interval.ts` |
209
+ | A destructive act asks through `ConfirmDialog` from `@ultimat3/ui` — Cancel focused, Esc closes, the backdrop never, a native post with scripting off, a refusal kept open and focused — never `window.confirm(`. A `// why:` on the line, or the line above, admits a call that is not that question | `guards/native-confirm.ts` |
210
+ | A channel's "this changed" re-reads a server-rendered page through `scheduleRefresh()`, never `refresh()`: nothing swaps under a reader ([Realtime](Realtime#a-channel-event-re-reads-the-page)) | **judgement** |
211
+ | Every state carries a `note` saying why it cannot be reached by clicking | **judgement** — `note` is optional, and it is the line that tells a reviewer what they are looking at |
212
+
213
+ An island with no states file has never been seen with a refused save, an empty read, or a label
214
+ three times as long in the next locale — not by a reviewer and not by a model. How to photograph
215
+ them: [Testing](Testing#seeing-what-you-built).
216
+
217
+ ## Lists and pickers
218
+
219
+ `As of 2026-11`.
220
+
221
+ | Rule | Refused by |
222
+ |---|---|
223
+ | Which list: records with columns `DataTable`; thousands of one-shape rows `VirtualList`; nesting `Tree`; what happened `Timeline`; facts about one thing `DescriptionList` | **judgement** — there is no activity-feed component to reach for instead: `Timeline` + `InfiniteScroll` + `RelativeTime` |
224
+ | A long list is a real `<ul>` that renders every row with scripting off; with script it mounts what is on screen | `VirtualList`'s server branch (`lists.test.ts`); under 100 of 10,000 mounted in Chrome (`ui-virtual-list.e2e.test.ts`) |
225
+ | A live prepend moves nothing being read, and keeps the focused row's node and focus | `ui-virtual-list.e2e.test.ts`; a repeated `itemKey` is `X_UI_INVALID_VALUE` |
226
+ | A `Tree` of links is `<details>` and links, 0 B; `role="tree"` only with `onSelect`, one tab stop | the `TreeProps` union; `lists.test.ts` |
227
+ | A date control is the native input first; the calendar is an extra on a fine pointer, and Esc returns focus to its opener | `DatePicker`/`DateRangePicker` (`pickers.test.ts`, `ui-date-pickers.e2e.test.ts`) |
228
+ | A window takes an explicit IANA zone | `TimeRangePickerProps.timeZone` is required; a non-IANA zone is `X_TIMEZONE_INVALID` |
229
+ | Window presets are the app's, never the framework's | `timeRangeUrl` ships no list; a preset with neither `seconds` nor `from`/`to` is `X_UI_INVALID_VALUE` |
230
+
231
+ ## Reordering and viewers
232
+
233
+ `As of 2026-11`.
234
+
235
+ | Rule | Refused by |
236
+ |---|---|
237
+ | A drag is Pointer Events with capture from a handle; never HTML5 drag events | `drag/pointer-drag.ts` is the one gesture; `ui-drag.e2e.test.ts` (keyboard, pointer, rtl) |
238
+ | A drop is a request: `onMove` once, the row held until it settles, a rejection restores and is announced | `sort-controller.test.ts`, `drag-interaction.test.ts` |
239
+ | Reordering works by keyboard alone and with scripting off | Space/arrows/Esc in `drag-interaction.test.ts`; the per-row `<form>` in `posts/board/page.test.ts` |
240
+ | A business rule on a drop is `canDrop`, never a fork | **judgement** — the one seam the components take |
241
+ | Text a model or member wrote renders as a tree, never HTML; only http/https/mailto/relative links | `viewers-no-html.test.ts`, `markdown-parse.test.ts`, no alert under CSP in `ui-viewers.e2e.test.ts` |
242
+ | A remote image in a body is a link to its alt unless `resolveImage` reserves its box | `viewers.test.ts` |
243
+ | An island passes only the languages it shows | `ui-barrel-bytes.test.ts`: `Markdown` alone retains no `languages/*` |
244
+ | A diff reads with colour off: `+`/`-` gutters and a spoken word per changed row | `viewers.test.ts` |
245
+
246
+ ## Stylesheets
247
+
248
+ Biome does not read `.scss`. These seven guards are the only thing that does. `As of 2026-10`.
249
+
250
+ | Rule | Refused by | The `fix:` it names |
251
+ |---|---|---|
252
+ | A length is a token or `rem()`. `1px` hairlines and `0` are not lengths anyone scales | `guards/raw-length.ts` — a `px` in a declaration, an `@include` argument or an inline `style` | the declaration to write: `padding: tokens.space(3)`, `font-size: tokens.rem(14px)`, `border: tokens.stroke(thick) solid …` |
253
+ | A viewport query names a rung of the one ladder | `guards/raw-breakpoint.ts` — `@media` with a `width` feature | `@include tokens.respond-to(md)` / `respond-down(md)` / `respond-between(md, lg)` |
254
+ | A layer is a name | `guards/raw-z-index.ts` — a number in `z-index` | `z-index: tokens.z(dropdown)`; behind its own stacking context, `calc(-1 * #{tokens.z(raised)})` |
255
+ | Elevation comes off the themed shadow scale | `guards/raw-shadow.ts` — a hand-written length in `box-shadow`, `text-shadow` or `drop-shadow()` | `box-shadow: tokens.shadow('sm')`, or `@include tokens.surface` |
256
+ | One tempo | `guards/raw-motion.ts` — a duration or `cubic-bezier()` literal in `transition*` / `animation*` | `transition: opacity tokens.duration(fast) tokens.easing(out)` |
257
+ | A class a component reads is a class its sheet compiles | `guards/undefined-style-class.ts` — `styles.x` / `styles['x-y']` against the COMPILED `.module.scss` | the nearest class the sheet declares, or the rule to add |
258
+ | A custom property a sheet reads is one the app declares | `guards/undeclared-custom-property.ts` — `var(--x)` in the compiled CSS with no declaration anywhere and no fallback | the steps the scale really has: `tokens.space(7)` → `0 \| 1 \| 2 \| …` |
259
+
260
+ | Not a finding | Why |
261
+ |---|---|
262
+ | `$gutter: 12px`, a map entry, a parameter default | a definition names a value in one place — which is what a token is. A use is reported |
263
+ | `tokens.rem(340px)`, `tokens.fluid(16px, 32px)` | the sanctioned spelling of an off-scale length |
264
+ | `@media (prefers-reduced-motion: …)`, `print`, `(hover: hover)`, `@container` | not a viewport width |
265
+ | `inset 0 0 0 1px …` | a hairline ring is not elevation |
266
+ | `linear`, `ease`, `0s`, `calc(#{tokens.duration(slower)} * 2)` | a keyword, a zero, a multiple of a token |
267
+ | `styles[name]` | a computed key is only known when the component runs |
268
+ | `var(--x, fallback)`; a property a `.ts`/`.tsx` file names | it declares its own answer; an inline `style` sets it |
269
+ | a sheet that does not compile | the build's refusal (`X_PRERENDER_FAILED`), not a guard's |
270
+
271
+ ## Data access
272
+
273
+ One shipped guard is not about the interface. Same contract — a file in `guards/`, a finding with
274
+ a `fix:` — documented where its subject is: [Entities and migrations](Entities-And-Migrations#the-repo).
275
+ `As of 2026-10`.
276
+
277
+ | Rule | Refused by | The `fix:` it names |
278
+ |---|---|---|
279
+ | A `repo.ts` reads through the typed handle | `guards/repo-raw-sql.ts` — a `sql` literal the handle can express: `select *`, `insert`, `update` or `delete` on one table, plain comparisons | the handle call: `db.posts.where({ orgId }).orderBy('createdAt', 'desc').limit(limit).all()` |
280
+
281
+ | Not a finding | Why |
282
+ |---|---|
283
+ | a join, an aggregate, a CTE, a window, an `or`, a function call | the handle has no word for it; raw SQL in the repo is the answer |
284
+ | a statement composed from another `sql` fragment | composition, not one statement |
285
+ | `sql` in a `query`'s source, a migration, a comment or a string | only a tagged template in a `repo.ts` is read |
286
+
287
+ A sheet that defines its own `@function rem` / `fluid` or `@mixin respond-*` is refused by
288
+ `raw-length` and `raw-breakpoint`: Sass lets a module that forwards the tokens redefine one with no
289
+ error, and the local copy silently wins.
290
+
291
+ ## Anti-patterns to flag
292
+
293
+ Read as a review checklist. Each one is a defect on sight.
294
+
295
+ | Flag | Why |
296
+ |---|---|
297
+ | `<div onClick={…}>` | a keyboard, a screen reader and a switch reach it in exactly one way, which is not at all |
298
+ | `role="button"` on anything but `<button>` | a role is a promise; the native element is the only thing that keeps it |
299
+ | `outline: none` with nothing painted back | deletes the only thing telling a keyboard user where they are |
300
+ | `transition: all` | animates properties nobody chose, including ones not yet written |
301
+ | `transition: 220ms ease` with no property named | an omitted property **is** `all` |
302
+ | `<img>` with no `width`/`height` and no `aspect-ratio` | every element under it jumps when the bytes land |
303
+ | `loading="lazy"` on the hero image | a deliberate delay on the one paint the metric measures |
304
+ | "No results" rendered from a pending state | the four-state bug; `empty` must be unreachable before a result arrives |
305
+ | a skeleton whose box differs from the loaded content | a layout shift with extra steps |
306
+ | a spinner on a sub-100ms action | reads as a stutter |
307
+ | a progress bar with no cancel past 10s | attention is already gone |
308
+ | an optimistic write to a balance, a stock count or a permission | the rollback is visible and it is the wrong number that is remembered |
309
+ | `onPaste` blocked on a password field | it makes password managers unusable and buys nothing |
310
+ | `type="number"` for a quantity or an amount | locale decimal separators do not survive it |
311
+ | a 14px input on a touch surface | iOS zooms the viewport on focus and does not zoom back |
312
+ | a toast holding the only "Undo" for a destructive action | it expires on a timer |
313
+ | a toast that moves focus | it interrupts typing to report something that was not asked for |
314
+ | a live region created in the same frame as its first message | it is not announced |
315
+ | an icon-only button with no `label` | silence to a screen reader |
316
+ | a `*.island.tsx` with no states file | nothing has ever seen it fail |
317
+ | a literal `cubic-bezier(…)` or `220ms` in a component stylesheet | the scale exists so the curve can be retuned in one file |
318
+ | a raw hex in a component stylesheet | it does not flip with the theme |
319
+ | a Previous button on a keyset list | a `Page` has no previous cursor, so it is disabled forever; `Pagination` draws First (`firstHref`) instead |
320
+ | a list sorted or paged through an island when a link would do | `listUrl` + a link-mode `DataTable` sort and page with scripting off |
321
+
322
+ ## Where each mechanism lives
323
+
324
+ | Mechanism | File |
325
+ |---|---|
326
+ | the four-state union | `packages/ui/src/components/async-branch.ts` |
327
+ | the motion scales | `packages/ui/src/tokens/_motion.scss` |
328
+ | the space, stroke, breakpoint, z and shadow scales | `packages/ui/src/tokens/_space.scss`, `_stroke.scss`, `_breakpoints.scss`, `_z.scss`, `_shadow.scss` |
329
+ | the global reduced-motion guard, and the default focus ring | `packages/ui/src/tokens/reset.scss` |
330
+ | `focus-ring`, `control`, `transition`, `visually-hidden` | `packages/ui/src/tokens/_mixins.scss` |
331
+ | contrast, as a failing test | `packages/ui/src/tokens/contrast.test.ts` |
332
+ | live regions and the focus trap | `packages/ui/src/a11y.ts` |
333
+ | the toast queue's rules | `packages/ui/src/toast/toast-state.ts` |
334
+ | a list's URL state (sort, cursor, limit, `q`) | `packages/ui/src/list-url.ts` |
335
+ | your app's guards | `guards/` in the app root — `x new` writes every name in `SHIPPED_GUARD_NAMES`, `x g guard <name>` adds one, `x doctor` lists the shipped ones missing |
336
+
337
+ Components, props and token vocabulary: [UI components](UI-Components). Colour roles and the
338
+ contrast table: [Theming](Theming). Seeing a component in a state you cannot click to:
339
+ [Testing](Testing#seeing-what-you-built).