hecks 1.2.0 → 1.3.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/lib/hecks/adapters/driven/heki/journal.rb +57 -0
- data/lib/hecks/adapters/driven/postgres_era.adapter +5 -0
- data/lib/hecks/adapters/driving/github_webhook.rb +145 -0
- data/lib/hecks/behaviors/expectations.rb +32 -4
- data/lib/hecks/bluebook/behaviour/domain_port.rb +24 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +25 -3
- data/lib/hecks/bluebook/model_check.rb +148 -17
- data/lib/hecks/forms/field_shape.rb +5 -3
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +266 -0
- data/lib/hecks/fuzzing/era_boundary.rb +105 -0
- data/lib/hecks/fuzzing/form_census.rb +184 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +198 -10
- data/lib/hecks/fuzzing/persistence_parity.rb +163 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +100 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +188 -13
- data/lib/hecks/fuzzing/properties/guards.rb +103 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +34 -1
- data/lib/hecks/fuzzing/properties/outbox.rb +142 -0
- data/lib/hecks/fuzzing/properties.rb +23 -2
- data/lib/hecks/fuzzing/replay.rb +130 -20
- data/lib/hecks/fuzzing/rotation_priority.rb +94 -0
- data/lib/hecks/fuzzing/self_consistency.rb +647 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +526 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +90 -26
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +50 -4
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +11 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +73 -9
- data/lib/hecks/fuzzing/sequence_generator.rb +47 -14
- data/lib/hecks/fuzzing/structural_skips.rb +146 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +53 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +149 -0
- data/lib/hecks/fuzzing/value_generator.rb +55 -3
- data/lib/hecks/fuzzing.rb +6 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +17 -2
- data/lib/hecks/naming.rb +70 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +50 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +31 -0
- data/lib/hecks/ports/persistence/repository_factory.rb +8 -5
- data/lib/hecks/projections/glossary/html.rb +250 -0
- data/lib/hecks/projections/glossary/markdown.rb +105 -0
- data/lib/hecks/projections/glossary/mermaid.rb +110 -0
- data/lib/hecks/projections/glossary/page.css +271 -0
- data/lib/hecks/projections/glossary/page.js +72 -0
- data/lib/hecks/projections/glossary/sections.rb +17 -0
- data/lib/hecks/projections/glossary/sentences.rb +205 -0
- data/lib/hecks/projections/glossary.rb +214 -286
- data/lib/hecks/projector/narrate_projector.rb +4 -11
- data/lib/hecks/query_specification/common/comparison.rb +27 -1
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +26 -8
- data/lib/hecks/runtime/command_rules/references.rb +75 -0
- data/lib/hecks/runtime/entity_element.rb +168 -16
- data/lib/hecks/runtime/entity_interpreter.rb +68 -3
- data/lib/hecks/runtime/query_interpreter.rb +66 -2
- data/lib/hecks/runtime/reaction_invocation.rb +70 -3
- data/lib/hecks/runtime/refusal_wording.rb +5 -2
- data/lib/hecks/runtime/registry.rb +12 -0
- data/lib/hecks/runtime/routing.rb +67 -2
- data/lib/hecks/runtime/saga_interpreter.rb +38 -1
- data/lib/hecks/runtime/value/coercion.rb +77 -115
- data/lib/hecks/runtime/value/entity_list_coercion.rb +248 -0
- data/lib/hecks/runtime/value.rb +7 -2
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +2 -1
- metadata +23 -2
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
require_relative "sections"
|
|
2
|
+
require_relative "sentences"
|
|
3
|
+
require_relative "mermaid"
|
|
4
|
+
require_relative "../statements"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Projections
|
|
8
|
+
module Glossary
|
|
9
|
+
# THE DOCUMENT ITSELF — `glossary.md`, the one source the page is
|
|
10
|
+
# rendered from. Written in the handful of Markdown constructs
|
|
11
|
+
# GitHub renders as-is (headings, blockquotes, ```mermaid fences,
|
|
12
|
+
# lists, in-page links), so the file is a complete, readable
|
|
13
|
+
# glossary on its own before any page is built from it:
|
|
14
|
+
#
|
|
15
|
+
# # Banking — Glossary
|
|
16
|
+
# > vision
|
|
17
|
+
# lede
|
|
18
|
+
# ```mermaid the map
|
|
19
|
+
# ## Account
|
|
20
|
+
# > description
|
|
21
|
+
# Starts out open. Can be open, frozen, or closed.
|
|
22
|
+
# **How it fits** ```mermaid
|
|
23
|
+
# **How it moves** ```mermaid
|
|
24
|
+
# **Always true** - one sentence per declared fact
|
|
25
|
+
# ### Account number one ### per term, A to Z
|
|
26
|
+
# Made up of a value (text).
|
|
27
|
+
# Always true: an account number is present.
|
|
28
|
+
module Markdown
|
|
29
|
+
LEDE = "Every term %<name>s uses, in the words of the people who work in it — grouped under the thing " \
|
|
30
|
+
"each belongs to, and listed A to Z within it. This page is generated from the working " \
|
|
31
|
+
"specification, so it says what the system does today, not what anyone hoped it would do. " \
|
|
32
|
+
"If a sentence here reads wrong to you, the specification is wrong: say so.".freeze
|
|
33
|
+
|
|
34
|
+
STANDING = {
|
|
35
|
+
ROLES => "Who does what. A role is named once here rather than under every term it touches.",
|
|
36
|
+
READ_MODELS => "Questions answered across more than one of the things above.",
|
|
37
|
+
REACTIONS => "What happens on its own, in response to something this specification does not itself raise."
|
|
38
|
+
}.freeze
|
|
39
|
+
|
|
40
|
+
module_function
|
|
41
|
+
|
|
42
|
+
def title(bluebook) = "#{bluebook.name} — Glossary"
|
|
43
|
+
|
|
44
|
+
def render(document)
|
|
45
|
+
parts = [header(document.bluebook)]
|
|
46
|
+
parts += document.sections.map { |section| section_text(section, document) }
|
|
47
|
+
"#{parts.join("\n\n")}\n"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def header(bluebook)
|
|
51
|
+
parts = ["# #{title(bluebook)}"]
|
|
52
|
+
parts << "> #{bluebook.vision}" if bluebook.vision
|
|
53
|
+
parts << format(LEDE, name: bluebook.name)
|
|
54
|
+
parts << fence(Mermaid.map(bluebook))
|
|
55
|
+
parts.join("\n\n")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def section_text(section, document)
|
|
59
|
+
parts = ["## #{section.title}"]
|
|
60
|
+
parts += section.aggregate ? opening(section.aggregate, document.bluebook) : ["> #{STANDING[section.name]}"]
|
|
61
|
+
parts += section.terms.map { |entry| term_text(entry, document.index) }
|
|
62
|
+
parts.join("\n\n")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# What an aggregate IS, what it can be, how it fits and moves,
|
|
66
|
+
# and what is always true of it — before a single term.
|
|
67
|
+
def opening(aggregate, bluebook)
|
|
68
|
+
parts = []
|
|
69
|
+
parts << "> #{aggregate.description}" if aggregate.description
|
|
70
|
+
parts << Sentences.lifecycle_sentence(aggregate.lifecycle) if aggregate.lifecycle
|
|
71
|
+
parts << "**How it fits**" << fence(Mermaid.context(aggregate, bluebook))
|
|
72
|
+
parts << "**How it moves**" << fence(Mermaid.lifecycle(aggregate)) if aggregate.lifecycle
|
|
73
|
+
statements = always_true(aggregate, bluebook)
|
|
74
|
+
parts << "**Always true**" << statements.map { |statement| "- #{statement}" }.join("\n") unless statements.empty?
|
|
75
|
+
parts
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# THE STATEMENTS PROJECTION'S OWN SENTENCES, SPOKEN. An invariant
|
|
79
|
+
# is the author's words and stays exactly so. A relationship
|
|
80
|
+
# sentence is built mechanically from construct names ("An
|
|
81
|
+
# ATMCard references an Account."), and a construct name is the
|
|
82
|
+
# one thing this page never shows as an identifier — so each is
|
|
83
|
+
# replaced with how it is said, whole-word, and nothing else in
|
|
84
|
+
# the sentence is touched.
|
|
85
|
+
def always_true(aggregate, bluebook)
|
|
86
|
+
names = bluebook.aggregates.flat_map { |other| [other.hecks_name, *other.entities.map(&:hecks_name)] }
|
|
87
|
+
[aggregate, *aggregate.entities].flat_map do |holder|
|
|
88
|
+
Statements.attribute_statements(holder).map { |sentence| spoken(sentence, names) } +
|
|
89
|
+
Statements.invariant_statements(holder)
|
|
90
|
+
end.uniq
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def spoken(sentence, names)
|
|
94
|
+
names.reduce(sentence) { |text, name| text.gsub(/\b#{Regexp.escape(name)}\b/, Naming.words(name)) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def term_text(entry, index)
|
|
98
|
+
["### #{entry.headword}", *Sentences.paragraphs(entry, index)].join("\n\n")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def fence(source) = "```mermaid\n#{source}\n```"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
require_relative "../../naming"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Glossary
|
|
6
|
+
# THE THREE PICTURES THE GLOSSARY DRAWS, each kept to the handful of
|
|
7
|
+
# things that matter — Evans' "three to five objects central to the
|
|
8
|
+
# issue at hand", never the whole model at once.
|
|
9
|
+
#
|
|
10
|
+
# map the aggregates and which points at which — one small
|
|
11
|
+
# graph at the top, for the reader who wants the shape
|
|
12
|
+
# of the domain before its words.
|
|
13
|
+
# context one aggregate, its entities, and the neighbours it
|
|
14
|
+
# points at or is pointed at by. Value objects are
|
|
15
|
+
# words in the text ("Made up of …"), not boxes here —
|
|
16
|
+
# Account alone has eight, and eight boxes say less
|
|
17
|
+
# than one sentence.
|
|
18
|
+
# lifecycle the states a thing can be in and what moves it.
|
|
19
|
+
#
|
|
20
|
+
# Every label is a person's spelling (`Naming.words`), and the
|
|
21
|
+
# edges read from the same declared facts `Projections::Diagrams`
|
|
22
|
+
# draws its own from (`reference?`, `type.target_name`, `name`,
|
|
23
|
+
# `lifecycle.transitions`) — a different picture of the same
|
|
24
|
+
# declaration, not a second declaration.
|
|
25
|
+
module Mermaid
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
def map(bluebook)
|
|
29
|
+
names = bluebook.aggregates.map(&:hecks_name)
|
|
30
|
+
lines = ["flowchart LR"]
|
|
31
|
+
names.each { |name| lines << node(name) }
|
|
32
|
+
bluebook.aggregates.each do |aggregate|
|
|
33
|
+
references(aggregate).each do |attribute|
|
|
34
|
+
target = attribute.type.target_name
|
|
35
|
+
lines << node(target) unless names.include?(target) || lines.include?(node(target))
|
|
36
|
+
lines << edge(aggregate.hecks_name, attribute, target)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
lines.join("\n")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def context(aggregate, bluebook)
|
|
43
|
+
focus = aggregate.hecks_name
|
|
44
|
+
lines = ["flowchart LR", node(focus, focus: true)]
|
|
45
|
+
entities(aggregate).each do |attribute|
|
|
46
|
+
lines << node(attribute.type.to_s)
|
|
47
|
+
lines << edge(focus, attribute, attribute.type.to_s)
|
|
48
|
+
end
|
|
49
|
+
references(aggregate).each do |attribute|
|
|
50
|
+
lines << node(attribute.type.target_name)
|
|
51
|
+
lines << edge(focus, attribute, attribute.type.target_name)
|
|
52
|
+
end
|
|
53
|
+
pointing_at(aggregate, bluebook).each do |holder, attribute|
|
|
54
|
+
lines << node(holder.hecks_name)
|
|
55
|
+
lines << edge(holder.hecks_name, attribute, focus)
|
|
56
|
+
end
|
|
57
|
+
lines << " classDef focus stroke-width:3px"
|
|
58
|
+
lines.uniq.join("\n")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def lifecycle(holder)
|
|
62
|
+
lifecycle = holder.lifecycle
|
|
63
|
+
lines = ["stateDiagram-v2"]
|
|
64
|
+
states = ([lifecycle.default] + lifecycle.transitions.map { |_name, transition| transition.target }).uniq
|
|
65
|
+
states.select { |state| state.to_s.include?("_") }.each do |state|
|
|
66
|
+
lines << " state \"#{state.to_s.tr('_', ' ')}\" as #{state}"
|
|
67
|
+
end
|
|
68
|
+
lines << " [*] --> #{lifecycle.default}"
|
|
69
|
+
lifecycle.transitions.each do |name, transition|
|
|
70
|
+
Array(transition.from).each { |from| lines << " #{from} --> #{transition.target}: #{Naming.words(name)}" }
|
|
71
|
+
end
|
|
72
|
+
lines.join("\n")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# ── the facts drawn ──────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
def references(holder) = holder.attributes.select(&:reference?)
|
|
78
|
+
|
|
79
|
+
# An entity is held as `list_of(LedgerEntry)` — the attribute
|
|
80
|
+
# whose element type names one of the aggregate's own entities.
|
|
81
|
+
def entities(aggregate)
|
|
82
|
+
names = aggregate.entities.map(&:hecks_name)
|
|
83
|
+
aggregate.attributes.select { |attribute| !attribute.reference? && names.include?(attribute.type.to_s) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def pointing_at(aggregate, bluebook)
|
|
87
|
+
bluebook.aggregates.reject { |other| other.equal?(aggregate) }.flat_map do |other|
|
|
88
|
+
references(other).select { |attribute| attribute.type.target_name == aggregate.hecks_name }
|
|
89
|
+
.map { |attribute| [other, attribute] }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# ── the drawing ──────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
# A node id the page source can carry without an identifier in
|
|
96
|
+
# it — `n_atm_card`, never `n_ATMCard`.
|
|
97
|
+
def id(name) = "n_#{Naming.snake(name).gsub(/[^a-z0-9_]/, '_')}"
|
|
98
|
+
|
|
99
|
+
def node(name, focus: false)
|
|
100
|
+
line = " #{id(name)}[\"#{Naming.words(name)}\"]"
|
|
101
|
+
focus ? "#{line}:::focus" : line
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def edge(from, attribute, to)
|
|
105
|
+
" #{id(from)} -->|\"#{Naming.words(attribute.name).downcase}\"| #{id(to)}"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/* The glossary page. Ledger paper: faint blue horizontal rules between
|
|
2
|
+
entries, one red vertical margin rule down the navigation. The rules
|
|
3
|
+
carry information — the red marks the section in view, the blue
|
|
4
|
+
separates entries — and nothing else on the page is decoration. */
|
|
5
|
+
|
|
6
|
+
:root {
|
|
7
|
+
--paper: #f3f5f8;
|
|
8
|
+
--panel: #ffffff;
|
|
9
|
+
--ink: #1a2b4a;
|
|
10
|
+
--ink-soft: #4a5873;
|
|
11
|
+
--ink-faint: #8592a8;
|
|
12
|
+
--rule: #c9d3e3;
|
|
13
|
+
--red: #b0413e;
|
|
14
|
+
--red-wash: rgba(176, 65, 62, 0.09);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@media (prefers-color-scheme: dark) {
|
|
18
|
+
:root:not([data-theme="light"]) {
|
|
19
|
+
--paper: #0f1726;
|
|
20
|
+
--panel: #141e33;
|
|
21
|
+
--ink: #e3e7ee;
|
|
22
|
+
--ink-soft: #b4bdd0;
|
|
23
|
+
--ink-faint: #7f8ba6;
|
|
24
|
+
--rule: #2a3854;
|
|
25
|
+
--red: #d9635f;
|
|
26
|
+
--red-wash: rgba(217, 99, 95, 0.14);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
:root[data-theme="dark"] {
|
|
31
|
+
--paper: #0f1726;
|
|
32
|
+
--panel: #141e33;
|
|
33
|
+
--ink: #e3e7ee;
|
|
34
|
+
--ink-soft: #b4bdd0;
|
|
35
|
+
--ink-faint: #7f8ba6;
|
|
36
|
+
--rule: #2a3854;
|
|
37
|
+
--red: #d9635f;
|
|
38
|
+
--red-wash: rgba(217, 99, 95, 0.14);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
html { color-scheme: light dark; scroll-behavior: smooth; }
|
|
42
|
+
|
|
43
|
+
body {
|
|
44
|
+
margin: 0;
|
|
45
|
+
background: var(--paper);
|
|
46
|
+
color: var(--ink);
|
|
47
|
+
font-family: "Newsreader", Georgia, "Times New Roman", serif;
|
|
48
|
+
font-size: 17px;
|
|
49
|
+
line-height: 1.55;
|
|
50
|
+
font-variant-numeric: oldstyle-nums;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
a { color: var(--red); }
|
|
54
|
+
:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }
|
|
55
|
+
|
|
56
|
+
.page {
|
|
57
|
+
display: grid;
|
|
58
|
+
grid-template-columns: 15.5rem minmax(0, 1fr);
|
|
59
|
+
min-height: 100vh;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* ── the rail ───────────────────────────────────────────────────── */
|
|
63
|
+
|
|
64
|
+
.rail {
|
|
65
|
+
position: sticky;
|
|
66
|
+
top: 0;
|
|
67
|
+
height: 100vh;
|
|
68
|
+
box-sizing: border-box;
|
|
69
|
+
overflow-y: auto;
|
|
70
|
+
padding: 2.4rem 1.3rem 2rem 1.6rem;
|
|
71
|
+
border-right: 1px solid var(--rule);
|
|
72
|
+
font-family: "Instrument Sans", system-ui, -apple-system, sans-serif;
|
|
73
|
+
font-size: 0.92rem;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.rail .domain {
|
|
77
|
+
font-family: "Newsreader", Georgia, serif;
|
|
78
|
+
font-size: 1.55rem;
|
|
79
|
+
font-weight: 600;
|
|
80
|
+
line-height: 1.1;
|
|
81
|
+
margin: 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.rail .sub { color: var(--ink-faint); margin: 0.15rem 0 1.4rem; font-size: 0.82rem; }
|
|
85
|
+
|
|
86
|
+
.rail input {
|
|
87
|
+
width: 100%;
|
|
88
|
+
box-sizing: border-box;
|
|
89
|
+
font: inherit;
|
|
90
|
+
padding: 0.45rem 0.65rem;
|
|
91
|
+
border: 1px solid var(--rule);
|
|
92
|
+
border-radius: 4px;
|
|
93
|
+
background: var(--panel);
|
|
94
|
+
color: var(--ink);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.rail input::placeholder { color: var(--ink-faint); }
|
|
98
|
+
|
|
99
|
+
.rail .count { color: var(--ink-faint); font-size: 0.78rem; margin: 0.45rem 0 1.2rem; }
|
|
100
|
+
|
|
101
|
+
.rail ol {
|
|
102
|
+
list-style: none;
|
|
103
|
+
margin: 0;
|
|
104
|
+
padding: 0;
|
|
105
|
+
border-left: 2px solid var(--red);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.rail li a {
|
|
109
|
+
display: block;
|
|
110
|
+
padding: 0.28rem 0.85rem;
|
|
111
|
+
margin-left: -2px;
|
|
112
|
+
border-left: 2px solid transparent;
|
|
113
|
+
color: var(--ink-soft);
|
|
114
|
+
text-decoration: none;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.rail li a:hover { color: var(--ink); }
|
|
118
|
+
|
|
119
|
+
.rail li a[aria-current="true"] {
|
|
120
|
+
color: var(--ink);
|
|
121
|
+
font-weight: 600;
|
|
122
|
+
border-left-color: var(--ink);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* ── the text column ────────────────────────────────────────────── */
|
|
126
|
+
|
|
127
|
+
main {
|
|
128
|
+
padding: 3.2rem 4rem 7rem 3.6rem;
|
|
129
|
+
max-width: 47rem;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
header h1 {
|
|
133
|
+
font-size: 3rem;
|
|
134
|
+
font-weight: 500;
|
|
135
|
+
line-height: 1.02;
|
|
136
|
+
letter-spacing: -0.012em;
|
|
137
|
+
margin: 0 0 1.1rem;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.vision {
|
|
141
|
+
font-size: 1.28rem;
|
|
142
|
+
font-style: italic;
|
|
143
|
+
line-height: 1.45;
|
|
144
|
+
color: var(--ink-soft);
|
|
145
|
+
margin: 0 0 1.2rem;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.lede { color: var(--ink-soft); margin: 0 0 1.6rem; max-width: 62ch; }
|
|
149
|
+
|
|
150
|
+
figure { margin: 1.2rem 0 1.8rem; }
|
|
151
|
+
|
|
152
|
+
figcaption, .caption {
|
|
153
|
+
font-family: "Instrument Sans", system-ui, sans-serif;
|
|
154
|
+
font-size: 0.8rem;
|
|
155
|
+
color: var(--ink-faint);
|
|
156
|
+
margin: 0 0 0.4rem;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.caption { margin-top: 1.6rem; }
|
|
160
|
+
|
|
161
|
+
pre.mermaid {
|
|
162
|
+
margin: 0;
|
|
163
|
+
padding: 1rem 1.2rem;
|
|
164
|
+
background: var(--panel);
|
|
165
|
+
border: 1px solid var(--rule);
|
|
166
|
+
border-radius: 6px;
|
|
167
|
+
overflow-x: auto;
|
|
168
|
+
font-family: "Instrument Sans", system-ui, sans-serif;
|
|
169
|
+
font-size: 0.85rem;
|
|
170
|
+
line-height: 1.4;
|
|
171
|
+
color: var(--ink-soft);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
pre.mermaid svg { display: block; max-width: 100%; height: auto; }
|
|
175
|
+
|
|
176
|
+
section { margin-top: 4.5rem; scroll-margin-top: 1.5rem; }
|
|
177
|
+
|
|
178
|
+
section h2 {
|
|
179
|
+
display: inline-block;
|
|
180
|
+
font-size: 2.1rem;
|
|
181
|
+
font-weight: 500;
|
|
182
|
+
line-height: 1.1;
|
|
183
|
+
margin: 0 0 0.5rem;
|
|
184
|
+
padding-bottom: 0.3rem;
|
|
185
|
+
border-bottom: 2px solid var(--red);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.about {
|
|
189
|
+
font-size: 1.18rem;
|
|
190
|
+
color: var(--ink-soft);
|
|
191
|
+
margin: 0.5rem 0 0.7rem;
|
|
192
|
+
max-width: 60ch;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
section > p:not(.about):not(.caption) { margin: 0 0 0.8rem; max-width: 62ch; }
|
|
196
|
+
|
|
197
|
+
.rules {
|
|
198
|
+
list-style: none;
|
|
199
|
+
margin: 0 0 1.2rem;
|
|
200
|
+
padding: 0;
|
|
201
|
+
max-width: 62ch;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.rules li {
|
|
205
|
+
padding: 0.38rem 0;
|
|
206
|
+
border-bottom: 1px solid var(--rule);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.terms { margin-top: 1.8rem; border-top: 1px solid var(--rule); }
|
|
210
|
+
|
|
211
|
+
.term {
|
|
212
|
+
display: grid;
|
|
213
|
+
grid-template-columns: 12.5rem minmax(0, 1fr);
|
|
214
|
+
column-gap: 1.6rem;
|
|
215
|
+
padding: 0.9rem 0;
|
|
216
|
+
border-bottom: 1px solid var(--rule);
|
|
217
|
+
scroll-margin-top: 1.5rem;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.term h3 {
|
|
221
|
+
font-size: 1.08rem;
|
|
222
|
+
font-weight: 600;
|
|
223
|
+
line-height: 1.35;
|
|
224
|
+
margin: 0.05rem 0 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.term p { margin: 0 0 0.4rem; }
|
|
228
|
+
.term p:last-child { margin-bottom: 0; }
|
|
229
|
+
|
|
230
|
+
.term p.rule { color: var(--ink-soft); font-size: 0.95rem; }
|
|
231
|
+
|
|
232
|
+
.term a, .rules a {
|
|
233
|
+
color: var(--red);
|
|
234
|
+
font-style: italic;
|
|
235
|
+
text-decoration: underline;
|
|
236
|
+
text-decoration-thickness: 1px;
|
|
237
|
+
text-underline-offset: 2px;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* A search narrows the page to the terms that match. */
|
|
241
|
+
section.searching figure,
|
|
242
|
+
section.searching .rules,
|
|
243
|
+
section.searching .caption,
|
|
244
|
+
section.searching .about,
|
|
245
|
+
section.searching > p { display: none; }
|
|
246
|
+
|
|
247
|
+
section.searching .terms { border-top: 0; margin-top: 0.8rem; }
|
|
248
|
+
|
|
249
|
+
/* Landing on a term from a link. */
|
|
250
|
+
.landed { animation: land 1.4s ease-out; }
|
|
251
|
+
|
|
252
|
+
@keyframes land {
|
|
253
|
+
from { background: var(--red-wash); }
|
|
254
|
+
to { background: transparent; }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
@media (max-width: 760px) {
|
|
258
|
+
.page { grid-template-columns: 1fr; }
|
|
259
|
+
.rail { position: static; height: auto; border-right: 0; border-bottom: 1px solid var(--rule); }
|
|
260
|
+
.rail ol { display: flex; flex-wrap: wrap; gap: 0.2rem 0.6rem; border-left: 0; }
|
|
261
|
+
.rail li a { padding: 0.2rem 0.4rem; border-left: 0; }
|
|
262
|
+
.rail li a[aria-current="true"] { border-bottom: 2px solid var(--red); }
|
|
263
|
+
main { padding: 1.8rem 1.2rem 4rem; }
|
|
264
|
+
header h1 { font-size: 2.3rem; }
|
|
265
|
+
.term { grid-template-columns: 1fr; row-gap: 0.25rem; }
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
@media (prefers-reduced-motion: reduce) {
|
|
269
|
+
html { scroll-behavior: auto; }
|
|
270
|
+
.landed { animation: none; }
|
|
271
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// The page's three behaviours: draw the diagrams, find a term, and
|
|
2
|
+
// keep the rail pointing at the section in view. Nothing here changes
|
|
3
|
+
// what the page says — that is all in the Markdown it was rendered from.
|
|
4
|
+
(function () {
|
|
5
|
+
var root = document.documentElement;
|
|
6
|
+
var dark = root.dataset.theme === "dark" ||
|
|
7
|
+
(root.dataset.theme !== "light" && window.matchMedia("(prefers-color-scheme: dark)").matches);
|
|
8
|
+
|
|
9
|
+
if (window.mermaid) {
|
|
10
|
+
mermaid.initialize({
|
|
11
|
+
startOnLoad: true,
|
|
12
|
+
theme: dark ? "dark" : "neutral",
|
|
13
|
+
fontFamily: "\"Instrument Sans\", system-ui, sans-serif",
|
|
14
|
+
flowchart: { curve: "basis", padding: 12 }
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
var input = document.querySelector(".rail input");
|
|
19
|
+
var count = document.querySelector(".rail .count");
|
|
20
|
+
var terms = Array.prototype.slice.call(document.querySelectorAll(".term"));
|
|
21
|
+
var sections = Array.prototype.slice.call(document.querySelectorAll("main section"));
|
|
22
|
+
var links = {};
|
|
23
|
+
Array.prototype.forEach.call(document.querySelectorAll(".rail a"), function (a) {
|
|
24
|
+
links[a.getAttribute("href").slice(1)] = a;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
function filter() {
|
|
28
|
+
var query = input.value.trim().toLowerCase();
|
|
29
|
+
var shown = 0;
|
|
30
|
+
terms.forEach(function (term) {
|
|
31
|
+
var hit = !query || term.textContent.toLowerCase().indexOf(query) !== -1;
|
|
32
|
+
term.hidden = !hit;
|
|
33
|
+
if (hit) shown += 1;
|
|
34
|
+
});
|
|
35
|
+
sections.forEach(function (section) {
|
|
36
|
+
var any = Array.prototype.some.call(section.querySelectorAll(".term"), function (t) { return !t.hidden; });
|
|
37
|
+
section.hidden = !!query && !any;
|
|
38
|
+
section.classList.toggle("searching", !!query);
|
|
39
|
+
var link = links[section.id];
|
|
40
|
+
if (link) link.parentNode.hidden = section.hidden;
|
|
41
|
+
});
|
|
42
|
+
count.textContent = query ? shown + " of " + terms.length + " terms" : terms.length + " terms";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
input.addEventListener("input", filter);
|
|
46
|
+
filter();
|
|
47
|
+
|
|
48
|
+
if ("IntersectionObserver" in window) {
|
|
49
|
+
var observer = new IntersectionObserver(function (entries) {
|
|
50
|
+
entries.forEach(function (entry) {
|
|
51
|
+
if (!entry.isIntersecting) return;
|
|
52
|
+
Object.keys(links).forEach(function (id) { links[id].removeAttribute("aria-current"); });
|
|
53
|
+
var link = links[entry.target.id];
|
|
54
|
+
if (link) link.setAttribute("aria-current", "true");
|
|
55
|
+
});
|
|
56
|
+
}, { rootMargin: "-8% 0px -82% 0px" });
|
|
57
|
+
sections.forEach(function (section) { observer.observe(section); });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// A link inside a definition lands on a term; a search in progress
|
|
61
|
+
// would hide it, so the search is cleared first.
|
|
62
|
+
document.addEventListener("click", function (event) {
|
|
63
|
+
var a = event.target.closest && event.target.closest("a[href^='#']");
|
|
64
|
+
if (!a) return;
|
|
65
|
+
var target = document.getElementById(a.getAttribute("href").slice(1));
|
|
66
|
+
if (!target) return;
|
|
67
|
+
if (input.value) { input.value = ""; filter(); }
|
|
68
|
+
target.classList.remove("landed");
|
|
69
|
+
void target.offsetWidth;
|
|
70
|
+
target.classList.add("landed");
|
|
71
|
+
});
|
|
72
|
+
})();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Projections
|
|
3
|
+
module Glossary
|
|
4
|
+
# THE THREE SECTIONS NOTHING HOMES TO ONE AGGREGATE. In their own
|
|
5
|
+
# file because `Markdown` reads them and loads before the
|
|
6
|
+
# projector's own body does.
|
|
7
|
+
ROLES = "Roles".freeze
|
|
8
|
+
READ_MODELS = "Read models".freeze
|
|
9
|
+
# Where a term lands when the fact it hangs on is raised by nothing
|
|
10
|
+
# this chapter declares — pizzas' `PizzaPaymentReceived` comes in
|
|
11
|
+
# through a port, so the policy reacting to it homes to no
|
|
12
|
+
# aggregate. Shown, not dropped: a visible blank is how a reader
|
|
13
|
+
# learns the language has a gap there.
|
|
14
|
+
REACTIONS = "Reactions".freeze
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|