Skip to main content

Writing These Docs

Where docs actually live

You are reading a file that lives in this repo, under docs/. It is not the Documentation site itself — the site is a separate Docusaurus repo (Alert.CA.Documentation) that renders these files. An Azure Pipeline keeps the two in sync automatically:

  • It triggers on any push to main that touches docs/**.
  • It clones Alert.CA.Documentation, copies every file from this repo's docs/ folder into docs/Alert.CA.Frontend/ there, and commits/pushes the result — a raw, one-way file copy with no filtering or validation step in between.

Practical consequence: always edit docs here, never in Alert.CA.Documentation directly. A direct edit there survives only until the next sync from this repo overwrites it. See documentation-sync.md at this repo's root for the full pipeline reference.

Status of the 2026-08 learning-docs overhaul

The full lesson-style rewrite of these docs was authored directly in Alert.CA.Documentation and has not yet been synced back into this repo's docs/ folder. Until it is (or the pipeline is retargeted), a push to this repo's main touching docs/** would overwrite the rewritten site with older content. Coordinate before touching docs/ here.

Because the copy is raw and unvalidated, anything that breaks the Docusaurus build only surfaces after it lands in the other repo — there is no local build step here to catch it first. That makes the next section load-bearing, not a style nitpick.

The MDX trap: literal {...} breaks the build

Docusaurus parses every .md file as MDX (v3), not plain Markdown. MDX treats an unescaped {...} outside a code span or fenced code block as a JavaScript expression to evaluate — not literal text. This has actually broken a production build in this project before: a sentence containing literal text like {center, extent, edge} compiled fine as Markdown but threw ReferenceError: center is not defined the moment it hit MDX, because MDX tried to evaluate center as a variable.

The fix is always the same: wrap the literal braces in backticks.

BAD: choose one of {center, extent, edge}
GOOD: choose one of `{center, extent, edge}`

This applies to anything with curly braces used as literal text — object-shorthand examples, set notation, template-looking strings — not just this specific phrase. When in doubt, backtick it.

Live code sandboxes: CodePlayground

For math or logic worth letting a reader edit and re-run, use the shared CodePlayground component instead of hand-rolling a BrowserOnly-wrapped inline component (the old pattern this overhaul replaced — it duplicated the same 50–150 lines of boilerplate in every playground doc).

interface CodePlaygroundProps {
code: string; // initial editable snippet
scope?: Record<string, unknown>;
title?: string;
renderResult?: boolean; // default true: live JSX preview. false: pure computed-output panel (use for math/logic)
noInline?: boolean; // default true: multi-statement snippet ending in a render(...) call
}

Import and use it from an .md file with two separate mdx-code-block fences — an import fence, then a usage fence:

import CodePlayground from '@site/src/components/docs/CodePlayground';

<CodePlayground
title="calculateZoom"
renderResult={false}
code={`
// Mirrored from util/cameras/calculateZoom.ts — verify against source if this looks stale
function calculateZoom(fovDegrees) { /* ... */ }
const result = calculateZoom(12.5);
render(<pre>{JSON.stringify(result, null, 2)}</pre>);
`}
/>

One hard rule first: render() must be given JSX, never a bare string. The react-live version in the platform silently renders nothing for render('some text') — no error, just an empty output panel. End logic demos with render(<pre style={{margin: 0}}>{text}</pre>).

Two more things worth knowing before you add one:

  • The Docusaurus build cannot resolve a cross-repo TypeScript import. You can't import { calculateZoom } from '@/util/cameras/calculateZoom' from a doc in this repo, because by the time this file is rendered, it's sitting in a completely different repo's build. Instead, copy the real function's logic directly into the code string, and leave a one-line comment noting which real source file it mirrors — see any file under deep-dives/ for the pattern. This means a playground can drift from the real implementation over time; when you touch the source function, check whether a playground mirrors it and update both together.
  • Only build a playground for genuinely standalone logic. Pure functions (math, parsing, diffing) are great candidates. Anything wired into Zustand, TanStack Query, ArcGIS, or SignalR is not — faking that kind of interactivity is worse than not having a demo. Write a clear prose + code-reference walkthrough instead; several docs in features/ and deep-dives/ do exactly this.

Diagrams: Mermaid by default

Mermaid is enabled site-wide — a ```mermaid fence renders directly, no import needed:

```mermaid
sequenceDiagram
participant A as Operator A
participant API as /api/lease
A->>API: POST { cameraId }
```

Mermaid should be your default for any flow, sequence, or state diagram. There is exactly one hand-built, animated, click-through diagram component in this whole documentation system — SignalRFanoutDiagram, used in deep-dives/signalr-realtime-architecture.md, built on a shared StepDiagram engine that lives in the Alert.CA.Documentation repo's src/components/docs/StepDiagram/. It exists because that specific flow (one hub connection fanning out to three different stores, three different ways) genuinely benefits from being walked through step-by-step with visual state. Reach for Mermaid first; only consider a custom StepDiagram instance for something with real branching, multiple independent actors, and enough steps that a single static diagram would be unreadable.

The information architecture

Docs are organized under docs/, one top-level folder per concern, each with a _category_.json carrying a unique position (don't renumber these without checking every sibling first — duplicate positions produce a nondeterministic sidebar order, which is exactly the bug this overhaul fixed):

FolderWhat goes here
get-started/Onboarding: what the app is, how to run it locally
core-concepts/The mental model: architecture, design tokens, analytics
features/One subfolder per user-facing capability — how it works, how it's built
state-management/Zustand store reference
deep-dives/The hardest subsystems, explained in full — math, concurrency, security, real-time
security/Auth, RBAC, and anything security-relevant
accessibility/WCAG/a11y guidance and tooling
testing/Test conventions and CI integration
contributing/This page, and anything else about maintaining the docs themselves

When you're not sure where something goes: a doc explaining how a feature works belongs in features/; a doc explaining why something is hard, or how several pieces interact under load/concurrency/security constraints belongs in deep-dives/. If you're only extending an existing doc, match its neighbors rather than inventing a new pattern — every doc under deep-dives/ follows the same shape (Overview → real source-grounded detail, with real file paths and function names → a diagram → a "Related" cross-link section at the end); that consistency is deliberate and worth preserving.

The standard a new doc should meet

Every fact in a doc should be something you verified by reading the actual current source — not something copied from an older doc, not something inferred from a variable name. This project has twice shipped a doc that confidently described behavior that no longer existed (an authentication flow that had been replaced, and a lease/concurrency flow with invented functions and events) — both were caught only when someone read the real source side-by-side with the doc. Read the code before you write about it, and if you find an existing doc that's wrong, fix it rather than building on top of it.