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
+7 -39
View File
@@ -431,31 +431,6 @@
.editor-content {
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). */
.cm-source-editor {
flex: 1; min-height: 0;
@@ -540,11 +515,11 @@
background: rgba(99, 102, 241, 0.22);
}
/* Tracked-change overlay (Phase 3, §8.10) — preview-pane equivalents of
the .editor-content .tiptap rules above. The Contribute pane shows
these by default; the Discuss pane gates them behind a toolbar
toggle. Hovering surfaces a ChangeTooltip with the source message
and reason. */
/* Tracked-change overlay (Phase 3, §8.10) — green/red decorations on
accepted-change spans inside the rendered preview. The Contribute
pane shows these by default; the Discuss pane gates them behind a
toolbar toggle. Hovering surfaces a ChangeTooltip with the source
message and reason. */
.markdown-preview .tracked-delete {
background: #fee2e2; color: #991b1b; text-decoration: line-through;
border-radius: 2px; padding: 1px 2px; cursor: pointer;
@@ -1057,16 +1032,9 @@
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 {
background: #fff; border: 1px solid #e5e5e5;
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
// 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({
renderer: {
code({ text, lang }) {
+52 -88
View File
@@ -6,9 +6,10 @@
// super-drafts. The discuss-vs-contribute flip on non-main / non-edit
// branches per §8.3 carries over unchanged.
//
// The active-RFC and super-draft surfaces share their editor,
// chat, change-card, DiffView, selection-tooltip, and PR-modal
// machinery; the only branchings are "Start Contributing" (which
// The active-RFC and super-draft surfaces share their editor, chat,
// change-card, preview-pane tracked-change overlay (§8.10), selection-
// tooltip, and PR-modal machinery; the only branchings are
// "Start Contributing" (which
// 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
// (super-draft-only per §9.5).
@@ -36,11 +37,9 @@ import {
import MarkdownSourceEditor from './MarkdownSourceEditor.jsx'
import MarkdownPreview from './MarkdownPreview.jsx'
import SelectionTooltip from './SelectionTooltip.jsx'
import { marked } from 'marked'
import PromptBar from './PromptBar.jsx'
import ChatPanel from './ChatPanel.jsx'
import ChangePanel, { diffWords } from './ChangePanel.jsx'
import DiffView from './DiffView.jsx'
import PRModal from './PRModal.jsx'
import GraduateDialog from './GraduateDialog.jsx'
import { claimOwnership } from '../api'
@@ -92,8 +91,6 @@ export default function RFCView({ viewer }) {
// selection is sourced from window.getSelection() inside the
// MarkdownPreview surface see MarkdownPreview's onSelectionChange.
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
// in the single-pane preview. Contribute mode is default-on (the
// pane exists for editorial review of your own work); Discuss mode
@@ -151,7 +148,6 @@ export default function RFCView({ viewer }) {
setChanges([])
setPendingDiscussChanges([])
setManualPending(null)
setReviewMode(false)
setDiscussShowChanges(false)
setSelection(null)
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
const onPickBranch = useCallback((name) => {
if (name === branchParam) return
@@ -505,7 +483,7 @@ export default function RFCView({ viewer }) {
const canContribute = branchView.capabilities?.can_contribute && branchParam !== 'main'
const canChangeSettings = branchView.capabilities?.can_change_branch_settings
const editorEditable = mode === 'contribute' && canContribute && !reviewMode
const editorEditable = mode === 'contribute' && canContribute
const showPromptBar = !!viewer
const inDiscuss = mode === 'discuss'
@@ -671,14 +649,6 @@ export default function RFCView({ viewer }) {
)}
{showContributeToolbar && (
<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">
{changes.filter(c => c.state === 'accepted').length} accepted ·{' '}
{pendingCount} pending
@@ -722,64 +692,58 @@ export default function RFCView({ viewer }) {
/>
</div>
)}
{reviewMode ? (
<DiffView html={reviewHTML} changes={changes} messages={messages} />
) : (
<>
{editorEditable ? (
<div className="editor-split">
<div className="editor-split-pane editor-split-raw">
<MarkdownSourceEditor
initialDoc={editorContent}
editorRef={editorRef}
onUpdate={handleEditorDocUpdate}
/>
</div>
<div className="editor-split-pane editor-split-preview">
<MarkdownPreview
content={previewContent}
onSelectionChange={handleSelectionChange}
changes={changes}
messages={messages}
showTrackedChanges
/>
</div>
</div>
) : (
{editorEditable ? (
<div className="editor-split">
<div className="editor-split-pane editor-split-raw">
<MarkdownSourceEditor
initialDoc={editorContent}
editorRef={editorRef}
onUpdate={handleEditorDocUpdate}
/>
</div>
<div className="editor-split-pane editor-split-preview">
<MarkdownPreview
content={editorContent}
content={previewContent}
onSelectionChange={handleSelectionChange}
className="markdown-preview-solo"
changes={changes}
messages={messages}
showTrackedChanges={inDiscuss && discussShowChanges}
showTrackedChanges
/>
)}
<SelectionTooltip
selection={selection}
onAsk={handleTooltipAsk}
onFlag={handleTooltipFlag}
disabled={isStreaming || !viewer}
models={models}
selectedModel={selectedModel}
onModelChange={setSelectedModel}
/>
{showPromptBar ? (
<PromptBar
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>
) : (
<MarkdownPreview
content={editorContent}
onSelectionChange={handleSelectionChange}
className="markdown-preview-solo"
changes={changes}
messages={messages}
showTrackedChanges={inDiscuss && discussShowChanges}
/>
)}
<SelectionTooltip
selection={selection}
onAsk={handleTooltipAsk}
onFlag={handleTooltipFlag}
disabled={isStreaming || !viewer}
models={models}
selectedModel={selectedModel}
onModelChange={setSelectedModel}
/>
{showPromptBar ? (
<PromptBar
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>