# Documentation examples

Good and bad documentation side by side; `style-guide.md` cites each example by number.

---

## Example 1: Function documentation

### Bad: full implementation

```markdown
### mergeData

function mergeData(server, local, changes) { const result = {}; const serverMap = new Map(server.map((i) => [i.id, i])); // ... 30 more lines return result; }
```

**Problems:** goes stale with the first refactor; shows *how* instead of *what*.

### Good: contract only

```markdown
### `mergeData(serverData, localData, changes)`

**Goal:** Merge server data with local changes

**Input:**

- `serverData` (array) - Fresh from API
- `localData` (array) - Current state
- `changes` (object) - `{ added, updated, removed }`

**Output:** `{ merged: array, conflicts: array }`

**Merge Rules:**

- Server wins for conflicts
- Local additions preserved
- Local removals marked as intent
```

---

## Example 2: State documentation

### Bad: state everywhere

```markdown
## Component State

const [items, setItems] = useState([]);

## Hook State

{ data: items, isLoading: loading, error: null }

## Redux State

{ items: [], loading: false, error: null }
```

**Problems:** documents transient state and repeats shapes.

### Good: persistence points only, in the API reference

Persisted shapes live in the API reference (`docs/architecture/api.md`), never in a feature document, where `## State` is a retired mechanism section.

```markdown
## Persisted shapes — items

**Frontend (at persistence):**

{
  "data": [],
  "changes": { "added": [], "updated": [], "removed": [] }
  // "cache": {}  // UNUSED: dead key; kept so old persisted state rehydrates
}

**Storage:** localStorage `app_state`

**Backend:**

export const items = pgTable('items', {
  id: text('id').primaryKey(),
  data: jsonb('data'),
});
```

---

## Example 3: Changelog

### Bad: vague

```markdown
# 2025-01-05 - Updates

## Changes

- Updated editor
- Fixed bugs
- Improved performance

## Files

- Various files in src/
```

### Good: behavior first, refinement context kept

```markdown
# 2025-01-05 · data-sync — Local edits survive a sync only when versions match

- **Ask:** "stop silently overwriting my offline edits — I'd rather see a conflict than lose work"
- **Iterations:** 2 · rejected: last-write-wins by timestamp, because device clocks drift and the newer-looking write lost real edits
- **Skills:** documentation@3.18.0 — survived
- **Change:** sync now keeps a local edit only when its base version matches the server's, and surfaces a conflict otherwise, because a stale local copy was overwriting newer server state. Merge and change detection run in one pass (`mergeAndDerive`)
- **Discovered:** two tabs on one device share a version counter, so a conflict between them never surfaces [source: runtime-discovery]
- **Class:** data · resilience
- **Pin:** `tests/sync/merge.test.ts` — version-mismatch keeps the server copy and reports the conflict
```

---

## Example 4: Flow

### Bad: mechanism narrated in a paragraph

```markdown
## Data Flow

When the page loads, the useAppInit hook calls the syncData function. This function first fetches data from the server using the API. Then it gets the local data from Redux. After that, it compares versions...
```

**Problems:** names a hook, a function and a store that a rename would invalidate (the stability test); runs parallel cases together in one paragraph; gives a scanning reader no label to land on; "Data Flow" is a retired mechanism section.

### Good: labelled bullets, a role-level diagram beside them

````markdown
## Flows

### Page load — item sync

- **Fresh data:** the page fetches the server's copy and the local copy on every load; nothing is cached between visits.
- **Matching versions:** local changes win and are kept.
- **Mismatched versions:** the server's copy wins and local changes are dropped.

```mermaid
sequenceDiagram
  participant Page
  participant Server
  participant Store
  Page->>Server: fetch the server's copy
  Page->>Store: read the local copy
  Page->>Store: write the merged copy
```
````

**Why it works:** every fact is in the bullets, so a reader who greps finds it without the diagram; the labels are noun phrases a scanning reader lands on; the diagram names roles, never a function or a file, so no rename touches it.

---

## Example 5: Architecture Decision revision

### Bad: supersession chain

```
docs/architecture/decisions/
├── AD-002-auth-keycloak.md        (status: Superseded)
├── AD-006-auth-better-auth.md     (status: Superseded, "supersedes AD-002")
└── AD-011-auth-better-auth-v2.md  (status: Accepted, "supersedes AD-006")
```

**Problems:** three files hold one subject; the reader must diff a chain to learn the current truth; two of the three are wrong-but-present (the same pathology as keeping `report_v1.sql` beside the live `report_v2.sql`); the reasoning that killed Keycloak is stranded in a dead file nobody opens.

### Good: one subject, updated in place

```
docs/architecture/decisions/
└── AD-002-auth.md                 (status: Accepted, last-updated bumped)
```

```markdown
## Decision

We use Better Auth with one-shot credential issuance…   ← current truth, no journey

…

## Decision Changelog

- 2026-05-02 — Moved issuance to one-shot tokens because refresh-token theft
  surfaced in review. Implemented by [plan](…). (Previously: long-lived refresh tokens.)
- 2026-03-18 — Replaced Keycloak with Better Auth because self-hosting cost
  outweighed SSO needs. Implemented by [plan](…). (Previously: Keycloak.)
- 2026-01-10 — Initial decision accepted. Implemented by [plan](…).
```

**Why it works:** the body answers "how does auth work and why" without archaeology; the tail preserves every reversal with its trigger; the rejected alternatives stay findable in the one file a reader actually opens.

---

## Example 6: Stripping history from a truth doc

Deleting narration is only half the move; the story **must** land in a record.

### Bad: the journey narrated in place

```markdown
## Export Pipeline

The exporter was extracted from the legacy `reports` worker (2026-06-02) and
now lives in `src/export/`. Phases 0–2 of the rewrite are complete; streaming
output is not yet implemented. Treat any reference to `reports/export.js` as
stale — it was superseded by `src/export/run.js`.
```

### Good: present-tense truth doc + a dated record carrying the story

The feature doc states only what is:

```markdown
## Export Pipeline

The exporter renders every export format from one job queue. Output is
buffered per job (streaming is out of scope — see `docs/history/backlog.md`).
```

And the change-story goes to `docs/history/changelog/2026-06-02-export-extraction.md`:

```markdown
# 2026-06-02 · export — Export runs as its own pipeline, outside the reports worker

- **Ask:** "exports shouldn't stall the reports queue"
- **Iterations:** 1 · rejected: none
- **Skills:** none used
- **Change:** export jobs run in their own pipeline under `src/export/`, so a slow export no longer holds a reports slot; every caller of the old worker entry is repointed
- **Discovered:** none
- **Class:** architecture · performance
- **Pin:** none — candidate: a queue test that a long export leaves report jobs flowing
```

**Why it works:** the truth doc needs no archaeology to act on; the dates, the predecessor, and the supersession live in a frozen record; the unbuilt part is a backlog pointer, not a "not yet" hedge; and no stale-reference tripwire survives because the references themselves were fixed.

---

## Quick comparison

| Aspect     | Bad                  | Good                        |
| ---------- | -------------------- | --------------------------- |
| Code       | Full implementations | Signatures + contracts      |
| State      | Every occurrence     | Persistence points only     |
| Flows      | Paragraphs naming functions | Labelled bullets in plain words, a role-level diagram beside them |
| Changelogs | "Updated X"          | Behavior first, then the ask, rejections and what running it taught |
| Decisions  | New doc per revision | One AD per subject, updated in place |
| History    | Narrated in the doc  | Present-tense doc + dated record |
| Length     | Padded with mechanism | The genre's own expectation; length is a signal, never a target |
