@warlock.js/core 5.34.2 → 5.35.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 (259) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/esm/cli/cli-commands.manager.mjs +1 -1
  3. package/esm/cli/cli-commands.manager.mjs.map +1 -1
  4. package/esm/cli/commands/add.command.mjs +1 -0
  5. package/esm/cli/commands/add.command.mjs.map +1 -1
  6. package/esm/cli/commands/build.command.mjs +30 -3
  7. package/esm/cli/commands/build.command.mjs.map +1 -1
  8. package/esm/cli/commands/check/check-command.action.mjs +1 -0
  9. package/esm/cli/commands/check/check-command.action.mjs.map +1 -1
  10. package/esm/cli/commands/check.command.mjs +5 -0
  11. package/esm/cli/commands/check.command.mjs.map +1 -1
  12. package/esm/cli/commands/explain/explain-command.action.mjs +21 -0
  13. package/esm/cli/commands/explain/explain-command.action.mjs.map +1 -1
  14. package/esm/cli/commands/explain.command.mjs +14 -4
  15. package/esm/cli/commands/explain.command.mjs.map +1 -1
  16. package/esm/cli/commands/generate/generate.command.mjs +19 -1
  17. package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
  18. package/esm/cli/commands/generate/generators/endpoint.generator.mjs +1 -1
  19. package/esm/cli/commands/generate/generators/endpoint.generator.mjs.map +1 -1
  20. package/esm/cli/commands/generate/generators/web.generator.mjs +169 -0
  21. package/esm/cli/commands/generate/generators/web.generator.mjs.map +1 -0
  22. package/esm/cli/commands/generate/templates/web-stubs.mjs +231 -0
  23. package/esm/cli/commands/generate/templates/web-stubs.mjs.map +1 -0
  24. package/esm/cli/commands/inspect/inspect-command.action.mjs +4 -3
  25. package/esm/cli/commands/inspect/inspect-command.action.mjs.map +1 -1
  26. package/esm/cli/commands/inspect.command.mjs +13 -8
  27. package/esm/cli/commands/inspect.command.mjs.map +1 -1
  28. package/esm/cli/commands/mcp/mcp-command.action.mjs +1 -1
  29. package/esm/cli/commands/mcp/mcp-command.action.mjs.map +1 -1
  30. package/esm/config/config-loader.d.mts.map +1 -1
  31. package/esm/config/config-loader.mjs +2 -0
  32. package/esm/config/config-loader.mjs.map +1 -1
  33. package/esm/config/load-config-files.mjs +4 -0
  34. package/esm/config/load-config-files.mjs.map +1 -1
  35. package/esm/dev-server/file-event-handler.mjs +11 -1
  36. package/esm/dev-server/file-event-handler.mjs.map +1 -1
  37. package/esm/dev-server/file-manager.d.mts +1 -1
  38. package/esm/dev-server/file-manager.d.mts.map +1 -1
  39. package/esm/dev-server/file-manager.mjs.map +1 -1
  40. package/esm/dev-server/files-orchestrator.mjs +16 -1
  41. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  42. package/esm/dev-server/files-watcher.mjs +13 -4
  43. package/esm/dev-server/files-watcher.mjs.map +1 -1
  44. package/esm/dev-server/loader/loader-port-acks.mjs +21 -9
  45. package/esm/dev-server/loader/loader-port-acks.mjs.map +1 -1
  46. package/esm/dev-server/loader/register-loader.mjs +31 -12
  47. package/esm/dev-server/loader/register-loader.mjs.map +1 -1
  48. package/esm/dev-server/module-loader.mjs +14 -0
  49. package/esm/dev-server/module-loader.mjs.map +1 -1
  50. package/esm/dev-server/special-file-patterns.mjs +4 -1
  51. package/esm/dev-server/special-file-patterns.mjs.map +1 -1
  52. package/esm/e2e/browser.d.mts.map +1 -1
  53. package/esm/e2e/browser.mjs +3 -1
  54. package/esm/e2e/browser.mjs.map +1 -1
  55. package/esm/e2e/index.d.mts +2 -2
  56. package/esm/e2e/page.d.mts +6 -1
  57. package/esm/e2e/page.d.mts.map +1 -1
  58. package/esm/e2e/page.mjs +36 -1
  59. package/esm/e2e/page.mjs.map +1 -1
  60. package/esm/e2e/types.d.mts +37 -0
  61. package/esm/e2e/types.d.mts.map +1 -1
  62. package/esm/e2e/types.mjs.map +1 -1
  63. package/esm/errors/error-codes.d.mts +48 -0
  64. package/esm/errors/error-codes.d.mts.map +1 -1
  65. package/esm/errors/error-codes.mjs +48 -0
  66. package/esm/errors/error-codes.mjs.map +1 -1
  67. package/esm/errors/index.d.mts +2 -2
  68. package/esm/errors/index.mjs +2 -2
  69. package/esm/generations/add-command.action.mjs +2 -0
  70. package/esm/generations/add-command.action.mjs.map +1 -1
  71. package/esm/generations/design-system-payload.mjs +53 -0
  72. package/esm/generations/design-system-payload.mjs.map +1 -0
  73. package/esm/generations/features/design-system.feature.mjs +38 -0
  74. package/esm/generations/features/design-system.feature.mjs.map +1 -0
  75. package/esm/generations/features/index.mjs +3 -1
  76. package/esm/generations/features/index.mjs.map +1 -1
  77. package/esm/generations/features/notifications.feature.mjs +13 -2
  78. package/esm/generations/features/notifications.feature.mjs.map +1 -1
  79. package/esm/generations/features/test.feature.mjs +48 -13
  80. package/esm/generations/features/test.feature.mjs.map +1 -1
  81. package/esm/generations/stubs.mjs +9 -5
  82. package/esm/generations/stubs.mjs.map +1 -1
  83. package/esm/http/error-mapper.d.mts +1 -1
  84. package/esm/http/error-mapper.mjs +3 -3
  85. package/esm/http/error-mapper.mjs.map +1 -1
  86. package/esm/http/error-response.d.mts +15 -0
  87. package/esm/http/error-response.d.mts.map +1 -0
  88. package/esm/http/error-response.mjs +47 -0
  89. package/esm/http/error-response.mjs.map +1 -0
  90. package/esm/http/errors/errors.d.mts +8 -0
  91. package/esm/http/errors/errors.d.mts.map +1 -1
  92. package/esm/http/errors/errors.mjs +13 -1
  93. package/esm/http/errors/errors.mjs.map +1 -1
  94. package/esm/http/index.d.mts +5 -3
  95. package/esm/http/index.mjs +3 -2
  96. package/esm/http/list-endpoint.d.mts +35 -0
  97. package/esm/http/list-endpoint.d.mts.map +1 -0
  98. package/esm/http/list-endpoint.mjs +28 -0
  99. package/esm/http/list-endpoint.mjs.map +1 -0
  100. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  101. package/esm/http/middleware/inject-request-context.mjs +12 -2
  102. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  103. package/esm/http/response.d.mts +2 -0
  104. package/esm/http/response.d.mts.map +1 -1
  105. package/esm/http/response.mjs +2 -0
  106. package/esm/http/response.mjs.map +1 -1
  107. package/esm/http/server.d.mts.map +1 -1
  108. package/esm/http/server.mjs +2 -0
  109. package/esm/http/server.mjs.map +1 -1
  110. package/esm/http/types.d.mts +7 -0
  111. package/esm/http/types.d.mts.map +1 -1
  112. package/esm/index.d.mts +10 -7
  113. package/esm/index.mjs +3 -2
  114. package/esm/openapi/security.mjs +12 -4
  115. package/esm/openapi/security.mjs.map +1 -1
  116. package/esm/openapi/types.d.mts +2 -0
  117. package/esm/openapi/types.d.mts.map +1 -1
  118. package/esm/production/build-budgets.d.mts +46 -0
  119. package/esm/production/build-budgets.d.mts.map +1 -0
  120. package/esm/production/build-budgets.mjs +109 -0
  121. package/esm/production/build-budgets.mjs.map +1 -0
  122. package/esm/production/production-builder.mjs +13 -1
  123. package/esm/production/production-builder.mjs.map +1 -1
  124. package/esm/production/resolve-build-config.d.mts +1 -1
  125. package/esm/production/resolve-build-config.d.mts.map +1 -1
  126. package/esm/production/resolve-build-config.mjs.map +1 -1
  127. package/esm/restful/restful.d.mts +1 -1
  128. package/esm/tests/fakes/storage-fake.d.mts +3 -1
  129. package/esm/tests/fakes/storage-fake.d.mts.map +1 -1
  130. package/esm/tests/fakes/storage-fake.mjs +3 -3
  131. package/esm/tests/fakes/storage-fake.mjs.map +1 -1
  132. package/esm/tests/fakes/test-fakes.d.mts +3 -1
  133. package/esm/tests/fakes/test-fakes.d.mts.map +1 -1
  134. package/esm/tests/fakes/test-fakes.mjs +3 -2
  135. package/esm/tests/fakes/test-fakes.mjs.map +1 -1
  136. package/esm/tests/index.d.mts +4 -2
  137. package/esm/tests/index.mjs +3 -1
  138. package/esm/tests/load-unit-config.mjs +24 -0
  139. package/esm/tests/load-unit-config.mjs.map +1 -0
  140. package/esm/tests/response-contract.d.mts +32 -0
  141. package/esm/tests/response-contract.d.mts.map +1 -0
  142. package/esm/tests/response-contract.mjs +116 -0
  143. package/esm/tests/response-contract.mjs.map +1 -0
  144. package/esm/tests/start-http-development-server.d.mts.map +1 -1
  145. package/esm/tests/start-http-development-server.mjs +10 -2
  146. package/esm/tests/start-http-development-server.mjs.map +1 -1
  147. package/esm/tests/test-lifecycle-state.mjs.map +1 -1
  148. package/esm/tests/test-locales.mjs +33 -0
  149. package/esm/tests/test-locales.mjs.map +1 -0
  150. package/esm/tests/test-runtime-scope.d.mts +15 -0
  151. package/esm/tests/test-runtime-scope.d.mts.map +1 -0
  152. package/esm/tests/test-runtime-scope.mjs +80 -0
  153. package/esm/tests/test-runtime-scope.mjs.map +1 -0
  154. package/esm/tests/vitest-setup.d.mts +13 -3
  155. package/esm/tests/vitest-setup.d.mts.map +1 -1
  156. package/esm/tests/vitest-setup.mjs +146 -18
  157. package/esm/tests/vitest-setup.mjs.map +1 -1
  158. package/esm/tooling/check/affected-files.d.mts +5 -0
  159. package/esm/tooling/check/affected-files.d.mts.map +1 -0
  160. package/esm/tooling/check/affected-files.mjs +56 -0
  161. package/esm/tooling/check/affected-files.mjs.map +1 -0
  162. package/esm/tooling/check/changed-files.d.mts +2 -0
  163. package/esm/tooling/check/changed-files.d.mts.map +1 -1
  164. package/esm/tooling/check/changed-files.mjs +2 -2
  165. package/esm/tooling/check/changed-files.mjs.map +1 -1
  166. package/esm/tooling/check/conventions-step.d.mts +2 -1
  167. package/esm/tooling/check/conventions-step.d.mts.map +1 -1
  168. package/esm/tooling/check/conventions-step.mjs +48 -6
  169. package/esm/tooling/check/conventions-step.mjs.map +1 -1
  170. package/esm/tooling/check/fixes.d.mts +8 -0
  171. package/esm/tooling/check/fixes.d.mts.map +1 -0
  172. package/esm/tooling/check/fixes.mjs +31 -0
  173. package/esm/tooling/check/fixes.mjs.map +1 -0
  174. package/esm/tooling/check/index.d.mts +12 -0
  175. package/esm/tooling/check/index.mjs +3 -2
  176. package/esm/tooling/check/run-check.d.mts +2 -0
  177. package/esm/tooling/check/run-check.d.mts.map +1 -1
  178. package/esm/tooling/check/run-check.mjs +20 -4
  179. package/esm/tooling/check/run-check.mjs.map +1 -1
  180. package/esm/tooling/check/run-tests.mjs +1 -1
  181. package/esm/tooling/check/source-ast.d.mts +1 -0
  182. package/esm/tooling/check/web-conventions.d.mts +10 -0
  183. package/esm/tooling/check/web-conventions.d.mts.map +1 -0
  184. package/esm/tooling/check/web-conventions.mjs +126 -0
  185. package/esm/tooling/check/web-conventions.mjs.map +1 -0
  186. package/esm/tooling/index.d.mts +7 -2
  187. package/esm/tooling/index.mjs +4 -2
  188. package/esm/tooling/inspect/collect-files.mjs +172 -0
  189. package/esm/tooling/inspect/collect-files.mjs.map +1 -0
  190. package/esm/tooling/inspect/collect-pages.mjs +3 -2
  191. package/esm/tooling/inspect/collect-pages.mjs.map +1 -1
  192. package/esm/tooling/inspect/collect-routes.mjs +1 -1
  193. package/esm/tooling/inspect/collect-routes.mjs.map +1 -1
  194. package/esm/tooling/inspect/explain-file.d.mts +12 -0
  195. package/esm/tooling/inspect/explain-file.d.mts.map +1 -0
  196. package/esm/tooling/inspect/explain-file.mjs +45 -0
  197. package/esm/tooling/inspect/explain-file.mjs.map +1 -0
  198. package/esm/tooling/inspect/index.d.mts +3 -2
  199. package/esm/tooling/inspect/index.mjs +2 -1
  200. package/esm/tooling/inspect/inspect-app.d.mts.map +1 -1
  201. package/esm/tooling/inspect/inspect-app.mjs +9 -3
  202. package/esm/tooling/inspect/inspect-app.mjs.map +1 -1
  203. package/esm/tooling/inspect/inspect-report.schema.mjs +34 -1
  204. package/esm/tooling/inspect/inspect-report.schema.mjs.map +1 -1
  205. package/esm/tooling/inspect/inspect-report.types.d.mts +17 -1
  206. package/esm/tooling/inspect/inspect-report.types.d.mts.map +1 -1
  207. package/esm/tooling/inspect/inspect-report.types.mjs +2 -1
  208. package/esm/tooling/inspect/inspect-report.types.mjs.map +1 -1
  209. package/esm/tooling/mcp/mcp-tools.d.mts.map +1 -1
  210. package/esm/tooling/mcp/mcp-tools.mjs +8 -2
  211. package/esm/tooling/mcp/mcp-tools.mjs.map +1 -1
  212. package/esm/tooling/problem.d.mts +4 -0
  213. package/esm/tooling/problem.d.mts.map +1 -1
  214. package/esm/tooling/problem.mjs +1 -0
  215. package/esm/tooling/problem.mjs.map +1 -1
  216. package/esm/tooling/syntax/ast.d.mts +2 -0
  217. package/esm/tooling/typecheck/run-typecheck.mjs +5 -1
  218. package/esm/tooling/typecheck/run-typecheck.mjs.map +1 -1
  219. package/esm/upgrade/codemods/toolchain-templates.mjs +3 -1
  220. package/esm/upgrade/codemods/toolchain-templates.mjs.map +1 -1
  221. package/esm/vite/index.d.mts +3 -1
  222. package/esm/vite/index.mjs +3 -1
  223. package/esm/vite/test-setup-reporter.d.mts +16 -0
  224. package/esm/vite/test-setup-reporter.d.mts.map +1 -0
  225. package/esm/vite/test-setup-reporter.mjs +55 -0
  226. package/esm/vite/test-setup-reporter.mjs.map +1 -0
  227. package/esm/vite/test-workers.d.mts +5 -0
  228. package/esm/vite/test-workers.d.mts.map +1 -0
  229. package/esm/vite/test-workers.mjs +38 -0
  230. package/esm/vite/test-workers.mjs.map +1 -0
  231. package/esm/warlock-config/index.d.mts +2 -1
  232. package/esm/warlock-config/types.d.mts +3 -0
  233. package/esm/warlock-config/types.d.mts.map +1 -1
  234. package/llms-full.txt +883 -70
  235. package/llms.txt +58 -56
  236. package/package.json +15 -14
  237. package/schemas/inspect-report.schema.json +47 -1
  238. package/skills/build-budgets/SKILL.md +51 -0
  239. package/skills/build-restful/SKILL.md +56 -0
  240. package/skills/configure-app/SKILL.md +34 -0
  241. package/skills/create-module/SKILL.md +9 -0
  242. package/skills/generate-openapi/SKILL.md +1 -1
  243. package/skills/generate-web-ui/SKILL.md +71 -0
  244. package/skills/index.md +3 -3
  245. package/skills/run-app/SKILL.md +1 -1
  246. package/skills/send-mail/SKILL.md +25 -0
  247. package/skills/send-response/SKILL.md +77 -0
  248. package/skills/test-browser/SKILL.md +54 -0
  249. package/skills/test-http/SKILL.md +91 -0
  250. package/skills/test-service/SKILL.md +161 -3
  251. package/skills/upgrade-from-5-29/SKILL.md +22 -0
  252. package/skills/use-localization/SKILL.md +21 -0
  253. package/skills/warlock-check/SKILL.md +72 -1
  254. package/skills/warlock-inspect/SKILL.md +45 -2
  255. package/skills/warlock-mcp/SKILL.md +4 -0
  256. package/skills/warlock-trace/SKILL.md +2 -2
  257. package/skills/write-cli-command/SKILL.md +20 -2
  258. package/esm/production/build-app-production.mjs +0 -14
  259. package/esm/production/build-app-production.mjs.map +0 -1
package/package.json CHANGED
@@ -25,12 +25,12 @@
25
25
  "@mongez/slug": "^1.0.7",
26
26
  "@mongez/supportive-is": "^3.0.0",
27
27
  "@mongez/time-wizard": "^1.0.6",
28
- "@warlock.js/cache": "5.34.2",
29
- "@warlock.js/cascade": "5.34.2",
30
- "@warlock.js/context": "5.34.2",
31
- "@warlock.js/fs": "5.34.2",
32
- "@warlock.js/logger": "5.34.2",
33
- "@warlock.js/seal": "5.34.2",
28
+ "@warlock.js/cache": "5.35.0",
29
+ "@warlock.js/cascade": "5.35.0",
30
+ "@warlock.js/context": "5.35.0",
31
+ "@warlock.js/fs": "5.35.0",
32
+ "@warlock.js/logger": "5.35.0",
33
+ "@warlock.js/seal": "5.35.0",
34
34
  "bcryptjs": "^3.0.3",
35
35
  "chokidar": "^5.0.0",
36
36
  "dayjs": "^1.11.19",
@@ -46,7 +46,8 @@
46
46
  "mime": "^4.1.0",
47
47
  "oxc-parser": "0.154.0",
48
48
  "pluralize-esm": "^9.0.5",
49
- "typescript": "7.0.2"
49
+ "typescript": "7.0.2",
50
+ "axe-core": "4.14.0"
50
51
  },
51
52
  "peerDependencies": {
52
53
  "@napi-rs/keyring": "^2.1.1",
@@ -55,12 +56,12 @@
55
56
  "@aws-sdk/lib-storage": "^3.955.0",
56
57
  "@aws-sdk/s3-request-presigner": "^3.955.0",
57
58
  "@react-email/render": "^2.0.5",
58
- "@warlock.js/access": "5.34.2",
59
- "@warlock.js/ai": "5.34.2",
60
- "@warlock.js/ai-tools": "5.34.2",
61
- "@warlock.js/devtools": "5.34.2",
62
- "@warlock.js/herald": "5.34.2",
63
- "@warlock.js/notifications": "5.34.2",
59
+ "@warlock.js/access": "5.35.0",
60
+ "@warlock.js/ai": "5.35.0",
61
+ "@warlock.js/ai-tools": "5.35.0",
62
+ "@warlock.js/devtools": "5.35.0",
63
+ "@warlock.js/herald": "5.35.0",
64
+ "@warlock.js/notifications": "5.35.0",
64
65
  "nodemailer": "^8.0.5",
65
66
  "react": "^19.2.3",
66
67
  "react-dom": "^19.2.3",
@@ -145,7 +146,7 @@
145
146
  "engines": {
146
147
  "node": ">=22.15"
147
148
  },
148
- "version": "5.34.2",
149
+ "version": "5.35.0",
149
150
  "type": "module",
150
151
  "main": "./esm/index.mjs",
151
152
  "module": "./esm/index.mjs",
@@ -47,7 +47,8 @@
47
47
  "pages",
48
48
  "jobs",
49
49
  "schedules",
50
- "packages"
50
+ "packages",
51
+ "files"
51
52
  ]
52
53
  }
53
54
  },
@@ -549,6 +550,51 @@
549
550
  }
550
551
  }
551
552
  }
553
+ },
554
+ "files": {
555
+ "type": "array",
556
+ "items": {
557
+ "type": "object",
558
+ "required": ["file", "module", "role", "exports", "imports", "importedBy"],
559
+ "properties": {
560
+ "file": {
561
+ "type": "string"
562
+ },
563
+ "module": {
564
+ "type": "string"
565
+ },
566
+ "role": {
567
+ "type": "string"
568
+ },
569
+ "exports": {
570
+ "type": "array",
571
+ "items": {
572
+ "type": "string"
573
+ }
574
+ },
575
+ "importedBy": {
576
+ "type": "array",
577
+ "items": {
578
+ "type": "string"
579
+ }
580
+ },
581
+ "imports": {
582
+ "type": "array",
583
+ "items": {
584
+ "type": "object",
585
+ "required": ["specifier", "file"],
586
+ "properties": {
587
+ "specifier": {
588
+ "type": "string"
589
+ },
590
+ "file": {
591
+ "type": ["string", "null"]
592
+ }
593
+ }
594
+ }
595
+ }
596
+ }
597
+ }
552
598
  }
553
599
  }
554
600
  }
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: build-budgets
3
+ description: "Configure build.budgets in warlock.config.ts, inspect per-page gzip JS/CSS and deferred assets, compare the last build, and enforce limits with warlock build --json. Use for client bundle size regressions and CI transfer-size gates. For field metrics use web measure-web-vitals."
4
+ ---
5
+
6
+ # Client build budgets
7
+
8
+ ```ts title="warlock.config.ts"
9
+ import { defineConfig } from "@warlock.js/core";
10
+
11
+ export default defineConfig({
12
+ build: {
13
+ budgets: {
14
+ page: { js: "150kb", css: "50kb" },
15
+ pages: { "/admin/**": { js: "220kb" } },
16
+ mode: "error",
17
+ },
18
+ },
19
+ });
20
+ ```
21
+
22
+ Run `npx warlock build` for the sorted table, or `npx warlock build --json`
23
+ for `{ success, budgets, error? }`. Progress goes to stderr in JSON mode.
24
+ `budgets` contains `version: 1`, `compression: "gzip"`, `pages` and
25
+ `violations`. API-only builds report an empty page list.
26
+
27
+ Page limits cover the hydration entry, page, layouts, root, setup registrations
28
+ and error boundary, plus their transitive static imports. Files are counted once
29
+ per page and gzipped individually. Dynamic imports beyond those required roots
30
+ are reported separately and do not consume the budget. Shared files count in
31
+ each page's first visit. This measures emitted JS/CSS, not HTML, fonts or images.
32
+
33
+ `pages` keys match route paths with `*` (within a segment), `**` (across
34
+ segments), or `?` (one non-slash character). Defaults apply first; matching rules
35
+ merge in insertion order and the last supplied field wins. Unsupported glob
36
+ syntax fails with an actionable error. Units are case-insensitive `b`, `kb` and
37
+ `mb`, with 1024 bytes per kb; equality passes. Omitted fields are unlimited.
38
+
39
+ Existing apps without budgets receive a report only. Configured budgets default
40
+ to `mode: "error"`; `"warn"` logs violations and completes. New project templates
41
+ configure 150kb JS / 50kb CSS. A failure names the page, limit, actual size and
42
+ the ten largest rendered modules. Those module sizes are Rollup attribution
43
+ estimates in uncompressed rendered bytes, not gzip shares; compression is not
44
+ additive across modules inside a file.
45
+
46
+ The successful build stores `build-budgets.json` in `build.outdir` (normally
47
+ `dist`). Preserve that directory before rebuilding to obtain deltas. A failed
48
+ build rolls back to the previous artifact and baseline. JSON output still
49
+ contains its attempted report. Changing output directories or deleting dist
50
+ starts a fresh baseline; new routes display no delta. Vite's private
51
+ `client/.vite/budget-modules.json` contains the full module attribution.
@@ -266,3 +266,59 @@ router.restfulResource("/orders", new OrdersRestful());
266
266
  - The `create-controller` topic — what the chain's handlers look like.
267
267
  - The `define-resource` topic — wire shape for the records the chain returns.
268
268
  - The `warlock-conventions` topic — controller/service/repository layering rules.
269
+
270
+ ## Bounded list endpoints
271
+
272
+ Use `listEndpoint(request, listCallback, options)` from `@warlock.js/core` to
273
+ produce `{ <pluralKey>: data, pagination }` from a page-based repository list.
274
+ The callback receives repository options, with sorting translated into the
275
+ repository's `[column, direction]` tuple. Wrap repository methods in an arrow
276
+ to preserve their receiver. Both `list` and `listCached` work.
277
+
278
+ ```ts
279
+ import { listEndpoint, type Request, type RequestHandler } from "@warlock.js/core";
280
+ import { v, type Infer } from "@warlock.js/seal";
281
+ import { productsRepository } from "../repositories/products.repository";
282
+
283
+ const querySchema = v.object({
284
+ page: v.int().min(1).default(1),
285
+ limit: v.int().min(1).default(20),
286
+ orderBy: v.literal("createdAt", "name").optional(),
287
+ orderDir: v.literal("asc", "desc").optional(),
288
+ status: v.literal("active", "archived").optional(),
289
+ });
290
+ type Query = Infer<typeof querySchema>;
291
+
292
+ export const listProducts: RequestHandler<Request<Query>> = async ({ request, response }) =>
293
+ response.success(
294
+ await listEndpoint(request, (options) => productsRepository.listCached(options), {
295
+ pluralKey: "products",
296
+ maxLimit: 100,
297
+ orderBy: ["createdAt", "name"],
298
+ filters: ["status"],
299
+ }),
300
+ );
301
+
302
+ listProducts.validation = { schema: querySchema, validating: ["query"] };
303
+ // Attach responseSchema with your ProductResource and pagination fields too.
304
+ ```
305
+
306
+ `request.validated()` must already contain schema-validated query values. Seal
307
+ handles coercion and filter validation; the helper does not read raw query or
308
+ body values. Configure the repository's `filterBy` for the chosen filter fields.
309
+ Only explicitly listed `filters` are forwarded; pagination and sort controls
310
+ cannot be declared as filters in the typed API. Filters remain flat repository
311
+ options, rather than a nested `filters` object.
312
+
313
+ Defaults are page 1, limit 20, maxLimit 100. The helper clamps a positive integer
314
+ limit to maxLimit (use `.max(100)` in the query schema instead if you prefer a
315
+ 422 for excessive requests). Invalid page/limit or unlisted sort columns throw
316
+ `AppError("invalidListQuery", 422, ...)`. Sort directions are restricted to
317
+ `asc`/`desc`, defaulting to `asc` when a column is provided. With no column,
318
+ repository default ordering applies. Invalid helper configuration throws a
319
+ `TypeError`. The plural key must be nonempty and cannot be `pagination`.
320
+
321
+ The helper forces page pagination and preserves the repository's pagination
322
+ metadata without recalculating it. Cursor and unpaginated endpoints need their
323
+ own controller. Add authorization constraints inside the callback using trusted
324
+ server values; never accept them as client filters.
@@ -421,3 +421,37 @@ export default {
421
421
  Keep the domain error class in an app module imported by both the service and
422
422
  this config. Return `undefined` for errors you do not recognize; a mapped 5xx
423
423
  is opaque unless the mapper explicitly chooses a public message.
424
+
425
+ ## Format every JSON error response
426
+
427
+ `http.errorMapper` classifies thrown non-`HttpError` errors as `{ status, message?, payload? }`. It does not see existing `HttpError` subclasses, returned validation errors, helper responses, unmatched routes, or global rate-limit failures.
428
+
429
+ Use `http.errorResponse` when the application needs one JSON error envelope across those paths:
430
+
431
+ ```ts title="src/config/http.ts"
432
+ import type { HttpConfigurations } from "@warlock.js/core";
433
+
434
+ export default {
435
+ errorMapper: () => ({ status: 500 }),
436
+ errorResponse({ status, requestId }) {
437
+ return {
438
+ error: {
439
+ code: status >= 500 ? "serverError" : `http${status}`,
440
+ message: status >= 500 ? "Internal server error." : "Request failed.",
441
+ },
442
+ requestId,
443
+ };
444
+ },
445
+ } satisfies HttpConfigurations;
446
+ ```
447
+
448
+ The synchronous formatter receives `HttpErrorResponseContext`: `{ status: number, body: unknown, requestId: string }`. Narrow `body` before reading fields; it is the parsed, serialized JSON body. Return a JSON-serializable object or `undefined` to keep the original. The formatter changes only the body, preserving the HTTP status and headers except for recalculated content length, a `private, no-store` cache policy, and the correlation header. If it throws or cannot serialize its result, core logs the formatter error and retains the original body.
449
+
450
+ The hook covers status 400–599 with `application/json` or an `application/*+json` content type, including Fastify parsing errors, unmatched-route 404s, validation 422s, rate-limit 429s, and 500s. Warlock responses use the ID already stamped from `request.id`. Before Warlock creates a Request, the formatter receives Fastify's `request.id` and echoes that same value under `http.requestId.header` (default `X-Request-Id`). Setting `http.requestId.enabled: false` suppresses that header; without a stamped Warlock ID, the formatter falls back to Fastify's request ID even for routed responses. Keep request-ID headers enabled when the body must correlate with Warlock request logs. HTML pages, files, raw text, streams, and errors after headers have been sent are not reformatted. With no formatter configured, existing response shapes are unchanged.
451
+
452
+ ## Co-located configuration tests
453
+
454
+ `src/config/mail.spec.ts` and `src/config/mail.test.tsx` are never imported as
455
+ configuration. Discovery and reload also exclude `test`, `tests`, `__tests__`,
456
+ `__mocks__`, `__fixtures__`, and `fixtures` directories at any depth. A real
457
+ `src/config/tests.ts` remains ordinary configuration.
@@ -195,3 +195,12 @@ Generated web starter files match the scaffold’s Oxfmt configuration and its
195
195
  built-in import sorting.
196
196
  After editing generated code, run `npm run format` and `npm run lint:check`.
197
197
  Formatting retains unused imports; the linter reports them.
198
+
199
+ ## Notification translation defaults
200
+
201
+ After configuring `app.localeCodes`, run `npx warlock add notifications`.
202
+ New notification dictionaries include `notFound` as a string in every configured
203
+ locale. English ("Notification not found") is the explicit fallback for each
204
+ locale without a bundled translation, including locale sets without English.
205
+ Replace fallback text with your translations before publishing. Existing module
206
+ files and existing translation keys are preserved.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: generate-openapi
3
- description: 'Generate an OpenAPI 3.1 document from the registered routes with `warlock generate.openapi` (default `storage/openapi/openapi.json`), write a Postman collection from it with `warlock generate.postman` (default `storage/postman/collection.json`), and read it live in the dev-only API docs page at `/__warlock/docs`. Documents paths, path/query parameters, request bodies from Seal validation, responses from `handler.responseSchema`, 401/422 and bearer/cookie security from `authMiddleware()`. Triggers: `generate.openapi`, `openApiGeneratorCommand`, `buildOpenApiDocument`, `getDevelopmentOpenApiDocument`, `generate.postman`, `openApiToPostmanCollection`, `getDevelopmentPostmanCollection`, `responseSchema`, `--include-pages`, `/__warlock/docs`; "OpenAPI", "Swagger", "API docs", "import my API into Postman or Insomnia", "document my routes". Skip: declaring response types on a handler — the `create-controller` topic; the devtools dashboard — the `devtools-overview` topic of the `warlock-js-devtools` skill; listing routes — the `warlock-routes` topic.'
3
+ description: 'Generate an OpenAPI 3.1 document from the registered routes with `warlock generate.openapi` (default `storage/openapi/openapi.json`), write a Postman collection from it with `warlock generate.postman` (default `storage/postman/collection.json`), and read it live in the dev-only API docs page at `/__warlock/docs`. Documents paths, path/query parameters, request bodies from Seal validation, responses from `handler.responseSchema`, 401/422 and bearer/cookie security from `authMiddleware()`. Triggers: `generate.openapi`, `openApiGeneratorCommand`, `buildOpenApiDocument`, `getDevelopmentOpenApiDocument`, `generate.postman`, `openApiToPostmanCollection`, `getDevelopmentPostmanCollection`, `responseSchema`, `--include-pages`, `/__warlock/docs`; "OpenAPI", "Swagger", "API docs", "import my API into Postman or Insomnia", "document my routes". Skip: declaring response types on a handler — the `create-controller` topic; the devtools dashboard — the `warlock-js-devtools` skill; listing routes — the `warlock-routes` topic.'
4
4
  ---
5
5
 
6
6
  # Warlock — generate an OpenAPI document
@@ -0,0 +1,71 @@
1
+ ---
2
+ name: generate-web-ui
3
+ description: "Generate web pages, schema-backed forms and CRUD UI with warlock generate page, form and crud-ui. Includes setup companions, design-system controls, useSubmitForm, optional guarded endpoints and module CRUD prerequisites."
4
+ ---
5
+
6
+ # Generate web UI
7
+
8
+ Run these commands from an app with `@warlock.js/web` installed and the app's
9
+ design system in `src/web/design-system/components/`. Controls are default
10
+ exports, imported individually through `web/design-system/components/<name>`;
11
+ they implement Mongez `useFormControl` and accept `name`, `label`, `hint` and
12
+ `error`. Forms import `Form` directly from `@mongez/react-form`.
13
+
14
+ ```sh
15
+ npx warlock generate page account/settings
16
+ npx warlock generate page dashboard --interactive
17
+ npx warlock generate form contact
18
+ npx warlock generate form feedback --module posts --endpoint
19
+ npx warlock generate crud-ui posts --dry-run --json
20
+ npx warlock generate crud-ui posts
21
+ ```
22
+
23
+ `page account/settings` creates `src/web/account/settings.page.tsx` and
24
+ `settings.setup.ts`, with `/account/settings`, a named route, a loader and
25
+ `config.metadata`. `--interactive` also creates a counter feature hook under
26
+ `hooks/`; replace that example with the feature's behavior. Executable page
27
+ exports belong in `.setup.ts`, UI in `.page.tsx`, state and effects in hooks.
28
+
29
+ `form contact` creates `src/web/forms/contact-form.tsx`, a props file, a
30
+ `use-contact-form` hook using `useSubmitForm`, and
31
+ `src/shared/schema/contact.schema.ts`. The starter schema has one `name`
32
+ string field; edit that shared schema and the controls together. Its default
33
+ submit target is `/contact`; supply a real endpoint or set the form's `path`.
34
+ With `--module posts`, output lives under `src/web/posts/`, the shared schema
35
+ under `src/shared/schema/posts/`, and the target is `/posts/contact`.
36
+ The module must exist. `--endpoint` requires `--module`, adds a controller
37
+ with `validation` and `responseSchema`, and appends a guarded POST route.
38
+ This initial endpoint validates and echoes the input; replace that body with a
39
+ service call to persist it. It does not create database persistence for you.
40
+
41
+ For CRUD UI, first generate a full module with
42
+ `npx warlock generate module posts`. Define its create/update schemas and
43
+ list query schema, then run `npx warlock generate crud-ui posts`.
44
+ The generator loads the real Seal schemas using the app loader, so their
45
+ imports must resolve. These schema modules must be safe to import on both
46
+ client and server. Plain string fields map to TextInput, password names to
47
+ PasswordInput, email/date/time/date-time formats to the corresponding controls,
48
+ and booleans to CheckboxInput. Unions, nullable fields, enums, numeric fields,
49
+ nested objects and arrays require custom control decisions and are rejected
50
+ before writing output. An empty module schema produces an empty form.
51
+
52
+ CRUD output contains list/create/edit pages and setup companions, separate
53
+ create/update forms, their hooks and props, list/edit/delete hooks, and a
54
+ delete action component. Lists use the existing GET endpoint and its
55
+ `data`/`pagination` contract (`page` and `pages`). Literal fields in
56
+ `listPostsQuerySchema` become filter inputs; `page` is controlled by navigation
57
+ and `limit` remains the endpoint default. Edit reads GET `/posts/:id`, create
58
+ submits POST `/posts`, update uses PUT `/posts/:id`, and delete uses DELETE
59
+ `/posts/:id`. Successful deletion refreshes the list. Mutation forms show
60
+ validation errors, pending state and success feedback.
61
+
62
+ CRUD generation accepts the conventional full module route file, allowing
63
+ formatting and comments. It fails closed for custom routes/guards instead of
64
+ inferring authorization. Pages require `requireUser({ userType: "user" })`,
65
+ matching the starter's `guarded` helper. Configure `web.session` with the auth
66
+ page-session resolver before visiting these pages. Endpoint guards still
67
+ run on every API request; customized authorization requires a hand-written UI.
68
+
69
+ All three commands support `--dry-run --json` through the normal generation
70
+ report. Existing generated files are skipped unless `--force` is explicit.
71
+ Route registration is idempotent and retains existing module content.
package/skills/index.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "The Warlock server framework: HTTP routes, controllers, responses, resources, repositories, CLI, mail, storage, security helpers. Exports `router`, `Request`, `Response`, `RequestHandler`, `useCase`, `RepositoryManager`, `defineResource`, `defineConfig`, `storage`, `sendMail`, `hashPassword`, `command`. Use for: add a route, write a controller, return a JSON response, hide a field from the response, upload a file, send an email, hash a password, add a CLI command, generate an OpenAPI document, type an API response, wire a socket, run or configure the app. Not this package: database models/queries → @warlock.js/cascade; input schemas → @warlock.js/seal; SSR pages → @warlock.js/web."
2
+ description: 'Warlock application runtime: `router`, `Request`, `Response`, `config`, `env`, `sendMail` and `defineConfig`. Use for "add an API route", controllers, middleware, CLI commands, config, uploads, mail, sockets, OpenAPI, testing or upgrades. Not this package → @warlock.js/web for React SSR pages; @warlock.js/cascade for database queries; @warlock.js/seal for standalone schemas; @warlock.js/auth for login.'
3
3
  ---
4
4
 
5
5
  # @warlock.js/core
@@ -18,11 +18,11 @@ When the framework is wrong or missing a capability, follow the `report-framewor
18
18
 
19
19
  ## Topics by area
20
20
 
21
- - **Foundations:** warlock-conventions, create-module, make-endpoint, configure-app, use-app-context, resolve-path, build-url, update-packages, lower-stage3-decorators, upgrade-from-5-29
21
+ - **Foundations:** warlock-conventions, create-module, make-endpoint, configure-app, build-budgets, use-app-context, resolve-path, build-url, update-packages, lower-stage3-decorators, upgrade-from-5-29
22
22
  - **HTTP & routing:** register-route, warlock-routes, create-controller, use-middleware, write-middleware, validate-input, use-request-locals, build-restful, wire-socket
23
23
  - **Responses & resources:** send-response, define-resource, use-model-transformers, use-localization
24
24
  - **Domain layers:** write-use-case, use-repository, write-seeder
25
- - **CLI & running the app:** run-app, write-cli-command, add-connector, health-checks, warlock-doctor, warlock-inspect, warlock-check, generate-openapi
25
+ - **CLI & running the app:** generate-web-ui, run-app, write-cli-command, add-connector, health-checks, warlock-doctor, warlock-inspect, warlock-check, generate-openapi
26
26
  - **Agent tooling:** warlock-mcp, warlock-trace, warlock-agents-md, warlock-board
27
27
  - **Storage, files & mail:** store-file, upload-file, process-image, send-mail
28
28
  - **Security & utilities:** hash-password, encrypt-data, store-secrets, retry-operation, benchmark-code, request-memo, request-tracing
@@ -182,7 +182,7 @@ Omit switches to run TypeScript and installed/configured Oxlint. Set `oxlint: fa
182
182
 
183
183
  ## `warlock build` — production bundle
184
184
 
185
- esbuild bundle of the app down to a single JS file in `dist/`. No flags — every setting comes from `warlock.config.ts > build`.
185
+ esbuild bundle of the app down to a single JS file in `dist/`. `--json` prints the client budget report; build settings come from `warlock.config.ts > build`. See the `build-budgets` topic for per-page gzip limits and deltas.
186
186
 
187
187
  ### `build.*` config knobs
188
188
 
@@ -400,3 +400,28 @@ await Mail.to(user.email)
400
400
 
401
401
  - The `configure-app` topic — `src/config/mail.ts` shape and `env()` patterns.
402
402
  - The `warlock-conventions` topic — where mail-sending services live.
403
+
404
+ ## Preview development mail in devtools
405
+
406
+ Run `npx warlock add devtools`, start development, and open `/__warlock` (or your
407
+ configured devtools path). In the mail panel, select a captured message to read
408
+ its recipients, subject, text, and sandboxed HTML preview. Send while the panel's
409
+ collector is attached; captures are held in memory and disappear on restart.
410
+
411
+ Use development mail mode so previews do not deliver real email:
412
+
413
+ ```ts
414
+ import { setMailMode, sendMail } from "@warlock.js/core";
415
+
416
+ setMailMode("development");
417
+ await sendMail({
418
+ to: "reader@example.com",
419
+ subject: "Preview",
420
+ html: "<p>Your code is 123456</p>",
421
+ });
422
+ ```
423
+
424
+ Development sends emit `mailEvents.onCaptured`, which devtools consumes; console
425
+ logs show metadata rather than sensitive bodies. Devtools is mounted only for
426
+ the development runtime and is not mounted in production. Keep production mail
427
+ mode configured for actual delivery. Test-mode assertions use the test mailbox.
@@ -481,3 +481,80 @@ export function requireProduct<T>(product: T | null | undefined): T {
481
481
  return product;
482
482
  }
483
483
  ```
484
+
485
+ ## Format every JSON error response
486
+
487
+ `http.errorMapper` classifies thrown non-`HttpError` errors as `{ status, message?, payload? }`. It does not see existing `HttpError` subclasses, returned validation errors, helper responses, unmatched routes, or global rate-limit failures.
488
+
489
+ Use `http.errorResponse` when the application needs one JSON error envelope across those paths:
490
+
491
+ ```ts title="src/config/http.ts"
492
+ import type { HttpConfigurations } from "@warlock.js/core";
493
+
494
+ export default {
495
+ errorMapper: () => ({ status: 500 }),
496
+ errorResponse({ status, requestId }) {
497
+ return {
498
+ error: {
499
+ code: status >= 500 ? "serverError" : `http${status}`,
500
+ message: status >= 500 ? "Internal server error." : "Request failed.",
501
+ },
502
+ requestId,
503
+ };
504
+ },
505
+ } satisfies HttpConfigurations;
506
+ ```
507
+
508
+ The synchronous formatter receives `HttpErrorResponseContext`: `{ status: number, body: unknown, requestId: string }`. Narrow `body` before reading fields; it is the parsed, serialized JSON body. Return a JSON-serializable object or `undefined` to keep the original. The formatter changes only the body, preserving the HTTP status and headers except for recalculated content length, a `private, no-store` cache policy, and the correlation header. If it throws or cannot serialize its result, core logs the formatter error and retains the original body.
509
+
510
+ The hook covers status 400–599 with `application/json` or an `application/*+json` content type, including Fastify parsing errors, unmatched-route 404s, validation 422s, rate-limit 429s, and 500s. Warlock responses use the ID already stamped from `request.id`. Before Warlock creates a Request, the formatter receives Fastify's `request.id` and echoes that same value under `http.requestId.header` (default `X-Request-Id`). Setting `http.requestId.enabled: false` suppresses that header; without a stamped Warlock ID, the formatter falls back to Fastify's request ID even for routed responses. Keep request-ID headers enabled when the body must correlate with Warlock request logs. HTML pages, files, raw text, streams, and errors after headers have been sent are not reformatted. With no formatter configured, existing response shapes are unchanged.
511
+
512
+ ## Typed domain failures
513
+
514
+ `AppError<Details, Code>` is available from `@warlock.js/core` and the lightweight
515
+ `@warlock.js/core/errors` entry. Its constructor is
516
+ `new AppError(code, status, publicMessage, details?)`. Status must be an integer
517
+ from 400 to 599. The message and details are explicitly public, including for
518
+ 5xx errors: never put diagnostics, credentials or private records there.
519
+
520
+ ```ts
521
+ import { AppError } from "@warlock.js/core/errors";
522
+
523
+ throw new AppError("quotaExceeded", 429, "Try again later.", { retryAfter: 30 });
524
+ ```
525
+
526
+ The default response is
527
+ `{ error: { code: "quotaExceeded", message: "Try again later.", details: { retryAfter: 30 } } }`.
528
+ Absent details are omitted; `false`, `0` and `null` details are preserved. Errors
529
+ retain the private, no-store cache policy, and server failures are logged.
530
+
531
+ `AppError` and `HttpError` are separate `Error` subclasses. Existing `HttpError`
532
+ constructors, inheritance, `payload` and legacy `{ error: message, payload? }`
533
+ bodies remain unchanged. Both bypass `http.errorMapper`; ordinary errors retain
534
+ the existing opaque 500 fallback. Choose `AppError` for new domain failures
535
+ that need stable codes, and keep existing HTTP error subclasses as they are.
536
+
537
+ `http.errorResponse` receives an optional typed `appError` alongside `body`,
538
+ `status` and `requestId`. No per-error mapper is needed:
539
+
540
+ ```ts
541
+ import type { HttpErrorResponseFormatter } from "@warlock.js/core";
542
+
543
+ const errorResponse: HttpErrorResponseFormatter = ({ appError, requestId }) => {
544
+ if (!appError) return undefined; // keep other JSON errors unchanged
545
+ return {
546
+ error: {
547
+ code: appError.code,
548
+ message: appError.publicMessage,
549
+ ...(appError.details === undefined ? {} : { details: appError.details }),
550
+ },
551
+ requestId,
552
+ };
553
+ };
554
+ // Set this function as http.errorResponse in your HTTP configuration.
555
+ ```
556
+
557
+ The formatter sees the original instance only for thrown `AppError`s handled
558
+ by the request lifecycle. Early Fastify rejections and returned helper responses
559
+ have no `appError`; use `body` and `status` for those cases. Do not spread an
560
+ error instance into the response.
@@ -103,6 +103,7 @@ port-file handshake above when the app supports binding `0` itself.
103
103
  document. Selector helpers do not enter frames or closed shadow roots.
104
104
 
105
105
  Use `--browser /path/to/chrome` or `WARLOCK_E2E_BROWSER` if discovery fails.
106
+ Set `browser.startupTimeoutMs` (default 30 seconds) when the browser is slow to start, for example on shared CI runners.
106
107
  `browser: { args: ["--no-sandbox"] }` is an explicit option for controlled root
107
108
  containers; it is not a default. On Windows, command arrays should invoke JS tools
108
109
  through `process.execPath`, avoiding `.cmd` executables that require a shell.
@@ -117,3 +118,56 @@ Cleanup verifies the unique supervisor command line before stopping only its
117
118
  recorded process group (Windows: verified PID tree). It retains disposable files
118
119
  if ownership verification fails and reports the failure in evidence. Do not add
119
120
  process-name or port-based kills to a spec.
121
+
122
+ ## Accessibility checks
123
+
124
+ Call `await page.assertAccessible()` after the content is visible and again after
125
+ opening menus, dialogs or validation feedback. The harness reads its pinned local
126
+ axe-core distribution and injects it with CDP `Runtime.evaluate`; it never fetches
127
+ a CDN script or adds axe to the app bundle. A missing dependency, injection error,
128
+ unmatched selector or scan exception fails the step.
129
+
130
+ ```ts
131
+ await page.assertAccessible();
132
+ await page.assertAccessible({
133
+ selector: "main",
134
+ tags: ["wcag2a", "wcag2aa", "wcag21aa"],
135
+ allow: [{ id: "color-contrast", selector: ".legacy-caption" }],
136
+ timeoutMs: 30_000,
137
+ });
138
+ ```
139
+
140
+ Defaults run all enabled axe rules on the document. `tags` uses axe's `runOnly`
141
+ tag filter; `selector` limits the scanned region. Allowances match exact rule IDs
142
+ and, when supplied, an exact single-element target selector. Omitting `selector`
143
+ from an allowance permits that rule everywhere; keep exceptions narrow. Nested
144
+ frame/shadow targets cannot match a simple selector allowance. Allowed nodes stay
145
+ in evidence with `allowed: true`; other nodes of the same rule still fail.
146
+
147
+ Evidence's `accessibility` array contains URL, viewport, timestamp, violations
148
+ with rule IDs and target selectors, and incomplete checks for manual review.
149
+ Incomplete checks do not fail automatically. A passing automated scan does not
150
+ replace keyboard, screen-reader, focus-order or contrast review. See the
151
+ [axe API](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md) for scan
152
+ semantics. Run at both 390 and 1280 pixels with `assertViewport()` as well.
153
+
154
+ ## Watched development checks
155
+
156
+ Use an owned app copy with `app.env: { NODE_ENV: "development" }` and
157
+ `app.start: [process.execPath, "node_modules/@warlock.js/core/bin/warlock.js", "dev"]`.
158
+ Do not attach to an existing server. The fixture's `directory` is available in
159
+ `run`; use `node:fs/promises` to edit only files inside that copy.
160
+
161
+ Wait for a visible revision marker after each edit; a successful file write alone
162
+ does not prove watcher invalidation or browser hydration. Exercise several edits,
163
+ including setup-file creation, changes and removal. After each revision, request
164
+ the page again, exercise a hydrated control, check imported `shared` in loaders
165
+ and middleware, and verify concurrent requests keep distinct values. Do not
166
+ allowlist request-store or hydration errors to make these checks pass.
167
+
168
+ For forms beneath layouts, compare server-rendered IDs with the hydrated hook
169
+ values (for example expose the hook value in a click handler), check repeated
170
+ instances, label `for` and ARIA references, then navigate away and back. Comparing
171
+ DOM IDs alone can miss a mismatch: React may retain the server attribute while
172
+ client handlers capture another ID. Capture evidence and screenshots at every
173
+ revision. Do not replace React's `useId()` globally or hide hydration warnings.