Skip to content

Latest commit

 

History

History
369 lines (289 loc) · 33.9 KB

File metadata and controls

369 lines (289 loc) · 33.9 KB

Markdown Feedback — Backlog & Roadmap

Completed

Phase 1: Intercept Spike

  • TipTap 3 + React 19 + Vite 7 scaffold
  • trackedDeletion mark (red strikethrough, contenteditable=false, non-inclusive)
  • trackedInsertion mark (green, inclusive, editable)
  • Intercept plugin via handleKeyDown, handleTextInput, handlePaste
  • Substitution pairs linked via pairedWith attribute (nanoid IDs)
  • Cursor skips over deletion spans
  • Editing within insertions works normally (no tracking)
  • Browser-validated, no console errors

Phase 2: CriticMarkup Serialization + Source View

  • Serialize editor state → CriticMarkup string (spans → {++…++}, {--…--}, {~~…~>…~~})
  • Collapsible source view panel (bottom, read-only, syntax-highlighted)
  • Live update on edit (debounced ~500ms)
  • Copy-to-clipboard button on source view
  • GitHub Pages deployment (auto-deploy on push to main)
  • Renamed app to "Markdown Feedback"
  • Browser-verified: source view renders, expands/collapses, syntax colors, copy button, debounce all confirmed working

Phase 3: Import / Export

  • CriticMarkup parser/deserializer (parseCriticMarkup.ts)
  • Paste import modal with automatic CriticMarkup parsing
  • Sample content loaded through parser (not hardcoded HTML)
  • Primary export: .md download with YAML frontmatter (criticmark: namespace)
  • Secondary exports: clean/accepted, original/rejected, copy to clipboard
  • Export dropdown menu component

Phase 3 scope decisions:

  • File picker and drag-and-drop deferred — paste is the primary import modality
  • Import always parses CriticMarkup (no "Start fresh" vs "Resume editing" prompt). The planned "rebaseline" feature (see Backlog > Source View Actions) is the path to clear markup when desired.
  • localStorage persistence deferred to Phase 6

Phase 4: Changes Panel

  • Right sidebar listing all tracked changes by document position
  • Change type icons + color coding (deletion/insertion/substitution)
  • Context snippets (~5 words before/after surrounding text)
  • Change count in panel header
  • Click-to-scroll: clicking a change scrolls editor to that location and selects it
  • Two-column layout (editor left, panel right, source view full-width below)

Phase 4 scope decisions:

  • Uncommented count and filter toggle deferred to Phase 5 (requires comments)
  • Scroll-to uses native text selection highlight (no pulse/glow decoration yet)

Phase 5: Annotation System

  • Inline comment input in Changes Panel per change (text field below each change card, auto-save on blur)
  • Standalone comment: select text in editor + Cmd+Shift+H to create a highlight + comment
  • Standalone comments appear in Changes Panel alongside edits, in document order
  • Keyboard shortcut (Tab) to jump focus from editor to comment input — only when cursor is on/adjacent to a tracked change or highlight; otherwise Tab functions normally
  • From comment input: Enter to save, Tab to return focus to editor
  • Edit comments serialize as {>>…<<} immediately after their change
  • Standalone comments serialize as {==highlighted text==}{>>comment<<}
  • Both comment types re-import on paste import
  • Orphaned comment handling (pruned on editor update when change ID no longer exists)
  • Comment count in panel header ("N changes, M comments")
  • TrackedHighlight mark (yellow highlight, non-inclusive)
  • Comments stored in React state as Record<string, string> (external to ProseMirror)
  • Source view syntax highlighting for highlights and comments

Phase 5 design decisions:

  • No separate Edit/Annotate mode — editor stays editable at all times
  • Comments live in React state, not ProseMirror mark attributes (substitutions have two marks; external state is simpler)
  • Tab only activates when cursor is on/adjacent to a tracked change; otherwise Tab functions normally
  • Custom DOM events (trackchanges:tab-to-comment, trackchanges:create-highlight) bridge plugin keyboard shortcuts to React state

Phase 6: Session Persistence + Undo

  • localStorage auto-save (debounced 1s)
  • Session recovery prompt on app load ("Resume your previous session?" with relative timestamp)
  • Undo/redo at tracked-change level — ProseMirror's History extension handles this natively because the intercept layer applies/removes marks rather than deleting text
  • Mixed-span operations (select across original + inserted + deleted text) — already working via collectTextRanges in Phase 1

Phase 6 design decisions:

  • Save format: serialized CriticMarkup string + comments map + timestamp (same as export format, human-readable in DevTools)
  • Restore path reuses the existing import flow (criticMarkupToHTMLsetContent + setComments)
  • Auto-save debounced at 1s via useDebouncedValue hook
  • Recovery modal: "Start Fresh" clears localStorage and keeps sample content; "Resume" restores via import path

Phase 10: Editor UX Hardening

Small UI improvements that benefit all platforms (web, VSCode, Tauri) before resuming native app work. Removes assumptions baked in from early prototyping (sample content, monolithic toolbar) and lays groundwork for rich markdown rendering.

  • Phase A: Empty editor default + placeholder text — remove hardcoded sample content; editor starts blank on all platforms. Web: recover from localStorage if session exists (existing RecoveryModal), otherwise blank. VSCode: loads opened document (existing loadDocument). Empty editor shows faint placeholder ("Click here to begin writing") via @tiptap/extension-placeholder. Placeholder disappears on first input or import.
  • Phase B: UI toolbar refactor — extract shared editor controls (track changes on/off, font selector, markdown decorations on/off) into an EditorControls component. Isolate web-only controls (Import, Export) so they are excluded from the VSCode WebView toolbar. Use platform adapter capabilities flags for conditional rendering. Font selector moved from AboutPanel to toolbar.
  • Phase C: Rich markdown decorations spike — time-boxed research. Evaluated TipTap StarterKit nodes/marks vs. tiptap-markdown vs. ProseMirror decorations vs. hybrid. Recommendation: Approach A (StarterKit — already active). Spike report: docs/markdown-decorations-spike.md.
  • Phase D: Rich markdown decorations (implement) — render markdown syntax as styled content (headings, bold, italic, code, strikethrough, lists, blockquotes, code blocks) while preserving the underlying CriticMarkup source. Toggle on/off via toolbar "Rich/Plain" button. Parser enhanced with richMode flag for inline markdown and block elements. Serializer emits markdown syntax for all inline marks and block node types. Round-trip safe. Does not interfere with the intercept model. Known defect (accepted): In Plain mode, heading ## markers are not displayed — heading levels cannot be changed in Plain mode. Workaround: edit in Rich mode.

Phase 10 design decisions:

  • StarterKit already includes heading, bold, italic, code, strikethrough, lists, blockquote, and code block nodes/marks — no new TipTap extensions needed.
  • criticMarkupToHTML accepts a richMode boolean. When true, inline markdown (**, *, `, ~~) is converted to HTML tags (<strong>, <em>, <code>, <s>). Block elements (##, - , 1. , > , triple-backtick code blocks) are converted to their HTML equivalents. When false, all syntax characters are emitted as literal text.
  • serializeCriticMarkup handles all block node types (heading, bulletList, orderedList, listItem, blockquote, codeBlock) and inline marks (bold, italic, code, strike) by emitting the appropriate markdown syntax.
  • Toggle re-parses the entire document from rawMarkup via toggleDecorations() in the store. This is a full re-import, not a decoration layer — the ProseMirror doc structure changes between modes.
  • Decorations preference persisted to localStorage under decorationsEnabled key.

Up Next

Phase 7: DOCX Import (Google Docs → CriticMarkup)

Import a .docx file exported from Google Docs (with Suggesting mode edits) and reconstruct all tracked changes and comments as CriticMarkup. All processing client-side — JSZip + browser-native DOMParser. Full spec: docs/docx-import.md.

  • Phase A: Basic .docx → markdown (no tracked changes) — JSZip + DOMParser, file picker UI
  • Phase B: Tracked changes → CriticMarkup ({++…++}, {--…--}, {~~…~~})
  • Phase C: Comments extraction from word/comments.xml{>>…<<}
  • Phase D: Comment-to-change attribution (comment on suggestion vs. comment on plain text)
  • Phase E: Polish — lists, moves, hyperlinks, edge cases, lazy loading (pick up opportunistically)

Phase 7 design decisions:

  • Output is a CriticMarkup markdown string — same format as paste import. Entire existing pipeline (criticMarkupToHTMLsetContent) works unchanged.
  • Google Docs puts <w:ins> before <w:del> for substitutions (reversed from Word). Walker detects both orderings.
  • Comment markers (commentRangeStart/End, commentReference) can nest inside <w:ins>/<w:del> elements — walker scans inside tracked changes to find them.
  • Comment on a substitution: extractCommentsFromSegments keys the comment by the deletion's ID (matching how extractChanges identifies substitution entries).
  • Lists: word/numbering.xml parsed to map (numId, ilvl)bullet/decimal, emitted as - or 1. prefixes.
  • Entries joined with \n\n (not \n) because criticMarkupToHTML splits blocks on double-newline.

Phase 8: Multi-Platform Foundation + macOS App

Platform adapter hardening, then native macOS app via Tauri 2. Full spec: docs/desktop-app.md.

Execution order note: Phases 8A–D, 9A–B, and 10A–D are all complete. Phase 10 (editor UX hardening) was done before resuming 8E because the toolbar refactor and empty-editor default improve the shared app that all native targets embed — better to get the UI right before wiring up native file operations.

  • Phase A: State management extraction — moved document state from Editor.tsx into Zustand store (documentStore.ts). Abstract persistence layer (stores/persistence/). Web app behavior unchanged.
  • Phase B: Track changes toggle — module-level _trackingEnabled flag, all three handlers check flag and passthrough when disabled. Toolbar toggle pill + Cmd+Shift+T shortcut. appendTransaction strips inclusive insertion marks from untracked text. Keyboard shortcuts section added to About panel. Ships on web.
  • Phase C: Platform adapter hardening — expand PlatformAdapter interface beyond persistence to cover file I/O (openFile, saveFile, getCurrentFilePath), platform signals (setDirty, onExternalFileChange), and a capabilities flag object for conditional UI. Web adapter satisfies all optional fields with no-ops. No user-visible change — prerequisite refactor for all native targets. (Do before Phase 9 VSCode and before Phase 8D Tauri.)
  • Phase D: Tauri shell — src-tauri/ boilerplate with Tauri 2, tauri.conf.json pointing at Vite, minimal Tauri platform adapter (stores/persistence/tauri.ts, localStorage-based stub), placeholder icons, npm run tauri:dev / tauri:build scripts. Vite config updated with conditional Tauri server settings. App compiles and opens in native macOS window. Not yet manually verified: full editor behavior in WKWebView (type, delete, substitute, comment, import/export) — compile-tested only.
  • Phase E: Native file operations — Open/Save/SaveAs via @tauri-apps/plugin-dialog + @tauri-apps/plugin-fs. Native menu bar (File/Edit/View). Dirty state in title bar. File associations for .md and .docx. Cmd+S saves to disk. (Resume after Phase 10.)
  • Phase F: Mac App Store preparation — sandbox entitlements, universal binary, code signing, notarization, GitHub Actions CI for macOS build, DMG for direct distribution. (Requires Apple Developer Program membership.)
  • Phase G: Polish — Recent Files menu, Edit menu integration, window state persistence, auto-update via Tauri updater plugin.

Phase 8 design decisions:

  • Tauri 2 chosen over Electron (3-8 MB binary vs 150-400 MB; WKWebView = no private API risk for MAS) and Swift+WKWebView (cross-platform > macOS-only).
  • Platform adapter pattern with runtime detection (window.__TAURI__ / acquireVsCodeApi), not build-time branching. npm run dev and npm run build never touch native targets.
  • State extraction (Phase A) is the prerequisite refactor — avoids cementing Editor.tsx as a God component before adding file path, dirty state, and tracking toggle.
  • Track changes toggle (Phase B) uses a module-level variable in trackChanges.ts (not TipTap extension storage, which resets on useEditor re-render). Text typed with tracking off becomes indistinguishable from "original" text (intentional, matches Word/Google Docs behavior).
  • Save format is CriticMarkup with YAML frontmatter (same as current export). Round-trip safe.
  • Known defect (accepted): iOS virtual keyboard does not reliably fire keydown for Backspace/Delete. Fixed: beforeinput handler implemented in trackChanges.ts (handles all deletion inputTypes with 50ms timestamp guard). See docs/ios-app.md §4.
  • Phase 8D note: The Tauri adapter is a localStorage stub identical to the web adapter. Real Tauri plugin integrations (@tauri-apps/plugin-fs, @tauri-apps/plugin-dialog) are deferred to Phase 8E. Detection uses runtime check ('__TAURI__' in window) in stores/persistence/index.ts.

Phase 9: VSCode Extension

Custom Text Editor for .md files. Hosts the existing React/TipTap app in a VS Code WebView. Full spec: docs/vscode-extension.md.

Why before Tauri (8D–G): No native toolchain required (Node/TypeScript only). Uses Electron/Chromium — same engine as the web app, no WKWebView compatibility risk. VS Code Marketplace distribution is simpler than Mac App Store. Validates the platform adapter pattern (Phase 8C) as its first real consumer before Tauri is built against it.

Phase 9A+B complete. Next: pre-built .vsix release asset, then Marketplace listing.

  • Phase A: Custom Editor (file mode A, CriticMarkup as file) — VS Code extension scaffold (package.json manifest, extension.ts), CustomTextEditorProvider for .md files, WebView hosting existing Vite React bundle, postMessage protocol (ready / loadDocument / documentChanged / saveRequested), Cmd+S integration via VS Code TextDocument API, VSCode platform adapter (replaces localStorage, routes file I/O through extension host). Build pipeline: extension host via esbuild + WebView via existing Vite build.
  • Phase B: File mode toggle (sidecar) — markdownFeedback.fileMode: "criticmarkup" | "sidecar" workspace setting. In sidecar mode: .md file holds clean markdown (accept-all), .criticmark JSON sidecar holds { markup, comments, savedAt }. On open: read sidecar if present, else treat .md as starting document. On save: write clean markdown to .md, write sidecar JSON to .criticmark. Status bar indicator showing active mode. Mode-switch confirmation when switching sidecar → CriticMarkup. Implemented in extension/src/sidecarManager.ts.

Phase 9 design decisions:

  • CustomTextEditorProvider (not CustomReadonlyEditorProvider) — VS Code owns the document model, so dirty state, Cmd+S, and undo stack integration are free.
  • Editor priority: "option" — do not replace the default markdown editor. User right-clicks → "Open With → Markdown Feedback Editor", or sets as default per-workspace via workbench.editorAssociations.
  • WebView is stateless between sessions — content always comes from the file (not localStorage). The VSCode adapter's load() is a no-op; content arrives via loadDocument message on WebView ready.
  • Sidecar filename: {basename}.criticmark alongside the .md file. Format matches the current localStorage payload for consistency.
  • File mode A is the correct default — CriticMarkup degrades gracefully in any markdown viewer, is Obsidian-compatible, and round-trips perfectly. Mode B is opt-in for workflows where non-tool users view the same files.
  • Single source tree — no fork. The extension packages the same Vite bundle as the web app; the platform adapter is the only new code.

Single-File Build + Release Process

  • vite-plugin-singlefile integration — npm run build:single produces a single self-contained HTML file (dist-single/index.html) with all JS, CSS, and assets inlined
  • Separate Vite config (vite.config.singlefile.ts) so normal GitHub Pages build is unaffected
  • /release slash command — discovers unreleased commits, writes changelog, builds single-file HTML, tags, pushes, creates GitHub release with HTML asset attached
  • CHANGELOG.md created with v1.0.0 entry
  • README updated with download link, trust model, and build instructions

Spike: Custom Domain (markdown-feedback.com) (COMPLETE)

Domain purchased via Cloudflare. Connected to GitHub Pages deployment.

  • Cloudflare DNS: CNAME records @ and wwwdudgeon.github.io (Proxied, SSL Full)
  • GitHub Pages custom domain: public/CNAME + gh api configuration
  • Vite base path: Changed from '/markdown-feedback/' to '/'
  • Update references: Live URL updated in README.md, CLAUDE.md, AboutPanel.tsx
  • Verify: https://markdown-feedback.com loads correctly, SSL valid via Cloudflare
  • HTTPS enforcement: GitHub Pages https_enforced: true, Let's Encrypt cert provisioned (required temporarily disabling Cloudflare proxy)

Responsive Design + About Panel

Tier 1: Usable on Small Screens (COMPLETE)

  • Collapsible changes panel — toggle button in toolbar, slide-over drawer from right on mobile (< lg:), inline on desktop
  • Single-column editor — below lg:, editor takes full width via lg:flex lg:gap-4
  • Responsive toolbar — extracted Toolbar.tsx with info icon, title, Import (icon-only on < md:), Export, panel toggle with badge
  • Responsive paddingp-4 lg:p-6 on outer container
  • Desktop panel restyle — removed box border/shadow, replaced with subtle left border for integrated look
  • About panel — left slide-in overlay per docs/about-panel.md: app description, "Why I built this", GitHub link, footer

Design decisions:

  • Desktop (> 1024px): panel visible by default, toggleable via toolbar button, inline in flex layout
  • Mobile (< 1024px): panel hidden by default, opens as right-side drawer with backdrop overlay
  • About panel: left slide-in overlay, closes via backdrop click or Escape
  • Import/Export: text labels on md:+, icon-only below md:

Tier 2: Touch-Friendly

Improvements for finger-based interaction (phone, tablet without keyboard).

  • Tap target sizing — ensure all buttons, change cards, and interactive elements meet 44×44px minimum
  • Import modal — increase textarea height on mobile (or make it flex-1 to fill available space); larger action buttons
  • Change card interaction — change cards should have generous padding and clear active/pressed states for touch
  • Scroll-to behavior — verify click-to-scroll works on touch; may need scrollIntoView with behavior: 'smooth' adjustment
  • Export dropdown repositioning — on small screens, anchor dropdown to viewport edge or use a bottom sheet instead of absolute right-0

Tier 3: Polish

Refinements that improve the feel but aren't blockers.

  • Responsive typography — scale heading and body text for readability on small screens
  • Landscape phone layout — test and handle landscape orientation where viewport is wide but very short
  • PWA viewport — add <meta name="viewport"> if missing; test pinch-zoom behavior doesn't break the editor
  • Keyboard avoidance — on mobile, ensure the editor isn't obscured by the software keyboard when typing

Repo Housekeeping

Root Folder Cleanup

  • Review and tidy the repo root — assess what's there (config files, stray docs, untracked artifacts, etc.) and decide what to move, rename, or remove
  • .docx test files in root (e.g. Untitled document (1).docx, Welcome to Markdown Feedback.docx) — move to a tests/fixtures/ folder or add to .gitignore if they shouldn't be tracked
  • Audit top-level files for anything that should live in docs/, be renamed, or be deleted

Known Issues & Tech Debt

Import parser (fixed)

  • Single-newline block separation drops content — fixed: replaced naive \n\n+ split with line-aware block grouper that treats headings as always-single-line blocks
  • YAML frontmatter not stripped on re-import — fixed: stripFrontmatter() removes --- ... --- before parsing

Editor rendering (fixed)

  • Ordered/unordered list bullets/numbers don't render — fixed: restored list-style-type: disc/decimal stripped by Tailwind preflight

Per-character deletion cursor dead zones (fixed)

  • Backspacing through original text created one contenteditable=false span per character, forming a cursor dead zone — fixed: handleSingleCharDelete now reuses the ID of an adjacent standalone deletion mark. ProseMirror merges marks with equal attributes into a single DOM span, eliminating the wall of adjacent non-editable elements. See docs/deletion-span-solutions.md for the full analysis.

Changes panel overflow (fixed)

  • Right-side changes/comment panel viewport area is not locked to the bottom of the screen — panel extends beyond viewport instead of scrolling internally — fixed: switched from page-scrollable layout with sticky panel to viewport-locked flex layout (h-dvh). Editor and panel scroll independently within their columns.

iOS input handling (fixed)

  • handleKeyDown in TrackChanges plugin does not reliably fire on iOS virtual keyboard for Backspace/Delete — fixed: added handleDOMEvents.beforeinput handler that catches all deletion inputTypes (deleteContentBackward, deleteContentForward, word/line/cut/drag deletions). On desktop, handleKeyDown returns truepreventDefault() stops beforeinput from firing. On iOS (where keydown fires with key: "Unidentified"), handleKeyDown returns false and beforeinput takes over. A 50ms timestamp guard prevents double-processing edge cases.
  • Full analysis: docs/desktop-app.md §8.1, docs/ios-app.md §4

Tab-to-comment broken

  • Tab key on a tracked change should shift focus to the comment input for that change in the Changes Panel — currently not working. The handler lives in trackChanges.ts (handleKeyDowntrackchanges:tab-to-comment custom event) and the listener is in Editor.tsx. Needs investigation — Phase 10B changes did not modify either file, so this may be a pre-existing regression.

DOCX import broken

  • .docx file import reports "No tracked changes found" even when the file contains tracked changes. After clicking "Load", the editor content does not update — text editor stays unchanged. The import flow is: ImportModal.tsx (file picker) → parseDocx.ts (JSZip + DOMParser) → docxToMarkdown.ts (OOXML walker) → criticMarkupToHTMLsetContent. Needs investigation — may be a parsing regression or a broken handoff between parse result and editor import.

Table column padding not applied

  • The serializer's padTableColumns function is implemented and produces correctly padded output in isolation, but trailing spaces are not appearing in the serialized output when run through the full editor pipeline. The serializeCriticMarkup function detects consecutive table-row paragraphs (via blockNode.attrs.tableRow === true) and passes them to padTableColumns, but the padding is not taking effect. Likely cause: the tableRow attribute is not being set on paragraph nodes when content is imported — the attribute is parsed from class="table-row" in the HTML, but may be stripped or not round-tripping correctly through TipTap's setContent. Needs investigation: verify that tableRow: true is present on paragraph nodes in the ProseMirror doc after import.

Plain mode hides heading-level syntax (accepted)

  • When toggling decorations to "Plain" mode, heading ## markers are not displayed in the editor and cannot be edited. This means heading levels cannot be changed while in Plain mode. Accepted defect — workaround is to switch to Rich mode to edit headings, or edit the source directly. Root cause: TipTap's StarterKit converts ## Heading into <h2> nodes; when decorations are toggled off, the content is re-parsed with decorationsEnabled: false, but the existing heading nodes in the ProseMirror doc are already block-level heading nodes, not paragraphs with ## prefix text. The serializer emits ## in the CriticMarkup source, so round-trip fidelity is preserved — this is purely a visual/editing limitation in Plain mode.

Session resume shows empty editor (fixed)

  • After page reload + "Resume" in recovery modal, the text editor appeared empty (shows placeholder text). Root cause: Same stale editor problem as the comment bug — importDocument called editor.commands.setContent(html) on the editor reference stored in Zustand, which TipTap 3's useEditor had silently replaced with a new instance. The setContent went to the stale editor. Fix: importDocument now accepts an editor parameter instead of reading get().editor. Recovery paths (resumeSession, checkForRecovery autoLoad) set a pendingImport field in the store, and an effect in Editor.tsx processes it with the live useEditor instance. ImportModal passes the live editor directly.

Comment submit hides changes with comments (fixed)

  • When saving a comment, all changes with comments disappeared from the Changes Panel. Root cause: addComment/editComment/deleteComment called extractChanges(editor.state.doc, ...) but the editor reference in the Zustand store was stale — TipTap 3's useEditor silently replaced the editor instance during React re-renders, so editor.state.doc was empty while the visual editor still showed content. Fix: Comment actions no longer read editor.state.doc. They update the existing changes array in-place by merging comment data into the matching entry. The rawMarkup resyncs on the next handleEditorChange call from onUpdate.

Comments not persisted across page reload (fixed)

  • After adding a comment on a tracked change, refreshing the page and resuming the session lost all comments (text and changes restored correctly). Root cause: addComment serialized rawMarkup by calling serializeCriticMarkup(editor.state.doc, updatedComments) on the store's editor reference, which was destroyed (isDestroyed: true) due to useEditor silently replacing the instance. The destroyed editor's empty doc produced rawMarkup: "", which was saved to localStorage, overwriting the correct markup. Fix: (1) Added useEffect in Editor.tsx to keep the store's editor ref in sync whenever useEditor returns a new instance. (2) Added !editor.isDestroyed safety guards in addComment, editComment, and deleteComment — if the editor is destroyed, the existing rawMarkup is preserved instead of re-serializing from an empty doc.

Cursor not refocusing after comment save (fixed)

  • After typing a comment and pressing Enter or Tab, the cursor did not return to the text editor. Root cause: onReturnToEditor called editor.commands.focus() synchronously, but the comment textarea was still mounted. When React unmounted the textarea (the previously focused element), the browser reset focus to <body>. Fix: Wrapped editor.commands.focus() in requestAnimationFrame() to defer focus until after React unmounts the textarea.

Serialization edge cases

  • Substitution over text that already contains old deletions — old deletions emit as standalone {--…--} outside the {~~…~~}, which is semantically correct but may look odd
  • Serializer only handles paragraphs and headings — lists, blockquotes, code blocks pass through without markdown prefixes
  • hardBreak nodes within a paragraph are not emitted as \n
  • No handling of {>>comment<<} yet — implemented in Phase 5

Naming cleanup (fixed)

  • docs/prd.md and docs/project-context.md still reference "CriticMark Editor" — updated to "Markdown Feedback"

Backlog (Unscheduled)

URL Parameter Import

  • Accept a URL query parameter (e.g. ?md=...) that pre-loads markdown content into the editor
  • Content should be URL-decoded and fed through the existing CriticMarkup import path
  • Enables external tools/workflows to link directly into the editor with pre-populated content

Prominent Import Button (Default State)

  • When the app loads with placeholder text (no locally saved session), style the Import button with primary coloring or heavy stroke to draw attention
  • Should use an existing Tailwind/library style for visual consistency — prominent but not jarring
  • Revert to normal button styling once user has imported content or started editing

Style Change Tracking

  • Track formatting/style changes (bold, italic, heading level, etc.) in the native editor — currently only insertions and deletions are intercepted
  • Represent style changes in CriticMarkup output (may need convention beyond the spec, e.g. comment annotations describing the style change)
  • Import style changes from .docx files (Google Docs track-changes includes formatting changes alongside text edits)

Dual-Document Diff Import

  • Add ability to import two copies of a document (original and changed) and infer the diffs
  • Reconstruct insertions, deletions, and substitutions as tracked changes in the editor
  • Allow further editing/markup on top of the inferred diffs
  • UI: could be a second textarea in the Import modal, or a two-file picker

Editor Polish

  • Paragraph-level deletions (CriticMarkup can't span paragraph boundaries)
  • Paste handling: strip formatting vs. preserve markdown-compatible formatting
  • Merge paragraphs (delete line break between them) — tracked change semantics
  • Rich markdown decorations — completed in Phase 10C+D

Source View Actions

  • Paste-to-replace button: replace editor content with clipboard contents (no tracked deletions — rebaselines the document)
  • Rebaseline button: accept all tracked changes in-place, clearing markup and producing a clean document

Share (Web Share API)

  • "Share" option in the Export dropdown menu, visible only when navigator.share is available (iOS Safari, Chrome Android)
  • Shares the full CriticMarkup markdown body as text via the native OS share sheet
  • Fallback: hidden on desktop browsers that don't support the Web Share API (existing clipboard/download exports cover that case)
  • Must be triggered from a user gesture (click handler) per browser security requirements
  • Stretch: option to share as a .md file attachment via navigator.share({ files }) for platforms that support it

Selection Comment Tooltip

  • When text is selected in the editor, show a small floating tooltip/popover beneath the selection with a comment icon/button
  • Clicking the button applies a highlight + focuses the comment input (same behavior as Cmd+Shift+H)
  • Tooltip should be discrete — small, subtle styling, no chrome; disappears when selection is cleared
  • Position using ProseMirror's coordsAtPos or the browser Selection API getBoundingClientRect
  • Complements the existing Cmd+Shift+H shortcut for users who don't know the keyboard shortcut

Keyboard Shortcuts in About Panel (COMPLETE — Phase 8B)

  • Add a "Keyboard Shortcuts" section to the About panel listing all user-facing shortcuts
  • Include: Cmd+Shift+T (toggle tracking), Cmd+Shift+H (highlight), Tab (jump to comment input when on a change), Enter/Tab in comment input (save/return to editor)
  • Keep in sync as new shortcuts are added

Accept / Reject

  • Accept/reject individual changes to produce a clean document
  • Accept all / Reject all bulk operations

Comment Categories (v1.5+)

  • Pre-defined tags: [tone], [clarity], [structure], [grammar], [concision], [accuracy]
  • Keyboard shortcuts or buttons for quick tagging
  • Serialize as prefixed comments: {>>[tone] comment text<<}

LLM Integration

  • LLM pre-edit: Claude proposes edits as CriticMarkup, human reviews in-editor (bidirectional workflow)
  • AI comment suggestions: given the change + context, suggest annotation text
  • Style rule extraction: analyze patterns across multiple CriticMarkup files to generate writing rules

Platform Expansion

  • VSCode extension — Phase 9 (see above; builds before Tauri)
  • macOS / Mac App Store — Phase 8D–G (see above; after VSCode)
  • Android — Tauri 2 supports Android; same beforeinput fix required as iOS; lower priority
  • iOS — Tauri 2 (shared src-tauri/ with macOS); requires beforeinput handler + Phase 8D first. Full spec: docs/ios-app.md
  • Windows / Linux — Tauri cross-platform via GitHub Actions CI; non-priority
  • Obsidian plugin (native integration into knowledge management ecosystem)
  • Export to Google Docs (CriticMarkup → .docx with Word track changes)
  • Diff fallback mode (alternative for users who prefer import-edit-diff workflow)
  • Session library (history of editing sessions for longitudinal analysis)
  • Multi-file projects
  • Collaborative editing

Non-Goals (Prototype)

These are explicitly out of scope per the PRD and should not creep into early phases:

  • Collaborative editing
  • Real-time sync / cloud storage
  • Full markdown spec (tables, footnotes, math)
  • Mobile-first design (responsive adaptation is in backlog — see Responsive Design — but mobile-first redesign remains out of scope)
  • Multi-file projects
  • In-app LLM calls