A
?
← Handbook

Performance and sequencing

The portal competes with a spreadsheet and a group chat. If a page takes thirty seconds, the team goes back to the spreadsheet, and every guardrail in this repo stops applying because nobody is using the thing that enforces them. Speed is not polish here — it is what makes the rules bind.

1. Nothing a human waits on may call a model

A page load must never await an LLM. Models are slow and variable; a text report that takes minutes is a failed feature no matter how good the writing is.

So the read path and the think path are separate:

ingest / schedule ──▶ model ──▶ rows in Postgres
                                     │
page load ───────────────────────────┴──▶ rendered instantly

Judgment happens when content arrives or on a schedule, and the result is stored. Pages read stored rows. This is the same principle as buying a scene once and compositing many creatives, and the same as pre-processing the ad backlog rather than analysing on every request.

The exception is an action a human explicitly triggered and is watching — pressing *generate*, or asking Atlas Brain a question. Those may take time because the person chose to wait. They still show progress, and they still never block an unrelated page.

2. Sequence to minimise rework, not to maximise features

The cheapest code is the code not rewritten. Build in dependency order: the thing several features read comes before the features that read it.

This is why the order in vision.md is what it is — client record, then the surface that reads it, then performance data, then the things that need performance data. Building a dashboard before the data it charts means building it twice.

Corollary: when a feature would need a foundation that does not exist yet, build the foundation or defer the feature. Do not fake it and plan to come back — that is the revision cost this rule exists to avoid.

3. Lean beats general

No abstraction until the third instance. Two similar things are a coincidence; three are a pattern. A framework built for one caller is a framework built wrong, and it still has to be maintained.

Concretely:

  • No repository layer over services/ — services/ already is that seam
  • No client-side state library; server components render from the database
  • No internal HTTP hops. A server component calls the service function directly, never fetch against our own API

4. Query shape is the performance ceiling

Almost every slow page in a system like this is N+1, not slow SQL.

  • One query per page where possible; join and aggregate in Postgres rather than in a loop
  • A list view never fetches per-row detail — aggregate counts in the same query (see listClientsWithReadiness)
  • Index what is filtered on before it gets big, not after it is slow

Known instance: src/app/page.tsx and GET /api/clients call listApproved once per client. Invisible at one client, thirty round trips at thirty. To be folded into one query when the client success page lands — recorded in register.md so it is not rediscovered.

5. Media never touches the request path

Covered in vision.md, restated because it is the one that kills agencies: presigned direct uploads, CDN downloads, transcoding as jobs. A Node process streaming a 4GB rush is a dead Node process.

6. Validate at the boundary, once

Cheap checks at the edge prevent expensive failures later — the font check at onboarding costs one HTTP HEAD and prevents a broken client. But validate once, at the point of entry, not defensively at every layer. Re-checking the same fact at four levels is enterprise bloat with a latency cost.