# Markdown Review Pane — Design System

**Status:** Implemented · 2026-05-21
**Scope:** `#review&type=doc` workspace inside the Code Review panel
**Files:** `claude-task-manager/public/css/reviews.css`, `claude-task-manager/public/js/reviews.js`

This doc captures the design rationale behind the May 2026 markdown-review
redesign so future work doesn't reintroduce the patterns we deliberately
removed.

---

## Problem we solved

The original markdown review pane was a code-editor surface re-skinned for
prose:

- Line-number gutter always visible in the rendered/docs view
- Single 24/19/16px heading scale, no clear hierarchy between H1 and H4
- Inline code rendered in **amber** (`#e0af68` Tokyo Night yellow), making
  every identifier read as a warning
- TOC sidebar prefixed every entry with a literal `#` glyph; hierarchy
  conveyed by glyph repetition (`#`, `##`, `###`) instead of indentation
- Hardcoded RGBA tints from the dark palette (`rgba(122,162,247,0.06)`)
  showed barely-visible on the light theme bg
- Workspace bg matched the page bg, so code blocks didn't read as
  contained content
- Article max-width of 920px exceeded the 80-character readability sweet
  spot

The result felt like a `cat foo.md` dump with extra chrome, not a designed
reading surface.

---

## Design tokens (theme-aware)

The whole redesign uses CSS custom properties from the existing CTM
palette. Both light and dark are defined in `public/index.html`:

| Token | Dark | Light | Role in the markdown pane |
|---|---|---|---|
| `--bg` | `#1a1b26` | `#f6f7f9` | Workspace surface |
| `--bg-light` | `#24283b` | `#ffffff` | Code-block & elevated card bg |
| `--bg-lighter` | `#1f2333` | `#eceef2` | Recessed (gutters, table th) |
| `--fg` | `#c0caf5` | `#0f172a` | Body text, primary headings |
| `--fg-dim` | `#a9b1d6` | `#3d4b5e` | H4, secondary metadata |
| `--fg-muted` | `#7f88a8` | `#52606f` | H5/H6 (kicker), line-gutter |
| `--accent` | `#7aa2f7` | `#3b66e0` | Links, active TOC rail, focus |
| `--accent-soft` | `rgba(122,162,247,0.13)` | `rgba(59,102,224,0.10)` | Inline code bg, segmented active fill, hovered block bg |
| `--border` | `#414868` | `rgba(15,23,42,0.10)` | H2 hairline, hr, card outline |

No hardcoded hex in the new CSS. Every color flows from the token system,
so the next theme palette retune Just Works.

---

## Five-level type scale

| Level | Size | Weight | Treatment |
|---|---|---|---|
| H1 | 28px | 700 | `letter-spacing: -0.01em`, 16px bottom margin |
| H2 | 22px | 650 | 28px top margin, 1px bottom hairline (`--border`) |
| H3 | 17px | 600 | 22px top margin |
| H4 | 15px | 600 | Muted color (`--fg-dim`) — sub-section label |
| H5/H6 | 12px | 700 | Uppercase, 0.08em tracking — used as "eyebrow" |

Body text: **15px / line-height 1.7 / `--fg`**. The 1.7 line-height puts
prose squarely in the GitHub-docs / Stripe-docs comfort band; the dense
1.62 we had before read as compressed.

`scroll-margin-top: 16px` on every heading so deep-link anchors don't
collide with sticky chrome.

---

## Layout

```
┌───────────────────────────────────────────────────────────────┐
│ CTM topbar                                                    │
├─────────────┬─────────────────────────────────────────────────┤
│  TOC rail   │      .cr-doc-article (max-width 800px)         │
│             │  ┌─────────────────────────────────────────┐    │
│  • Heading  │  │ # Document Title                        │    │
│   Heading   │  │ ─────────                               │    │
│    Heading  │  │ ## Section H2 ─────────────────────────│    │
│             │  │                                         │    │
│  ─ active   │  │ Body paragraph rendered at 15/1.7…      │    │
│             │  │                                         │    │
│             │  │ `inline code` reads as body, faintly    │    │
│             │  │ tinted accent-soft.                     │    │
│             │  │                                         │    │
│             │  │ ```                                     │    │
│             │  │ block code on --bg-light w/ border      │    │
│             │  │ ```                                     │    │
│             │  └─────────────────────────────────────────┘    │
└─────────────┴─────────────────────────────────────────────────┘
```

- **Article width capped at 800px.** Past ~75 characters per line,
  readability drops sharply (Bringhurst, *The Elements of Typographic
  Style*). 800px at 15px font sits at ~75ch.
- **Top padding 24px, side padding 32px, bottom padding 96px.** Bottom
  pad lets the user scroll the last section into a comfortable reading
  position rather than glued to the viewport bottom.

---

## What we hide vs reveal

| Element | Default state | Hover/active state |
|---|---|---|
| `.cr-doc-block-lines` (line-range gutter) | Opacity 0, positioned `left: -44px` | Opacity 0.55 on block hover; full opacity in source/split pane |
| `.cr-doc-comment-btn` (the `+` button) | Opacity 0 | Opacity 1 on block hover, active, or focus-visible |
| Source-pane line numbers | Visible by default | Same |

Rationale: line numbers and the `+` glyph belong with **source-code
review**. In **rendered mode** they're affordances you reach for, not
content. Hiding them by default keeps the surface looking like a doc;
hover/focus brings them back when you need to anchor a review comment.

---

## TOC sidebar

- **No `#` glyph.** Markdown syntax in a parsed UI looked like a bug.
- **Depth via padding-left.** H1 = 8px, H2 = 14px, H3 = 28px, H4 = 42px,
  H5/H6 = 56px. Combined with shrinking font size (12 → 11.5 → 11) and
  color stepping (`--fg` → `--fg-dim` → `--fg-muted`) the hierarchy
  reads at a glance.
- **Active state.** Subtle `--accent-soft` fill + `box-shadow: inset 2px
  0 0 var(--accent)` rail on the left edge. Reads as "you are here"
  without dominating.

---

## Scroll-spy

`setupDocScrollSpy()` in `reviews.js` uses an `IntersectionObserver`
scoped to `.cr-diff-area`. Each `.cr-doc-block` is observed; the topmost
intersecting block (by `boundingClientRect.top`, smallest = closest to
top of viewport) becomes the "active" reader position. Its nearest
preceding heading block is then resolved (`headingFor` map) and that
heading's TOC entry gets the `.active` class.

```
rootMargin: '-12px 0px -70% 0px'
```

That means a heading is "active" only while it's in the top **30%** of
the visible pane — same band Stripe Docs and Notion use. Anything below
that doesn't fight for the active marker.

The observer is rebuilt every render (the DOM is reset). Old observer
is `disconnect()`-ed first so we don't leak listeners.

---

## Non-goals

- **No syntax highlighting on `<pre><code>` blocks.** The existing
  markdown renderer doesn't ship highlight.js; adding it is a larger
  scope. Block code is monospaced on `--bg-light` with a border and the
  user reads it as code through layout cues, not color.
- **No print stylesheet.** Out of scope.
- **No front-matter parsing.** YAML-style front matter (if any) renders
  as ordinary markdown. Adding a `<div class="front-matter">` wrapper
  needs a renderer change; deferred.
- **No collapsible H2 sections.** Considered, rejected — collapsing
  hides content the reader explicitly opened a doc to see.

---

## Verification

Validated on an isolated dev instance (`/ctm-dev`, random port 4464),
loading a real markdown spec
(`wall-e/docs/superpowers/specs/2026-04-15-mempalace-gap-analysis-v2.md`,
22 headings, 64 blocks):

**Dark mode, light mode**, both verified via `getComputedStyle`:

| Check | Result |
|---|---|
| TOC `#` glyph removed | `iconText: ""`, `display: none` |
| TOC indentation by level | 8px → 14px → 28px |
| Article width | 800px |
| Workspace bg | `--bg` resolves correctly per theme |
| H1 | 28px / 700 |
| H2 | 22px + 1px `--border` bottom |
| Line-gutter (rendered) | opacity 0, off-screen |
| Comment `+` (default) | opacity 0 (revealed on hover/active/focus) |
| Inline code | accent-soft bg + `--fg` text (no amber) |
| Segmented "active" | accent-soft fill (not solid accent) |

---

## Related

- [CTM light-mode contrast redesign](https://github.com/Student ExampleLi/tools/commit/0ce352b)
  — established the `--fg-dim` / `--fg-muted` / `--accent-soft` tokens
  this work depends on.
- [xterm.js minimumContrastRatio](https://github.com/Student ExampleLi/tools/commit/28576de)
  — same "don't fight the theme palette, fix the contrast at the edge"
  principle applied to terminals.
