Tech Architecture Document
Stack
| Layer |
Choice |
Rationale |
| Language |
TypeScript |
Type safety for arithmetic logic; catches operand/type bugs at compile time. |
| UI Framework |
React 18 (Vite) |
Lightweight component model; Vite gives fast dev HMR and a zero-config static build output. |
| Styling |
CSS Modules |
Scoped styles, no runtime overhead, easy responsive layout. |
| Testing |
Vitest + React Testing Library |
Co-located with Vite; unit tests for arithmetic logic, component interaction tests for UI. |
| Build / Deploy |
Vite build → static files |
Produces a plain dist/ folder deployable to any static host (Netlify, GitHub Pages, S3, etc.). |
| CI |
GitHub Actions |
Lint, type-check, test, and build on every push; deploy dist/ on merge to main. |
Components
graph TD
App["App (root)"]
Display["Display\n• shows current operand A, operator, operand B\n• shows result or error"]
OperandInput["OperandInput × 2\n• controlled numeric text field\n• accepts decimal & negative values"]
OperatorSelector["OperatorSelector\n• four buttons: + − × ÷\n• highlights active operator"]
ActionBar["ActionBar\n• = (Calculate) button\n• C (Clear) button"]
Calculator["calculator.ts\n(pure logic module)"]
App --> Display
App --> OperandInput
App --> OperatorSelector
App --> ActionBar
App -- "calls" --> Calculator
Component responsibilities
| Component |
Responsibility |
App |
Owns all state (operandA, operandB, operator, result, error). Wires child events to state updates and calls the calculator module. |
Display |
Pure presentational; renders the current expression and result/error string. |
OperandInput |
Controlled <input type="text"> that accepts numeric strings including decimals; reports onChange to App. |
OperatorSelector |
Renders four operator buttons; reports onSelect to App. |
ActionBar |
Renders = and C buttons; fires onCalculate / onClear callbacks. |
calculator.ts |
Pure-function module: calculate(a: number, op: Operator, b: number): number | CalcError. No DOM dependency; fully unit-testable. |
Data Model
All state is ephemeral (in-memory React state). There is no database, localStorage, or network persistence.
erDiagram
APP_STATE {
string operandA "raw input string for operand A"
string operandB "raw input string for operand B"
Operator operator "'+' | '-' | '*' | '/'"
number result "computed result (null until calculated)"
string errorMessage "non-null when a calc error occurs"
}
Type definitions (TypeScript)
type Operator = '+' | '-' | '*' | '/';
type CalcError =
| { kind: 'DIVISION_BY_ZERO' }
| { kind: 'INVALID_INPUT'; field: 'a' | 'b' };
type CalcResult = { ok: true; value: number } | { ok: false; error: CalcError };
calculator.ts exports a single function:
export function calculate(a: number, op: Operator, b: number): CalcResult;
Integrations
This version has no external integrations. All computation runs entirely in the browser.
| Integration |
Status |
Notes |
| Backend API |
None |
All arithmetic is client-side; NFR-6 (static deployability) rules out a server runtime. |
| Authentication service |
None |
No accounts or sessions (Assumption 2). |
| Analytics / telemetry |
None |
Out of scope for v1; can be added later without architectural change. |
| CDN / static host |
Optional |
Any static host works (dist/ is self-contained). Recommended: Netlify or GitHub Pages for zero-cost deployment. |
Non-Functional Requirements
| ID |
NFR |
Architectural approach |
| NFR-1 |
Result ≤ 100 ms |
All logic runs synchronously in the browser; no network round-trip. Vite-bundled output is ≤ 50 kB gzipped. |
| NFR-2 |
Responsive (desktop + mobile) |
CSS Modules with a fluid grid; OperandInput and OperatorSelector reflow to single-column below 480 px. |
| NFR-3 |
WCAG 2.1 AA |
Semantic HTML (<button>, <label>, <input>); colour contrast validated in design tokens; all interactive elements reachable via Tab; aria-live region for result/error announcements. |
| NFR-4 |
Chrome / Firefox / Safari / Edge |
TypeScript compiled to ES2020 (broadly supported); no experimental browser APIs used. Vitest browser-mode smoke test on CI. |
| NFR-5 |
Modular + unit-tested |
calculator.ts is a pure module with 100 % branch coverage required in CI gate. |
| NFR-6 |
Static deployable |
Vite build emits plain HTML/CSS/JS to dist/; no Node.js runtime needed at serve time. |
Risks
| # |
Risk |
Likelihood |
Impact |
Mitigation |
| R-1 |
Floating-point display artefacts (e.g. 0.1 + 0.2 = 0.30000000000000004) |
High |
Low |
Round display output to a configurable number of significant figures (default 10); document IEEE 754 behaviour in the UI tooltip. |
| R-2 |
Invalid / non-numeric operand input |
Medium |
Medium |
Parse operands before passing to calculate(); show inline validation error via CalcError.INVALID_INPUT rather than NaN propagation. |
| R-3 |
Division-by-zero unhandled path |
Low |
Medium |
calculate() returns CalcError.DIVISION_BY_ZERO as a typed value; App maps this to a user-readable message. Unit-tested. |
| R-4 |
Accessibility regressions on future style changes |
Low |
Medium |
Axe-core accessibility checks run in CI on every PR via @axe-core/react dev integration. |
| R-5 |
Scope creep (expression parser, history, etc.) |
Medium |
Medium |
Documented out-of-scope items in Working Spec Assumptions 2, 3, 6, 7; change requests require a new spec revision. |