Every quality attribute, factor, weight, option, qaFit value, rule, preset, fitness template,
and string lives in src/config/ or src/i18n/ — never hard-coded in components. The
scoring math is pure and lives in src/lib/scoring.ts. So the model is auditable and extensible:
edit data, not UI.
Canonical source. The numeric model is frozen in
docs/03-blueprint/model-data-sheet.mdand the computation inscoring-algorithm.md. The bilingual educational copy is inoption-content-sheet.md. When you change a model value in code, change it in the Model Data Sheet too and re-run the guards (below) so the docs and the app never drift.
| You want to change… | Edit | Notes |
|---|---|---|
| Quality attributes (the 12 QAs) | src/config/qualityAttributes.ts |
Keep QA_ORDER and the qaFit vector order in sync |
| Project factors (the 14) | src/config/factors.ts |
label/levels/help are canonical; question/gloss/primary are guided-mode UI copy |
| Factor → QA influence matrix | src/config/factorQaMatrix.ts |
budget is inverted in the engine — don't add a sign here |
Dimension options + qaFit |
src/config/dimensions.ts |
Vectors in QA_ORDER; option order = tie-break order |
| Default factor levels | src/config/defaults.ts |
All 0 except ttm:1, budget:2 |
| Scenario presets | src/config/presets.ts |
Full 14-level sets; must hit the SRS Section 5.3 targets |
| Anti-pattern rules | src/config/antiPatterns.ts |
Pure predicate over factors + selected options |
| Fitness-function templates | src/config/fitnessFunctions.ts |
One per QA |
| Risk register data | src/config/risks.ts |
Keyed "<dim>:<optId>"; ported from the Option Content Sheet |
| Cost/ops indicators | src/config/costOps.ts |
Per D1 option (defensible defaults — see DECISIONS.md) |
| Detail narrative + option blurbs | src/config/dimensionContent.ts |
"good / cost / know" + per-option one-liners |
| Migration paths | src/config/migrationPaths.ts |
fresh / big / mix step lists |
| Glossary terms | src/config/glossary.ts |
Method terms (QAs come from qualityAttributes.ts) |
| Methodology references | src/config/references.ts |
Surfaced in the "How it works" panel |
| UI chrome strings | src/i18n/dict.ts |
{ en, id }; add a key, use t('key') |
All copy is { en, id }. To add a third language: widen the Lang type in src/types.ts, add the
language to every Bilingual value (config + dict.ts), and add a toggle option in the header.
A t(key) / tr(bilingual) helper from useI18n() resolves the active language.
The toolbar (and command palette) export the current setup as JSON (scenario = factors +
selections + overrides + mode + language) and re-import it; the full state is also encoded in the
share-URL hash. See src/lib/scenarioIO.ts and src/lib/urlState.ts.
Articles are Markdown files under content/<section>/<slug>.md (git-as-CMS; PR-reviewed). They
are the teaching layer around the Advisor — plain-language for newcomers, deeper for experts — and
every article is bound to the frozen model.
- Create
content/<section>/<slug>.md(<section>∈ catalog · playbook · review · library; more sections are defined insrc/config/sections.tsbut gatedavailable: falseuntil built). - Fill the frontmatter — the contract is
src/config/contentSchema.ts:title_id/_en,slug(must equal the filename),section,audience(awam/expert),summary_tldr_id/_en,evidence_strength(strong/moderate/emerging),last_reviewedandreview_due = last_reviewed + 12 months,translation_status(must includeen— English-first content, decision 2026-07),related_advisor({dimensions, options} — every id must exist insrc/config/dimensions.ts),sources(≥1 with a well-formed URL/DOI),status,author. - Write the body: TL;DR → explanation → deep dive → "Try in the Advisor" → the credibility block renders sources + evidence badge + review date automatically. Diagrams: hand-built SVG or static images only — no Mermaid (see DECISIONS.md).
- Validate:
npm run content:validate(the "Minimum Viable Article" gate — schema, primary source, review dates, unique slug, and therelated_advisor↔ model check). It runs in CI.
Guided / Expert layers. Wrap depth in :::expert … ::: (and plain intros in :::guided …
:::) — the safe renderer maps these to the app's .expert-only / .guided-only classes, so the
Insights header's Guided/Expert toggle tailors the reading for newcomers vs experts.
The per-architecture pages are data-driven, not Markdown. All four sections render an entry for every architecture the Advisor evaluates (all 21 D1–D5 options) from four datasets keyed by the frozen model:
- Catalog —
src/config/readerContent.ts(what it is / when it fits / what it costs / deeper, plusREADER_CITATIONS). - Playbook —
src/config/insightPlaybooks.ts(goal, prerequisites, steps, best practices, pitfalls). - Review —
src/config/insightReviews.ts(overview, pros/cons, performance, scalability, developer experience, use cases, verdict). - Library —
src/config/insightLibrary.ts(definition, key concepts, related patterns, terminology).
To extend an architecture's coverage, edit its 'D<n>:<optionId>' entry in the matching dataset —
do not add a per-architecture Markdown file. A unit test (LearnView.test.tsx) asserts 21×4
parity, so a missing entry fails the build. Hand-authored Markdown is for cross-cutting guides,
methods, and Library further-reading articles.
Article bodies are English (the site's default language; ID titles/TL;DRs stay in frontmatter
for the language toggle); content never re-copies model data — reference src/config so it can't
drift from the engine. Link liveness (links:check) and review cadence (content:review) are
separate non-blocking checks, not build gates (deferred to a later wave).
These sections curate and exercise the content above — they never carry their own explanations:
- Add a learning path — append to
LEARNING_PATHSinsrc/config/insightRoadmaps.ts. Steps are{ kind: 'arch', dim, optionId, lens },{ kind: 'article', slug }, or{ kind: 'advisor' }; every target must already exist (src/config/waveC.test.tsresolves them all, so a typo fails the build). - Add a quiz module/question — edit
ACADEMY_QUIZZESinsrc/config/academyQuizzes.ts.answeris an index intochoices; give every question anexplainand areviewdeep link to the page that teaches it (both unit-tested). Scoring stays client-side — no persistence. - Add a Lab experiment — append to
LAB_EXPERIMENTSinsrc/config/labExperiments.ts. Set all 14 factors inlevels(values 0–2, validated againstFACTORSby the unit test), state the hypothesis, what to watch, the takeaway, and thefocuslist of"D<n>:<optionId>"keys (rendered as deep-link chips). "Run it" loads the levels into the live Advisor.
Holistic parity (don't break it): like the four lenses (21×4), each Wave C section must cover
all 21 architectures — every one in ≥1 learning path, reviewed by ≥1 quiz question, and in play
in ≥1 experiment. waveC.test.ts asserts this, so removing a step/question/experiment that was
the sole cover for an architecture fails the build until you cover it elsewhere. If you add a new
option to a dimension (model change), all three Wave C datasets need an entry covering it, along
with the four lens datasets.
npm run build ends with scripts/generate-seo.mjs, which writes dist/sitemap.xml and a static
HTML snapshot per article (dist/insights/<section>/<slug>/) — new articles are picked up
automatically. If the deployed origin ever changes, update SITE_URL in src/config/site.ts and
the canonical tag in index.html and public/robots.txt — the script fails the build until all
three agree.
node scripts/verify-model.mjs # the math + fixtures + all 25 preset targets hold
node scripts/cross-check-docs.mjs # the docs agree with each other and the prototype
npm run test # the engine's TypeScript twin (fixtures A–C, overrides, presets)
npm run lint && npm run build # types + lint + production buildIf a model change breaks a preset target, adjust the preset levels — not the targets — and re-run, per the Model Data Sheet Section 6.