Architektur

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 schemasconfig / 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

  1. Copy template/ into <engagement>/site/. Copy site/eleventy.config.reference.js up 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.njk is the built deploy entry; site/index.html stays the no-build / copy-me dev artifact.
  2. Pin the kernel + shell via the _methodology submodule (the reference config already wires the _methodology + _brand passthroughs and the app.js ES-module graph; customers/ehimare is the live worked example). Load order + exact paths: shell/README.md § Consuming it. The brand base.css is 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 the app.css rule keeps only the size/rhythm delta — never re-set font-family/font-weight per bespoke class (that’s how headings silently drifted to 400).
  3. Fill the data in phase order, each file replacing its PLATZHALTER stubs: mission.json (Phase 1 — use cases with currentState/improvementLevel/enables, stakeholders, stress test) → criteria.json (Phase 2 — each criterion on one axis, Fit ones carrying useCase + area) → decisions.json + architectures.json (Phase 3) → solutions.json
    • fillers.json (Phase 4 — the scored compositions, their coverage, and the gap-filler library) → optionally landscape.json + research-index.json (the Tools + Report surfaces, the latter carrying the rounds[] research log). Shapes are the JSON Schema in contract/schema/ (doctrine in contract.md). Validate with node contract/validate.mjs <engagement>/data after edits.
  4. 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 transitioncurrentState (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 is model.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 by area (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 an ai-assist level 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’s closedBy) 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 kernel verdict/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 declares body/bodySelector/flipRows
    • onClick/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’s recommended + 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 by solutions.json (+ architectures.json for the columns). The per-architecture board: one column per architecture (grouped off each solution’s architecture field), a card per solution carrying the four lens icons (☑ Gate · ▥ Fit · € Cost · ⚠ Risk) each with a knowledge-state dot (offen/geschätzt/bekannt), an N Lücken count, 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 kernel verdict/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 same rankAll the 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 is model.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.solutionDetailctx.openPane; scrim/✕/Esc close). Opened from any surface that emits data-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 same rankAll(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). Supply spec.solutionTitle(key, ctx) so the screen heading + breadcrumb label survive a cold reload — see shell/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: its coverage rows over the criteria (clustered by area/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 (in but 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-hardcoded CLUSTERSarea, SIDECARSfillers.json). The ✨ assist row is solution-aware (B6): it renders for this solution only when the solution’s own coverage references the assisting filler (aug:/part:) — a filler the solution can’t carry shows no ✨ row (contract.md § fillsAssistFor). A filler’s eliminated[] ways render struck under its Baustein card (the never-deleted rule, below solution level).
  • 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 real decisions.json fork 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.tierWeights are 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.

  • ToolsrenderLandscape, backed by landscape.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 own criteria.json Gate-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 is research-briefs.md).
  • ReportrenderReport, backed by research-index.json (the append-only rounds[] 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:

  1. Copy explorer.html into the engagement folder.
  2. Add criteria.json (produced in Phase 2 — the requirements as data).
  3. Add one matrix-<key>.json per category.
  4. Add config.json (title, subtitle, categories, default tier weights).
  5. Serve the folder and open it:
    python -m http.server 8000
    # → http://localhost:8000/explorer.html
    
    The file:// gotcha: opening via file:// is blocked by browser CORS for fetch — always serve over http://.

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 0 is 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.tierWeights are just the starting values — good for sensitivity-testing a recommendation in front of a client.
  • pricing renders generically: every key you include shows as a labelled row in the detail view, and annual (if present) becomes an informational matrix row. Set verified: true once 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.

← Architektur-Überblick