Skip to content

feat(editor): markdown edit mode — direct document editing with diff-to-agent feedback#890

Open
backnotprop wants to merge 2 commits into
mainfrom
feat/md-ed
Open

feat(editor): markdown edit mode — direct document editing with diff-to-agent feedback#890
backnotprop wants to merge 2 commits into
mainfrom
feat/md-ed

Conversation

@backnotprop

Copy link
Copy Markdown
Owner

Part of #887 (Phase 1). Closes the core ask from #424.

What this is

An Edit toggle in the plan review / annotate UI that swaps the rendered Viewer for a live-preview markdown editor — Obsidian-style: syntax shows on the active line, renders everywhere else. The raw markdown text is the source of truth; rendering is purely view-layer decorations (atomic-editor, CM6-based).

Preview:

editor2.mp4

How edits reach the agent

On Approve / Request Changes / Send Annotations, a "Direct Edits" section — a unified diff against the as-submitted document — is prepended to the feedback and rides the existing channels. Zero server changes; works on every runtime (Bun + Pi parity verified by inspection), and crucially works in plan mode where there is no file to write.

  • Edits and annotations compose freely; nothing sends until the user decides
  • Edits count as feedback in every gate (edits-only approve warns on Claude Code; Send Feedback / Cmd+Enter route edits through deny, the only channel whose output carries feedback there)
  • Committed edits appear as a pinned card in the annotations sidebar: +N/−M, expandable inline diff, two-step discard; the panel auto-opens on commit
  • Annotations re-anchor by text search after edits; block anchors are remapped against the new parse, and annotations whose text vanished are reported via toast (never silently broken)
  • Theme bridge maps the editor onto Plannotator tokens in dark and light mode; editor body sizing matches the Viewer prose

Why this editor (and not Milkdown/ProseMirror)

Parse→regenerate editors normalize markdown on serialize (bullet markers, escaping, spacing), so a one-word edit produces a noisy diff that would mislead the agent. atomic-editor keeps the document byte-for-byte and decorates the view instead. That property is enforced by test:

Fidelity guarantee

packages/ui/markdownEditorFidelity.test.tsx mounts the editor and asserts getMarkdown() is byte-identical to the input — across 3 synthetic PFM-torture fixtures (committed) plus a deterministic 150-file sample of real plans from ~/.plannotator/history (runtime-only, skips on CI). Run: DOM_TESTS=1 bun test markdownEditorFidelity. The happy-dom preload is opt-in via DOM_TESTS=1 so server-oriented tests keep native globals.

Gating

Edit is available only on the main plan / annotate-file markdown: hidden in archive, HTML render, plan diff, linked docs, message picker, folder browser (pre-selection), shared sessions, and after submit. Sidebar file/archive selection is blocked with a toast while an edit session is open.

Numbers

  • Bundle: +682 KB raw / +236 KB gzip on the single-file hook HTML (~+3.6%)
  • 962 insertions / 54 deletions across 10 files; new deps: @atomic-editor/editor + CM6 peers in packages/ui

Known deferrals (tracked in #887 Phase 2+)

  • Edits don't survive page refresh (draft persistence)
  • Warning-dialog copy says "annotations" where it now also covers edits
  • Cmd+Enter is inert while focus is inside the editor (consistent with other text fields)
  • PFM syntax (:::callouts, wiki-links) renders as plain text inside the editor; rendered view is unaffected
  • Direct file-write for annotate on local files (Phase 3); review-surface edits (Phase 4)

…to-agent feedback

Adds an Edit toggle to the plan/annotate UI that swaps the Viewer for a
CM6 live-preview editor (@atomic-editor/editor, Obsidian-style: raw
markdown is the source of truth, rendering is decoration-only). On
submit, user edits travel as a 'Direct Edits' unified diff through the
existing feedback channels — works on every surface including plan
mode, where no file exists. Zero server changes.

- Edit toggle in the Wide/Focus switcher row; toolstrip hidden while editing
- Committed edits surface as a pinned card in the annotations sidebar
  (+N/−M, expandable diff, two-step discard); panel auto-opens on commit
- Edits count as feedback in every submit gate (approve warning, Send
  Feedback routing, Cmd+Enter router, annotate gate mode)
- Annotations re-anchor by text search after edits; stale block anchors
  remapped, vanished text reported via toast
- Theme bridge maps --atomic-editor-* vars to Plannotator tokens (dark +
  light, including the specificity override atomic's light palette needs)
- Fidelity test: byte-identical load→read round-trips on 3 PFM fixtures
  + a 150-file sample of real plan history (DOM_TESTS=1 bun test, happy-dom
  preload is opt-in so server-oriented tests keep native globals)
- Bundle delta: +682 KB raw / +236 KB gzip on the single-file hook HTML

Part of #887
The editor component now ships as a standalone public package
(github.com/plannotator/markdown-editor, npm @plannotator/markdown-editor)
so other apps and the enterprise standalone can reuse it. The monorepo
keeps a ~40-line theme-bridging shim: App.tsx renders ThemeProvider
inside its own JSX, so the resolved color mode is read beneath the
provider and passed to the package as a prop; gridEnabled maps to the
package's cardClassName with the design-system utilities.

- bunfig: @plannotator/markdown-editor added to minimumReleaseAgeExcludes
  (same first-party exception as @pierre/diffs)
- local markdown-editor.css deleted after diff-verification against the
  published dist (all 24 variable declarations + light-mode block +
  transition rule byte-identical)
- gates unchanged: fidelity corpus 4/4, build delta +0.4 KB, suite at
  baseline

Tracked in plannotator/markdown-editor#1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant