The front end — five layers, the template, the two registers, and the bundled explorer
references/cockpit.md — direkt aus der Skill-Doktrin gerendert.
This is the home for everything you render: the layer model that says which file you’re allowed to touch, the copy-the-template-fill-it build recipe, the two registers (the narrated Reise — four canonical stations — and the Exploration deck of analyst instruments), the two-tier brand nav that separates them, and the bundled tool-level explorer. For a solution-design / buy-vs-build engagement the deliverable is a cockpit — a scored, walkable comparison of solutions on Gate · Fit · Cost · Risk under one shared Scenario, with a client walkthrough. For a single tool pick the deliverable is the lighter bundled explorer (last section). Both read their data as JSON; neither is built from scratch and neither is forked from another engagement.
Schemas for every JSON file live in contract.md — the always-open lookup from Phase 2; the
mount(spec) / ctx contract lives in shell/README.md. This file cites both rather than
restating them.
The five layers — know which one you’re touching
This file owns the five-layer model. (CLAUDE.md only routes up to here; if it carries a copy,
it’s a stale duplicate — slim it to a pointer.) The model exists to keep one rule enforceable:
you never edit the layer you depend on.
| Layer | What | Your relationship to it |
|---|---|---|
| Contract | the data schemas — config / mission / criteria / matrix / landscape / research-index / scenario / decisions / architectures → solutions / fillers.json (all defined in contract.md) |
you fill the data |
| Kernel | pure compute — engine/kernel.js (gate/fit/cost/risk, fork→architecture narrowing) |
pinned, never edited |
| Shell | the mechanism — shell/shell.js (DecisionShell.mount, render/URL/keyboard loop, walkthrough frame, fork/architecture renderers, FLIP, copy accessors, the solution detail pane) |
pinned, never edited |
| Template | template/ — a born-split ES-module graph (plan-14): app.js (the thin entry = STATIONS + the DecisionShell.mount spec) + state.js + data/ (constants leaf) + logic/ (cost · scoring · knowledge · status · solutions · infogain) + views/ (arbeitsbrett · verdikt · schaerfe) + app.css + the deploy pair (index.njk + eleventy.config.reference.js) + standalone index.html + stub data |
copied once, then customized |
| Instance | the filled template — the engagement’s content + any bespoke later views | you own it |
The one rule (D4): you pin the kernel + shell — via the _methodology submodule, exactly
like brand → website/_brand — and never touch them; you copy the template once and then it’s
yours. Improvements to kernel or shell flow back to the decision-methodology repo and you re-pin;
they are never patched in place in a consumer.
Copy the template, never fork another engagement. A new cockpit is built by copying
template/ and filling it — never by cloning customers/skischule or customers/ehimare and
deleting their content. Forking an instance drags its case-specific cost engine, its bespoke
copy, and its half-finished later views into a job they don’t fit.
Reference instances are not templates. demo/ (the cats-off-counter walkthrough) and the live
cockpits (customers/skischule, customers/ehimare) are filled templates — read them for ideas,
but copy template/, not them. Treating a filled instance as the scaffold is what produces
improvised, half-built early pages.
Station renderers live in the template, not the shell. The shell owns the invariant mechanism (mount, the frame, the fork/architecture renderers that are identical everywhere). The station renderers (the use-case grid, criteria-by-axis, the architecture finding+rail) live in the template, because engagements visibly customize the early pages — Ehimare’s Mission is an elaborate layered card set; the demo’s is a simple grid. Copy the canon, then adapt it.
How to build one
- Copy
template/into<engagement>/site/. Copysite/eleventy.config.reference.jsup to<engagement>/.eleventy.js— it carries the module-graph cache-bust + passthrough conventions so you inherit them instead of reinventing them (plan-14).site/index.njkis the built deploy entry;site/index.htmlstays the no-build / copy-me dev artifact. - Pin the kernel + shell via the
_methodologysubmodule (the reference config already wires the_methodology+_brandpassthroughs and theapp.jsES-module graph;customers/ehimareis the live worked example). Load order + exact paths:shell/README.md§ Consuming it. The brandbase.cssis REQUIRED in the chain (tokens → base → components → utilities → shell → app): it carries the heading contract (default weights +.h1/.h2/.h3/.display+.eyebrow+ the fluid--fs-*tokens). Instance heading elements wear a brand class (<div class="h3 vl-name">,<div class="eyebrow cl-head">) and theapp.cssrule keeps only the size/rhythm delta — never re-setfont-family/font-weightper bespoke class (that’s how headings silently drifted to 400). - Fill the data in phase order, each file replacing its
PLATZHALTERstubs:mission.json(Phase 1 — use cases withcurrentState/improvementLevel/enables, stakeholders, stress test) →criteria.json(Phase 2 — each criterion on one axis, Fit ones carryinguseCase+area) →decisions.json+architectures.json(Phase 3) →solutions.jsonfillers.json(Phase 4 — the scored compositions, theircoverage, and the gap-filler library) → optionallylandscape.json+research-index.json(the Tools + Report surfaces, the latter carrying therounds[]research log). Shapes are the JSON Schema incontract/schema/(doctrine incontract.md). Validate withnode contract/validate.mjs <engagement>/dataafter edits.
- Customize the stations in
app.js— keep the canonical shape, change the content.
Register-3 copy overrides (B8). The mount spec’s copy: { forks } override map polishes
client-facing strings; it keys by data id (fork id, option key) and falls back silently to
register 2 on a miss. Keys MUST be a subset of the data’s ids/option-keys — a renamed id leaves
an orphaned override key that silently stops applying. The shell emits a dev-mode warning for
orphans (guarded by a dev flag, silent in production). Where the override map lives + the closed-set
rule: model.md § Three registers; the mount(spec)/copy contract: shell/README.md.
The two registers — the Reise and the Exploration deck
A cockpit holds two registers, and the nav makes the seam legible:
- Die Reise — a curated story you narrate: a linear, paced walkthrough through Auftrag → Anforderungen → Architektur → Lösungen, with prev/next, dots, and the presenter keyboard. It is the four canonical stations, and it is the whole story — nothing else is a chapter. (Station 4 · Lösungen is interactive — it carries the same live knobs/FLIP it had as a view; the shell scope-refreshes its body just like a view’s.)
- Die Exploration — a non-linear analyst deck: the Arbeitsbrett, the Verdikt, the Schärfe-Matrix, the Matrix, the Tools landscape, the Report. You jump between these at will to work the solution space. They are instruments, not chapters.
This is one model, one URL space, one data spine — every register reads the same
solutions.json + ctx.kernel.* + the template-level infoGain(ctx). There is no second URL
space and no client/consultant mode split (the old continuum rule’s real content, which still
holds). The split only names the spine’s two halves so the nav can show them — it does not
reintroduce a peer-app or an audience mode. Drill-downs (a solution’s full detail opened in the
shell detail pane from any surface; the Schärfe-Matrix) still live inside Exploration.
The nav is the brand “Navigation E” two-tier docnav, rendered by the shell VERBATIM from the
view registry (not a hand-rolled toggle). Each view declares a group ('reise' | 'exploration');
the spec’s nav block lists the groups (tier-1 = Die Reise · Exploration, the topbar main-links),
and the shell renders tier-2 from the active group’s members into the brand left sidebar
(.docnav-side) — the Reise’s four stations as jump-links, or the Exploration screens as
view-switches. The wordmark cell carries the EV logo + the page title (the brand
.docnav-brand-tag, e.g. “Template”/“Demo”) — there is no separate title bar. (The topbar
util slot can hold an optional .docnav-cta link/button — util:{href,label} or
util:{action,label} — but the template/demo declare none.) This is the engine’s own docs
layout used as-is; you don’t touch any of it, you just load the brand CSS + shell and supply the
skeleton (#shellNav topbar, then a .docnav-shell with <aside id="shellSide" class="docnav-side">
beside <main class="docnav-page"> holding #app). A consumer that declares no groups keeps the
old flat .view-btn behaviour. Replaces the per-instance .view-toggle. (Detail: shell/README.md
§ nav.)
The Reise — four canonical stations (shape fixed, content yours)
Stations 1–4 mirror the first four methodology phases (process.md) and live in the template’s
STATIONS array (titled 1 · Auftrag / 2 · Anforderungen / 3 · Architektur / 4 · Lösungen).
Stations 1–3 are inline render functions; Station 4 reuses the solutions renderers
(renderLoesungen/loesungenBody) and declares body/bodySelector/flipRows + onClick/onInput
so the shell scope-refreshes it like an interactive view. They are the entire narrated arc — Phase 5
renders as Exploration instruments (below), not as a station. Keep the shape and the data sources;
adapt the voice and depth. The
canon is grounded in the two real cockpits (Skischule ⋂ Ehimare) — when you customize, match what
they actually do; don’t invent a new layout, and don’t let the template/demo drift from the real
instances.
- Station 1 · Mission — backed by
mission.json. The tool-neutral use cases (what the system must do, never a product), each carrying its transition —currentState(none/manual/digitized) →improvementLevel(enable/digitize/improve/ai-assist) — plus the stakeholders (decides / uses-daily / sceptic) and the one stress test. Use the word use cases, not “jobs” (the use-case spine ismodel.md). Ehimare dresses this as an elaborate layered card set — that’s instance copy over the same mission data; the canonical generic is the use-case grid. - Station 2 · Requirements — backed by
criteria.json. All criteria, grouped by axis (Gate / Fit / Risk) and, within Fit, clustered byarea(the use-case clusters). The canonical way to show a functional use case is its Ist → Soll ladder: what the team does today (currentState) → how the solution lifts it (improvementLevel), with the optional ✨ AI-assist tier where anai-assistlevel applies. This is the generalization of Ehimare’s hand-built “Heute → Digital → KI” ladder — same shape, now driven by the use-case + criterion fields instead of hardcoded. Plus the cost-is-its-own-axis note (cost must never contaminate Fit —model.md). The requirements are the criteria, each on exactly one axis (single-placement —model.md). - Station 3 · Architecture — backed by
decisions.json+architectures.json. Leads with the structural finding (a closed fork’sclosedBy) and the architectures along the buy↔build spectrum (ctx.forks.rail, with the “← der Kern trägt alles / Du baust mehr selbst →” framing). It names the forks; it does not parade them — the interactive value-fork belongs in the Verdikt screen, where it resolves. (Ehimare renders each architecture as a rich position card with a role badge + Gewinnt/Gibt-auf tradeoffs; that’s a richer skin over the same rail — adopt it when the architectures deserve the room. Putting the interactive narrowing control here inverts the phase: the space comes before the lever that reshapes it. Architecture-is-not-solution:model.md.) - Station 4 · Lösungen — backed by
solutions.json+ the kernelverdict/ranking. The evolved Cockpit as the closing chapter (Phase 4 · Solutions): the verdict-ranked table (Fit / Jahr-1 / Risk / Verdikt columns) with live day-rate + preset knobs and FLIP re-rank, topped by the verdict banner (names #1 at the current weighting, invites override) + the ★ recommended preset. Unlike Stations 1–3 it is interactive — it declaresbody/bodySelector/flipRowsonClick/onInput, so the shell scope-refreshes just#loesungenBody(with FLIP) on a knob, the same machinery a view uses. Each row is click-to-open in the detail pane. One ranking path powers both this station and the Verdikt screen (the two-surface proof —model.md). Two distinct elements, never conflated (B4): (a) the authored verdict — reads the data’srecommended+verdictNote(contract.md) — is the lead; (b) the kernel ranking sits below it, labelled “das Instrument (drehbar)”. When the authored favourite ≠ the instrument’s current #1 (a legitimate divergence —model.md§ the verdict is authored), show both and say why. The banner must never pass the instrument’s top row off as the recommendation.
Bespoke later views (a configurator, a presentation mode) are pure instance code.
The Exploration deck — the analyst instruments
These are the Exploration register (above) — peer screens reached from the Exploration menu, not
chapters of the Reise. They share one data spine. Anywhere a solution is listed — a Lösungen row, a
Verdikt card, an Arbeitsbrett card, a Matrix column — the row/card is click-to-open: it emits
data-shell-action="opensol" data-sol="<key>" and the shell slides in the solution detail pane
(below). The Schärfe-Matrix keeps its own inline rail.
- Arbeitsbrett (view
arbeitsbrett) — backed bysolutions.json(+architectures.jsonfor the columns). The per-architecture board: one column per architecture (grouped off each solution’sarchitecturefield), a card per solution carrying the four lens icons (☑ Gate · ▥ Fit · € Cost · ⚠ Risk) each with a knowledge-state dot (offen/geschätzt/bekannt), anN Lückencount, a status pill (live/offen/neu/raus), the best-leverage card flagged (“ein Zug hier schärft am meisten”), and eliminated cards shown as eliminated with their cause named — a failed gate, or the decision that ruled them out (the two elimination causes —model.md; eliminations show, never vanish — P1; a decision-elimination is reversible and is never rendered as a fabricated gate-0). Clicking a card opens that solution in the detail pane (below) — which carries the Composition the card used to expand inline, plus the full Gate·Fit·Cost·Risk picture. The Reise’s exit (and Esc) lands here. A shape with no solutions — deferred or not yet surveyed (status:"deferred",contract.md) — still renders as a labelled empty “zurückgestellt” column, never dropped, so the architecture set matches the Reise (B7). - Verdikt (view
verdikt) — backed by the kernelverdict/ranking. The top solution + 2 alternatives + their tradeoffs (each card click-to-open in the detail pane); the live value-forks resolve here (turn a hinge → the architecture rail narrows); a banner names #1 at the current weighting and invites override; the verdict reads off the samerankAllthe Lösungen list uses (the two-surface proof). The verdict is the consultant’s judgment instrument: the consultant authors it, the agent (and the cockpit) presents it and never decides or eliminates on its own (the doctrine ismodel.md; eliminations show as a hard-filter 0, never a removed row). - Solution detail pane (per-solution) —
renderSolutionDetail(sol, ctx)rendered into the shell-owned slide-in pane (spec.solutionDetail→ctx.openPane; scrim/✕/Esc close). Opened from any surface that emitsdata-shell-action="opensol"(Lösungen row, Verdikt card, Arbeitsbrett card, Matrix ⓘ). For one solution, with room to breathe: the verdict score + the three axis bars (fit/cost/risk at the live weighting), the Fit breakdown, the 4-bucket Year-1 cost, the risk dimensions, the tradeoff, the knowledge lenses, and finally the Composition (renderComposition(sol, ctx)— the same coverage detail the Arbeitsbrett used to expand inline). It reads the samerankAll(ctx)the lists rank by. The pane header carries an ⤢ enlarge button (next to ✕) that promotes the same body to a full-page detail screen with a breadcrumb back to the originating view (URL-synced as?sol=, closeable via breadcrumb/Esc/browser-back). Supplyspec.solutionTitle(key, ctx)so the screen heading + breadcrumb label survive a cold reload — seeshell/README.md§ Solution detail pane. - Composition detail (per-solution) —
renderComposition(sol, ctx), now rendered inside the detail pane (it was reached by selecting an Arbeitsbrett card). For one solution: itscoveragerows over the criteria (clustered byarea/axis)- ✨ assists + the Baustein (filler) cards from
fillers.json+ the “Kern + Bausteine + Verbindungs-Schicht = eine Lösung” equation. Render coverage as a glyph for the qualitative shape with the Fit score on hover — the two are distinct fields and a mismatch (inbut Fit 2) must stay visible, never collapsed (the coverage→Fit bridge,model.md). Pre-score like Tools — no scoring leak. This is Ehimare’s Lösungs-Explorer generalized (de-hardcodedCLUSTERS→area,SIDECARS→fillers.json). The ✨ assist row is solution-aware (B6): it renders for this solution only when the solution’s owncoveragereferences the assisting filler (aug:/part:) — a filler the solution can’t carry shows no ✨ row (contract.md§ fillsAssistFor). A filler’seliminated[]ways render struck under its Baustein card (the never-deleted rule, below solution level).
- ✨ assists + the Baustein (filler) cards from
- Schärfe-Matrix (view
schaerfe) —renderSchaerfe, an Exploration instrument: the candidates × five-facet knowledge grid (open, high-separation cells light up) beside a ranked “Vorgeschlagene Züge · nach Informationsgewinn” rail. A 🎯 fork move deep-links the realdecisions.jsonfork into the Verdikt screen (where it resolves); facet moves only highlight (no auto-action — P1). (The verdict-ranked solutions list — the evolved Cockpit — is no longer an Exploration screen; it is Station 4 · Lösungen of the Reise, above.) - Matrix (view
matrix) —renderMatrix, the full scoring grid: criteria × solutions, grouped by tier (Hard Filters / kritisch / standard / nice-to-have), sortable (click a solution column → by Total, a criterion row → by that criterion; survivors always rank above gate-eliminated columns, which render struck + dimmed), live tier weights (the per-tier<input>re-ranks subtotals + Total in place —config.tierWeightsare just the starting values), tier show/hide toggles, per-tier Fit subtotals + a grand Total (Fit %), and reasoning on hover (solutions[].reasoning[critId], graceful fallback to the criterion description). Below the grid, a generic cost/effort block (Aufwand PT · Projekt-Kosten · laufend/Jahr · Jahr-1) from the kernel TCO — no case-specific work-package steps; that detail stays in an engagement (e.g. Skischule’s Chatwoot configurator). Subtotals + Total sum Fit-axis only (Risk is its own axis, shown not rolled up; Cost lives in the cost block). Sticky frozen header + first column; the active sort round-trips in the URL. Each solution column header carries a small ⓘ button that opens that solution in the detail pane (the header name stays the sort handle — the ⓘ never sorts).
infoGain(ctx) is template-level, not kernel. The separation/leverage heuristic
(gain = w_open × separation × leverage) that drives the Schärfe rail and the Arbeitsbrett
best-leverage flag lives in the instance app.js, exactly like coverageRollup — it graduates to the kernel
when it generalizes (a second engagement wants the same heuristic). Until then it is copied template code.
The Tools + Report views (optional, copied like the stations)
Beyond the scoring surfaces (Station 4 · Lösungen / the Matrix view), the template ships two more
that surface the Phase-4 research itself. Both are optional — drop the data file and the view shows
an empty note — and both are copied template code (renderLandscape / renderReport in
app.js), not shell.
- Tools →
renderLandscape, backed bylandscape.json. The raw, pre-score candidate list from the landscape survey — filter / search / click→detail, with gate badges and the filter chips derived from the engagement’s owncriteria.jsonGate-axis entries (and the category buttons + capability chips auto-derived from the data). It is the unscored surface; the scored one is Lösungen/Matrix (or, for a pure tool pick, the bundled explorer below). It carries no ranking of its own — a landscape report’s offered winner is discarded on ingest (the ingest/audit procedure isresearch-briefs.md). - Report →
renderReport, backed byresearch-index.json(the append-onlyrounds[]log —contract.md). The research narrative: one proposal sketch per architecture (position), cross-cutting assist briefs, open meeting questions, links to the raw research files. The Report is the history — stations show the latest judgment, the Report shows how it got there (regression appends, never rewinds —process.md). A standout chip jumps into the Tools view.
Only the mechanism is canonical — the data is engagement substance. Don’t carry another
engagement’s landscape.json/research-index.json content across; re-derive it from this job’s own
survey. (These views graduated into the template from Skischule + Ehimare; the transfer discipline
is transferring-between-engagements.md.)
The one math rule
The instance app.js carries no scoring/cost math except a single
instanceCost(sol, scenario) hook — because per the kernel contract, recurring/usage/setup
pricing is engagement code, not kernel code (the TCO method is pricing-tco.md; the pricing
schema fields are contract.md). Everything else routes through ctx.kernel.*
(ranking/fit/risk/viewCriteria/passesHardFilters) and ctx.forks.*. The cost engine is
the one place a cockpit legitimately diverges per engagement (seats × month, usage, a flat
retainer, a configurator) — keep it in instanceCost, out of the kernel.
The bundled tool-level explorer
When the job is a single-category tool pick (not a composed solution), use the bundled
explorer.html instead of a cockpit. It is a single-file, dependency-free HTML app that renders a
tool-selection matrix as three views:
- Ranked — who survived the hard filters, ordered by weighted Fit score.
- Matrix — the full grid, reasoning on hover, live tier weighting.
- Detail — one tool at a time: verdict, strengths/weaknesses, risks, fit, pricing, full score breakdown.
Scope — this is the tool-level surface. It renders gates + the weighted Fit score for a matrix
of tools. It does not render the solution-level model — the shared Scenario, the Cost/Risk
axes, compositions (tools + build + integration), or the re-weightable verdict instrument. That is
the shared shell + kernel; for a solution-design / buy-vs-build engagement, build a cockpit by
copying template/ (above). Use this explorer for single-category tool picks and to drive the Fit
axis of a larger engagement. It is project-agnostic: it reads its data from sibling JSON files; you
never edit the HTML for a new engagement.
Set up an engagement:
- Copy
explorer.htmlinto the engagement folder. - Add
criteria.json(produced in Phase 2 — the requirements as data). - Add one
matrix-<key>.jsonper category. - Add
config.json(title, subtitle, categories, default tier weights). - Serve the folder and open it:
Thepython -m http.server 8000 # → http://localhost:8000/explorer.htmlfile://gotcha: opening viafile://is blocked by browser CORS forfetch— always serve overhttp://.
The example files shipped beside it (config.example.json, criteria.example.json,
matrix-booking.json) are a complete working demo — copy them next to explorer.html, rename
*.example.json → *.json, serve, and it runs. The schema for config.json, criteria.json, and
matrix-<key>.json is defined authoritatively in contract.md — read that, don’t
reverse-engineer it from the example files.
Runtime behaviours worth knowing:
- A tool with any hard-filter score of
0is eliminated and drops to the bottom, struck through, with the failed filter named — an elimination is a recorded hard-filter 0, never a deleted row. - An omitted score shows as
?(not evaluated) and doesn’t count against the tool — so the explorer is useful even half-filled, which is the normal state mid-loop. - Tier weights are editable live in Matrix view;
config.tierWeightsare just the starting values — good for sensitivity-testing a recommendation in front of a client. pricingrenders generically: every key you include shows as a labelled row in the detail view, andannual(if present) becomes an informational matrix row. Setverified: trueonce a human confirms a figure. (Pricing runs after scoring and never enters the Fit number —model.md.)
Contract details for every JSON file: contract.md. The mount(spec) / ctx contract:
shell/README.md. The TCO method behind instanceCost: pricing-tco.md. The discipline if you
ever move a pattern between instances: transferring-between-engagements.md.