Skip to content

Duplicate MarkdownFormatter in app/ and website/ with active drift #983

@MaxGhenis

Description

@MaxGhenis

Summary

MarkdownFormatter.tsx exists in two places with active drift — every security fix must be written twice and they already disagree on real bugs (see sibling filings on useEffect-deps divergence and on the module-level document mutation). Single-source this into a shared package.

Location

  • app/src/components/blog/MarkdownFormatter.tsx (1032 lines)
  • website/src/components/blog/MarkdownFormatter.tsx (968 lines)

What goes wrong

Divergences between the two files as of origin/main:

Area app/ website/
useEffect deps for table <Td>, <Tr>, HighlightedBlock [ref.current?.cellIndex] (buggy — refs aren't reactive) [] (correct)
img renderer custom OptimisedImage plain <img>
blockquote Twitter-embed detection present (href?.startsWith('https://twitter.com/')) absent
li footnote parsing custom branch absent
rehype-raw pin ^9.0.0 ^10.1.0

Consequences:

  1. Security fixes must be duplicated. The footnote href-scheme validation added in Security: validate footnote inline link href schemes #950 only landed in one copy of the tree; raw <a> sanitization (see related bug about isSafeHref coverage) must be handled twice.
  2. Regressions. The correct [] deps in website/ have not been back-ported to app/ — a real user-facing bug persists because the two files drifted.
  3. Dependency divergence. rehype-raw v9 vs v10 have different AST guarantees and different behavior around whitespace-only nodes; bugs can reproduce in one tree but not the other.

Suggested fix

Extract the component and its helpers (isSafeHref, safeJsonParse, HighlightedBlock, the components map) into a shared package that both trees depend on. Options, ordered by preference:

  1. Publish from a monorepo workspace (e.g., packages/markdown-formatter) consumed by app/, website/, and calculator-app/.
  2. Symlink a shared/components/blog/MarkdownFormatter.tsx into both trees via TS path aliases.
  3. At minimum, add a CI check that diffs the two files and fails if they diverge outside a known allow-list.

Pin both trees to the same rehype-raw / react-markdown versions before extracting.

Severity

High — force-multiplier on every security and correctness bug in this file.

Relates to

Sibling filings in this batch: module-level DOM mutation in app/ only; useEffect deps fixed only in website/; missing parts[1] guard; isSafeHref coverage of raw HTML <a>. Also #949, #950, #956.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions