02 — The Marked Language
Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.
Status: phases 1 + 1.5 shipped (2026-07-17) — the resolver, the <Term>
mark + passport, semantic bindings, and file-level pack/tenant provenance,
on three surfaces (Runs, Signals, entity headers). Phases 2–4 below remain
open, as do the questions. Implementation reference at the end.
The idea
Section titled “The idea”jinflow speaks four vocabularies at once — platform, pack, tenant, standards (see the index). Today the reader cannot tell them apart on screen. Signal (platform), Material (pack), whatever the tenant renamed a lab to (tenant), and NTU (standard, ISO 7027) all render in the same ink, as if one authority stood behind all of them. Ubiquitous language works because everyone knows which language a word belongs to — jinflow’s UI currently hides exactly that.
The proposal, in two strokes:
- Every term in the UI is marked along its bounded context — a quiet, consistent visual signature per vocabulary.
- Hovering a term shows its passport — who owns the word, what it means, where its authority comes from.
An earlier thread (“coloring platform terms”) circled the first stroke and
never fully landed — nothing on main marks terms today. This chapter
supersedes it and widens it from platform terms to all four vocabularies,
because a mark that only exists on one context answers no question: the value
is in the contrast.
Why this is a DDD act, not a UI garnish
Section titled “Why this is a DDD act, not a UI garnish”A bounded context that cannot be seen gets violated politely and constantly: a consultant reads a pack term as a platform promise; a tenant reads a renamed label as universal vocabulary; a screenshot travels to another tenant where the same word means something else. Marking the context makes the boundary part of the reading experience — the map, drawn on the territory itself.
It also disciplines us. The moment every term must declare its context to be rendered, an unmarked term becomes a lint warning for vocabulary we never governed. The marked language is a registry with a UI, and the registry is the point.
What already exists (the passport is ~80% written)
Section titled “What already exists (the passport is ~80% written)”| Vocabulary | Source of truth, today |
|---|---|
| Platform | i18n dictionaries (en/de/fr/it.json) + the engine-level glossary sections (Product / Architecture in About → Glossary) |
| Pack | glossary/domain_glossary.yaml — tri-lingual term, aliases, definition, already shipped per pack |
| Tenant | The Sense 43 cascade: display.nav_labels, entity YAML labels — every resolved label already knows which level won |
| Standards | The Codex (Sense 53): id, binding kinds, and the standards registry JinDesk already renders |
Nothing new must be authored to start; the work is resolution and render, not content creation.
Design sketch
Section titled “Design sketch”The mark. A subtle signature, one per vocabulary — the working idea is a colored dotted underline (platform / pack / tenant / standards each get a hue from the theme), consistent everywhere, quiet enough to ignore, present enough to learn within a day. No glyphs inline; the text stays text.
The passport (hover). A small card:
- the term, in the reader’s locale, with its siblings (de/fr/it),
- a context badge — Platform · Pack (nuMetrix) · Tenant (hospital_zeta) · Standard (ISO 7027) — naming the owner, not just the tier,
- the definition from the owning glossary,
- provenance when the cascade spoke: a tenant-renamed label shows its lineage (“Tenant override of pack term Price Palette”) — the pack ≠ tenant distinction, made visible exactly where it lives,
- a link to the authority: glossary entry, Codex standard page, Sense doc.
One component, one resolver. A <Term> (or {@render term(...)})
primitive plus a server-side resolver that answers “which vocabulary owns this
concept, in this tenant, in this locale” from the four sources above. The
resolver is the real deliverable — the same lookup should serve search
(aliases already exist), the glossary tab, and future authoring lint.
Semantic match, not string match (decided 2026-07-17 — Mig’s correction of phase 1’s shipped behavior). Phase 1 looked terms up by their rendered string. On reflection that is name-based inference at render time — the same move the pipeline principles forbid (“the schema is explicit and read literally; heuristics belong only in authoring”). The correct model:
- The surface declares which concept it renders. The Runs page knows
it renders
platform.run; the entity header knows it renders entitymaterials.<Term id=…>, never “guess from my text”. - Entries declare their bindings. A term entry claims the i18n keys
and entity keys it stands behind (
bindings: { i18n: ['runs.title', 'nav.runs'], entities: [] }) — so a surface holding only a key resolves semantically, in every locale and plural, forever. The “Materials” gap dissolves: it was never a missing plural alias, it was a missing binding from glossary entrymaterialto entitymaterials. - String lookup survives only as a bridge for free prose, until authoring-time marking replaces it there too (phase 2+). The lint reports unbound surfaces, not unmatched strings.
Where to mark — opt-in surfaces, not everywhere. Marking every word in a data table is noise; marking the vocabulary-bearing surfaces is signal: page and section headers, nav labels, registry/interpretation prose, fact labels on detail pages, the About/field-manual corpus. Data cells stay unmarked. (Form follows Data; marks follow language.)
Open questions (the argument this chapter wants)
Section titled “Open questions (the argument this chapter wants)”-
Is four the right number of marks? Standards could arguably render as pack-vocabulary-with-citation. Counter: a standard’s authority is exactly what a hospital auditor wants to distinguish. Current lean: four.
-
Where does a pack term inherited unchanged sit? (Upgraded from a lean to a position, 2026-07-17 — Mig: the pack ≠ tenant distinction is more needed than first thought.) The tenant speaks it, the pack owns it — and the distinction is load-bearing for two concrete reasons:
- “Pack seeds, tenant owns” makes provenance drift silently. The
tenant’s
domain_glossary.yamlis a tenant-owned copy; the moment a tenant edits an entry, it becomes tenant vocabulary while still wearing the pack’s badge. No new authoring is needed to fix this:afs updatealready keeps.pack-sync.json, the SHA-256 base manifest for three-way merge — the same mechanism derives “pack voice, spoken unchanged” vs “tenant-diverged” per file (per entry, with a finer manifest). - Pack vocabulary is the lingua franca of a fleet facet (Sense 62). Two hospitals on nuMetrix are comparable in the cockpit because they share the pack’s words; a tenant that diverges starts needing translation at exactly the boundary S62 cares about. An operator wants to see that.
So the mark carries three states, not two: pack (unchanged), tenant-override (of a pack term, with lineage), tenant-native (no pack ancestor). The fleet cockpit speaks pack; the tenant JinDesk speaks tenant; the passport says which register you are reading.
- “Pack seeds, tenant owns” makes provenance drift silently. The
tenant’s
-
Identifier-tier words (
smebit_*,bb_*, dotted verb names likeafs.update): marked as platform, with the passport carrying the two-layer policy (“display name: Subject Matter; frozen identifier: smebit”) — the policy itself becomes discoverable where people meet it. -
Authoring gap: platform i18n keys have no definitions today (the glossary covers the big nouns, not every label). Do we backfill, or does the passport degrade gracefully to “Platform term, no definition yet”? Lean: degrade gracefully, and let the gap list drive glossary work.
Phasing
Section titled “Phasing”- Resolver + passport on one surface — prove the lookup and the hover.
✅ Shipped 2026-07-17:
$lib/terms/registry.ts(pure index + rank rules),$lib/terms/platform_glossary.ts(16 engine nouns, quad-lingual),$lib/server/terms.ts(four sources merged, 60s cache),GET /[tenant]/api/terms,<Term>component (mark + passport), wired on the Runs and Signals headers and entity page headers. Shipped with exact-string lookup — superseded the same day by the semantic-match decision above; the “Materials” header on nuMetrix stays unmarked until bindings land. 1.5. Semantic bindings —<Term binding=…>+ entrybindings(i18n keys, entity keys); rewire the three shipped surfaces; string lookup demoted to a prose bridge. Three-state pack/tenant provenance via the.pack-sync.jsonbase manifest. ✅ Shipped 2026-07-17: the entity headers that string matching missed now mark in every locale (32 entity entries from the KLS entity registry,entity:<key>bindings); glossary provenance is file-level SHA-256 against the sync base — match reads “pack voice, spoken unchanged”, divergence “tenant-override”, unknown says nothing. Per-entry provenance still waits for a finer manifest. - Nav + registry prose — the highest-traffic vocabulary surfaces.
✅ Nav + legend shipped 2026-07-17:
NavItem.termBindingin the destinations registry (ten vocabulary-bearing items), the Sidebar renders them through<Term interactive={false}>(a button may not nest in a nav anchor — plain span, hover-only passport there), all six remaining list-page headers wired (theses,verdicts,perspectives,findings,dossiers,subject-matter), and the user-facing legend — “The four colors of jinflow’s language”, quad-lingual — lives on What-is-this → mold, above the product glossary. ✅ 2b (prose marking, plain-text surfaces) shipped 2026-07-18 — with the taste rules that kept it from shipping casually, set by Mig’s framing “a pack is structurally another tenant; a tenant should know where its words come from”: prose marks pack, standard and tenant vocabulary only (platform words are chrome, already marked in nav and titles — marking every “signal” in a sentence is noise, not provenance); first occurrence per term per block (the academic idiom); longest term wins at a shared position; hard cap per block.segmentProse(pure, tested) +<TermProse>, wired on signal descriptions, thesis statements, dossier narrative previews. Markdown-rendered prose (interpretations, notes) remains open — it needs DOM-walking after the @html render, deliberately a separate care. - The lint — report of unbound vocabulary surfaces per tenant (the governance payoff). ✅ Shipped 2026-07-17: /[tenant]/admin/vocabulary — suggested glossary↔entity bindings with the exact AFS declaration, unbound pack terms, definition gaps; chips carry full passports.
- Search integration — aliases feed the jump bar.
Implementation reference (phases 1 + 1.5, 2026-07-17)
Section titled “Implementation reference (phases 1 + 1.5, 2026-07-17)”The four hues. One per vocabulary, identical in light and dark, on both the mark and the passport badge. The mark itself was revised 2026-07-17 (Mig): not a dotted underline but a small colored dot after the word — a footnote marker in the vocabulary’s hue. Underlines fought the nav’s own hover styles; the dot stays out of the text’s way.
| Vocabulary | Hue | |
|---|---|---|
| Platform | #2f6feb | blue — the engine’s voice |
| Pack | #8957e5 | violet — the domain’s voice |
| Tenant | #2ea043 | green — the local voice |
| Standard | #d29922 | amber — the outside world’s rule |
These are declarations, not theme configuration — the mark must mean the same thing in every tenant, or it means nothing. (Display config may some day soften rendering; it must never remap which hue means which vocabulary.)
The pieces.
| Piece | Where |
|---|---|
Pure model: TermEntry, text + binding indexes, collision rank (tenant > pack > standard > platform), Provenance | explorer/src/lib/terms/registry.ts |
| Platform vocabulary: 16 engine nouns, quad-lingual, plural aliases, i18n bindings | explorer/src/lib/terms/platform_glossary.ts |
Server resolver: merges the four sources, 60s cache per tenant; entity entries from the KLS entity_registry; glossary provenance vs .pack-sync.json | explorer/src/lib/server/terms.ts |
| Registry endpoint | GET /[tenant]/api/terms |
| Client lookup (fetch once per session; binding + text indexes) | explorer/src/lib/stores/terms.ts |
The mark + passport (inline disclosure <button>, hover/focus) | explorer/src/lib/components/Term.svelte |
| Shipped surfaces | /runs h1 (i18n:runs.title), /signals h1 (i18n:signals.title), entity headers (entity:<key>) |
The prose rules (phase 2b, normative — enforced by
$lib/terms/prose.ts and pinned by its tests):
| # | Rule | Value |
|---|---|---|
| 1 | Vocabulary scope in prose | pack · standard · tenant — never platform (chrome is marked in nav/titles; provenance, not noise) |
| 2 | Matching | whole words, case-insensitive, all locale forms + aliases |
| 3 | Collision at a position | longest term wins (“Material Flow” beats “Material”) |
| 4 | Occurrences | first per term per block — the academic define-at-first-use idiom |
| 5 | Cap | 12 marks per block |
| 6 | Integrity | lossless reassembly; on any failure the text renders plain — never broken |
| 7 | Surfaces | plain-text prose only (signal descriptions, thesis statements, dossier previews); markdown prose open |
Component contract. <Term binding="i18n:runs.title" text={$t('runs.title')} />
— binding is the semantic path and the pattern for every new surface;
text alone falls back to exact-string lookup (the prose bridge). An
unresolved term renders as plain text: a glossary gap, never an error.
Not yet documented for end users. The marks appear in the product but no user-facing legend explains them — that legend (a small “the four colors of jinflow’s language” block on the What-is-this page, itself passport-linked) belongs to phase 2 alongside the nav rollout.