# Spec: Fix Gantt Bar Distortion and Document Timing

**Created by:** AnaPlan
**Date:** 2026-05-15
**Scope:** .ana/plans/active/fix-gantt-bar-distortion/scope.md

## Approach

The Gantt chart in `PipelineGantt.tsx` uses `timing.totalMinutes` (wall-clock duration) as the denominator when computing bar widths. For proofs where idle time between sessions inflates wall-clock relative to active time, the last bar absorbs the gap — 86% width for a 3-minute verify phase. The fix changes the denominator from wall-clock to the sum of the values actually being rendered. Both the segment path and the flat path get the same treatment.

The cumulative positioning approach (computing left positions from running totals, deriving widths from adjacent positions) stays — it's sound math. Only the denominator changes. With the correct denominator, cumulative positions reach 100% naturally, so the `lefts.push(100)` sentinel is removed from both paths.

The `total === 0` early return guard currently uses `timing.totalMinutes`. After the fix, it uses the computed sum instead. An entry with wall-clock > 0 but all phases = 0 returns empty (renders "No timing data") — correct behavior.

Negative phase values (theoretically possible from clock skew) are clamped to 0 with `Math.max(0, value)` when accumulating and when reading values.

The docs expansion adds a subsection to "The timeline" in `reading-a-proof.mdx` explaining what each phase measures, what `_started_at` timestamps are, why duration exceeds the sum of phases, and how multi-phase timing works. Matches the existing style: terse paragraphs, bold-text subsections (no H3s), specific proof slugs as examples.

After editing MDX, regenerate `llms-full.txt` and `search-index.json` via `cd website && pnpm prebuild`.

## Output Mockups

**Before fix — `findings-expand-collapse` (13m think, 2m plan, 3m build, 3m verify):**
```
THINK    ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░    13m
PLAN     █░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░     2m
BUILD    █░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░     3m
VERIFY   ██████████████████████████████████████████     3m  ← 86% width (wrong)
```

**After fix — same proof:**
```
THINK    █████████████████████████░░░░░░░░░░░░░░░░░    13m  ← 62% width
PLAN     █████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░     2m  ← 10% width
BUILD    ██████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░     3m  ← 14% width
VERIFY   ██████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░     3m  ← 14% width
```

Bars are proportional to active phase duration. The header still says the wall-clock duration — the docs explain the distinction.

## File Changes

### `website/components/docs/proof/PipelineGantt.tsx` (modify)
**What changes:** `buildGanttBars` denominator changes from `timing.totalMinutes` to the sum of phase values being rendered. Remove `lefts.push(100)` sentinel from both paths. Move the zero guard to use the computed sum. Add `Math.max(0, ...)` clamping for negative values.
**Pattern to follow:** The existing cumulative positioning approach in the same function — same structure, just different denominator.
**Why:** Without this, 24% of proof entries render with a distorted last bar.

### `website/content/docs/guides/reading-a-proof.mdx` (modify)
**What changes:** Expand "The timeline" section (currently 2 sentences at lines 57-60) to explain: what each phase measures (session start to artifact save), what `_started_at` timestamps are, why duration exceeds the sum of phases (idle time between sessions), and how multi-phase proofs show per-phase bars. Keep the `ana:dynamic medianTimings` comment in place.
**Pattern to follow:** The existing prose style in `reading-a-proof.mdx` — terse paragraphs, `<p>` tags with inline styles matching `fontSize:"13px", color:"var(--ink-60)"`, bold text for emphasis (no H3 headings within sections), specific proof slugs as real examples.
**Why:** Users see "2h 9m" in the header and "21m" in the bars and have no way to reconcile the numbers without this explanation.

### `website/public/llms-full.txt` (modify — regenerated)
**What changes:** Regenerated by `cd website && pnpm prebuild` after MDX edits.
**Pattern to follow:** N/A — machine-generated.
**Why:** Search and LLM context must reflect current docs content.

### `website/public/search-index.json` (modify — regenerated)
**What changes:** Regenerated by `cd website && pnpm prebuild` after MDX edits.
**Pattern to follow:** N/A — machine-generated.
**Why:** Search index must reflect current docs content.

## Acceptance Criteria

- [ ] AC1: Gantt bars are proportional to active phase duration — each bar's width is `phase_minutes / sum_of_all_phases * 100%`
- [ ] AC2: No `lefts.push(100)` sentinel in either the flat or segment path of `buildGanttBars`
- [ ] AC3: For `findings-expand-collapse` (13m think, 2m plan, 3m build, 3m verify): Verify bar renders at ~14% width, not 86%
- [ ] AC4: For `content-pages` (8-segment multi-phase): last segment renders proportionally, not inflated
- [ ] AC5: Zero-duration phases still render with a minimum width (existing `value === 0 ? 2 : ...` guard preserved)
- [ ] AC6: "The timeline" section in `reading-a-proof.mdx` explains: what each phase measures, what `_started_at` timestamps are, why duration exceeds the sum of phases, and how multi-phase timing works
- [ ] AC7: `llms-full.txt` and `search-index.json` regenerated
- [ ] AC8: Website builds cleanly (`pnpm build` from repo root)
- [ ] AC9: Negative phase values clamped to 0 — no division-by-zero or negative widths
- [ ] AC10: Zero active time (all phases 0) returns empty array regardless of wall-clock value

## Testing Strategy

- **No unit tests.** No website test suite exists. `buildGanttBars` is a pure function with no consumers outside `PipelineGantt.tsx`. Adding a test framework for this single component would be scaffolding — the fix simplifies the math and the 103 proof entries provide real-world regression coverage through the build.
- **Build verification:** `pnpm build` from repo root confirms no compilation errors and all proof pages render.
- **Visual verification:** After build, check the proof detail pages for `findings-expand-collapse` and `content-pages` to confirm bar proportions are correct.

## Dependencies

None. All affected files exist and have no external dependencies beyond the website's existing stack.

## Constraints

- Do not change `timing.totalMinutes` in the data model or anywhere outside `buildGanttBars`. The wall-clock value is correct — the rendering was wrong.
- Do not remove or move the `{/* ana:dynamic medianTimings */}` comment in `reading-a-proof.mdx`. The build pipeline uses this marker.
- The `PipelineGantt` component's `timing.totalMinutes === 0` check (line 107) stays as-is — it controls whether the component renders "No timing data" and operates on the original data, not the computed sum.

## Gotchas

- **Two zero guards, different purposes.** `buildGanttBars` has a zero guard for the computed sum (returns empty array). The `PipelineGantt` component has a separate zero guard for `timing.totalMinutes` (renders "No timing data" text). These are independent — don't conflate them.
- **Segment sum ≠ flat field sum.** For multi-phase entries, the flat fields (think/plan/build/verify) are aggregates across all phases, while segments are per-phase. The segment path must sum `timing.segments[].minutes`. The flat path must sum `timing[stage.key]` for each STAGE. These are already separate code paths — keep them separate.
- **Pre-existing tsc error.** `pnpm --filter anatomia-website check` fails with a Next.js type error unrelated to this work. Use `pnpm build` as the build verification command instead.
- **Proof finding: `formatDuration` unused.** There's a proof finding noting `formatDuration` is defined but unused in PipelineGantt. This is pre-existing and not in scope — don't remove it as part of this fix.

## Build Brief

### Rules That Apply
- 2-space indentation
- Explicit return types on exported functions (already present on `buildGanttBars`)
- `import type` for type-only imports (already correct in the file)
- No H3 headings within sections in MDX guides — use bold text for subsections
- Inline styles in MDX use the existing design token pattern: `var(--ink-60)`, `var(--font-mono)`, etc.

### Pattern Extracts

**Cumulative positioning (current code, lines 46-54 of PipelineGantt.tsx) — this is the structure to preserve, only the denominator changes:**
```typescript
    const bars: GanttBar[] = [];
    let cumulative = 0;
    const lefts: number[] = [];

    for (const seg of timing.segments) {
      lefts.push(total > 0 ? (cumulative / total) * 100 : 0);
      cumulative += seg.minutes;
    }
    lefts.push(100); // sentinel — right edge of last bar  ← REMOVE THIS
```

**MDX prose style (lines 57-60 of reading-a-proof.mdx) — match this voice and formatting:**
```
Four bars showing Think, Plan, Build, and Verify. The shape tells you what kind of work
this was. Security-hardening: Think 2m, Plan 15m, Build 53m, Verify 5m — a heavy build,
light verification. Compare with [worktree-isolation](/docs/proof/worktree-isolation):
Think 3m, Plan 22m, Build 63m, Verify 11m — similar build but double the verification
time because the contract had 45 assertions.
```

### Proof Context
- `[code] formatDuration defined but unused in PipelineGantt` — pre-existing, not in scope. Do not address.

### Checkpoint Commands

- After modifying `PipelineGantt.tsx`: verify no TypeScript errors in the file with `npx tsc --noEmit website/components/docs/proof/PipelineGantt.tsx` — Expected: clean (note: full project tsc has pre-existing errors, but individual file compilation should work if imports resolve)
- After modifying `reading-a-proof.mdx`: `cd website && pnpm prebuild` — Expected: regenerates `llms-full.txt` and `search-index.json`
- After all changes: `pnpm build` — Expected: clean build, all pages generated
- Lint: `pnpm run lint`

### Build Baseline
- No website tests exist. CLI tests are not affected by this change.
- Current website build: succeeds with all proof pages generated
- After build: same build output, no new pages, no removed pages
- Regression focus: proof detail pages that render `PipelineGantt` — specifically `findings-expand-collapse` and `content-pages` as visual regression checks
