The front end — five layers, the template, the two registers, and the bundled explorer
references/cockpit.md — rendered straight from the skill doctrine.
On this page
- The five layers — know which one you’re touching
- How to build one
- The two registers — the Journey and the Exploration deck
- The Journey — four canonical stations (shape fixed, content yours)
- The Exploration deck — the analyst instruments
- The Tools + Report views (optional, copied like the stations)
- The one math rule
- The provenance invariant (Plan 23)
- The bundled tool-level explorer
More architecture topics
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 Journey — 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) + 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 canonical version, 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.
- When
solutions.json/research-index.jsongrow too big to hand-edit (an engagement can reach 700+ KB / hundreds of solutions / 1000+ facts), shard them (Plan 24): author small per-record YAML underdata/solutions/<key>.yaml+data/rounds/<roundId>.yamldata/research-index.yaml, and the two.jsonfiles become a generated, gitignored artifact rebuilt by the pinned assembler. One-shotnode _methodology/contract/assemble.mjs explode data, then addassembleahead of every build (assemble → validate → 11ty). The shards are the source of truth — never hand-edit the assembled JSON. Full doctrine + the surgicaldata-edit.mjseditor:contract.md§ Authoring at scale;customers/ehimareis the live worked example.
- 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 Journey and the Exploration deck
A cockpit holds two registers, and the nav makes the seam legible:
- The Journey — a curated story you narrate: a linear, paced walkthrough through Mission → Requirements → Architecture → Solutions, 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 · Solutions 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.)
- The Exploration — a non-linear analyst deck: the Board, the Verdict, the Sharpness 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.*. 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 Sharpness 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 = Journey · 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 Journey’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 Journey — 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 · Mission / 2 · Requirements / 3 · Architecture / 4 · Solutions).
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 current-state → target-state 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 “Today → Digital → AI” 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 “← the core carries everything / you build more yourself →” framing). It names the forks; it does not parade them — the interactive value-fork belongs in the Verdict screen, where it resolves. (Ehimare renders each architecture as a rich position card with a role badge + wins/gives-up 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 · Solutions — backed by
solutions.json+ the kernelverdict/ranking. The evolved Cockpit as the closing chapter (Phase 4 · Solutions): the verdict-ranked table (Fit / Year-1 / Risk / Verdict 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 Verdict 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 “the instrument (turnable)”. 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 Journey. They share one data spine. Anywhere a solution is listed — a Solutions row, a
Verdict card, a Board 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 Sharpness Matrix keeps its own inline rail.
- Board (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 gapscount, a status pill (live/offen/neu/raus), the best-leverage card flagged (“one move here sharpens the most”), 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 Journey’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 “deferred” column, never dropped, so the architecture set matches the Journey (B7). - Verdict (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 Solutions 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"(Solutions row, Verdict card, Board 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 a Board card). For one solution: itscoveragerows over the criteria (clustered byarea/axis)- ✨ assists + the building block (filler) cards from
fillers.json+ the “core + building blocks + integration layer = one solution” 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 solution 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 building-block card (the never-deleted rule, below solution level).
- ✨ assists + the building block (filler) cards from
- Sharpness Matrix (view
schaerfe) —renderSchaerfe, an Exploration instrument: the candidates × five-facet knowledge grid (open, high-separation cells light up) beside a ranked “suggested moves · by information gain” rail. A 🎯 fork move deep-links the realdecisions.jsonfork into the Verdict 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 · Solutions of the Journey, 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).
The Sharpness Matrix is grid-only (Plan 25). The ranked next-move „rail" — the
separation/leverage heuristic infoGain (gain = w_open × separation × leverage) that once
drove the rail and the Board best-leverage flag — was retired: no engagement kept it, so the
Sharpness Matrix is now the knowledge-state grid alone (candidates × facets, ● bekannt · ◐
geschätzt · ◌ offen). Coverage reads in the Lücken-Schnitt; ranking in the Verdikt/Lösungen.
The Tools + Report views (optional, copied like the stations)
Beyond the scoring surfaces (Station 4 · Solutions / 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 Solutions/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 provenance invariant (Plan 23)
Every asserted value in a cockpit must let the reader instantly verify it. A score, gate, risk, coverage verdict, plan-gating, deployment/property, the €-figure (cost), knowledge facet or landscape tool property is not “clean” because it is filled — it is clean only when it ties back to the research that produced it. The cockpit makes that tie a first-class affordance, and where it is missing it says so out loud. The bar is the whole board, not the scored axes — the surfaces a reader actually chooses on (a price, a “Data residency: Germany”) are held to the same standard as a Fit score (Plan 23 §G).
- Carry the refs in the data. Each fact-bearing carrier is a Plan-19/20/23 object that
co-locates the value with its provenance
facts[](coverageEntry,scoreEntry,deploymentMode,planGating[], the descriptive property objects (dataResidency,crmCustomTypes, …),costInputs(the price basis, §G1),knowledgeFacet, and a landscape tool’sproperties). Thefacts[]ids resolve intoresearch-index.jsonfacts{}({claim, research, confidence, links[], revisedIn}). The criterion/facet id appears once (as the map key), so a value and its provenance can never drift apart. Latest research round wins (^r(\d+)in the fact id) — the freshest fact that states a thing is the one shown. - Property + cost cells verify EXACTLY like a score cell (§G). There is ONE matrix verify
vocabulary, not a per-surface one: every asserted cell — score, descriptive property
(“Properties” rows, via
spec.propertiesprov(s,ctx)+provRef+anchor), and the cost figures (every “Cost”/“recurring” row, carriercostInputs) — ismx-cell-openwith the claim+evidence in its hover tooltip and a click that lands on the detail-pane section it’s explained in (anchor→data-sd-anchor/-crit); ungrounded shows the same honest○. No bespoke dot/badge per surface. The detail-pane fact-sheet still carries the fullprovBadgeas the landing target. - Clickability ≠ groundedness — don’t couple them. The cell-nav (hover-tooltip + click-to-pane)
is the
cfg.openParts/cfg.provenancegate and applies to EVERY non-empty asserted cell. A descriptive property with noprovcarrier (Architecture, Connection) and EVERY cost row (not just the grounded “Cost/year” headline) are still click-to-detail — they just carry no○(they assert no researched fact). Provenance only layers the claim+evidence /○on top when aprovcarrier exists. So every property/cost cell needs a real, existing pane anchor (anchor/data-sd-anchor), or its click lands nowhere; landing on a generic catch-all (the baselinespezif) for a value shown elsewhere reads as a broken click. All gated bycfg.provenance/cfg.openParts(default off → byte-parity). - The shell verifies, uniformly. One primitive —
DecisionShell.factProvenance→provBadge— renders the same affordance on every surface: hover shows the claim, the cell is click-through to the solution-detail point where it’s explained, and the evidence (source links ↗) + research (the research unit) are uncollapsable. Turn it on with the matrix / solution-detailprovenance: truecfg slot (default off → parity-safe; an instance opts in). - Ungrounded is shown, never hidden. A value with no resolving
facts[]renders an explicitunbelegtchip — an honest, surfaced research gap, not a blank. The validator (scripts/provenance-report.mjs) counts grounded vsunbelegtper surface and emits the backfill worklist; it warns, never blocks (progressive enforcement). - Aggregates stay fact-free (transitive provenance). Facts attach to the value cells only;
derived rollups (Verdict %, weighted Fit, composite Risk) inherit provenance through the cells
they sum — never carry their own
facts[]. The kernel reads.val/.cov/.leveland ignoresfacts[], so provenance is purely a contract+shell concern (parity guardrail). - Never invent a source. Backfilling provenance means finding the round that already states
the value; if no past research states it, it stays
unbelegt. A fabricated link is worse than an honest gap.
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.