All method pages

MethodFrontend

partial some parts checked against the code, others not yet · what the statuses mean

Frontend architecture

In simple terms

The frontend is the web page you interact with. It’s built with React (a library for building UIs out of reusable “components”) and TypeScript (JavaScript with type-checking). When you click or type, small pieces of the page react, sometimes ask the backend for numbers, and redraw. There’s no page reload — it’s a single-page app.

Why it’s built this way

The UI needs to feel like a live instrument: edit a size, watch the numbers tween and the flow diagram re-light. React’s component model + local state makes that natural, and TypeScript keeps the many data shapes (results, financials, sweeps) honest as the app grows.

Where it lives

  • Entry: frontend/src/main.tsx → frontend/src/App.tsx.
  • Layouts: frontend/src/layouts/ · Pages: frontend/src/pages/ · Rooms: frontend/src/rooms/ · Shared UI: frontend/src/components/.
  • Data seam: frontend/src/data/ (client.ts, api.ts, types.ts).
  • Styling: frontend/src/index.css (tokens) + frontend/src/App.css.

How it fits together

  1. main.tsx mounts <App/> inside a <BrowserRouter> and <StrictMode>.
  2. App.tsx is the route table. AppLayout wraps everything with global chrome (the one sidebar — projects, the open project’s stages, rooms and tools, the account menu — and a slim top bar: breadcrumb + an actions slot). Under /projects/:projectId, ProjectLayout adds the project context and the stage strip; the book version bar lives in the Assumptions tab. Each stage frame (PrefeasShell, FeasibilityShell) fills the top bar’s slot once, for every tab, with the stage’s Export ▾ and Snapshot (shell/StageActions.tsx).
  3. A room (e.g. the Prefeasibility cockpit) owns a screen. It reads/writes data through client.ts and renders shared components.

Routing

Verified in App.tsx — react-router-dom with BrowserRouter:

  • / → redirect to /projects (the Portfolio is the home).
  • /projects — portfolio landing; /projects/:projectId/<room> — the rooms (prefeasibility, feasibility, assumptions, snapshots, derivatives, the feasibility sub-rooms, and the methods explainers).
  • /settings/dab — the default-assumption-book editor.
  • /settings/workspace — the workspace’s name, members, one-time links and owner.
  • /docs and /docs/:slug — this documentation (lazy-loaded / code-split).
  • Outside the app (no session needed): /sign-in, /setup (the first run) and /join?token=… (a one-time invitation link).

What a role may do (since 2026-11-04): auth/access.ts copies the server’s action → least-role table (a backend test fails when the two differ); useCan(action) reads it. A pure write control is not drawn for a role that may not use it — through the hook, or by carrying data-write="<action>", which one CSS rule hides from a role useRoleOnDocument marked on <html>. Inline editors (book cells, sizing) turn read-only, and each page shows a viewer ONE muted note. All of it is cosmetic: the server refuses the request anyway.

See 03-frontend/routing.md (planned) for the full table and the stage/room model.

The data seam (the one boundary to respect)

UI component → client.ts → api.ts → FastAPI.

  • client.ts exposes intention-revealing functions (solveProject, rightSize, getFeasibilityStatus, …). Components call these; they never fetch directly.
  • api.ts does the typed fetch against VITE_API_BASE (default http://localhost:8000 under the dev server; /api on the page’s own address when the backend served the built app — serve.py --share, since 2026-11-04) and declares the TypeScript response shapes.
  • Those shapes mirror the backend. The rule: never invent a field the backend doesn’t send — change the core/ producer, the api.ts type, and every consumer together.

State management

There is no global state library (no Redux/Zustand). Verified pattern:

  • Local state: each room holds its own useState/useEffect. This is where most state lives (sizing inputs, the current solve bundle, chart hover, etc.).
  • Shared per-project state: a ProjectProvider React context carries the active project + the last-visited stage across rooms.
  • Server state: fetched imperatively on mount / on edit, with no cache layer. Expensive async work (feasibility solve, right-size landscape, storage sweep) is a backend thread-job the room polls until done.
  • Theme: a useTheme hook toggles light/dark by stamping data-theme on the root; all colors are CSS variables so both themes work.

Forms, validation, and locale

  • Forms are plain controlled inputs (e.g. NewProjectForm, the design panel’s fields in design/fields.tsx, the book editors). Numeric inputs use text inputs + parseDecimal (in format.ts) so both . and , decimals work — the dev machine’s OS locale is French, so this matters.
  • Client-side validation is light; the backend is the strict validator (Pydantic models + explicit range checks return 422 with a specific message, which the UI surfaces).

Loading, error, and empty states

Rooms consistently show: a solving indicator (SolverStatus shimmer / poll progress), an error panel when a request fails (often with a hint to start the backend), and a graceful empty state before the first solve. The recurring hero motion is recalculation — numbers tween, bars/flows spring — not entrance animation.

Design system

  • Apple-HIG-inspired, dual light + dark via CSS variables in index.css. Accent = electric blue (--primary); green (--go) = “feasible/go”; amber (--warn) = bottleneck/constraint (always paired with an icon/label, never hue alone).
  • Type: system UI stack for text, JetBrains Mono for all numbers (tabular).
  • Charts are in-house SVG/CSS on the tokens (no chart library) — FlowDiagram, CostBuildup, EnergyBalance, CapexBreakdown, LcoBreakdown, the feasibility dispatch/duration/heatmap charts. Every chart carries axis labels + units, a legend, and a hover read-out (a ratified legibility standard).
  • Panel + RoomHeader unify every room’s structure; the Methods rooms and this /docs explorer reuse the same tokens.

Build & tooling

  • Vite dev server (npm run dev, port 5173) and build (npm run build = tsc -b && vite build). Lint: npm run lint (ESLint).
  • Unit tests run on Node’s own test runner (npm test, frontend/tests/): a test loads a pure module — a sentence or a verdict in a non-component file — through Vite and checks it, with no browser and no extra dependency. The other quality gates are tsc + eslint + a successful build, plus headless browser drives for behaviour. (Introducing Vitest is a tracked option — internal known-gaps list, docs/08-maintenance/known-gaps.md.)

How to modify this safely

  • Changing a response shape? Update core/ + api.ts + all client.ts/room consumers in one change; run tsc to catch every break.
  • New room? Add the route in App.tsx, the room folder under rooms/, and reuse RoomHeader/Panel. Register it in the stage/room model (stages.ts/rooms.ts).
  • Respect the recalculation-as-hero motion and the chart legibility standard.

From the app’s documentation, docs/03-frontend/frontend-architecture.md · Hyprisma 1.0.5