Contribute rewrite Phase 1: CM6 markdown source editor

Swap the Contribute-mode editing surface from Tiptap WYSIWYG to a
CodeMirror 6 markdown source editor. Discuss mode and any read-only
viewing continues to render through Tiptap.

The §8.11 manual-edit debounce now reads the raw markdown source via
CodeMirror's doc, eliminating the lossy `editor.getText()` round-trip
that RFCView.jsx flagged as a §19.2 candidate; what the contributor
typed is exactly what gets POSTed to manual flush.

The §8.10 paragraph-margin gutter accent on Tiptap is dropped — it was
dead in read-only Discuss anyway, and Phase 4 of the Contribute
rewrite adds a change-anchored gutter against the CM6 raw pane.

The Tiptap-side accept-time injection of <span class="tracked-*">
markup also drops out — CM6 can't render those spans, and Phase 3's
preview pane is the proper home for tracked changes. The reviewMode /
DiffView toggle still works against marked-rendered HTML in the
interim until Phase 7 retires it.

Notes:
- Bundle gzipped grows ~50KB for CM6 modules (state/view/commands/
  language/lang-markdown). Mermaid in Phase 2 will be the larger lift
  and should lazy-load.
- Verified the CM6 editor mounts cleanly via Vite dev preview: ref
  handle (`view/getDoc/setDoc`) is wired, `onUpdate` fires on doc
  changes, gutter / line-numbers / active-line all render, and the
  source round-trips verbatim. Could not drive the full RFCView
  Contribute flow end-to-end without a running backend; the manual
  countdown + save-now + accept/decline pathways are verified by
  inspection only.
- 125 backend integration tests still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-05-25 09:49:17 -07:00
parent 55a8be051a
commit 13d59b5d26
6 changed files with 427 additions and 135 deletions
+26 -12
View File
@@ -1,4 +1,4 @@
/* Adapted from the prototype's App.css per §18, narrowed to slice-1 surfaces. */
/* App.css — narrowed to slice-1 surfaces. */
.boot {
display: flex; align-items: center; justify-content: center;
@@ -441,13 +441,6 @@
.editor-content .tiptap ul, .editor-content .tiptap ol { padding-left: 24px; }
.editor-content .tiptap code { background: #f0f0ee; padding: 1px 5px; border-radius: 3px; font-size: 13px; }
.editor-content .tiptap .paragraph-changed {
border-left: 3px solid #f59e0b;
padding-left: 10px;
margin-left: -13px;
background: linear-gradient(to right, #fffbeb 0%, transparent 60%);
border-radius: 0 4px 4px 0;
}
.editor-content .tiptap .selection-highlight {
background: rgba(99, 102, 241, 0.15);
border-radius: 2px;
@@ -463,6 +456,27 @@
border-radius: 2px; padding: 1px 2px; cursor: pointer;
}
/* CodeMirror 6 source editor (Contribute mode). */
.cm-source-editor {
flex: 1; min-height: 0;
display: flex; flex-direction: column;
overflow: hidden;
}
.cm-source-editor .cm-editor {
flex: 1; min-height: 0;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
font-size: 13px; line-height: 1.6;
background: #fff;
}
.cm-source-editor .cm-editor.cm-focused { outline: none; }
.cm-source-editor .cm-scroller { padding: 24px 0; }
.cm-source-editor .cm-content { padding: 0 32px; max-width: 880px; }
.cm-source-editor .cm-gutters {
background: #fafafa; border-right: 1px solid #f0f0ee; color: #b0b0b0;
}
.cm-source-editor .cm-activeLineGutter { background: #f3f4f6; color: #555; }
.cm-source-editor .cm-activeLine { background: #fafbfc; }
.readonly-bar {
border-top: 1px solid #e5e5e5;
padding: 10px 16px; text-align: center;
@@ -1236,8 +1250,8 @@
color: #fbbf24; /* admin link sits a notch warmer to signal authority */
}
/* /philosophy — the §14.2 read surface. The body inherits the
prototype's markdown styling; the header is a thin chrome strip. */
/* /philosophy — the §14.2 read surface. The body uses the shared
markdown styling; the header is a thin chrome strip. */
.chrome-pane {
flex: 1; min-width: 0; overflow: auto;
padding: 0; background: #fff;
@@ -1298,8 +1312,8 @@
}
/* Richer landing page (§14.1) — adds the three-item deck under the
pitch. The .landing container's flex centering stays from the
prototype; the new content lives inside .landing-inner. */
pitch. The .landing container handles flex centering; the new
content lives inside .landing-inner. */
.landing-inner {
max-width: 620px;
display: flex; flex-direction: column; align-items: center;