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.
- checksums.yaml +4 -4
- data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
- data/exe/hecks +4 -4
- data/lib/hecks/adapters/driven/d1.rb +2 -10
- data/lib/hecks/adapters/driven/heki.adapter +1 -1
- data/lib/hecks/adapters/driven/heki.rb +2 -12
- data/lib/hecks/adapters/driven/lambda.adapter +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +3 -18
- data/lib/hecks/adapters/driven/postgres.rb +4 -11
- data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
- data/lib/hecks/adapters/driven/sqlite.rb +2 -10
- data/lib/hecks/adapters/driving/admission.rb +46 -0
- data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
- data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
- data/lib/hecks/adapters/driving/cli.rb +127 -0
- data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +155 -0
- data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
- data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
- data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +69 -0
- data/lib/hecks/adapters/driving/command_request.rb +121 -0
- data/lib/hecks/adapters/driving/handle.rb +214 -0
- data/lib/hecks/adapters/driving/json.rb +136 -0
- data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
- data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
- data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
- data/lib/hecks/adapters/driving/mcp.rb +159 -0
- data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
- data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
- data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
- data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
- data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
- data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
- data/lib/hecks/adapters/driving/ruby.rb +83 -0
- data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
- data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
- data/lib/hecks/behaviors/expectations/boot.rb +1 -1
- data/lib/hecks/bench/ruby_runner.rb +1 -1
- data/lib/hecks/bluebook/ask_resolution.rb +130 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
- data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
- data/lib/hecks/bluebook/assembly/marks.rb +1 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/command.rb +13 -8
- data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
- data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
- data/lib/hecks/bluebook/capabilities.rb +91 -9
- data/lib/hecks/bluebook/domain_port.rb +16 -4
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
- data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
- data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
- data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
- data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
- data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
- data/lib/hecks/bluebook/expression/projection.json +36 -0
- data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
- data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
- data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
- data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
- data/lib/hecks/bluebook/expression/resolver.rb +20 -1
- data/lib/hecks/bluebook/hecksagon.rb +23 -3
- data/lib/hecks/bluebook/lifecycle.rb +22 -3
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
- data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/readings.rb +18 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
- data/lib/hecks/bluebook/meta_validator.rb +12 -12
- data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
- data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
- data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
- data/lib/hecks/bluebook/model_check.rb +8 -2
- data/lib/hecks/bluebook/policy.rb +4 -2
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/bluebook/synthesizer.rb +2 -0
- data/lib/hecks/bluebook.rb +1 -0
- data/lib/hecks/cli/command_table.rb +1 -1
- data/lib/hecks/cli/console.rb +2 -1
- data/lib/hecks/cli/document.rb +1 -1
- data/lib/hecks/cli/launcher_words.rb +4 -4
- data/lib/hecks/cli/mcp.rb +4 -4
- data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
- data/lib/hecks/cli/project_cli/templates.rb +5 -5
- data/lib/hecks/cli/project_cli.rb +9 -6
- data/lib/hecks/cli/run.rb +2 -2
- data/lib/hecks/cli/smoke_http/checks.rb +4 -1
- data/lib/hecks/cli/smoke_http/transport.rb +3 -3
- data/lib/hecks/cli/smoke_http.rb +3 -0
- data/lib/hecks/codemod.rb +5 -2
- data/lib/hecks/corpus/routes.rb +7 -0
- data/lib/hecks/corpus/rust.rb +3 -2
- data/lib/hecks/deploy/bluebook/deploy.bluebook +124 -2
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
- data/lib/hecks/deploy/oidc.json +16 -3
- data/lib/hecks/doc/reference/rendering.rb +6 -3
- data/lib/hecks/forms/app/commands.rb +8 -7
- data/lib/hecks/forms/app/queries.rb +2 -1
- data/lib/hecks/forms/app.rb +1 -1
- data/lib/hecks/forms/record_renderer.rb +1 -1
- data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
- data/lib/hecks/fuzzing/failure_triage.rb +2 -1
- data/lib/hecks/fuzzing/mutation/run.rb +11 -4
- data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
- data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
- data/lib/hecks/fuzzing/properties/guards.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency.rb +6 -2
- data/lib/hecks/fuzzing/shrinker.rb +3 -1
- data/lib/hecks/fuzzing/value_generator.rb +19 -5
- data/lib/hecks/gate/stages.yml +4 -10
- data/lib/hecks/grammar/evolve.rb +1 -1
- data/lib/hecks/grammar/expression_operators.json +372 -0
- data/lib/hecks/grammar/operators.rb +3 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
- data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
- data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
- data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
- data/lib/hecks/hecks/adapters/deploy_toolchain/clearing.rb +51 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -0
- data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
- data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
- data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
- data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
- data/lib/hecks/hecks/adapters/local_files.rb +2 -2
- data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
- data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
- data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
- data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
- data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
- data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
- data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
- data/lib/hecks/hecks/adapters/site_toolchain/role_prober.rb +78 -0
- data/lib/hecks/hecks/adapters/site_toolchain.rb +29 -4
- data/lib/hecks/hecks/adapters/terminal.rb +13 -11
- data/lib/hecks/hecks/adapters/world_clearing.rb +65 -0
- data/lib/hecks/hecks/codebase.behaviors +13 -1
- data/lib/hecks/hecks/codebase.bluebook +239 -5
- data/lib/hecks/hecks/custodian.behaviors +24 -6
- data/lib/hecks/hecks/custodian.bluebook +6 -1227
- data/lib/hecks/hecks/deploy.behaviors +1 -1
- data/lib/hecks/hecks/deploy.world +1 -3
- data/lib/hecks/hecks/hecks.behaviors +1 -1
- data/lib/hecks/hecks/hecks.hecksagon +61 -18
- data/lib/hecks/hecks/hecks.world +11 -10
- data/lib/hecks/hecks/rehearsal.bluebook +477 -0
- data/lib/hecks/hecks/site.behaviors +1 -1
- data/lib/hecks/hecks/site.world +0 -1
- data/lib/hecks/hecks/tooling.bluebook +1238 -0
- data/lib/hecks/indifferent_key.rb +20 -0
- data/lib/hecks/language/adapter.bluebook +13 -3
- data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
- data/lib/hecks/language/bluebook/entity.bluebook +18 -0
- data/lib/hecks/language/bluebook/policy.bluebook +9 -1
- data/lib/hecks/language/bluebook/query.bluebook +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
- data/lib/hecks/language/oidc.json +10 -0
- data/lib/hecks/language/port.bluebook +4 -4
- data/lib/hecks/literal.rb +12 -3
- data/lib/hecks/ports/key_vault.rb +7 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
- data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +15 -0
- data/lib/hecks/projections/deploy/fargate/settings.rb +3 -1
- data/lib/hecks/projections/deploy/fargate/stack/readers.rb +1 -0
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/shared.rb +5 -2
- data/lib/hecks/projections/deploy/templates/fargate/Makefile.tmpl +7 -4
- data/lib/hecks/projections/model/deviations.rb +2 -2
- data/lib/hecks/projections/model.rb +2 -2
- data/lib/hecks/projections/oidc.rb +13 -1
- data/lib/hecks/projections/shape.rb +5 -2
- data/lib/hecks/projections/site/cms_editor/attributes.rb +95 -0
- data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
- data/lib/hecks/projections/site/cms_editor/checks.rb +77 -0
- data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
- data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
- data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
- data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
- data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
- data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
- data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
- data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
- data/lib/hecks/projections/site/cms_editor/media.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
- data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
- data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/pretty.rb +40 -0
- data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
- data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
- data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
- data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
- data/lib/hecks/projections/site/cms_editor/schema.rb +161 -0
- data/lib/hecks/projections/site/cms_editor/setting.rb +130 -0
- data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +370 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/session.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/sso.ts.tmpl +20 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +133 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +29 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +177 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/multipart.ts.tmpl +53 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/sniff.ts.tmpl +94 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/storage.ts.tmpl +50 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +214 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/server.ts.tmpl +65 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +123 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_parse.js.tmpl +320 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +92 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +63 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
- data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +17 -0
- data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
- data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
- data/lib/hecks/projections/site/cms_editor.rb +133 -0
- data/lib/hecks/projections/site/role_probe.rb +74 -0
- data/lib/hecks/projector/cli_audience.rb +2 -1
- data/lib/hecks/projector/cli_projector/listing.rb +1 -1
- data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
- data/lib/hecks/projector/docs_projector/closing.rb +1 -1
- data/lib/hecks/projector/docs_projector.rb +1 -1
- data/lib/hecks/projector/exporter/bindings.rb +40 -10
- data/lib/hecks/projector/exporter/capability_values.rb +55 -0
- data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
- data/lib/hecks/projector/target.rb +11 -0
- data/lib/hecks/projector.rb +32 -5
- data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
- data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
- data/lib/hecks/quality_control/quality_control.bluebook +6 -2
- data/lib/hecks/release/approval.rb +64 -0
- data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
- data/lib/hecks/router/namespace_installer.rb +16 -16
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/best_effort.rb +22 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
- data/lib/hecks/runtime/command_interpreter.rb +3 -2
- data/lib/hecks/runtime/command_rules/emission.rb +1 -1
- data/lib/hecks/runtime/dispatcher.rb +4 -4
- data/lib/hecks/runtime/entity_element/location.rb +1 -1
- data/lib/hecks/runtime/event.rb +5 -0
- data/lib/hecks/runtime/identity.rb +7 -6
- data/lib/hecks/runtime/interpreting.rb +2 -1
- data/lib/hecks/runtime/invocation_builder.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
- data/lib/hecks/runtime/loader.rb +10 -10
- data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
- data/lib/hecks/runtime/policy_interpreter.rb +6 -6
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +1 -1
- data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
- data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
- data/lib/hecks/runtime/registry/merging.rb +1 -1
- data/lib/hecks/runtime/registry/providers.rb +16 -0
- data/lib/hecks/runtime/registry/read_repository.rb +6 -4
- data/lib/hecks/runtime/registry/verification.rb +4 -1
- data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
- data/lib/hecks/runtime/registry.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
- data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter.rb +3 -3
- data/lib/hecks/runtime/tenant_check.rb +5 -4
- data/lib/hecks/runtime/value/coercion.rb +2 -2
- data/lib/hecks/runtime/value/validation.rb +16 -4
- data/lib/hecks/runtime.rb +9 -9
- data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
- data/lib/hecks/rust_build/wasm.rb +1 -1
- data/lib/hecks/rust_build/wasm_browser.rb +1 -1
- data/lib/hecks/rust_build.rb +11 -0
- data/lib/hecks/served_options.rb +53 -0
- data/lib/hecks/site/bluebook/site.bluebook +29 -1
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
- data/lib/hecks/storehouse/audit.rb +1 -1
- data/lib/hecks/storehouse/dispatching.rb +8 -6
- data/lib/hecks/storehouse/introspection.rb +2 -2
- data/lib/hecks/storehouse/querying.rb +6 -4
- data/lib/hecks/storehouse.rb +4 -4
- data/lib/hecks/tools/comment_style/source_file.rb +4 -1
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
- data/lib/hecks/tools/lanes/rows.rb +6 -0
- data/lib/hecks/tools/oidc_manifests.rb +4 -5
- data/lib/hecks/tools/regeneration_run.rb +11 -4
- data/lib/hecks/tools/site_routes/cli.rb +1 -1
- data/lib/hecks/tools/site_routes/editor_files.rb +45 -0
- data/lib/hecks/tools/site_routes.rb +14 -7
- data/lib/hecks/tools/tenant_provisioning.rb +2 -2
- data/lib/hecks/tools/tools_doc.rb +4 -4
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +4 -4
- data/lib/hecks.rb +15 -15
- data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
- data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
- data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
- data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
- data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
- data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
- data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
- data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
- data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
- data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
- data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
- data/rust/.gitignore +6 -0
- data/rust/Cargo.toml +9 -1
- data/rust/build/src/binding_pass.rs +103 -0
- data/rust/build/src/cargo_sync.rs +6 -0
- data/rust/build/src/lineage_pass.rs +3 -3
- data/rust/build/src/main.rs +17 -5
- data/rust/build/src/pipeline.rs +48 -5
- data/rust/build/src/seams_pass.rs +123 -0
- data/rust/build/src/translation_pass.rs +167 -0
- data/rust/codegen/src/asks.rs +103 -0
- data/rust/codegen/src/bridging.rs +1 -1
- data/rust/codegen/src/commands.rs +13 -8
- data/rust/codegen/src/constraints.rs +25 -0
- data/rust/codegen/src/domain_generator.rs +11 -9
- data/rust/codegen/src/expr_emitter.rs +13 -0
- data/rust/codegen/src/json_codec.rs +54 -15
- data/rust/codegen/src/main.rs +2 -1
- data/rust/codegen/src/mutations.rs +174 -4
- data/rust/codegen/src/naming.rs +30 -0
- data/rust/codegen/src/queries.rs +11 -2
- data/rust/codegen/src/reactions.rs +1 -1
- data/rust/codegen/src/types.rs +166 -8
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +4 -4
- data/rust/host/src/bin/validate_harness.rs +43 -0
- data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
- data/rust/host/src/boundary_fuzz/ir.rs +1 -0
- data/rust/host/src/boundary_fuzz/server.rs +1 -1
- data/rust/host/src/checkout.rs +35 -19
- data/rust/host/src/commerce_ir.rs +408 -2
- data/rust/host/src/expr_json.rs +118 -0
- data/rust/host/src/mint.rs +1 -1
- data/rust/host/src/payments/tests.rs +27 -1
- data/rust/host/src/payments.rs +12 -4
- data/rust/host/src/query_step.rs +22 -4
- data/rust/host/src/reference_validate.rs +653 -108
- data/rust/host/src/resend.rs +2 -0
- data/rust/host/src/server.rs +19 -6
- data/rust/host/src/ui_schema.rs +132 -5
- data/rust/host/src/web/newsletter.rs +7 -8
- data/rust/host/src/web/newsletter_send.rs +38 -13
- data/rust/host/src/web/registrations.rs +229 -26
- data/rust/host/src/web.rs +3 -3
- data/rust/parser/src/emit.rs +38 -6
- data/rust/parser/src/expr/ast_json.rs +7 -1
- data/rust/parser/src/expr/resolver.rs +171 -6
- data/rust/parser/src/ir.rs +7 -0
- data/rust/parser/src/keywords.rs +24 -0
- data/rust/parser/src/main.rs +3 -0
- data/rust/parser/src/parse/aggregate.rs +2 -1
- data/rust/parser/src/parse/chapter.rs +2 -2
- data/rust/parser/src/parse/command.rs +2 -0
- data/rust/parser/src/parse/hecksagon.rs +5 -2
- data/rust/parser/src/parse/lifecycle.rs +81 -0
- data/rust/parser/src/parse/mod.rs +15 -1
- data/rust/parser/src/parse/policy.rs +13 -2
- data/rust/parser/src/parse/value_object.rs +13 -8
- data/rust/parser/src/ruby_value.rs +1 -1
- data/rust/src/exemplar/commands.rs +4 -4
- data/rust/src/exemplar/constraints.rs +1 -1
- data/rust/src/exemplar/json.rs +43 -1
- data/rust/src/kernel/cli.rs +7 -5
- data/rust/src/kernel/dispatch.rs +2 -2
- data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
- data/rust/src/kernel/expr.rs +73 -4
- data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
- data/rust/src/kernel/expression_operators/presence.rs +41 -3
- data/rust/src/kernel/expression_operators/text.rs +26 -2
- data/rust/src/kernel/json.rs +15 -0
- data/rust/src/kernel/mod.rs +1 -1
- data/rust/src/kernel/orchestrate.rs +54 -5
- data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
- data/rust/src/kernel/pattern.rs +60 -0
- data/rust/src/kernel/reference_lookup.rs +90 -7
- metadata +174 -36
- data/lib/hecks/doors/cli_door/nesting.rb +0 -34
- data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
- data/lib/hecks/doors/cli_door.rb +0 -124
- data/lib/hecks/doors/cli_runner/answers.rb +0 -138
- data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
- data/lib/hecks/doors/cli_runner/settling.rb +0 -115
- data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
- data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
- data/lib/hecks/doors/cli_runner.rb +0 -66
- data/lib/hecks/doors/command_request.rb +0 -119
- data/lib/hecks/doors/handle.rb +0 -208
- data/lib/hecks/doors/json_door.rb +0 -132
- data/lib/hecks/doors/launcher_options.rb +0 -247
- data/lib/hecks/doors/mcp_door/properties.rb +0 -47
- data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
- data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
- data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
- data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
- data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
- data/lib/hecks/doors/mcp_door.rb +0 -156
- data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
- data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
- data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
- data/lib/hecks/doors/mcp_door_scope.rb +0 -201
- data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
- data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
- data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
- data/lib/hecks/doors/ruby_door.rb +0 -80
- data/lib/hecks/doors/usage_cache.rb +0 -139
- data/prebuilt/0ee7f83cba59ce7d1c45c65ba166b071779f70143449f584f33ff7989f64eccb.marshal +0 -0
- data/prebuilt/6e4a6bcf0aa880e2e6b056e1846b70e5a768090b9dc998bbadeaf5d6bcfd3db8.marshal +0 -0
- data/prebuilt/verdicts-91aaf93ce8981bd9a754948f075d95650dc2ec6435015376f5c60992b36a24b1.json +0 -1
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
__BANNER__
|
|
2
|
+
|
|
3
|
+
// A list of mixed blocks (or of one kind of group, with no kind) as an ordered list of cards. Each
|
|
4
|
+
// card is one block: its kind as a badge, one line that says what it holds, tools to move it up
|
|
5
|
+
// and down and remove it, and, when open, only the slots its kind uses (see block_list.js). The
|
|
6
|
+
// list is plain markup that posts the same dotted-path fields every value object does, so a
|
|
7
|
+
// browser with no script still edits what it has; browser/blocks.js adds adding, removing,
|
|
8
|
+
// reordering and opening cards. A card holds its repeated parts as groups of cards one level in.
|
|
9
|
+
//
|
|
10
|
+
// A new card is made from a `<template>` the page carries per kind, written with the index left
|
|
11
|
+
// as `ZZI0ZZ` (and `ZZI1ZZ` one level in), so a new card is what the server would have drawn.
|
|
12
|
+
// A group's members show all their slots, each required as its value object declares, within the
|
|
13
|
+
// limits the holder's rules give (see `Limits` in the generator).
|
|
14
|
+
import type { Aggregate, Attr, BlockList } from "../schema.ts";
|
|
15
|
+
import { esc } from "./html.ts";
|
|
16
|
+
import { icon } from "./icons.ts";
|
|
17
|
+
import { filled, kindOf, lacking, slotsOf, summaryOf } from "./block_list.js";
|
|
18
|
+
import { label, limitsNote, pluralOf, singular } from "./words.ts";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A refused value object: where it is in the command's arguments (`path`) and in the form (`name`, the
|
|
22
|
+
* dotted name its fields share: `content.items.3` or `content.items.3.parts.1`), which of its
|
|
23
|
+
* parts are at fault, and why.
|
|
24
|
+
*/
|
|
25
|
+
export interface Problem {
|
|
26
|
+
path: (string | number)[];
|
|
27
|
+
name: string;
|
|
28
|
+
slots: string[];
|
|
29
|
+
message: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** What the list needs from the form around it. */
|
|
33
|
+
export interface Rendering {
|
|
34
|
+
/** One part's control: its markup at `prefix + part.name`, flagged with `message` when it is the one a refusal names. */
|
|
35
|
+
slot: (part: Attr, prefix: string, value: unknown, message?: string, required?: boolean, rows?: boolean) => string;
|
|
36
|
+
/** Whether the list saves a draft by itself (see browser/autosave.js). */
|
|
37
|
+
draft: boolean;
|
|
38
|
+
/** The blocks a refused command named. */
|
|
39
|
+
problems: Problem[];
|
|
40
|
+
/** The one-line caption of the list: its label, with a mark when it is required. */
|
|
41
|
+
caption: string;
|
|
42
|
+
/** Whether a picture picker is on offer (`data-media`'s address), else the key is typed. */
|
|
43
|
+
media?: string;
|
|
44
|
+
/** What the domain said about the list as a whole (its size), when it did. */
|
|
45
|
+
message?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type Block = Record<string, unknown>;
|
|
49
|
+
|
|
50
|
+
/** A part of a block as one kind shows it (see `slotsOf`). */
|
|
51
|
+
interface Slot {
|
|
52
|
+
part: Attr;
|
|
53
|
+
required: boolean;
|
|
54
|
+
mentioned: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The index placeholders a card's template is written with, by how deep the card is. */
|
|
58
|
+
const TOKEN = ["ZZI0ZZ", "ZZI1ZZ"];
|
|
59
|
+
|
|
60
|
+
const safe = (text: string): string => text.replace(/[^A-Za-z0-9]+/g, "-");
|
|
61
|
+
|
|
62
|
+
/** The control of the part that holds the kind: the kind as a hidden input, in the value object's own shape. */
|
|
63
|
+
function kindInput(agg: Aggregate, list: BlockList, parts: Attr[], prefix: string, kind: string): string {
|
|
64
|
+
const part = parts.find((candidate) => candidate.name === list.discriminator);
|
|
65
|
+
const inner = part?.kind === "object" ? (agg.valueObjects[part.type] ?? [])[0]?.name : undefined;
|
|
66
|
+
const name = `${prefix}${list.discriminator}${inner ? `.${inner}` : ""}`;
|
|
67
|
+
return `<input type="hidden" name="${esc(name)}" value="${esc(kind)}" data-kind-input>`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const tool = (action: string, glyph: string, text: string, what: string): string =>
|
|
71
|
+
`<button type="button" class="btn btn-ghost btn-square btn-sm" data-action="${action}" aria-label="${esc(`${text} ${what}`)}" title="${esc(`${text} ${what}`)}">${icon(glyph)}</button>`;
|
|
72
|
+
|
|
73
|
+
/** The tools of a card: shown by the script, which is what makes them work. */
|
|
74
|
+
function tools(noun: string, number: number | string, summary: string): string {
|
|
75
|
+
const what = `${noun} ${number}${summary ? `, ${summary}` : ""}`;
|
|
76
|
+
const move = [tool("up", "arrow-up", "Move up", what), tool("down", "arrow-down", "Move down", what)];
|
|
77
|
+
return `<div class="flex shrink-0 items-center" data-tools hidden>${[...move, tool("remove", "trash", "Remove", what)].join("")}</div>`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface Place {
|
|
81
|
+
/** The list's name, then the index, as the path of the card's fields. */
|
|
82
|
+
path: string;
|
|
83
|
+
index: number | string;
|
|
84
|
+
depth: number;
|
|
85
|
+
noun: string;
|
|
86
|
+
/** The value object a card of this place holds one of. */
|
|
87
|
+
type: string;
|
|
88
|
+
list: BlockList;
|
|
89
|
+
parts: Attr[];
|
|
90
|
+
/** True in a list of one kind of group: what a part is required to be is what its value object declares. */
|
|
91
|
+
declared: boolean;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function badge(kind: string | null): string {
|
|
95
|
+
return kind ? `<span class="badge badge-soft badge-neutral shrink-0 font-semibold" data-kind-badge>${esc(label(kind))}</span>` : "";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The slots of a card, each as its control, with a picture's parts and a repeated part drawn as groups. */
|
|
99
|
+
function slotsOfCard(agg: Aggregate, place: Place, rendering: Rendering, block: Block, kind: string | null, flagged: Problem | undefined): string {
|
|
100
|
+
const prefix = `${place.path}.${place.index}.`;
|
|
101
|
+
const picture = place.list.pictures?.[place.type];
|
|
102
|
+
const plain = (part: Attr): Slot => ({ part, required: place.declared && !part.optional, mentioned: false });
|
|
103
|
+
const shown: Slot[] = kind === null ? place.parts.map(plain) : (slotsOf(place.list, place.parts, kind) as Slot[]);
|
|
104
|
+
const held = shown.filter((slot) => !(picture && (slot.part.name === picture.alt || slot.part.name === picture.caption)));
|
|
105
|
+
const draw = held.map(({ part, required, mentioned }) => {
|
|
106
|
+
const note = mentioned ? `<p class="-mt-3 mb-3 text-xs text-muted">A rule of this kind of ${esc(singular(place.noun))} reads this.</p>` : "";
|
|
107
|
+
const message = flagged?.slots.includes(part.name) ? flagged.message : undefined;
|
|
108
|
+
if (picture && part.name === picture.key) return pictureGroup(agg, place, rendering, block, picture, flagged, prefix);
|
|
109
|
+
if (place.depth === 0 && part.list && part.kind === "object") return group(agg, place, part, rendering, block, prefix, message);
|
|
110
|
+
const rows = place.depth >= 1 && part.list && part.kind === "object";
|
|
111
|
+
return `${rendering.slot(part, prefix, block[part.name], message, required, rows)}${note}`;
|
|
112
|
+
});
|
|
113
|
+
return draw.join("\n");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** A picture's parts as one group: the key and the alt text and a button that opens the picker. */
|
|
117
|
+
function pictureGroup(agg: Aggregate, place: Place, rendering: Rendering, block: Block, picture: { key: string; alt: string; caption?: string }, flagged: Problem | undefined, prefix: string): string {
|
|
118
|
+
const find = (name: string): Attr | undefined => place.parts.find((part) => part.name === name);
|
|
119
|
+
const key = find(picture.key);
|
|
120
|
+
const alt = find(picture.alt);
|
|
121
|
+
const caption = picture.caption ? find(picture.caption) : undefined;
|
|
122
|
+
const control = (part: Attr | undefined): string => (part ? rendering.slot(part, prefix, block[part.name], flagged?.slots.includes(part.name) ? flagged.message : undefined) : "");
|
|
123
|
+
const pick = rendering.media ? `<button type="button" class="btn btn-sm w-fit" data-action="pick-picture" data-media="${esc(rendering.media)}">${icon("image")}<span>Choose a picture</span></button>` : "";
|
|
124
|
+
const thumb = `<img class="size-20 shrink-0 border border-base-300 object-cover" alt="" width="80" height="80" data-picture-thumb hidden>`;
|
|
125
|
+
return `<div class="my-4 grid gap-1 border border-base-300 p-3" data-picture data-key="${esc(prefix + picture.key)}" data-alt="${esc(prefix + picture.alt)}">
|
|
126
|
+
<p class="font-semibold">Picture</p>
|
|
127
|
+
<div class="flex flex-wrap items-start gap-3">${thumb}<div class="grid min-w-0 flex-1 gap-1 max-sm:basis-full">${control(key)}${control(alt)}${control(caption)}${pick}</div></div>
|
|
128
|
+
<p class="text-xs text-muted">A picture needs a description for people who cannot see it.</p>
|
|
129
|
+
</div>`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** One card: its head and, in the panel, its slots. `kind` is null for a block with no kind (a repeated part). */
|
|
133
|
+
function card(agg: Aggregate, place: Place, rendering: Rendering, block: Block, kind: string | null): string {
|
|
134
|
+
const id = `blk-${safe(place.path)}-${place.index}`;
|
|
135
|
+
const flagged = rendering.problems.find((problem) => problem.name === `${place.path}.${place.index}`);
|
|
136
|
+
const picture = place.list.pictures?.[place.type];
|
|
137
|
+
const summary = kind === null && !place.list ? "" : String(summaryOf(place.list, place.parts, block, picture) ?? "");
|
|
138
|
+
const holds = rendering.problems.some((problem) => problem.name.startsWith(`${place.path}.${place.index}.`));
|
|
139
|
+
const open = flagged || holds ? "" : " hidden";
|
|
140
|
+
const attention = flagged ? `<span class="badge badge-soft badge-error shrink-0 gap-1 font-semibold">${icon("alert")}Needs attention</span>` : "";
|
|
141
|
+
const problem = flagged && flagged.slots.length === 0 ? `<p class="text-sm font-semibold text-error" id="${id}-problem">${esc(flagged.message)}</p>` : "";
|
|
142
|
+
const describe = flagged && flagged.slots.length === 0 ? ` aria-describedby="${id}-problem"` : "";
|
|
143
|
+
const number = typeof place.index === "number" ? place.index + 1 : place.index;
|
|
144
|
+
const kindField = kind === null ? "" : kindInput(agg, place.list, place.parts, `${place.path}.${place.index}.`, kind);
|
|
145
|
+
const toggle = `<button type="button" class="btn btn-ghost h-auto min-h-11 min-w-0 flex-1 flex-wrap justify-start gap-x-2 gap-y-0 py-2 text-left font-normal" data-action="toggle" aria-expanded="${flagged || holds ? "true" : "false"}" aria-controls="${id}-panel"${describe} hidden>${icon("chevron-right")}<span class="shrink-0 font-semibold" data-card-title>${esc(label(place.noun))} <span data-card-number>${number}</span></span>${badge(kind)}<span class="min-w-0 flex-1 truncate text-muted max-sm:basis-full max-sm:pl-7" data-card-summary>${esc(summary)}</span>${attention}</button>`;
|
|
146
|
+
const plain = `<span class="flex min-w-0 flex-1 flex-wrap items-center gap-2 py-2" data-card-static><span class="font-semibold">${esc(label(place.noun))} ${number}</span>${badge(kind)}<span class="min-w-0 text-muted">${esc(summary)}</span>${attention}</span>`;
|
|
147
|
+
const heading = `<h${place.depth + 3} class="m-0 flex min-w-0 flex-1 text-base">${plain}${toggle}</h${place.depth + 3}>`;
|
|
148
|
+
const valid = `${flagged ? ` data-invalid` : ""}${holds ? ` data-open` : ""}`;
|
|
149
|
+
const required = kind === null ? (place.declared ? place.parts.filter((part) => !part.optional && !part.list) : []) : [];
|
|
150
|
+
const requires = kind === null ? required.map((part) => part.name).join(" ") : (place.list.rules?.[kind]?.requires ?? []).join(" ");
|
|
151
|
+
const needs = requires ? ` data-requires="${esc(requires)}"` : "";
|
|
152
|
+
const said = picture && place.declared && place.depth === 0 ? ` data-summary-alt="${esc(picture.alt)}"` : "";
|
|
153
|
+
return `<li class="min-w-0 border border-base-300 bg-base-100" id="${id}" data-card data-index="${place.index}" data-kind="${esc(kind ?? "")}"${needs}${said}${valid}>
|
|
154
|
+
<div class="flex items-center gap-1 pl-2" data-card-head>${heading}${tools(place.noun, number, summary)}</div>
|
|
155
|
+
<div id="${id}-panel" class="border-t border-base-300 px-4 pb-4 [&>*:first-child]:mt-2" data-card-panel>
|
|
156
|
+
${problem}${kindField}
|
|
157
|
+
${slotsOfCard(agg, place, rendering, block, kind, flagged)}
|
|
158
|
+
</div>
|
|
159
|
+
</li>`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** The blocks nested one level in a card (buttons, entries): a group of cards with one template. */
|
|
163
|
+
function group(agg: Aggregate, place: Place, part: Attr, rendering: Rendering, block: Block, prefix: string, problem?: string): string {
|
|
164
|
+
const parts = agg.valueObjects[part.type] ?? [];
|
|
165
|
+
const noun = singular(part.name.replace(/([^aeiou])ies$/, "$1y").replace(/s$/, ""));
|
|
166
|
+
const path = `${prefix}${part.name}`;
|
|
167
|
+
const held = Array.isArray(block[part.name]) ? (block[part.name] as Block[]) : [];
|
|
168
|
+
const limit = part.max ?? place.list.limits?.[part.name];
|
|
169
|
+
const pictures = { ...place.list.pictures, ...(part.picture ? { [part.type]: part.picture } : {}) };
|
|
170
|
+
const nested: BlockList = { discriminator: "", kinds: [], pictures };
|
|
171
|
+
const inside = (index: number | string, item: Block): string => card(agg, { path, index, depth: 1, noun, type: part.type, list: nested, parts, declared: place.declared }, rendering, item, null);
|
|
172
|
+
const cards = held.map((item, index) => inside(index, item));
|
|
173
|
+
const blank = inside(TOKEN[1], {});
|
|
174
|
+
const max = limit ? ` data-max="${limit}"` : "";
|
|
175
|
+
const required = place.declared ? !part.optional : place.list.rules?.[kindOf(place.list, block)]?.requires.includes(part.name);
|
|
176
|
+
const note = limitsNote(part.min, limit);
|
|
177
|
+
const need = `${required ? "" : ` <span class="ml-1 text-xs font-normal text-muted">optional</span>`}${note ? ` <span class="ml-1 text-xs font-normal text-muted">${esc(note)}</span>` : ""}`;
|
|
178
|
+
const bad = problem ? ` aria-invalid="true" aria-describedby="blk-${safe(path)}-problem"` : "";
|
|
179
|
+
const why = problem ? `<p class="clear-both text-sm font-semibold text-error" id="blk-${safe(path)}-problem">${esc(problem)}</p>` : "";
|
|
180
|
+
return `<fieldset class="mt-6 border-t border-base-300 pt-3" data-group data-path="${esc(path)}" data-noun="${esc(noun)}"${max} data-depth="1"${bad}>
|
|
181
|
+
<legend class="float-left mb-2 w-full text-base font-semibold">${esc(label(part.name))}${need}</legend>
|
|
182
|
+
<ol class="clear-both grid list-none grid-cols-[minmax(0,1fr)] gap-2 p-0" data-cards>
|
|
183
|
+
${cards.join("\n")}
|
|
184
|
+
</ol>
|
|
185
|
+
${why}
|
|
186
|
+
<template data-card-template data-kind="">${blank}</template>
|
|
187
|
+
<button type="button" class="btn btn-ghost btn-sm mt-2" data-action="add" hidden>${icon("plus")}<span>Add ${esc(noun)}</span></button>
|
|
188
|
+
</fieldset>`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** What a list of one kind of group is drawn from: a block list that has no kind, its limits and its member's picture. */
|
|
192
|
+
function plainList(attr: Attr): BlockList {
|
|
193
|
+
return { discriminator: "", kinds: [], pictures: attr.picture ? { [attr.type]: attr.picture } : undefined };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** The add control and a template for each kind (or the one template, for a list that has no kinds). */
|
|
197
|
+
function adder(agg: Aggregate, attr: Attr, name: string, rendering: Rendering, noun: string): { control: string; templates: string } {
|
|
198
|
+
const list = attr.blockList ?? plainList(attr);
|
|
199
|
+
const parts = agg.valueObjects[attr.type] ?? [];
|
|
200
|
+
const place: Place = { path: name, index: TOKEN[0], depth: 0, noun, type: attr.type, list, parts, declared: !attr.blockList };
|
|
201
|
+
const button = `<button type="button" class="btn btn-neutral" data-action="add">${icon("plus")}<span>Add ${esc(singular(noun))}</span></button>`;
|
|
202
|
+
if (!attr.blockList) {
|
|
203
|
+
return { control: `<div class="mt-4 flex flex-wrap items-end gap-2" data-adder hidden>${button}</div>`, templates: `<template data-card-template data-kind="">${card(agg, place, rendering, {}, null)}</template>` };
|
|
204
|
+
}
|
|
205
|
+
const options = list.kinds.map((kind) => `<option value="${esc(kind)}">${esc(label(kind))}</option>`).join("");
|
|
206
|
+
const id = `blk-${safe(name)}-kind`;
|
|
207
|
+
const control = `<div class="mt-4 flex flex-wrap items-end gap-2" data-adder hidden>
|
|
208
|
+
<div class="grid gap-1"><label for="${id}" class="font-semibold">Kind of ${esc(singular(noun))} to add</label><select id="${id}" class="select" data-add-kind>${options}</select></div>
|
|
209
|
+
${button}
|
|
210
|
+
</div>`;
|
|
211
|
+
const templates = list.kinds.map((kind) => `<template data-card-template data-kind="${esc(kind)}">${card(agg, place, rendering, { [list.discriminator]: kindValue(agg, parts, list, kind) }, kind)}</template>`);
|
|
212
|
+
return { control, templates: templates.join("\n") };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** A kind as the value the discriminator part holds: wrapped when the part is a one-part value object. */
|
|
216
|
+
function kindValue(agg: Aggregate, parts: Attr[], list: BlockList, kind: string): unknown {
|
|
217
|
+
const part = parts.find((candidate) => candidate.name === list.discriminator);
|
|
218
|
+
const inner = part?.kind === "object" ? (agg.valueObjects[part.type] ?? [])[0]?.name : undefined;
|
|
219
|
+
return inner ? { [inner]: kind } : kind;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** What the page says at the top of a refused list: how many blocks need attention and where each is. */
|
|
223
|
+
function summaryOfProblems(name: string, noun: string, problems: Problem[], titles: Map<string, string>): string {
|
|
224
|
+
if (problems.length === 0) return "";
|
|
225
|
+
const items = problems.map((problem) => {
|
|
226
|
+
const rest = problem.name.slice(name.length + 1).split(".");
|
|
227
|
+
const id = `blk-${safe(name)}-${rest[0]}`;
|
|
228
|
+
const where = rest.length > 2 ? `, ${esc(label(rest[1]))} ${Number(rest[2]) + 1}` : "";
|
|
229
|
+
return `<li><a href="#${id}">${esc(titles.get(rest[0]) ?? "")}${where}</a>: ${esc(problem.message)}</li>`;
|
|
230
|
+
});
|
|
231
|
+
const words = problems.length === 1 ? `1 ${esc(singular(noun))} needs attention` : `${problems.length} ${esc(pluralOf(singular(noun)))} need attention`;
|
|
232
|
+
return `<div class="alert alert-error alert-soft mb-4 block" role="alert" tabindex="-1" data-block-problems><p class="font-semibold">${words}</p><ul class="mt-1 list-disc pl-5">${items.join("")}</ul></div>`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** An attribute that is a list of mixed blocks, or of one kind of group, as its cards, its add control and its templates. */
|
|
236
|
+
export function blockList(agg: Aggregate, attr: Attr, name: string, value: unknown, rendering: Rendering): string {
|
|
237
|
+
const list = attr.blockList ?? plainList(attr);
|
|
238
|
+
const parts = agg.valueObjects[attr.type] ?? [];
|
|
239
|
+
const noun = singular(attr.type);
|
|
240
|
+
const plural = esc(pluralOf(noun));
|
|
241
|
+
const held = Array.isArray(value) ? (value as Block[]) : [];
|
|
242
|
+
const own = rendering.problems.filter((problem) => problem.name.startsWith(`${name}.`));
|
|
243
|
+
const place = (index: number): Place => ({ path: name, index, depth: 0, noun, type: attr.type, list, parts, declared: !attr.blockList });
|
|
244
|
+
const cards = held.map((block, index) => card(agg, place(index), rendering, block, kindOf(list, block) || null));
|
|
245
|
+
const titles = new Map(held.map((block, index) => [String(index), `${label(noun)} ${index + 1}${kindOf(list, block) ? ` (${label(kindOf(list, block))})` : ""}`]));
|
|
246
|
+
const adding = adder(agg, attr, name, rendering, noun);
|
|
247
|
+
const max = attr.max ? ` data-max="${attr.max}"` : "";
|
|
248
|
+
const draft = rendering.draft ? " data-draft" : "";
|
|
249
|
+
const id = `blk-${safe(name)}`;
|
|
250
|
+
const limits = limitsNote(attr.min, attr.max);
|
|
251
|
+
const help = `<p class="clear-both mb-3 text-sm text-muted" id="${id}-help">Open a ${esc(singular(noun))} to edit it. Move buttons change the order and Remove asks first. ${limits} Without scripts every ${esc(singular(noun))} stays open and cannot be added, moved or removed.</p>`;
|
|
252
|
+
const invalid = rendering.message ? ` aria-invalid="true"` : "";
|
|
253
|
+
const said = rendering.message ? `<p class="clear-both mb-3 text-sm font-semibold text-error" id="${id}-error">${esc(rendering.message)}</p>` : "";
|
|
254
|
+
const described = rendering.message ? `${id}-help ${id}-error` : `${id}-help`;
|
|
255
|
+
return `<fieldset class="mt-6 [&>legend+*]:clear-both" id="${id}" data-block-list data-group data-path="${esc(name)}" data-noun="${esc(noun)}" data-depth="0"${max}${draft}${invalid} aria-describedby="${described}">
|
|
256
|
+
<legend class="float-left mb-2 w-full border-t border-base-300 pt-4 text-lg font-semibold">${rendering.caption} <span class="text-sm font-normal text-muted" data-count>(${held.length})</span></legend>
|
|
257
|
+
${help}
|
|
258
|
+
${said}
|
|
259
|
+
${summaryOfProblems(name, noun, own, titles)}
|
|
260
|
+
<ol class="grid list-none grid-cols-[minmax(0,1fr)] gap-3 p-0" data-cards>
|
|
261
|
+
${cards.join("\n")}
|
|
262
|
+
</ol>
|
|
263
|
+
<p class="mt-2 text-sm text-muted" data-empty${held.length > 0 ? " hidden" : ""}>No ${plural} yet.</p>
|
|
264
|
+
${adding.control}
|
|
265
|
+
<p class="sr-only" role="status" aria-live="polite" data-block-say></p>
|
|
266
|
+
${adding.templates}
|
|
267
|
+
<dialog class="modal" id="${id}-remove" aria-labelledby="${id}-remove-title" data-remove-dialog>
|
|
268
|
+
<div class="modal-box"><h2 id="${id}-remove-title" class="text-xl" data-remove-title>Remove ${esc(singular(noun))}?</h2>
|
|
269
|
+
<p class="mt-2 text-muted">It is taken out of the list when the list is saved.</p>
|
|
270
|
+
<div class="modal-action"><button type="button" class="btn btn-ghost" data-action="remove-cancel">Keep it</button><button type="button" class="btn btn-error" data-action="remove-confirm">Remove</button></div></div>
|
|
271
|
+
</dialog>
|
|
272
|
+
</fieldset>`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export { filled, lacking };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
__BANNER__
|
|
2
|
+
|
|
3
|
+
// The structured document body (`blocks` of `spans` and `items`) as read-only HTML and as form
|
|
4
|
+
// fields. Plain JavaScript with no dependencies, so the same file runs in node (the pages) and in
|
|
5
|
+
// the browser (the widget). The domain's own tree is the only format: nothing here is an editor
|
|
6
|
+
// format, and every text, address and attribute value is escaped on its way into markup.
|
|
7
|
+
|
|
8
|
+
export const KINDS = ["paragraph", "heading", "quote", "bullet_list", "numbered_list", "image", "divider"];
|
|
9
|
+
export const ALIGNS = ["left", "center", "right", "justify"];
|
|
10
|
+
export const MARKS = ["bold", "italic", "underline", "strike", "code"];
|
|
11
|
+
export const TAGS = { bold: "strong", italic: "em", underline: "u", strike: "s", code: "code" };
|
|
12
|
+
export const MAX_INDENT = 8;
|
|
13
|
+
export const MAX_DEPTH = 5;
|
|
14
|
+
|
|
15
|
+
/** The body of a document with nothing in it. */
|
|
16
|
+
export const emptyBody = () => ({ blocks: [] });
|
|
17
|
+
|
|
18
|
+
/** Escapes text for an HTML body or a double-quoted attribute. */
|
|
19
|
+
export function esc(value) {
|
|
20
|
+
return String(value ?? "").replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The address when it is one a link may carry: a path (not `//`), http://, https://, mailto: or
|
|
25
|
+
* tel:, with no whitespace or control characters; otherwise null.
|
|
26
|
+
*/
|
|
27
|
+
export function safeHref(value) {
|
|
28
|
+
const text = String(value ?? "");
|
|
29
|
+
if (text === "" || /[\u0000- \u007f-\u009f\\]/.test(text)) return null;
|
|
30
|
+
return /^(\/(?!\/)|https?:\/\/|mailto:|tel:)/i.test(text) ? text : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const markName = (mark) => (typeof mark === "string" ? mark : mark?.name);
|
|
34
|
+
const whole = (value, low, high) => (Number.isInteger(Number(value)) && value !== "" && value !== null ? Math.min(high, Math.max(low, Number(value))) : null);
|
|
35
|
+
const list = (value) => (Array.isArray(value) ? value : []);
|
|
36
|
+
|
|
37
|
+
function wrap(span, inner) {
|
|
38
|
+
const names = list(span.marks).map(markName).filter((name) => MARKS.includes(name));
|
|
39
|
+
const marked = [...names].reverse().reduce((html, name) => `<${TAGS[name]}>${html}</${TAGS[name]}>`, inner);
|
|
40
|
+
const href = safeHref(span.href);
|
|
41
|
+
return href ? `<a href="${esc(href)}" rel="noopener noreferrer">${marked}</a>` : marked;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The spans as inline HTML; a span of "\n" is a line break. */
|
|
45
|
+
export function spansToHtml(spans) {
|
|
46
|
+
return list(spans)
|
|
47
|
+
.map((span) => String(span?.text ?? "").split("\n").map((part, at) => wrap(span, (at > 0 ? "<br>" : "") + esc(part))).join(""))
|
|
48
|
+
.join("");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const attrs = (pairs) => pairs.filter(([, value]) => value !== null && value !== undefined).map(([name, value]) => ` ${name}="${esc(value)}"`).join("");
|
|
52
|
+
|
|
53
|
+
function layout(block) {
|
|
54
|
+
const align = ALIGNS.includes(block.align) ? block.align : null;
|
|
55
|
+
return attrs([["data-align", align], ["data-indent", whole(block.indent, 0, MAX_INDENT)]]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function itemHtml(item) {
|
|
59
|
+
const kind = KINDS.slice(3, 5).includes(item?.list_kind) ? item.list_kind : null;
|
|
60
|
+
const depth = whole(item?.depth, 0, MAX_DEPTH);
|
|
61
|
+
return `<li${attrs([["data-depth", depth], ["data-list-kind", kind]])}>${spansToHtml(item?.spans)}</li>`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function imageHtml(block) {
|
|
65
|
+
const note = `<span class="body-media">image <code>${esc(block.media_ref ?? "")}</code>${block.alt ? ` ${esc(block.alt)}` : ""}</span>`;
|
|
66
|
+
const caption = block.caption ? `<figcaption>${esc(block.caption)}</figcaption>` : "";
|
|
67
|
+
return `<figure${attrs([["data-media-ref", block.media_ref ?? ""], ["data-alt", block.alt], ["data-caption", block.caption]])}>${note}${caption}</figure>`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* One block as HTML. An unknown kind is shown as a paragraph. `demote` moves a heading down that
|
|
72
|
+
* many levels (to at most 6), for a body shown inside a page that has its own headings above it.
|
|
73
|
+
*/
|
|
74
|
+
export function blockToHtml(block, demote = 0) {
|
|
75
|
+
const kind = block?.kind;
|
|
76
|
+
if (kind === "divider") return "<hr>";
|
|
77
|
+
if (kind === "image") return imageHtml(block);
|
|
78
|
+
if (kind === "bullet_list" || kind === "numbered_list") {
|
|
79
|
+
const tag = kind === "bullet_list" ? "ul" : "ol";
|
|
80
|
+
return `<${tag}${layout(block)}>${list(block.items).map(itemHtml).join("")}</${tag}>`;
|
|
81
|
+
}
|
|
82
|
+
const level = Math.min(6, (whole(block?.level, 1, 4) ?? 2) + demote);
|
|
83
|
+
const tag = kind === "heading" ? `h${level}` : kind === "quote" ? "blockquote" : "p";
|
|
84
|
+
return `<${tag}${layout(block ?? {})}>${spansToHtml(block?.spans)}</${tag}>`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The body as HTML, one block after another; the same body gives the same text. */
|
|
88
|
+
export function bodyToHtml(body, demote = 0) {
|
|
89
|
+
return list(body?.blocks).map((block) => blockToHtml(block, demote)).join("\n");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const present = (value) => value !== undefined && value !== null && value !== "";
|
|
93
|
+
|
|
94
|
+
function spanFields(span, name) {
|
|
95
|
+
const marks = list(span.marks).map(markName).filter(present).map((mark, at) => [`${name}.marks.${at}.name`, mark]);
|
|
96
|
+
return [[`${name}.text`, span.text ?? ""], ...marks, ...(present(span.href) ? [[`${name}.href`, span.href]] : [])];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function itemFields(item, name) {
|
|
100
|
+
const spans = list(item.spans).flatMap((span, at) => spanFields(span, `${name}.spans.${at}`));
|
|
101
|
+
return [...spans, ...(present(item.depth) ? [[`${name}.depth`, item.depth]] : []), ...(present(item.list_kind) ? [[`${name}.list_kind`, item.list_kind]] : [])];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function blockFields(block, name) {
|
|
105
|
+
const spans = list(block.spans).flatMap((span, at) => spanFields(span, `${name}.spans.${at}`));
|
|
106
|
+
const items = list(block.items).flatMap((item, at) => itemFields(item, `${name}.items.${at}`));
|
|
107
|
+
const scalars = ["level", "align", "indent", "media_ref", "alt", "caption"].filter((key) => present(block[key])).map((key) => [`${name}.${key}`, block[key]]);
|
|
108
|
+
return [[`${name}.kind`, block.kind], ...scalars, ...spans, ...items];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The body as the form's own fields: `[name, value]` pairs named by dotted path under `name`
|
|
113
|
+
* (`body.blocks.0.spans.1.marks.0.name`), the encoding the editor's other value objects and lists
|
|
114
|
+
* use, which the server reads back into the tree the command takes.
|
|
115
|
+
*/
|
|
116
|
+
export function bodyToFields(body, name) {
|
|
117
|
+
return list(body?.blocks).flatMap((block, at) => blockFields(block, `${name}.blocks.${at}`));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The fields as hidden inputs. */
|
|
121
|
+
export function fieldsToInputs(pairs) {
|
|
122
|
+
return pairs.map(([name, value]) => `<input type="hidden" name="${esc(name)}" value="${esc(value)}">`).join("\n");
|
|
123
|
+
}
|