# Architecture

This document describes the structure of the Professional Calculator scientific
engine: its layers, the module dependency graph, runtime data flow, and the
design decisions behind them.

> **Sub‑domain page set.** Reference pages ship with the app and cross‑link:
> **ARCHITECTURE** (how it's built — *this page*) ·
> [SPECS](../SPECS.md) (the full operation catalog + benchmark) ·
> [DOCUMENTATION](../DOCUMENTATION.md) (how to use it) ·
> [STUDY ATLAS](MATH_STUDY_ATLAS.md) (617 courses/topics to learn, UG→PhD).

> Diagrams use [Mermaid](https://mermaid.js.org/); GitHub renders them inline.

---

## 1. Design goals

| Goal | How it's met |
|---|---|
| **Numerics independent of the DOM** | All of `math/` is pure functions over plain values — no DOM, no globals, no I/O. It runs identically in Node and the browser. |
| **Correctness you can audit** | Every routine has a closed-form test anchor. No "trust me" numerics. |
| **No injection surface** | The expression engine is a hand-written parser + tree-walking evaluator. No `eval`, no `Function`. |
| **Zero runtime dependencies** | Only dev tooling (jest, typescript). Nothing ships to the user. |
| **No build step** | Native ES modules + JSDoc types checked by `tsc`. Ship the source. |
| **Progressive** | The 4-function button calculator works even if the engine modules fail to load (lazy import, graceful degradation). |

---

## 2. Layered architecture

```mermaid
flowchart TB
    subgraph UI["UI layer"]
        HTML["index.html"]
        CSS["styles.css"]
        MAIN["main.js — bootstrap"]
    end
    subgraph CTRL["Controller / presentation"]
        CONTROLLER["controller.js<br/>(button calculator)"]
        REPL["repl.js<br/>(scientific REPL)"]
        VIEW["view.js"]
        STATE["state.js"]
        HISTORY["history.js"]
        STEM["stem.js (STEM Lab)"]
        SUITE["suite.js<br/>(48-tile calculator suite)"]
        LAB["lab.js<br/>(live engine panels)"]
        DOMAINS["domains.js<br/>(Knowledge Explorer UI)"]
    end
    subgraph KNOW["Knowledge / orchestration"]
        ORCH["orchestrator.js<br/>(routing + verification contract)"]
        KNOWLEDGE["knowledge.js<br/>(RAG retrieval)"]
        KB[("knowledge/*.json<br/>taxonomy + atlas")]
    end
    subgraph FACADE["Engine facade"]
        INDEX["math/index.js"]
    end
    subgraph APPLIED["Applied math"]
        MATRIX["matrix.js"]
        CALC["calculus.js"]
        STATS["stats.js"]
        UNITS["units.js"]
        FIN["finance.js"]
        QUANTUM["quantum.js"]
        CIRCUIT["circuit.js"]
        PHYSICS["physics.js"]
        PLOT["plot.js"]
        SYMBOLIC["symbolic.js"]
        NUMTHEORY["numtheory.js"]
        SIGNAL["signal.js"]
        INTERP["interpolate.js"]
        OPT["optimize.js"]
        GEOM["geometry.js"]
        COMBO["combinatorics.js"]
        DECOMP["decomposition.js"]
        COORD["coordinates.js"]
        RANDOM["random.js"]
    end
    subgraph CORE["Core math"]
        COMPLEX["complex.js"]
        RATIONAL["rational.js"]
        SPECIAL["special.js"]
        PARSER["parser.js"]
        CONST["constants.js"]
    end

    HTML --> MAIN
    CSS -.-> HTML
    MAIN --> CONTROLLER
    MAIN --> REPL
    MAIN --> STEM
    MAIN --> SUITE
    MAIN --> LAB
    MAIN --> DOMAINS
    SUITE --> INDEX
    LAB --> INDEX
    DOMAINS --> ORCH
    DOMAINS --> KNOWLEDGE
    DOMAINS --> SUITE
    ORCH --> KNOWLEDGE
    ORCH --> INDEX
    KNOWLEDGE --> KB
    CONTROLLER --> VIEW
    CONTROLLER --> STATE
    CONTROLLER --> HISTORY
    REPL --> PARSER
    REPL --> SYMBOLIC
    MAIN --> INDEX
    INDEX --> APPLIED
    INDEX --> CORE
    APPLIED --> CORE
    PARSER --> COMPLEX
    PARSER --> SPECIAL
    PARSER --> CONST
    PARSER --> MATRIX
    STATS --> SPECIAL
    FIN --> STATS
    MATRIX --> COMPLEX
    QUANTUM --> COMPLEX
    CIRCUIT --> QUANTUM
    PHYSICS --> CONST
    SYMBOLIC --> PARSER
    INTERP --> MATRIX
    DECOMP --> MATRIX
    OPT --> CALC
    PARSER --> NUMTHEORY
    PARSER --> COMBO
    STEM --> PLOT
    STEM --> QUANTUM
    STEM --> SIGNAL
```

**Dependency rule:** edges only point downward. Core math depends on nothing in
the project; applied math depends only on core; the facade depends on both; the
presentation layer depends on the facade; the UI wires it together. There are no
cycles.

---

## 2a. Calculator Suite layer (`suite.js`)

The **Calculator Suite** is a data‑driven presentation module that sits beside the
REPL and labs. It depends only on the engine facade (`math/index.js`) and the DOM —
never on internal math modules directly.

**Registry → renderer pattern.** A single literal, `PAGES`, declares the entire
product surface: 4 pages × 12 tiles, each tile carrying its `inputs` and an array
of named `ops`. A generic renderer (`initSuite`) walks the registry and builds the
DOM; there is **no per‑calculator view code**. Consequences:

- Adding a calculator is adding a data row — the renderer, accessibility wiring,
  error handling, and reactivity come for free.
- The catalog in [SPECS § 3](../SPECS.md#3-operation-catalog) is *generated* from
  the same registry via `suiteManifest()`, so docs cannot drift from behavior.
- `suiteOpCount()` / `suiteTileCount()` / `suitePageCount()` expose the totals the
  UI badge and tests assert against (currently **257 / 48 / 4**).

```mermaid
flowchart LR
    PAGES["PAGES registry<br/>(4×12 tiles, 257 ops)"] --> RENDER["initSuite()<br/>generic renderer"]
    RENDER --> TABS["page tabs<br/>(aria-pressed)"]
    RENDER --> GRID["12-tile grid<br/>(auto-fill minmax)"]
    GRID --> TILE["tile: title · op-select · inputs · Run · aria-live result"]
    TILE -->|"op.run(io)"| IO["io facade<br/>n / nums / s / R / fmt / M"]
    IO --> INDEX2["math/index.js"]
    PAGES --> MANIFEST["suiteManifest()"]
    MANIFEST -.generates.-> SPECS["SPECS.md catalog"]
```

**The `io` facade.** Each `op.run` receives a small, uniform interface so closures
stay terse and never touch the DOM: `io.n(k)` (number), `io.nums(k)` (number list),
`io.s(k)` (raw string), `io.R(expr)` (evaluate through the parser, formatted),
`io.fmt(x)` (compact number), and `io.M` (the whole engine namespace). Every call
is wrapped in try/catch; a throw becomes a visible `⚠ <reason>` rather than a
silent wrong answer.

**Safety & parsing boundaries.** Expression and function‑of‑`x` fields are
evaluated by the hand‑written Pratt parser (`io.R` / `io.M.compute`) — no `eval`.
Matrix literals, number lists, and graph edge lists are parsed explicitly by typed
helpers (`matOf`, `parseNums`, `edgeObjs`/`wadjOf`/`adjOf`), each of which throws a
descriptive error on malformed input.

---

## 2b. Knowledge & orchestration layers (`knowledge.js`, `orchestrator.js`, `domains.js`)

A three-tier knowledge stack turns the engine into a *mathematical operating
system* — a knowledge base, a reasoning router, and a per-domain UI — without
adding a server or a runtime build step. The whole stack is client-side; the
"full stack" is **data → orchestration → presentation**.

**Data tier — `knowledge/*.json` (compiled, committed).** A one-off compiler,
[`tools/build-knowledge.mjs`](../tools/build-knowledge.mjs), reads the governing
blueprint XML taxonomy and the [Study Atlas](MATH_STUDY_ATLAS.md) and emits two
queryable artifacts: `taxonomy.json` (**21 domains → 190 subdomains → 1,094
topics**) and `atlas.json` (**617 leveled courses/topics**). The output is
committed, so the running app fetches plain JSON and the no-build / zero-dependency
contract holds — the XML is never parsed at runtime.

**Retrieval tier — `knowledge.js`.** `createKnowledge(taxonomy, atlas)` builds a
flat, tokenized search corpus once and exposes a pure, deterministic surface:
`search(query, {type, domainId, limit})` (lexical ranking with phrase/title
boosts), `domain(id)`, `atlasFor(id)`, and `suiteTilesFor(id)` (the Suite↔domain
map). It is DOM-free and fetch-free so it is fully testable in Node; a thin
`loadKnowledge()` does the browser fetch.

**Orchestration tier — `orchestrator.js`.** `createOrchestrator(math, knowledge)`
implements the blueprint's **rigor contract**: every `analyze(query)` returns a
research card — `input · assumptions · method · result · verification ·
limitations` plus the best-fit domain and related topics. A query is *classified*
as a computation (routed to the math kernel and **independently re-evaluated** for
determinism, with finiteness/overflow and complex-result checks) or a *knowledge*
lookup (routed to retrieval, explicitly labelled as un-computed). Overflow surfaces
as a `warning`, never a silent number.

**Presentation tier — `domains.js`.** `initDomains(...)` renders the Knowledge
Explorer: 21 domain pages (summary, capabilities, leveled study path from the
atlas, subdomain topic chips, and the *live Suite calculators* mapped to the
domain via `suiteTilesFor` + `suiteManifest()`), plus the orchestrator query box
and its research-card view. All DOM via `createElement`/`textContent`; results in
`aria-live` regions.

```mermaid
flowchart LR
    BP["blueprint XML"] -->|build-knowledge.mjs| KBJSON[("knowledge/*.json")]
    ATLAS["MATH_STUDY_ATLAS.md"] -->|build-knowledge.mjs| KBJSON
    KBJSON --> KNOW2["knowledge.js<br/>createKnowledge()"]
    Q["query"] --> ORCH2["orchestrator.js<br/>classify → solve/retrieve → verify"]
    ORCH2 --> KNOW2
    ORCH2 --> ENG["math/index.js"]
    ORCH2 --> CARD["research card<br/>(rigor contract)"]
    KNOW2 --> UI2["domains.js<br/>21 domain pages"]
    UI2 -.suiteTilesFor.-> SUITE2["suite.js"]
```

---

## 3. Module dependency graph (math only)

```mermaid
flowchart LR
    parser --> complex
    parser --> special
    parser --> constants
    parser --> matrix
    matrix --> complex
    stats --> special
    finance --> stats
    quantum --> complex
    circuit --> quantum
    physics --> constants
    symbolic --> parser
    parser --> numtheory
    parser --> combinatorics
    interpolate --> matrix
    decomposition --> matrix
    optimize --> calculus
    calculus -.-> |callbacks only| none[" "]
    rational --> none2[" "]
    style none fill:transparent,stroke:transparent
    style none2 fill:transparent,stroke:transparent
```

- `complex`, `rational`, `constants` are **leaves** (no internal deps).
- `quantum` builds on `complex`; `circuit` is a fluent builder over `quantum`.
- `physics` reads `constants`; `symbolic` walks the `parser` AST; `plot` is
  dependency-free pure geometry.
- `parser` also depends on `matrix` (matrix/vector literal grammar) and on
  `numtheory` + `combinatorics` (scalar REPL functions like `isprime`, `catalan`).
- `special` is a leaf used by `stats`, `finance`, and `parser`.
- `calculus` takes plain `(x:number)=>number` callbacks, so it is decoupled from
  every other module — anything that produces a function can drive it. `optimize`
  reuses its numerical `gradient`.
- `interpolate` and `decomposition` build on `matrix` (LU `solve`); `signal`,
  `geometry`, `combinatorics`, `coordinates`, and `random` are dependency-free
  leaves.
- `index.js` re-exports all of the above behind namespaces.

---

## 4. Runtime data flow — expression evaluation

```mermaid
sequenceDiagram
    participant U as User
    participant R as repl.js
    participant P as parser.tokenize/parse
    participant E as parser.evaluate
    participant C as complex.js
    U->>R: types "2 + 3i" + Enter
    R->>R: detect assignment? (no)
    R->>P: parse("2 + 3i")
    P->>P: tokenize → [num, op, num, name(i)]
    P->>P: Pratt parse → AST (binary +)
    P-->>R: AST
    R->>E: evaluate(AST, scope)
    E->>C: add({re:2,im:0}, {re:0,im:3})
    C-->>E: {re:2, im:3}
    E-->>R: {re:2, im:3}
    R->>R: format → "2 + 3i"; scope.ans = result
    R-->>U: render log entry
```

Assignment (`x = expr`) is detected at the REPL layer by a regex before parsing,
because `=` is not part of the expression grammar. The RHS is evaluated in the
current scope and bound to the name.

---

## 5. The button calculator (MVC)

The original 4-function calculator is a clean MVC triad, independent of the
engine:

```mermaid
flowchart LR
    DOM[(DOM events)] --> CONTROLLER
    CONTROLLER -->|mutates| STATE[state.js<br/>StateMachine]
    CONTROLLER -->|records| HISTORY[history.js<br/>undo/redo + log]
    CONTROLLER -->|renders| VIEW[view.js]
    VIEW --> DOM
```

- **`state.js`** — an explicit finite-state machine (`idle → entering →
  operator_set → …`) with a frozen transition table; illegal transitions are
  rejected.
- **`history.js`** — a `CircularBuffer` (O(1) push/pop) backing undo, a separate
  redo stack (invalidated on new input), and a completed-calculation log for the
  sidebar.
- **`view.js`** — the only module that touches calculator DOM; a single shared
  ARIA live region announces results.

See [DATA_MODEL.md](DATA_MODEL.md) for the state diagram and entity model.

---

## 6. Key design decisions

### 6.1 Pratt parser over shunting-yard or PEG
Precedence climbing (Pratt) gives correct precedence and associativity with a
tiny, readable recursive-descent core, and makes right-associative `^` and
postfix `!` trivial. No parser-generator dependency.

### 6.2 Everything complex
The evaluator computes over ℂ uniformly; reals are `{re, im:0}`. This means
`sqrt(-4)` just works (`2i`) without special-casing, and the same code serves
real and complex inputs. The UI collapses `im≈0` back to a real for display.

### 6.3 Jacobi for symmetric, QR for general eigenproblems
Symmetric matrices use cyclic Jacobi — simple, robust, and it yields orthonormal
eigenvectors for free. General matrices reduce to Hessenberg form then run
shifted QR to a real Schur form, reading eigenvalues from 1×1 and 2×2 diagonal
blocks (the 2×2 blocks produce complex-conjugate pairs).

### 6.4 Exact rationals via BigInt
`rational.js` keeps `n/d` in lowest terms with `gcd` normalization, so
`0.1 + 0.2` is exactly `3/10`. Integer powers stay exact to arbitrary size.

### 6.5 Dimensional analysis as a safeguard
`units.js` represents quantities as a magnitude plus a 7-vector of SI base
exponents. Addition requires equal dimensions; multiplication adds exponents.
This is the check that rejects `3 kg + 2 m`.

### 6.6 Lazy-loaded engine, graceful degradation
`main.js` boots the button calculator synchronously, then `import()`s the engine
asynchronously. If the engine fails to load, the calculator still works and the
REPL shows a friendly error.

---

## 7. Error handling strategy

| Layer | Strategy |
|---|---|
| Core math | Throw typed errors (`RangeError`, `TypeError`, `SyntaxError`, `ReferenceError`) with precise messages. Pure — never swallow. |
| Parser | `SyntaxError` with character positions; `ReferenceError` for unknown symbols/functions. |
| REPL | Catches all throws and renders them as log entries — never crashes the page. |
| Calculator controller | Each operation wrapped in an error boundary; errors surface as a typed display state with auto-recovery. |

---

## 8. Why no framework

The entire UI is ~3 small controllers over the DOM. A framework would add a
build step, a runtime dependency, and bundle weight for no functional gain. The
engine is the value; the UI is a thin, accessible shell. Native ES modules give
us code-splitting (`import()`), and `tsc` gives us types — without webpack/vite.
