hecks 3.9.1 → 3.11.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 (504) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
  3. data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
  4. data/exe/hecks +4 -4
  5. data/lib/hecks/adapters/driven/d1.rb +2 -10
  6. data/lib/hecks/adapters/driven/heki.adapter +1 -1
  7. data/lib/hecks/adapters/driven/heki.rb +2 -12
  8. data/lib/hecks/adapters/driven/lambda.adapter +1 -1
  9. data/lib/hecks/adapters/driven/lambda.rb +3 -18
  10. data/lib/hecks/adapters/driven/postgres.rb +4 -11
  11. data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
  12. data/lib/hecks/adapters/driven/sqlite.rb +2 -10
  13. data/lib/hecks/adapters/driving/admission.rb +46 -0
  14. data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
  15. data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
  16. data/lib/hecks/adapters/driving/cli.rb +127 -0
  17. data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
  18. data/lib/hecks/adapters/driving/cli_runner/resolution.rb +155 -0
  19. data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
  20. data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
  21. data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
  22. data/lib/hecks/adapters/driving/cli_runner.rb +69 -0
  23. data/lib/hecks/adapters/driving/command_request.rb +121 -0
  24. data/lib/hecks/adapters/driving/handle.rb +214 -0
  25. data/lib/hecks/adapters/driving/json.rb +136 -0
  26. data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
  27. data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
  28. data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
  29. data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
  30. data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
  31. data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
  32. data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
  33. data/lib/hecks/adapters/driving/mcp.rb +159 -0
  34. data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
  35. data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
  36. data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
  37. data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
  38. data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
  39. data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
  40. data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
  41. data/lib/hecks/adapters/driving/ruby.rb +83 -0
  42. data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
  43. data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
  44. data/lib/hecks/behaviors/expectations/boot.rb +1 -1
  45. data/lib/hecks/bench/ruby_runner.rb +1 -1
  46. data/lib/hecks/bluebook/ask_resolution.rb +130 -0
  47. data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
  48. data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
  49. data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
  50. data/lib/hecks/bluebook/assembly/marks.rb +1 -0
  51. data/lib/hecks/bluebook/attribute.rb +1 -1
  52. data/lib/hecks/bluebook/behaviour/command.rb +13 -8
  53. data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
  54. data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
  55. data/lib/hecks/bluebook/capabilities.rb +91 -9
  56. data/lib/hecks/bluebook/domain_port.rb +16 -4
  57. data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
  58. data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
  59. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
  60. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
  61. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
  62. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
  63. data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
  64. data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
  65. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
  66. data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
  67. data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
  68. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
  69. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
  70. data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
  71. data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
  72. data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
  73. data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
  74. data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
  75. data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
  76. data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
  77. data/lib/hecks/bluebook/expression/projection.json +36 -0
  78. data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
  79. data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
  80. data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
  81. data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
  82. data/lib/hecks/bluebook/expression/resolver.rb +20 -1
  83. data/lib/hecks/bluebook/hecksagon.rb +23 -3
  84. data/lib/hecks/bluebook/lifecycle.rb +22 -3
  85. data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
  86. data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
  87. data/lib/hecks/bluebook/meta_validator/readings.rb +18 -1
  88. data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
  89. data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
  90. data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
  91. data/lib/hecks/bluebook/meta_validator.rb +12 -12
  92. data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
  93. data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
  94. data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
  95. data/lib/hecks/bluebook/model_check.rb +8 -2
  96. data/lib/hecks/bluebook/policy.rb +4 -2
  97. data/lib/hecks/bluebook/smoke_test.rb +1 -1
  98. data/lib/hecks/bluebook/synthesizer.rb +2 -0
  99. data/lib/hecks/bluebook.rb +1 -0
  100. data/lib/hecks/cli/command_table.rb +1 -1
  101. data/lib/hecks/cli/console.rb +2 -1
  102. data/lib/hecks/cli/document.rb +1 -1
  103. data/lib/hecks/cli/launcher_words.rb +4 -4
  104. data/lib/hecks/cli/mcp.rb +4 -4
  105. data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
  106. data/lib/hecks/cli/project_cli/templates.rb +5 -5
  107. data/lib/hecks/cli/project_cli.rb +9 -6
  108. data/lib/hecks/cli/run.rb +2 -2
  109. data/lib/hecks/cli/smoke_http/checks.rb +4 -1
  110. data/lib/hecks/cli/smoke_http/transport.rb +3 -3
  111. data/lib/hecks/cli/smoke_http.rb +3 -0
  112. data/lib/hecks/codemod.rb +5 -2
  113. data/lib/hecks/corpus/routes.rb +7 -0
  114. data/lib/hecks/corpus/rust.rb +3 -2
  115. data/lib/hecks/deploy/bluebook/deploy.bluebook +124 -2
  116. data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
  117. data/lib/hecks/deploy/oidc.json +16 -3
  118. data/lib/hecks/doc/reference/rendering.rb +6 -3
  119. data/lib/hecks/forms/app/commands.rb +8 -7
  120. data/lib/hecks/forms/app/queries.rb +2 -1
  121. data/lib/hecks/forms/app.rb +1 -1
  122. data/lib/hecks/forms/record_renderer.rb +1 -1
  123. data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
  124. data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
  125. data/lib/hecks/fuzzing/failure_triage.rb +2 -1
  126. data/lib/hecks/fuzzing/mutation/run.rb +11 -4
  127. data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
  128. data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
  129. data/lib/hecks/fuzzing/properties/guards.rb +1 -1
  130. data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
  131. data/lib/hecks/fuzzing/self_consistency.rb +6 -2
  132. data/lib/hecks/fuzzing/shrinker.rb +3 -1
  133. data/lib/hecks/fuzzing/value_generator.rb +19 -5
  134. data/lib/hecks/gate/stages.yml +4 -10
  135. data/lib/hecks/grammar/evolve.rb +1 -1
  136. data/lib/hecks/grammar/expression_operators.json +372 -0
  137. data/lib/hecks/grammar/operators.rb +3 -1
  138. data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
  139. data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
  140. data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
  141. data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
  142. data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
  143. data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
  144. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
  145. data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
  146. data/lib/hecks/hecks/adapters/deploy_toolchain/clearing.rb +51 -0
  147. data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
  148. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -0
  149. data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
  150. data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
  151. data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
  152. data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
  153. data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
  154. data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
  155. data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
  156. data/lib/hecks/hecks/adapters/local_files.rb +2 -2
  157. data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
  158. data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
  159. data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
  160. data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
  161. data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
  162. data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
  163. data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
  164. data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
  165. data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
  166. data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
  167. data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
  168. data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
  169. data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
  170. data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
  171. data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
  172. data/lib/hecks/hecks/adapters/site_toolchain/role_prober.rb +78 -0
  173. data/lib/hecks/hecks/adapters/site_toolchain.rb +29 -4
  174. data/lib/hecks/hecks/adapters/terminal.rb +13 -11
  175. data/lib/hecks/hecks/adapters/world_clearing.rb +65 -0
  176. data/lib/hecks/hecks/codebase.behaviors +13 -1
  177. data/lib/hecks/hecks/codebase.bluebook +239 -5
  178. data/lib/hecks/hecks/custodian.behaviors +24 -6
  179. data/lib/hecks/hecks/custodian.bluebook +6 -1227
  180. data/lib/hecks/hecks/deploy.behaviors +1 -1
  181. data/lib/hecks/hecks/deploy.world +1 -3
  182. data/lib/hecks/hecks/hecks.behaviors +1 -1
  183. data/lib/hecks/hecks/hecks.hecksagon +61 -18
  184. data/lib/hecks/hecks/hecks.world +11 -10
  185. data/lib/hecks/hecks/rehearsal.bluebook +477 -0
  186. data/lib/hecks/hecks/site.behaviors +1 -1
  187. data/lib/hecks/hecks/site.world +0 -1
  188. data/lib/hecks/hecks/tooling.bluebook +1238 -0
  189. data/lib/hecks/indifferent_key.rb +20 -0
  190. data/lib/hecks/language/adapter.bluebook +13 -3
  191. data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
  192. data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
  193. data/lib/hecks/language/bluebook/entity.bluebook +18 -0
  194. data/lib/hecks/language/bluebook/policy.bluebook +9 -1
  195. data/lib/hecks/language/bluebook/query.bluebook +1 -1
  196. data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
  197. data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
  198. data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
  199. data/lib/hecks/language/oidc.json +10 -0
  200. data/lib/hecks/language/port.bluebook +4 -4
  201. data/lib/hecks/literal.rb +12 -3
  202. data/lib/hecks/ports/key_vault.rb +7 -2
  203. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
  204. data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
  205. data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
  206. data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
  207. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
  208. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
  209. data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +15 -0
  210. data/lib/hecks/projections/deploy/fargate/settings.rb +3 -1
  211. data/lib/hecks/projections/deploy/fargate/stack/readers.rb +1 -0
  212. data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
  213. data/lib/hecks/projections/deploy/shared.rb +5 -2
  214. data/lib/hecks/projections/deploy/templates/fargate/Makefile.tmpl +7 -4
  215. data/lib/hecks/projections/model/deviations.rb +2 -2
  216. data/lib/hecks/projections/model.rb +2 -2
  217. data/lib/hecks/projections/oidc.rb +13 -1
  218. data/lib/hecks/projections/shape.rb +5 -2
  219. data/lib/hecks/projections/site/cms_editor/attributes.rb +95 -0
  220. data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
  221. data/lib/hecks/projections/site/cms_editor/checks.rb +77 -0
  222. data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
  223. data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
  224. data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
  225. data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
  226. data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
  227. data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
  228. data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
  229. data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
  230. data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
  231. data/lib/hecks/projections/site/cms_editor/media.rb +85 -0
  232. data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
  233. data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
  234. data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
  235. data/lib/hecks/projections/site/cms_editor/pretty.rb +40 -0
  236. data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
  237. data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
  238. data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
  239. data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
  240. data/lib/hecks/projections/site/cms_editor/schema.rb +161 -0
  241. data/lib/hecks/projections/site/cms_editor/setting.rb +130 -0
  242. data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
  243. data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +36 -0
  244. data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +370 -0
  245. data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
  246. data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +87 -0
  247. data/lib/hecks/projections/site/cms_editor/templates/src/auth/session.ts.tmpl +36 -0
  248. data/lib/hecks/projections/site/cms_editor/templates/src/auth/sso.ts.tmpl +20 -0
  249. data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
  250. data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
  251. data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
  252. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
  253. data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
  254. data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
  255. data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
  256. data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
  257. data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
  258. data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
  259. data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
  260. data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
  261. data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
  262. data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
  263. data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
  264. data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
  265. data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +133 -0
  266. data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +29 -0
  267. data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
  268. data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +43 -0
  269. data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +177 -0
  270. data/lib/hecks/projections/site/cms_editor/templates/src/media/multipart.ts.tmpl +53 -0
  271. data/lib/hecks/projections/site/cms_editor/templates/src/media/sniff.ts.tmpl +94 -0
  272. data/lib/hecks/projections/site/cms_editor/templates/src/media/storage.ts.tmpl +50 -0
  273. data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
  274. data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +214 -0
  275. data/lib/hecks/projections/site/cms_editor/templates/src/server.ts.tmpl +65 -0
  276. data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
  277. data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
  278. data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
  279. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +123 -0
  280. data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_parse.js.tmpl +320 -0
  281. data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
  282. data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
  283. data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +275 -0
  284. data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +61 -0
  285. data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
  286. data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +92 -0
  287. data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
  288. data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
  289. data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +63 -0
  290. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +213 -0
  291. data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
  292. data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
  293. data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
  294. data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
  295. data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
  296. data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
  297. data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
  298. data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
  299. data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
  300. data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
  301. data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
  302. data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +17 -0
  303. data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
  304. data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
  305. data/lib/hecks/projections/site/cms_editor.rb +133 -0
  306. data/lib/hecks/projections/site/role_probe.rb +74 -0
  307. data/lib/hecks/projector/cli_audience.rb +2 -1
  308. data/lib/hecks/projector/cli_projector/listing.rb +1 -1
  309. data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
  310. data/lib/hecks/projector/docs_projector/closing.rb +1 -1
  311. data/lib/hecks/projector/docs_projector.rb +1 -1
  312. data/lib/hecks/projector/exporter/bindings.rb +40 -10
  313. data/lib/hecks/projector/exporter/capability_values.rb +55 -0
  314. data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
  315. data/lib/hecks/projector/target.rb +11 -0
  316. data/lib/hecks/projector.rb +32 -5
  317. data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
  318. data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
  319. data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
  320. data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
  321. data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
  322. data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
  323. data/lib/hecks/quality_control/quality_control.bluebook +6 -2
  324. data/lib/hecks/release/approval.rb +64 -0
  325. data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
  326. data/lib/hecks/router/namespace_installer.rb +16 -16
  327. data/lib/hecks/router.rb +1 -1
  328. data/lib/hecks/runtime/best_effort.rb +22 -0
  329. data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
  330. data/lib/hecks/runtime/command_interpreter.rb +3 -2
  331. data/lib/hecks/runtime/command_rules/emission.rb +1 -1
  332. data/lib/hecks/runtime/dispatcher.rb +4 -4
  333. data/lib/hecks/runtime/entity_element/location.rb +1 -1
  334. data/lib/hecks/runtime/event.rb +5 -0
  335. data/lib/hecks/runtime/identity.rb +7 -6
  336. data/lib/hecks/runtime/interpreting.rb +2 -1
  337. data/lib/hecks/runtime/invocation_builder.rb +1 -1
  338. data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
  339. data/lib/hecks/runtime/loader.rb +10 -10
  340. data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
  341. data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
  342. data/lib/hecks/runtime/policy_interpreter.rb +6 -6
  343. data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
  344. data/lib/hecks/runtime/reaction_invocation.rb +1 -1
  345. data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
  346. data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
  347. data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
  348. data/lib/hecks/runtime/registry/merging.rb +1 -1
  349. data/lib/hecks/runtime/registry/providers.rb +16 -0
  350. data/lib/hecks/runtime/registry/read_repository.rb +6 -4
  351. data/lib/hecks/runtime/registry/verification.rb +4 -1
  352. data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
  353. data/lib/hecks/runtime/registry.rb +1 -1
  354. data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
  355. data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
  356. data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
  357. data/lib/hecks/runtime/saga_interpreter.rb +3 -3
  358. data/lib/hecks/runtime/tenant_check.rb +5 -4
  359. data/lib/hecks/runtime/value/coercion.rb +2 -2
  360. data/lib/hecks/runtime/value/validation.rb +16 -4
  361. data/lib/hecks/runtime.rb +9 -9
  362. data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
  363. data/lib/hecks/rust_build/wasm.rb +1 -1
  364. data/lib/hecks/rust_build/wasm_browser.rb +1 -1
  365. data/lib/hecks/rust_build.rb +11 -0
  366. data/lib/hecks/served_options.rb +53 -0
  367. data/lib/hecks/site/bluebook/site.bluebook +29 -1
  368. data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
  369. data/lib/hecks/storehouse/audit.rb +1 -1
  370. data/lib/hecks/storehouse/dispatching.rb +8 -6
  371. data/lib/hecks/storehouse/introspection.rb +2 -2
  372. data/lib/hecks/storehouse/querying.rb +6 -4
  373. data/lib/hecks/storehouse.rb +4 -4
  374. data/lib/hecks/tools/comment_style/source_file.rb +4 -1
  375. data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
  376. data/lib/hecks/tools/lanes/rows.rb +6 -0
  377. data/lib/hecks/tools/oidc_manifests.rb +4 -5
  378. data/lib/hecks/tools/regeneration_run.rb +11 -4
  379. data/lib/hecks/tools/site_routes/cli.rb +1 -1
  380. data/lib/hecks/tools/site_routes/editor_files.rb +45 -0
  381. data/lib/hecks/tools/site_routes.rb +14 -7
  382. data/lib/hecks/tools/tenant_provisioning.rb +2 -2
  383. data/lib/hecks/tools/tools_doc.rb +4 -4
  384. data/lib/hecks/version.rb +1 -1
  385. data/lib/hecks/vocabulary.rb +4 -4
  386. data/lib/hecks.rb +15 -15
  387. data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
  388. data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
  389. data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
  390. data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
  391. data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
  392. data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
  393. data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
  394. data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
  395. data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
  396. data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
  397. data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
  398. data/rust/.gitignore +6 -0
  399. data/rust/Cargo.toml +9 -1
  400. data/rust/build/src/binding_pass.rs +103 -0
  401. data/rust/build/src/cargo_sync.rs +6 -0
  402. data/rust/build/src/lineage_pass.rs +3 -3
  403. data/rust/build/src/main.rs +17 -5
  404. data/rust/build/src/pipeline.rs +48 -5
  405. data/rust/build/src/seams_pass.rs +123 -0
  406. data/rust/build/src/translation_pass.rs +167 -0
  407. data/rust/codegen/src/asks.rs +103 -0
  408. data/rust/codegen/src/bridging.rs +1 -1
  409. data/rust/codegen/src/commands.rs +13 -8
  410. data/rust/codegen/src/constraints.rs +25 -0
  411. data/rust/codegen/src/domain_generator.rs +11 -9
  412. data/rust/codegen/src/expr_emitter.rs +13 -0
  413. data/rust/codegen/src/json_codec.rs +54 -15
  414. data/rust/codegen/src/main.rs +2 -1
  415. data/rust/codegen/src/mutations.rs +174 -4
  416. data/rust/codegen/src/naming.rs +30 -0
  417. data/rust/codegen/src/queries.rs +11 -2
  418. data/rust/codegen/src/reactions.rs +1 -1
  419. data/rust/codegen/src/types.rs +166 -8
  420. data/rust/host/HECKS_RELEASE +1 -1
  421. data/rust/host/src/api.rs +4 -4
  422. data/rust/host/src/bin/validate_harness.rs +43 -0
  423. data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
  424. data/rust/host/src/boundary_fuzz/ir.rs +1 -0
  425. data/rust/host/src/boundary_fuzz/server.rs +1 -1
  426. data/rust/host/src/checkout.rs +35 -19
  427. data/rust/host/src/commerce_ir.rs +408 -2
  428. data/rust/host/src/expr_json.rs +118 -0
  429. data/rust/host/src/mint.rs +1 -1
  430. data/rust/host/src/payments/tests.rs +27 -1
  431. data/rust/host/src/payments.rs +12 -4
  432. data/rust/host/src/query_step.rs +22 -4
  433. data/rust/host/src/reference_validate.rs +653 -108
  434. data/rust/host/src/resend.rs +2 -0
  435. data/rust/host/src/server.rs +19 -6
  436. data/rust/host/src/ui_schema.rs +132 -5
  437. data/rust/host/src/web/newsletter.rs +7 -8
  438. data/rust/host/src/web/newsletter_send.rs +38 -13
  439. data/rust/host/src/web/registrations.rs +229 -26
  440. data/rust/host/src/web.rs +3 -3
  441. data/rust/parser/src/emit.rs +38 -6
  442. data/rust/parser/src/expr/ast_json.rs +7 -1
  443. data/rust/parser/src/expr/resolver.rs +171 -6
  444. data/rust/parser/src/ir.rs +7 -0
  445. data/rust/parser/src/keywords.rs +24 -0
  446. data/rust/parser/src/main.rs +3 -0
  447. data/rust/parser/src/parse/aggregate.rs +2 -1
  448. data/rust/parser/src/parse/chapter.rs +2 -2
  449. data/rust/parser/src/parse/command.rs +2 -0
  450. data/rust/parser/src/parse/hecksagon.rs +5 -2
  451. data/rust/parser/src/parse/lifecycle.rs +81 -0
  452. data/rust/parser/src/parse/mod.rs +15 -1
  453. data/rust/parser/src/parse/policy.rs +13 -2
  454. data/rust/parser/src/parse/value_object.rs +13 -8
  455. data/rust/parser/src/ruby_value.rs +1 -1
  456. data/rust/src/exemplar/commands.rs +4 -4
  457. data/rust/src/exemplar/constraints.rs +1 -1
  458. data/rust/src/exemplar/json.rs +43 -1
  459. data/rust/src/kernel/cli.rs +7 -5
  460. data/rust/src/kernel/dispatch.rs +2 -2
  461. data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
  462. data/rust/src/kernel/expr.rs +73 -4
  463. data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
  464. data/rust/src/kernel/expression_operators/presence.rs +41 -3
  465. data/rust/src/kernel/expression_operators/text.rs +26 -2
  466. data/rust/src/kernel/json.rs +15 -0
  467. data/rust/src/kernel/mod.rs +1 -1
  468. data/rust/src/kernel/orchestrate.rs +54 -5
  469. data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
  470. data/rust/src/kernel/pattern.rs +60 -0
  471. data/rust/src/kernel/reference_lookup.rs +90 -7
  472. metadata +174 -36
  473. data/lib/hecks/doors/cli_door/nesting.rb +0 -34
  474. data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
  475. data/lib/hecks/doors/cli_door.rb +0 -124
  476. data/lib/hecks/doors/cli_runner/answers.rb +0 -138
  477. data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
  478. data/lib/hecks/doors/cli_runner/settling.rb +0 -115
  479. data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
  480. data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
  481. data/lib/hecks/doors/cli_runner.rb +0 -66
  482. data/lib/hecks/doors/command_request.rb +0 -119
  483. data/lib/hecks/doors/handle.rb +0 -208
  484. data/lib/hecks/doors/json_door.rb +0 -132
  485. data/lib/hecks/doors/launcher_options.rb +0 -247
  486. data/lib/hecks/doors/mcp_door/properties.rb +0 -47
  487. data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
  488. data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
  489. data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
  490. data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
  491. data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
  492. data/lib/hecks/doors/mcp_door.rb +0 -156
  493. data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
  494. data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
  495. data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
  496. data/lib/hecks/doors/mcp_door_scope.rb +0 -201
  497. data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
  498. data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
  499. data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
  500. data/lib/hecks/doors/ruby_door.rb +0 -80
  501. data/lib/hecks/doors/usage_cache.rb +0 -139
  502. data/prebuilt/0ee7f83cba59ce7d1c45c65ba166b071779f70143449f584f33ff7989f64eccb.marshal +0 -0
  503. data/prebuilt/6e4a6bcf0aa880e2e6b056e1846b70e5a768090b9dc998bbadeaf5d6bcfd3db8.marshal +0 -0
  504. data/prebuilt/verdicts-91aaf93ce8981bd9a754948f075d95650dc2ec6435015376f5c60992b36a24b1.json +0 -1
@@ -0,0 +1,320 @@
1
+ __BANNER__
2
+
3
+ // HTML read back into the domain's body tree, with no DOM: a small tokenizer and tree, so the same
4
+ // function runs in node (tests, the server) and in the browser (the widget, a paste). What the body
5
+ // cannot hold is reduced to the nearest block or mark it can, and every reduction adds a note, so a
6
+ // paste never loses anything without saying so. Output is deterministic: the same HTML gives the
7
+ // same tree, with adjacent spans of the same marks merged and empty ones dropped.
8
+ import { ALIGNS, MARKS, MAX_DEPTH, MAX_INDENT, safeHref } from "./body_model.js";
9
+
10
+ const VOID = new Set(["br", "hr", "img", "input", "meta", "link", "wbr", "col", "area", "base", "source"]);
11
+ const SKIPPED = new Set(["script", "style", "head", "title", "template", "noscript", "iframe", "object", "embed", "svg", "math"]);
12
+ const TRANSPARENT = new Set(["html", "body", "span", "font", "main", "article", "section", "header", "footer", "nav", "aside", "tbody", "thead", "tfoot", "center", "label", "abbr", "cite", "q", "small", "big", "time", "wbr"]);
13
+ const MARK_TAGS = { strong: "bold", b: "bold", em: "italic", i: "italic", u: "underline", ins: "underline", s: "strike", strike: "strike", del: "strike", code: "code", tt: "code", kbd: "code", samp: "code" };
14
+ const BLOCKISH = new Set(["p", "div", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "li", "blockquote", "hr", "figure", "pre", "table", "tr", "td", "th", "dl", "dt", "dd", "details", "summary", "form", "fieldset"]);
15
+ const ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: " ", hellip: "…", mdash: "—", ndash: "–", lsquo: "‘", rsquo: "’", ldquo: "“", rdquo: "”", copy: "©", middot: "·", bull: "•" };
16
+ const LIMIT = 100;
17
+
18
+ function decode(text) {
19
+ return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (whole, name) => {
20
+ if (name[0] !== "#") return ENTITIES[name.toLowerCase()] ?? whole;
21
+ const code = name[1].toLowerCase() === "x" ? parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10);
22
+ if (!(code > 0 && code <= 0x10ffff) || (code >= 0xd800 && code < 0xe000)) return "�";
23
+ return code === 160 ? " " : String.fromCodePoint(code);
24
+ });
25
+ }
26
+
27
+ function attributesOf(source) {
28
+ const found = {};
29
+ for (const m of source.matchAll(/([^\s"'<>\/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g)) {
30
+ const name = m[1].toLowerCase();
31
+ if (!(name in found)) found[name] = decode(m[2] ?? m[3] ?? m[4] ?? "");
32
+ }
33
+ return found;
34
+ }
35
+
36
+ /** The tokens of `html`: text, open tags (with attributes) and close tags. Comments are dropped. */
37
+ function tokenize(html) {
38
+ const tokens = [];
39
+ const pattern = /<!--[\s\S]*?(?:-->|$)|<![^>]*>|<\?[^>]*>|<\/([a-z][a-z0-9-]*)[^>]*>|<([a-z][a-z0-9-]*)((?:"[^"]*"|'[^']*'|[^>"'])*)>/gi;
40
+ let at = 0;
41
+ let m;
42
+ while ((m = pattern.exec(html)) !== null) {
43
+ if (m.index > at) tokens.push({ text: html.slice(at, m.index) });
44
+ at = m.index + m[0].length;
45
+ if (m[1]) tokens.push({ close: m[1].toLowerCase() });
46
+ else if (m[2]) tokens.push({ open: m[2].toLowerCase(), attrs: attributesOf(m[3]) });
47
+ if (m[2] && /^(script|style|textarea)$/i.test(m[2])) {
48
+ at = skipRaw(html, at, m[2].toLowerCase(), tokens);
49
+ pattern.lastIndex = at;
50
+ }
51
+ }
52
+ if (at < html.length) tokens.push({ text: html.slice(at) });
53
+ return tokens;
54
+ }
55
+
56
+ function skipRaw(html, from, tag, tokens) {
57
+ const end = new RegExp(`</${tag}[^>]*>`, "i").exec(html.slice(from));
58
+ tokens.push({ close: tag });
59
+ return end ? from + end.index + end[0].length : html.length;
60
+ }
61
+
62
+ const closesLi = (stack) => {
63
+ for (let i = stack.length - 1; i >= 0; i -= 1) {
64
+ if (stack[i].tag === "li") return i;
65
+ if (stack[i].tag === "ul" || stack[i].tag === "ol") return -1;
66
+ }
67
+ return -1;
68
+ };
69
+
70
+ /** The nodes of `html` as a tree of `{ tag, attrs, kids }` and `{ text }`. */
71
+ function parseTree(html) {
72
+ const root = { tag: "#root", attrs: {}, kids: [] };
73
+ const stack = [root];
74
+ const ignored = {};
75
+ const top = () => stack[stack.length - 1];
76
+ for (const token of tokenize(html)) {
77
+ if ("text" in token) top().kids.push({ text: token.text });
78
+ else if (token.open) open(token, stack, ignored, top);
79
+ else close(token.close, stack, ignored);
80
+ }
81
+ return root.kids;
82
+ }
83
+
84
+ function open(token, stack, ignored, top) {
85
+ const tag = token.open;
86
+ if (tag === "li" && closesLi(stack) > 0) stack.length = closesLi(stack);
87
+ if (BLOCKISH.has(tag) && top().tag === "p") stack.pop();
88
+ const node = { tag, attrs: token.attrs, kids: [] };
89
+ if (stack.length > LIMIT) {
90
+ if (!VOID.has(tag)) ignored[tag] = (ignored[tag] ?? 0) + 1;
91
+ return;
92
+ }
93
+ top().kids.push(node);
94
+ if (!VOID.has(tag)) stack.push(node);
95
+ }
96
+
97
+ function close(tag, stack, ignored) {
98
+ if (ignored[tag] > 0) {
99
+ ignored[tag] -= 1;
100
+ return;
101
+ }
102
+ for (let i = stack.length - 1; i > 0; i -= 1) {
103
+ if (stack[i].tag === tag) {
104
+ stack.length = i;
105
+ return;
106
+ }
107
+ }
108
+ }
109
+
110
+ function styleOf(attrs) {
111
+ const style = {};
112
+ for (const part of String(attrs.style ?? "").split(";")) {
113
+ const [name, ...value] = part.split(":");
114
+ if (name && value.length) style[name.trim().toLowerCase()] = value.join(":").trim().toLowerCase();
115
+ }
116
+ return style;
117
+ }
118
+
119
+ const STYLE_MARKS = (style) => {
120
+ const found = [];
121
+ const weight = style["font-weight"];
122
+ if (weight === "bold" || weight === "bolder" || Number(weight) >= 600) found.push("bold");
123
+ if (style["font-style"] === "italic") found.push("italic");
124
+ const line = `${style["text-decoration"] ?? ""} ${style["text-decoration-line"] ?? ""}`;
125
+ if (line.includes("underline")) found.push("underline");
126
+ if (line.includes("line-through")) found.push("strike");
127
+ return found;
128
+ };
129
+
130
+ const STYLE_QUIET = new Set(["font-weight", "font-style", "text-decoration", "text-decoration-line", "text-align", "white-space", "margin-left", "padding-left"]);
131
+
132
+ function noteStyle(style, ctx) {
133
+ if (Object.keys(style).some((name) => !STYLE_QUIET.has(name))) ctx.note("inline styles other than bold, italic, underline, strike-through and alignment were dropped");
134
+ }
135
+
136
+ function alignOf(attrs, style) {
137
+ const value = String(attrs["data-align"] ?? attrs.align ?? style["text-align"] ?? "").toLowerCase();
138
+ return ALIGNS.includes(value) ? value : undefined;
139
+ }
140
+
141
+ function indentOf(attrs) {
142
+ const value = Number(attrs["data-indent"]);
143
+ return attrs["data-indent"] !== undefined && Number.isInteger(value) ? Math.min(MAX_INDENT, Math.max(0, value)) : undefined;
144
+ }
145
+
146
+ function layoutOf(attrs) {
147
+ const style = styleOf(attrs);
148
+ const layout = {};
149
+ const align = alignOf(attrs, style);
150
+ const indent = indentOf(attrs);
151
+ if (align !== undefined) layout.align = align;
152
+ if (indent !== undefined) layout.indent = indent;
153
+ return layout;
154
+ }
155
+
156
+ const textOf = (nodes) => nodes.map((node) => ("text" in node ? decode(node.text) : textOf(node.kids))).join("");
157
+
158
+ /** Spans from inline nodes; `state` carries the marks and the address in force. */
159
+ function inline(nodes, state, ctx, out) {
160
+ for (const node of nodes) {
161
+ if ("text" in node) pushText(decode(node.text), state, out);
162
+ else inlineElement(node, state, ctx, out);
163
+ }
164
+ }
165
+
166
+ function pushText(raw, state, out) {
167
+ const text = raw.replace(/[ \t]*[\r\n\t][ \t\r\n]*/g, " ");
168
+ if (text !== "") out.push({ text, marks: state.marks.map((name) => ({ name })), ...(state.href ? { href: state.href } : {}) });
169
+ }
170
+
171
+ function inlineElement(node, state, ctx, out) {
172
+ const tag = node.tag;
173
+ if (SKIPPED.has(tag)) return ctx.note(`<${tag}> content was removed`);
174
+ if (tag === "br") return out.push({ text: "\n", marks: state.marks.map((name) => ({ name })), ...(state.href ? { href: state.href } : {}) });
175
+ if (tag === "img") return ctx.note("an image with no media key was reduced to its alt text"), pushText(node.attrs.alt ?? "", state, out);
176
+ const style = styleOf(node.attrs);
177
+ noteStyle(style, ctx);
178
+ const added = [MARK_TAGS[tag], ...STYLE_MARKS(style)].filter(Boolean);
179
+ const href = tag === "a" ? linkOf(node.attrs.href, ctx) : undefined;
180
+ const next = { marks: [...state.marks, ...added.filter((name) => !state.marks.includes(name))], href: href ?? state.href };
181
+ if (!MARK_TAGS[tag] && tag !== "a" && !TRANSPARENT.has(tag)) ctx.note(`<${tag}> formatting was reduced to plain text`);
182
+ inline(node.kids, next, ctx, out);
183
+ }
184
+
185
+ function linkOf(value, ctx) {
186
+ if (value === undefined) return undefined;
187
+ const href = safeHref(value);
188
+ if (!href) ctx.note("a link to an address that is not a path, http(s), mailto or tel was reduced to text");
189
+ return href ?? undefined;
190
+ }
191
+
192
+ /** Adjacent spans with the same marks and address become one; empty text is dropped. */
193
+ function merged(spans) {
194
+ const out = [];
195
+ for (const span of spans) {
196
+ const last = out[out.length - 1];
197
+ const same = last && last.text !== "\n" && span.text !== "\n" && last.href === span.href && JSON.stringify(last.marks) === JSON.stringify(span.marks);
198
+ if (same) last.text += span.text;
199
+ else if (span.text !== "") out.push({ ...span, marks: span.marks.map((mark) => ({ ...mark })) });
200
+ }
201
+ return out;
202
+ }
203
+
204
+ function spansOf(nodes, ctx, state = { marks: [], href: undefined }) {
205
+ const out = [];
206
+ inline(nodes, state, ctx, out);
207
+ const only = out.length === 1 && out[0].text === "\n";
208
+ return only ? [] : merged(out);
209
+ }
210
+
211
+ function stateOf(node, ctx) {
212
+ const style = styleOf(node.attrs);
213
+ noteStyle(style, ctx);
214
+ return { marks: STYLE_MARKS(style), href: undefined };
215
+ }
216
+
217
+ const block = (kind, layout, spans, level) => ({ kind, ...(level ? { level } : {}), ...layout, spans });
218
+
219
+ function headingLevel(tag, ctx) {
220
+ const level = Number(tag[1]);
221
+ if (level > 4) ctx.note(`<${tag}> was reduced to a level 4 heading`);
222
+ return Math.min(4, level);
223
+ }
224
+
225
+ function figure(node, ctx) {
226
+ const attrs = node.attrs;
227
+ const kids = node.kids.filter((kid) => kid.tag === "figcaption");
228
+ const caption = attrs["data-caption"] ?? (kids.length ? textOf(kids[0].kids).trim() : "");
229
+ const made = { kind: "image" };
230
+ for (const [key, source] of [["media_ref", "data-media-ref"], ["alt", "data-alt"]]) if (attrs[source]) made[key] = attrs[source];
231
+ if (caption) made.caption = caption;
232
+ if (made.media_ref) return [made];
233
+ ctx.note("an image with no media key was reduced to its caption");
234
+ return caption ? [block("paragraph", {}, spansOf([{ text: caption }], ctx))] : [];
235
+ }
236
+
237
+ function listBlock(node, ctx) {
238
+ const kind = node.tag === "ol" ? "numbered_list" : "bullet_list";
239
+ const items = [];
240
+ collectItems(node, kind, 0, items, ctx);
241
+ return [{ kind, ...layoutOf(node.attrs), items }];
242
+ }
243
+
244
+ function collectItems(list, blockKind, depth, items, ctx) {
245
+ const own = list.tag === "ol" ? "numbered_list" : "bullet_list";
246
+ for (const child of list.kids) {
247
+ if (child.tag === "li") itemOf(child, own, blockKind, depth, items, ctx);
248
+ else if (child.tag === "ul" || child.tag === "ol") collectItems(child, blockKind, Math.min(depth + 1, MAX_DEPTH), items, ctx);
249
+ else if (child.tag && !("text" in child)) collectItems({ ...child, tag: own }, blockKind, depth, items, ctx);
250
+ }
251
+ }
252
+
253
+ function itemOf(li, own, blockKind, depth, items, ctx) {
254
+ const nested = li.kids.filter((kid) => kid.tag === "ul" || kid.tag === "ol");
255
+ const here = li.kids.filter((kid) => !nested.includes(kid));
256
+ const marked = Number(li.attrs["data-depth"]);
257
+ const level = li.attrs["data-depth"] !== undefined && Number.isInteger(marked) ? Math.min(MAX_DEPTH, Math.max(0, marked)) : depth;
258
+ const given = li.attrs["data-list-kind"];
259
+ const kind = given === "bullet_list" || given === "numbered_list" ? given : own !== blockKind ? own : undefined;
260
+ if (depth > MAX_DEPTH) ctx.note(`a list nested deeper than ${MAX_DEPTH} levels was kept at depth ${MAX_DEPTH}`);
261
+ items.push({ spans: spansOf(here, ctx), depth: level, ...(kind ? { list_kind: kind } : {}) });
262
+ for (const list of nested) collectItems(list, blockKind, Math.min(level + 1, MAX_DEPTH), items, ctx);
263
+ }
264
+
265
+ /** Blocks from block-level nodes; loose inline content between blocks becomes paragraphs. */
266
+ function blocks(nodes, ctx, quote = false) {
267
+ const out = [];
268
+ let run = [];
269
+ const flush = () => {
270
+ const spans = spansOf(run, ctx);
271
+ const blank = spans.every((span) => span.text.trim() === "" && span.text !== "\n");
272
+ if (!blank) out.push(block(quote ? "quote" : "paragraph", {}, spans));
273
+ run = [];
274
+ };
275
+ for (const node of nodes) {
276
+ if ("text" in node || !isBlock(node)) run.push(node);
277
+ else {
278
+ flush();
279
+ out.push(...blockOf(node, ctx, quote));
280
+ }
281
+ }
282
+ flush();
283
+ return out;
284
+ }
285
+
286
+ const isBlock = (node) => BLOCKISH.has(node.tag) || node.tag === "figcaption" ;
287
+
288
+ function blockOf(node, ctx, quote) {
289
+ const tag = node.tag;
290
+ if (tag === "hr") return [{ kind: "divider" }];
291
+ if (tag === "figure") return figure(node, ctx);
292
+ if (tag === "ul" || tag === "ol") return listBlock(node, ctx);
293
+ if (tag === "blockquote") return blocks(node.kids, ctx, true);
294
+ if (/^h[1-6]$/.test(tag)) return [block("heading", layoutOf(node.attrs), spansOf(node.kids, ctx, stateOf(node, ctx)), headingLevel(tag, ctx))];
295
+ if (tag === "pre") return preformatted(node, ctx);
296
+ if (tag === "p" || tag === "li" || tag === "dt" || tag === "dd") return [block(quote ? "quote" : "paragraph", layoutOf(node.attrs), spansOf(node.kids, ctx, stateOf(node, ctx)))];
297
+ if (tag === "div" && !node.kids.some((kid) => kid.tag && isBlock(kid))) return [block(quote ? "quote" : "paragraph", layoutOf(node.attrs), spansOf(node.kids, ctx, stateOf(node, ctx)))];
298
+ if (tag === "table" || tag === "tr" || tag === "td" || tag === "th") ctx.note("a table was reduced to paragraphs");
299
+ return blocks(node.kids, ctx, quote);
300
+ }
301
+
302
+ function preformatted(node, ctx) {
303
+ ctx.note("preformatted text was reduced to a paragraph in code");
304
+ const lines = textOf(node.kids).replace(/\r/g, "").split("\n");
305
+ const spans = lines.flatMap((line, at) => [...(at > 0 ? [{ text: "\n", marks: [] }] : []), ...(line ? [{ text: line, marks: [{ name: "code" }] }] : [])]);
306
+ return [block("paragraph", {}, spans)];
307
+ }
308
+
309
+ /** The body for `html`, with the notes naming whatever was reduced to fit. */
310
+ export function htmlToBodyWithNotes(html) {
311
+ const notes = [];
312
+ const ctx = { note: (message) => { if (!notes.includes(message)) notes.push(message); } };
313
+ const tree = parseTree(String(html ?? ""));
314
+ return { body: { blocks: blocks(tree, ctx) }, notes };
315
+ }
316
+
317
+ /** The body for `html`; anything the body cannot hold is reduced to the nearest block or mark. */
318
+ export function htmlToBody(html) {
319
+ return htmlToBodyWithNotes(html).body;
320
+ }
@@ -0,0 +1,88 @@
1
+ __BANNER__
2
+
3
+ // One instance: its values as a single document column, and beside it a margin panel with its
4
+ // lifecycle state and the commands that apply now. Values are shown by shape (a value object as its
5
+ // parts, a list of value objects as a small table, a rich-text body as typeset text). A command that
6
+ // asks for nothing and is safe runs from a button; one that asks for input opens its own page; one
7
+ // that is destructive asks first, in a native dialog (the page of the command is the same question
8
+ // for a browser with no script).
9
+ import { at } from "../names.ts";
10
+ import type { Aggregate, Command } from "../schema.ts";
11
+ import { available, needsPage } from "./availability.ts";
12
+ import type { Problem } from "./blocks.ts";
13
+ import { confirmDialog, dialogId } from "./dialogs.ts";
14
+ import { copyButton, esc, href, refusal, statusBadge } from "./html.ts";
15
+ import { relatedPanels } from "./related.ts";
16
+ import { schedulePanel } from "./schedule.ts";
17
+ import type { Tools } from "./preview.ts";
18
+ import { valueRows } from "./values.ts";
19
+ import type { World } from "./world.ts";
20
+ import { commandLabel, label, singular } from "./words.ts";
21
+
22
+ export { available, needsPage };
23
+
24
+ type State = Record<string, unknown>;
25
+
26
+ /** What a submitted command left behind: the refusal to show, and the values to put back. */
27
+ export interface Submitted {
28
+ command: string;
29
+ message: string | null;
30
+ values: State;
31
+ /** The attribute the refusal names, when it names one. */
32
+ field?: string;
33
+ /** The blocks of a list of mixed blocks the refusal is about (see blocks.ts). */
34
+ problems?: Problem[];
35
+ }
36
+
37
+ function action(agg: Aggregate, id: string, command: Command, first: boolean): string {
38
+ const to = href(...at(agg), "id", id, command.name);
39
+ const text = esc(commandLabel(agg, command));
40
+ const tone = command.destructive ? "btn btn-error btn-outline lg:w-full" : first ? "btn btn-neutral lg:w-full" : "btn lg:w-full";
41
+ if (!needsPage(command)) return `<form method="post" action="${esc(to)}"><button type="submit" class="${tone}">${text}</button></form>`;
42
+ const confirm = command.destructive && command.attributes.length === 0 ? ` data-confirm="${dialogId(command)}"` : "";
43
+ return `<a class="${tone}" href="${esc(to)}"${confirm}>${text}</a>`;
44
+ }
45
+
46
+ function dialogs(agg: Aggregate, id: string, commands: Command[]): string {
47
+ return commands
48
+ .filter((command) => command.destructive && command.attributes.length === 0)
49
+ .map((command) => confirmDialog(agg, command, href(...at(agg), "id", id, command.name), id))
50
+ .join("\n");
51
+ }
52
+
53
+ function panel(agg: Aggregate, id: string, values: State, commands: Command[]): string {
54
+ const status = agg.lifecycle ? `<div class="lg:pb-5"><h2 class="mb-2 text-sm">Status</h2><p>${statusBadge(agg, values[agg.lifecycle.field])}</p></div>` : "";
55
+ const rule = agg.lifecycle ? "lg:border-t lg:border-base-300 lg:pt-5" : "";
56
+ const buttons = commands.map((command, at) => action(agg, id, command, at === 0));
57
+ const list = commands.length === 0 ? `<p class="text-muted">Nothing to do with this ${esc(singular(agg.name))} right now.</p>` : `<div class="flex flex-wrap gap-2 lg:grid">${buttons.join("\n")}</div>`;
58
+ return `<aside class="order-first flex flex-wrap items-start gap-x-8 gap-y-4 border-b border-base-300 pb-5 lg:sticky lg:top-20 lg:order-none lg:block lg:border-b-0 lg:pb-0" aria-label="Status and actions">${status}<div class="${rule}"><h2 class="mb-2 text-sm">Actions</h2>${list}</div></aside>`;
59
+ }
60
+
61
+ /** The badge that says an edit is saved and not put live yet, when the aggregate keeps one beside its live content. */
62
+ function unpublished(agg: Aggregate, values: State): string {
63
+ const held = agg.drafts ? values[agg.drafts.attribute] : undefined;
64
+ const saved = held !== undefined && held !== null && !(Array.isArray(held) && held.length === 0);
65
+ return saved ? `<p class="mt-2"><span class="badge badge-soft badge-info">Changes not published yet</span></p>` : "";
66
+ }
67
+
68
+ /** One instance: its values, its status, a button for each command that applies to it now, and the records and actions that go with it. */
69
+ export function detailPage(agg: Aggregate, id: string, values: State, shown: Submitted | null, world: World, preview: Tools): string {
70
+ const commands = agg.commands.filter((command) => command.on === agg.name && available(agg, command, values));
71
+ const here = href(...at(agg), "id", id);
72
+ const panels = [relatedPanels(agg, id, world, here), schedulePanel(agg, id, world, here)].filter((html) => html !== "");
73
+ return `<div class="mb-6 flex flex-wrap items-start justify-between gap-3">
74
+ <div><h1 class="flex items-center gap-2 break-all text-3xl">${esc(id)}${copyButton(id, "id")}</h1><p class="mt-1 text-muted">${esc(label(agg.name))}</p>${unpublished(agg, values)}</div>
75
+ ${preview.button}
76
+ </div>
77
+ ${refusal(shown?.message ?? null)}
78
+ <div class="grid items-start gap-6 lg:grid-cols-[minmax(0,1fr)_16rem]">
79
+ <div class="grid min-w-0 gap-6">
80
+ <section class="min-w-0 border border-base-300 bg-base-100 px-4 sm:px-6" aria-label="${esc(label(agg.name))}">
81
+ ${valueRows(agg, agg.attributes, values)}
82
+ </section>
83
+ ${panels.join("\n")}
84
+ </div>
85
+ ${panel(agg, id, values, commands)}
86
+ </div>
87
+ ${dialogs(agg, id, commands)}`;
88
+ }
@@ -0,0 +1,43 @@
1
+ __BANNER__
2
+
3
+ // The question a command asks before it runs, as a native `<dialog>`: a destructive command (cancel,
4
+ // discard, withdraw) and a command that publishes. The button that asks carries `data-confirm` with
5
+ // the dialog's id; the page's script opens it, and a browser with no script follows the button's link
6
+ // to a page that asks the same question.
7
+ import type { Aggregate, Command } from "../schema.ts";
8
+ import { esc } from "./html.ts";
9
+ import { backInput } from "./links.ts";
10
+ import type { Back } from "./links.ts";
11
+ import { commandLabel } from "./words.ts";
12
+
13
+ /** What makes one dialog differ from another on a page that holds several. */
14
+ export interface Asking {
15
+ /** Told apart when one command is asked about on several rows. */
16
+ suffix?: string;
17
+ /** Where a person is sent once the command has run. */
18
+ back?: Back | null;
19
+ /** The button's colour: `error` for something that cannot be undone, `neutral` for the rest. */
20
+ tone?: "error" | "neutral";
21
+ /** The words for the button, when they are not the command's own. */
22
+ button?: string;
23
+ /** Whether a draft still being written is saved first (see browser/autosave.js). */
24
+ flush?: boolean;
25
+ }
26
+
27
+ /** The id of a command's dialog, with the suffix that tells rows apart. */
28
+ export const dialogId = (command: Command, suffix = ""): string => `confirm-${command.name}${suffix ? `-${suffix}` : ""}`;
29
+
30
+ /** The question a command asks, as a native dialog that posts to `action` when confirmed. */
31
+ export function confirmDialog(agg: Aggregate, command: Command, action: string, id: string, form?: string, asking: Asking = {}): string {
32
+ const dialog = dialogId(command, asking.suffix);
33
+ const what = esc(asking.button ?? commandLabel(agg, command));
34
+ const detail = command.goal ? `${esc(command.goal)}.` : `This applies to ${esc(id)}.`;
35
+ const owner = form ? ` form="${esc(form)}"` : "";
36
+ const tone = asking.tone === "neutral" ? "btn btn-neutral" : "btn btn-error";
37
+ const head = `<h2 id="${dialog}-title" class="text-xl">${what}?</h2>\n<p class="mt-2 text-muted">${detail}</p>`;
38
+ const dismiss = what === "Cancel" ? "Go back" : "Cancel";
39
+ const buttons = `<div class="modal-action"><button type="submit" class="btn btn-ghost" formmethod="dialog" formnovalidate>${dismiss}</button><button type="submit" class="${tone}"${owner}>${what}</button></div>`;
40
+ const box = form ? `${head}\n${buttons}` : `<form method="post" action="${esc(action)}"${asking.flush ? " data-flush-draft" : ""}>\n${backInput(asking.back ?? null)}${head}\n${buttons}\n</form>`;
41
+ const close = `<form method="dialog" class="modal-backdrop"><button aria-label="Close">close</button></form>`;
42
+ return `<dialog class="modal" id="${dialog}" aria-labelledby="${dialog}-title">\n<div class="modal-box">\n${box}\n</div>\n${close}\n</dialog>`;
43
+ }