Contribute rewrite Phase 7: retire DiffView

The Phase 3 preview-pane inline overlay and Phase 4 raw-pane gutter
accent together cover the review affordance DiffView was the interim
home for, so the toolbar toggle collapses and the surface goes.

- frontend/src/components/DiffView.jsx deleted.
- RFCView.jsx: drop the DiffView + marked imports, the reviewMode /
  reviewHTML state, the toggleReviewMode callback, the "Review changes"
  toolbar button, and the reviewMode ternary in render. editorEditable
  simplifies to mode === 'contribute' && canContribute. File-header
  docstring updated: "DiffView" → "preview-pane tracked-change overlay
  (§8.10)".
- App.css: remove the .diff-view-wrapper and .diff-view-empty rules;
  relabel the DiffView section header as ChangeTooltip (which lives on
  as the Phase 3 overlay's hover affordance). Drop the dead .editor-
  content .tiptap rules — Tiptap had no remaining consumers after
  DiffView's deletion. The Contribute toolbar still renders the
  accepted/pending hint + Phase 6 Chat toggle; only the Review-changes
  button is removed.
- MarkdownPreview.jsx: comment updated to drop the DiffView mention
  from the scoped-Marked-instance rationale.
- SPEC.md: §8.10 retitled "Tracked-change markup" (was "...and the
  review-mode toggle"); the legacy-DiffView paragraph rewritten as a
  one-sentence retirement note. §8.1, §8.15, §9.4, §9.5 references to
  DiffView / review-mode cleaned up. §19.2's persistent-accepted-change
  candidate updated to point at the preview-pane overlay rather than
  DiffView.

No DiffView orphans remain (grep confirms .btn-review-toggle is still
the Discuss-mode "Show tracked changes" toggle, ChangeTooltip and
trackedOverlay carry historical comments only, PRView's §19.2 comment
is unrelated). Frontend reload shows no console errors.

Backend integration suite: 125 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-05-25 12:33:14 -07:00
parent 49e91f8829
commit 46049d296b
5 changed files with 87 additions and 206 deletions
+27 -30
View File
@@ -635,8 +635,8 @@ The RFC view uses a three-column shape:
- **Left column** — the RFC catalog from §7, unchanged. - **Left column** — the RFC catalog from §7, unchanged.
- **Center column** — a thin breadcrumb strip at the top showing the - **Center column** — a thin breadcrumb strip at the top showing the
current branch with a dropdown affordance; the editor (or diff view current branch with a dropdown affordance; the editor below it; a
in review mode) below it; a prompt bar at the bottom for chat input. prompt bar at the bottom for chat input.
- **Right column** — the chat thread for the currently-selected - **Right column** — the chat thread for the currently-selected
branch, with a change-card panel below it in contexts where editing branch, with a change-card panel below it in contexts where editing
is enabled (see §8.3). is enabled (see §8.3).
@@ -834,7 +834,7 @@ considered and rejected, not just what they accepted. To re-propose,
ask the AI again — the new proposal lands as a new card with the ask the AI again — the new proposal lands as a new card with the
declined predecessor still visible. declined predecessor still visible.
### 8.10 Tracked-change markup and the review-mode toggle ### 8.10 Tracked-change markup
Two visual layers carry change information in the Contribute split. Two visual layers carry change information in the Contribute split.
The first is a paragraph-margin marker on the left raw-source pane — The first is a paragraph-margin marker on the left raw-source pane —
@@ -871,12 +871,10 @@ marked span surfaces a tooltip with the change's type badge (`ai` or
`was_edited_before_accept` flag where set, the user prompt and `was_edited_before_accept` flag where set, the user prompt and
selection-quote that drove the change, and the AI's `reason`. selection-quote that drove the change, and the AI's `reason`.
DiffView is the legacy read-only render surface invoked via the DiffView, the legacy full-editor swap-in for inspecting accepted
Contribute-mode toolbar toggle (§8.15) — a full-editor swap-in that changes in context, was retired in the Contribute rewrite once the
reads the same `changes` table. It is retained as an interim path while raw-pane gutter and the preview-pane inline overlay together covered
the split-pane layers mature, and is slated for retirement once its review affordance.
the raw-pane gutter and the preview-pane inline overlay together cover
its review affordance; at that point the toolbar toggle collapses.
### 8.11 Manual edits and collisions with AI proposals ### 8.11 Manual edits and collisions with AI proposals
@@ -1047,10 +1045,9 @@ conversation produced it on.
These affordances are scoped to the current branch: These affordances are scoped to the current branch:
the selection tooltip (elaborated in §8.12 as the range-thread the selection tooltip (elaborated in §8.12 as the range-thread
entry point); the review-mode toggle and `DiffView` for inspecting entry point); the discuss-mode banner indicating read-only status;
accepted changes in context (§8.10); the discuss-mode banner the `<change>` / `<original>` / `<proposed>` / `<reason>` AI
indicating read-only status; the `<change>` / `<original>` / protocol (§18).
`<proposed>` / `<reason>` AI protocol (§18).
The following are implementation-level details that the build session The following are implementation-level details that the build session
will decide: will decide:
@@ -1249,12 +1246,11 @@ inline with the edit branches in the dropdown.
The document pane uses the same Tiptap editor as active RFCs, in The document pane uses the same Tiptap editor as active RFCs, in
read-only mode by default, identical to §8.2. The toolbar shape, the read-only mode by default, identical to §8.2. The toolbar shape, the
selection-tooltip from §8.12, the §8.13 flag affordances, and the selection-tooltip from §8.12, and the §8.13 flag affordances all map
review-mode/DiffView toggle from §8.10 all map across. The discuss across. The discuss vs. contribute mode toggle from §8.3 exists; the
vs. contribute mode toggle from §8.3 exists; the canonical body is canonical body is in discuss mode like main on an active RFC, and the
in discuss mode like main on an active RFC, and the "Start "Start Contributing" affordance cuts an edit branch per §9.5 rather
Contributing" affordance cuts an edit branch per §9.5 rather than than flipping mode inline.
flipping mode inline.
§8.7's read-only fallbacks apply identically. Anonymous viewers see §8.7's read-only fallbacks apply identically. Anonymous viewers see
the full body, the selection tooltip works but its submit is the full body, the selection tooltip works but its submit is
@@ -1285,10 +1281,11 @@ On the edit branch, everything from §8.4 through §8.14 applies
unchanged. The per-branch chat (§8.4), AI proposals materializing unchanged. The per-branch chat (§8.4), AI proposals materializing
as `<change>` rows, accept/decline/edit-before-accept (§8.9), as `<change>` rows, accept/decline/edit-before-accept (§8.9),
manual-edit flushes (§8.6), the change-card panel (§8.8), range and manual-edit flushes (§8.6), the change-card panel (§8.8), range and
paragraph sub-threads (§8.12), flags (§8.13), DiffView (§8.10), paragraph sub-threads (§8.12), flags (§8.13), the tracked-change
stale-change handling (§8.11). The "branch" abstraction §8 was markup (§8.10), stale-change handling (§8.11). The "branch"
written against is the meta-repo edit branch; the machinery does abstraction §8 was written against is the meta-repo edit branch;
not care whether the underlying repo is per-RFC or meta. the machinery does not care whether the underlying repo is per-RFC
or meta.
Opening a PR is §10.1's gesture, with the meta-repo branch as Opening a PR is §10.1's gesture, with the meta-repo branch as
source and the meta repo as target. The PR creation modal (§10.2), source and the meta repo as target. The PR creation modal (§10.2),
@@ -2907,13 +2904,13 @@ binding.
file-rename bot sequence, the redirect handling for any links file-rename bot sequence, the redirect handling for any links
into the old slug, and the cache and threads migration. into the old slug, and the cache and threads migration.
- **Persistent accepted-change markup for returning contributors.** - **Persistent accepted-change markup for returning contributors.**
§8.10 commits the editor's tracked-change markup to session- §8.10 commits the editor's tracked-change markup to session-local
local scope and points returning-contributor needs at DiffView. scope via the preview-pane overlay (default-on in Contribute,
A future session may revisit this with a per-user, per-branch toggle-gated in Discuss). A future session may revisit this with a
seen-cursor for accepted changes (mirroring §10.3's PR seen- per-user, per-branch seen-cursor for accepted changes (mirroring
cursor) — markup persisting across reloads, dismissible with a §10.3's PR seen-cursor) — markup persisting across reloads,
"mark as seen" gesture. Triggered by evidence of contributors dismissible with a "mark as seen" gesture. Triggered by evidence
asking for it, not ahead of evidence. of contributors asking for it, not ahead of evidence.
- **AI participation as a notification source.** Topic 13 settled - **AI participation as a notification source.** Topic 13 settled
that user-driven events carry the underlying user as actor and that user-driven events carry the underlying user as actor and
system-generated events carry null. AI participant completions system-generated events carry null. AI participant completions
+7 -39
View File
@@ -431,31 +431,6 @@
.editor-content { .editor-content {
max-width: 720px; margin: 0 auto; outline: none; max-width: 720px; margin: 0 auto; outline: none;
} }
.editor-content .tiptap {
outline: none; font-size: 15px; line-height: 1.75; color: #1a1a1a;
}
.editor-content .tiptap h1 { font-size: 22px; font-weight: 700; margin: 24px 0 12px; }
.editor-content .tiptap h2 { font-size: 17px; font-weight: 600; margin: 20px 0 8px; }
.editor-content .tiptap h3 { font-size: 15px; font-weight: 600; margin: 16px 0 6px; }
.editor-content .tiptap p { margin: 0 0 12px; }
.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 .selection-highlight {
background: rgba(99, 102, 241, 0.15);
border-radius: 2px;
outline: 1px solid rgba(99, 102, 241, 0.3);
outline-offset: 1px;
}
.editor-content .tiptap .tracked-delete {
background: #fee2e2; color: #991b1b; text-decoration: line-through;
border-radius: 2px; padding: 1px 2px; cursor: pointer;
}
.editor-content .tiptap .tracked-insert {
background: #dcfce7; color: #166534;
border-radius: 2px; padding: 1px 2px; cursor: pointer;
}
/* CodeMirror 6 source editor (Contribute mode). */ /* CodeMirror 6 source editor (Contribute mode). */
.cm-source-editor { .cm-source-editor {
flex: 1; min-height: 0; flex: 1; min-height: 0;
@@ -540,11 +515,11 @@
background: rgba(99, 102, 241, 0.22); background: rgba(99, 102, 241, 0.22);
} }
/* Tracked-change overlay (Phase 3, §8.10) preview-pane equivalents of /* Tracked-change overlay (Phase 3, §8.10) green/red decorations on
the .editor-content .tiptap rules above. The Contribute pane shows accepted-change spans inside the rendered preview. The Contribute
these by default; the Discuss pane gates them behind a toolbar pane shows these by default; the Discuss pane gates them behind a
toggle. Hovering surfaces a ChangeTooltip with the source message toolbar toggle. Hovering surfaces a ChangeTooltip with the source
and reason. */ message and reason. */
.markdown-preview .tracked-delete { .markdown-preview .tracked-delete {
background: #fee2e2; color: #991b1b; text-decoration: line-through; background: #fee2e2; color: #991b1b; text-decoration: line-through;
border-radius: 2px; padding: 1px 2px; cursor: pointer; border-radius: 2px; padding: 1px 2px; cursor: pointer;
@@ -1057,16 +1032,9 @@
font-size: 13px; font-weight: 600; cursor: pointer; font-size: 13px; font-weight: 600; cursor: pointer;
} }
/* ── DiffView ──────────────────────────────────────────────────── */ /* ChangeTooltip (shared between the Phase 3 preview-pane overlay
and the §8.10 hover affordance) */
.diff-view-wrapper {
flex: 1; overflow-y: auto;
padding: 32px 48px;
}
.diff-view-empty {
font-size: 13px; color: #999;
text-align: center; padding: 24px;
}
.diff-tooltip { .diff-tooltip {
background: #fff; border: 1px solid #e5e5e5; background: #fff; border: 1px solid #e5e5e5;
border-radius: 8px; padding: 10px 12px; border-radius: 8px; padding: 10px 12px;
-48
View File
@@ -1,48 +0,0 @@
// DiffView.jsx the §8.10 read-only render surface for accepted changes.
//
// In contribute mode, a toolbar toggle replaces the editor with this
// view. We reconstruct the markup for every accepted change in branch
// history by reading the `changes` table (passed in as `changes`) plus
// the current rendered HTML; hovering any tracked span surfaces a
// tooltip with the change's type/model/prompt/reason context.
import { useState, useCallback } from 'react'
import ChangeTooltip from './ChangeTooltip.jsx'
export default function DiffView({ html, changes, messages }) {
const [tooltip, setTooltip] = useState(null)
const handleMouseMove = useCallback((e) => {
const span = e.target.closest('[data-change-id]')
if (span) {
const id = span.getAttribute('data-change-id')
const change = changes.find(c => String(c.id) === String(id))
if (change) {
setTooltip({ change, position: { x: e.clientX, y: e.clientY } })
return
}
}
setTooltip(null)
}, [changes])
const acceptedCount = changes.filter(c => c.state === 'accepted').length
return (
<div className="diff-view-wrapper">
{acceptedCount === 0 && (
<div className="diff-view-empty">
No accepted changes yet on this branch. Accept proposals from the
change panel to see them rendered here in place.
</div>
)}
<div className="editor-content">
<div
className="tiptap diff-view-document"
onMouseMove={handleMouseMove}
onMouseLeave={() => setTooltip(null)}
dangerouslySetInnerHTML={{ __html: html }}
/>
</div>
{tooltip && <ChangeTooltip change={tooltip.change} messages={messages} position={tooltip.position} />}
</div>
)
}
+1 -1
View File
@@ -49,7 +49,7 @@ function escapeHtml(s) {
} }
// Scoped Marked instance so the mermaid-fence renderer doesn't leak // Scoped Marked instance so the mermaid-fence renderer doesn't leak
// to other call sites (Editor.jsx, DiffView review HTML, etc.). // to any other call site that might construct its own Marked.
const previewMarked = new Marked({ const previewMarked = new Marked({
renderer: { renderer: {
code({ text, lang }) { code({ text, lang }) {
+52 -88
View File
@@ -6,9 +6,10 @@
// super-drafts. The discuss-vs-contribute flip on non-main / non-edit // super-drafts. The discuss-vs-contribute flip on non-main / non-edit
// branches per §8.3 carries over unchanged. // branches per §8.3 carries over unchanged.
// //
// The active-RFC and super-draft surfaces share their editor, // The active-RFC and super-draft surfaces share their editor, chat,
// chat, change-card, DiffView, selection-tooltip, and PR-modal // change-card, preview-pane tracked-change overlay (§8.10), selection-
// machinery; the only branchings are "Start Contributing" (which // tooltip, and PR-modal machinery; the only branchings are
// "Start Contributing" (which
// dispatches to promote-to-branch for active main and start-edit-branch // dispatches to promote-to-branch for active main and start-edit-branch
// for a super-draft's canonical body per §9.5) and the metadata pane // for a super-draft's canonical body per §9.5) and the metadata pane
// (super-draft-only per §9.5). // (super-draft-only per §9.5).
@@ -36,11 +37,9 @@ import {
import MarkdownSourceEditor from './MarkdownSourceEditor.jsx' import MarkdownSourceEditor from './MarkdownSourceEditor.jsx'
import MarkdownPreview from './MarkdownPreview.jsx' import MarkdownPreview from './MarkdownPreview.jsx'
import SelectionTooltip from './SelectionTooltip.jsx' import SelectionTooltip from './SelectionTooltip.jsx'
import { marked } from 'marked'
import PromptBar from './PromptBar.jsx' import PromptBar from './PromptBar.jsx'
import ChatPanel from './ChatPanel.jsx' import ChatPanel from './ChatPanel.jsx'
import ChangePanel, { diffWords } from './ChangePanel.jsx' import ChangePanel, { diffWords } from './ChangePanel.jsx'
import DiffView from './DiffView.jsx'
import PRModal from './PRModal.jsx' import PRModal from './PRModal.jsx'
import GraduateDialog from './GraduateDialog.jsx' import GraduateDialog from './GraduateDialog.jsx'
import { claimOwnership } from '../api' import { claimOwnership } from '../api'
@@ -92,8 +91,6 @@ export default function RFCView({ viewer }) {
// selection is sourced from window.getSelection() inside the // selection is sourced from window.getSelection() inside the
// MarkdownPreview surface see MarkdownPreview's onSelectionChange. // MarkdownPreview surface see MarkdownPreview's onSelectionChange.
const [selection, setSelection] = useState(null) const [selection, setSelection] = useState(null)
const [reviewMode, setReviewMode] = useState(false)
const [reviewHTML, setReviewHTML] = useState('')
// Phase 3 Discuss-mode opt-in for the §8.10 tracked-change overlay // Phase 3 Discuss-mode opt-in for the §8.10 tracked-change overlay
// in the single-pane preview. Contribute mode is default-on (the // in the single-pane preview. Contribute mode is default-on (the
// pane exists for editorial review of your own work); Discuss mode // pane exists for editorial review of your own work); Discuss mode
@@ -151,7 +148,6 @@ export default function RFCView({ viewer }) {
setChanges([]) setChanges([])
setPendingDiscussChanges([]) setPendingDiscussChanges([])
setManualPending(null) setManualPending(null)
setReviewMode(false)
setDiscussShowChanges(false) setDiscussShowChanges(false)
setSelection(null) setSelection(null)
setMode('discuss') setMode('discuss')
@@ -467,24 +463,6 @@ export default function RFCView({ viewer }) {
} }
}, []) }, [])
const toggleReviewMode = useCallback(() => {
setReviewMode(prev => {
if (!prev) {
// CM6 is the contribute-mode editor in Phase 1, so source HTML
// comes from rendering the current markdown via marked. The
// per-session inline tracked-change spans that Tiptap accumulated
// are gone for now; Phase 3's preview pane is the proper home
// for tracked changes anyway.
const editor = editorRef.current
const md = typeof editor?.getDoc === 'function'
? editor.getDoc()
: (branchView?.body || '')
setReviewHTML(marked.parse(md))
}
return !prev
})
}, [branchView])
// Branch dropdown navigation // Branch dropdown navigation
const onPickBranch = useCallback((name) => { const onPickBranch = useCallback((name) => {
if (name === branchParam) return if (name === branchParam) return
@@ -505,7 +483,7 @@ export default function RFCView({ viewer }) {
const canContribute = branchView.capabilities?.can_contribute && branchParam !== 'main' const canContribute = branchView.capabilities?.can_contribute && branchParam !== 'main'
const canChangeSettings = branchView.capabilities?.can_change_branch_settings const canChangeSettings = branchView.capabilities?.can_change_branch_settings
const editorEditable = mode === 'contribute' && canContribute && !reviewMode const editorEditable = mode === 'contribute' && canContribute
const showPromptBar = !!viewer const showPromptBar = !!viewer
const inDiscuss = mode === 'discuss' const inDiscuss = mode === 'discuss'
@@ -671,14 +649,6 @@ export default function RFCView({ viewer }) {
)} )}
{showContributeToolbar && ( {showContributeToolbar && (
<div className="editor-toolbar"> <div className="editor-toolbar">
<button
type="button"
className={`btn-review-toggle${reviewMode ? ' active' : ''}`}
onClick={toggleReviewMode}
title="Toggle DiffView: read-only render of accepted changes in context"
>
{reviewMode ? '← Back to editing' : 'Review changes'}
</button>
<span className="editor-toolbar-hint"> <span className="editor-toolbar-hint">
{changes.filter(c => c.state === 'accepted').length} accepted ·{' '} {changes.filter(c => c.state === 'accepted').length} accepted ·{' '}
{pendingCount} pending {pendingCount} pending
@@ -722,64 +692,58 @@ export default function RFCView({ viewer }) {
/> />
</div> </div>
)} )}
{reviewMode ? ( {editorEditable ? (
<DiffView html={reviewHTML} changes={changes} messages={messages} /> <div className="editor-split">
) : ( <div className="editor-split-pane editor-split-raw">
<> <MarkdownSourceEditor
{editorEditable ? ( initialDoc={editorContent}
<div className="editor-split"> editorRef={editorRef}
<div className="editor-split-pane editor-split-raw"> onUpdate={handleEditorDocUpdate}
<MarkdownSourceEditor />
initialDoc={editorContent} </div>
editorRef={editorRef} <div className="editor-split-pane editor-split-preview">
onUpdate={handleEditorDocUpdate}
/>
</div>
<div className="editor-split-pane editor-split-preview">
<MarkdownPreview
content={previewContent}
onSelectionChange={handleSelectionChange}
changes={changes}
messages={messages}
showTrackedChanges
/>
</div>
</div>
) : (
<MarkdownPreview <MarkdownPreview
content={editorContent} content={previewContent}
onSelectionChange={handleSelectionChange} onSelectionChange={handleSelectionChange}
className="markdown-preview-solo"
changes={changes} changes={changes}
messages={messages} messages={messages}
showTrackedChanges={inDiscuss && discussShowChanges} showTrackedChanges
/> />
)} </div>
<SelectionTooltip </div>
selection={selection} ) : (
onAsk={handleTooltipAsk} <MarkdownPreview
onFlag={handleTooltipFlag} content={editorContent}
disabled={isStreaming || !viewer} onSelectionChange={handleSelectionChange}
models={models} className="markdown-preview-solo"
selectedModel={selectedModel} changes={changes}
onModelChange={setSelectedModel} messages={messages}
/> showTrackedChanges={inDiscuss && discussShowChanges}
{showPromptBar ? ( />
<PromptBar )}
selection={selection?.text || null} <SelectionTooltip
onSubmit={handlePrompt} selection={selection}
disabled={isStreaming} onAsk={handleTooltipAsk}
models={models} onFlag={handleTooltipFlag}
selectedModel={selectedModel} disabled={isStreaming || !viewer}
onModelChange={setSelectedModel} models={models}
discussMode={inDiscuss} selectedModel={selectedModel}
/> onModelChange={setSelectedModel}
) : ( />
<div className="readonly-bar"> {showPromptBar ? (
Read-only view. <a href="/auth/login">Sign in</a> to participate. <PromptBar
</div> selection={selection?.text || null}
)} onSubmit={handlePrompt}
</> disabled={isStreaming}
models={models}
selectedModel={selectedModel}
onModelChange={setSelectedModel}
discussMode={inDiscuss}
/>
) : (
<div className="readonly-bar">
Read-only view. <a href="/auth/login">Sign in</a> to participate.
</div>
)} )}
</div> </div>