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.
- **Center column** — a thin breadcrumb strip at the top showing the
current branch with a dropdown affordance; the editor (or diff view
in review mode) below it; a prompt bar at the bottom for chat input.
current branch with a dropdown affordance; the editor below it; a
prompt bar at the bottom for chat input.
- **Right column** — the chat thread for the currently-selected
branch, with a change-card panel below it in contexts where editing
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
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.
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
selection-quote that drove the change, and the AI's `reason`.
DiffView is the legacy read-only render surface invoked via the
Contribute-mode toolbar toggle (§8.15) — a full-editor swap-in that
reads the same `changes` table. It is retained as an interim path while
the split-pane layers mature, and is slated for retirement once
the raw-pane gutter and the preview-pane inline overlay together cover
its review affordance; at that point the toolbar toggle collapses.
DiffView, the legacy full-editor swap-in for inspecting accepted
changes in context, was retired in the Contribute rewrite once the
raw-pane gutter and the preview-pane inline overlay together covered
its review affordance.
### 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:
the selection tooltip (elaborated in §8.12 as the range-thread
entry point); the review-mode toggle and `DiffView` for inspecting
accepted changes in context (§8.10); the discuss-mode banner
indicating read-only status; the `<change>` / `<original>` /
`<proposed>` / `<reason>` AI protocol (§18).
entry point); the discuss-mode banner indicating read-only status;
the `<change>` / `<original>` / `<proposed>` / `<reason>` AI
protocol (§18).
The following are implementation-level details that the build session
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
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
review-mode/DiffView toggle from §8.10 all map across. The discuss
vs. contribute mode toggle from §8.3 exists; the canonical body is
in discuss mode like main on an active RFC, and the "Start
Contributing" affordance cuts an edit branch per §9.5 rather than
flipping mode inline.
selection-tooltip from §8.12, and the §8.13 flag affordances all map
across. The discuss vs. contribute mode toggle from §8.3 exists; the
canonical body is in discuss mode like main on an active RFC, and the
"Start Contributing" affordance cuts an edit branch per §9.5 rather
than flipping mode inline.
§8.7's read-only fallbacks apply identically. Anonymous viewers see
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
as `<change>` rows, accept/decline/edit-before-accept (§8.9),
manual-edit flushes (§8.6), the change-card panel (§8.8), range and
paragraph sub-threads (§8.12), flags (§8.13), DiffView (§8.10),
stale-change handling (§8.11). The "branch" abstraction §8 was
written against is the meta-repo edit branch; the machinery does
not care whether the underlying repo is per-RFC or meta.
paragraph sub-threads (§8.12), flags (§8.13), the tracked-change
markup (§8.10), stale-change handling (§8.11). The "branch"
abstraction §8 was written against is the meta-repo edit branch;
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
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
into the old slug, and the cache and threads migration.
- **Persistent accepted-change markup for returning contributors.**
§8.10 commits the editor's tracked-change markup to session-
local scope and points returning-contributor needs at DiffView.
A future session may revisit this with a per-user, per-branch
seen-cursor for accepted changes (mirroring §10.3's PR seen-
cursor) — markup persisting across reloads, dismissible with a
"mark as seen" gesture. Triggered by evidence of contributors
asking for it, not ahead of evidence.
§8.10 commits the editor's tracked-change markup to session-local
scope via the preview-pane overlay (default-on in Contribute,
toggle-gated in Discuss). A future session may revisit this with a
per-user, per-branch seen-cursor for accepted changes (mirroring
§10.3's PR seen-cursor) — markup persisting across reloads,
dismissible with a "mark as seen" gesture. Triggered by evidence
of contributors asking for it, not ahead of evidence.
- **AI participation as a notification source.** Topic 13 settled
that user-driven events carry the underlying user as actor and
system-generated events carry null. AI participant completions
+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>