ADR Related Document Link Normalization + Mermaid Activation

SummaryResolve 2 defects discovered during Sprint 161 user visual verification: (1) Meta sidebar RelatedLinks rendered as <span> — not clickable (2) ADR body .md markdown links produce 404 on static export

Date
ImpactHigh

Key Decisions

ID → URL conversion + span fallback pattern adopted

resolveAdrUrl(id, locale) helper returns null on pattern match failure → existing span preserved. Prevents 404 anchor creation

rehype plugin-based link normalization

Separated from parser.ts metadata extraction (extractOutgoingLinks) as a render-only transformation. Separation of concerns

Null byte placeholder pattern

Escaped pipe (\|) handling via \x00PIPE\x00 substitution → split → restore. No general text collision possible

CodeBlock → Mermaid early return branch

When extractLanguage yields 'mermaid', reuse existing <Mermaid> component (dynamic import, SSG-safe). Zero new implementation

Critic P2 data integrity resolution

sprint-67 related_adrs: [ADR-004, ADR-005] references non-existent ADRs → frontmatter removed

Goals

  • Resolve 2 defects discovered during Sprint 161 user visual verification: (1) Meta sidebar RelatedLinks rendered as <span> — not clickable (2) ADR body .md markdown links produce 404 on static export
  • Resolve Sprint 161 Critic R2 P3 carry-over: splitTableRow escaped pipe handling
  • Activate Mermaid code fence rendering in ADR — reuse existing Mermaid component

Decisions

  • ID → URL conversion + span fallback pattern adopted: resolveAdrUrl(id, locale) helper returns null on pattern match failure → existing span preserved. Prevents 404 anchor creation
  • rehype plugin-based link normalization: Separated from parser.ts metadata extraction (extractOutgoingLinks) as a render-only transformation. Separation of concerns
  • Null byte placeholder pattern: Escaped pipe (\|) handling via \x00PIPE\x00 substitution → split → restore. No general text collision possible
  • CodeBlock → Mermaid early return branch: When extractLanguage yields 'mermaid', reuse existing <Mermaid> component (dynamic import, SSG-safe). Zero new implementation
  • Critic P2 data integrity resolution: sprint-67 related_adrs: [ADR-004, ADR-005] references non-existent ADRs → frontmatter removed
PhaseOwnerChangeLines
A — RelatedLinks anchor conversionarchitectadr-meta-sidebar.tsx+46 −6
B — rehype link normalizationarchitectrehype-adr-link-rewrite.ts new + markdown.ts + adr-detail-view.tsx+90 −2
C — escaped pipe handlingarchitectparser.ts+5 −1
D — Mermaid activationarchitectcode-block.tsx + sprint-160.md Mermaid diagram+19
Critic R1 P2architectsprint-67.md KR+EN frontmatter−2

Detailed Changes

  1. adr-meta-sidebar.tsx (+46 −6): resolveAdrUrl(id, locale) helper — sprint-NNN → /adr/sprints/NNN/, ADR-NNN → /adr/permanent/NNN/, unsupported patterns → null. RelatedLinks <span><a> anchor conversion + unsupported ID span fallback (graceful degradation). locale prop added
  2. rehype-adr-link-rewrite.ts (+75, new): unist-util-visit-based rehype plugin. ADR body ./sprint-NNN.md / ./topics/SLUG.md / ../adr-en/sprints/sprint-NNN.md relative paths → static export URL conversion
  3. markdown.ts (+13 −1): renderAdrMdx(source, locale) signature extended + [rehypeAdrLinkRewrite, { locale }] inserted into rehypePlugins chain (after rehypeSlug, before rehypeHighlight)
  4. adr-detail-view.tsx (+2 −1): locale prop passed to renderAdrMdx
  5. parser.ts (+5 −1): splitTableRow\| escaped pipe replaced with null byte placeholder (\x00PIPE\x00) → split → restore
  6. code-block.tsx (+8): extractLanguage result 'mermaid'<Mermaid chart={chartSource} /> early return branch
  7. docs/adr/sprints/sprint-160.md (+11): Phase A→F 6-step workflow Mermaid flowchart added
  8. docs/adr/sprints/sprint-67.md (−1): Non-existent ADR-004, ADR-005 references removed (Critic R1 P2)
  9. docs/adr-en/sprints/sprint-67.md (−1): Same change

Verification

ItemResult
tsc --noEmitclean (0 errors)
npm run build247 pages static export success
check-adr-en-coverage --strict110/110 (100.0%) PASS
check-doc-refs287 files 0 broken refs
Critic R1 (Codex, session 019e3ec2-5445-70d0-96e3-9d71fa8d8640)P0/P1 0 items, P2 1 item (sprint-67 non-existent ADR) → resolved (commit 23002cd), P3 2 items → Sprint 163 carry-over
Critic R2 (Codex, R2-sprint162-20260519)PASS. R1 P2 resolution confirmed, 0 new P0/P1/P2, P3 2 items (existing)

Branch Discipline ✅ 30 sprints of consecutive compliance

  • New branch feat/sprint-162-adr-links-mermaid + Squash merge pending
  • 0 direct commits to main, 0 --no-verify uses

New Patterns

  1. ID → URL conversion + span fallback patternresolveAdrUrl returns null on pattern match failure → existing span preserved. Prevents 404 anchor creation
  2. rehype plugin-based link normalization — Separated from parser.ts metadata extraction (extractOutgoingLinks) as a render-only transformation. Separation of concerns
  3. Null byte placeholder pattern\x00 used for escaped pipe handling ensures no general text collision
  4. CodeBlock → Mermaid early return branch — Reuses existing Mermaid component (dynamic import, SSG-safe). Zero new implementation
  5. Critic P2 → data integrity resolution — Critic detects frontmatter data issues, not code defects. Resolved via data correction rather than code modification
  • sprint-161.md — Previous sprint, user visual verification defect discovery pattern inheritance
  • sprint-160.md — Mermaid flowchart addition target