- TipTap 3 + React 19 + Vite 7 scaffold
-
trackedDeletionmark (red strikethrough, contenteditable=false, non-inclusive) -
trackedInsertionmark (green, inclusive, editable) - Intercept plugin via
handleKeyDown,handleTextInput,handlePaste - Substitution pairs linked via
pairedWithattribute (nanoid IDs) - Cursor skips over deletion spans
- Editing within insertions works normally (no tracking)
- Browser-validated, no console errors
- 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
- CriticMarkup parser/deserializer (
parseCriticMarkup.ts) - Paste import modal with automatic CriticMarkup parsing
- Sample content loaded through parser (not hardcoded HTML)
- Primary export:
.mddownload 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
- 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)
- 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
- 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
collectTextRangesin 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 (
criticMarkupToHTML→setContent+setComments) - Auto-save debounced at 1s via
useDebouncedValuehook - Recovery modal: "Start Fresh" clears localStorage and keeps sample content; "Resume" restores via import path
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
EditorControlscomponent. Isolate web-only controls (Import, Export) so they are excluded from the VSCode WebView toolbar. Use platform adaptercapabilitiesflags 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-markdownvs. 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
richModeflag 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.
criticMarkupToHTMLaccepts arichModeboolean. 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.serializeCriticMarkuphandles 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
rawMarkupviatoggleDecorations()in the store. This is a full re-import, not a decoration layer — the ProseMirror doc structure changes between modes. - Decorations preference persisted to
localStorageunderdecorationsEnabledkey.
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 (
criticMarkupToHTML→setContent) 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:
extractCommentsFromSegmentskeys the comment by the deletion's ID (matching howextractChangesidentifies substitution entries). - Lists:
word/numbering.xmlparsed to map(numId, ilvl)→bullet/decimal, emitted as-or1.prefixes. - Entries joined with
\n\n(not\n) becausecriticMarkupToHTMLsplits blocks on double-newline.
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
_trackingEnabledflag, all three handlers check flag and passthrough when disabled. Toolbar toggle pill + Cmd+Shift+T shortcut.appendTransactionstrips inclusive insertion marks from untracked text. Keyboard shortcuts section added to About panel. Ships on web. - Phase C: Platform adapter hardening — expand
PlatformAdapterinterface beyond persistence to cover file I/O (openFile,saveFile,getCurrentFilePath), platform signals (setDirty,onExternalFileChange), and acapabilitiesflag 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.jsonpointing at Vite, minimal Tauri platform adapter (stores/persistence/tauri.ts, localStorage-based stub), placeholder icons,npm run tauri:dev/tauri:buildscripts. 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.mdand.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 devandnpm run buildnever 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 onuseEditorre-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 fireFixed:keydownfor Backspace/Delete.beforeinputhandler implemented intrackChanges.ts(handles all deletion inputTypes with 50ms timestamp guard). Seedocs/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) instores/persistence/index.ts.
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.jsonmanifest,extension.ts),CustomTextEditorProviderfor.mdfiles, WebView hosting existing Vite React bundle,postMessageprotocol (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:.mdfile holds clean markdown (accept-all),.criticmarkJSON sidecar holds{ markup, comments, savedAt }. On open: read sidecar if present, else treat.mdas 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 inextension/src/sidecarManager.ts.
Phase 9 design decisions:
CustomTextEditorProvider(notCustomReadonlyEditorProvider) — 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 viaworkbench.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 vialoadDocumentmessage on WebView ready. - Sidecar filename:
{basename}.criticmarkalongside the.mdfile. 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.
-
vite-plugin-singlefileintegration —npm run build:singleproduces 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 -
/releaseslash command — discovers unreleased commits, writes changelog, builds single-file HTML, tags, pushes, creates GitHub release with HTML asset attached -
CHANGELOG.mdcreated with v1.0.0 entry - README updated with download link, trust model, and build instructions
Domain purchased via Cloudflare. Connected to GitHub Pages deployment.
- Cloudflare DNS: CNAME records
@andwww→dudgeon.github.io(Proxied, SSL Full) - GitHub Pages custom domain:
public/CNAME+gh apiconfiguration - Vite base path: Changed from
'/markdown-feedback/'to'/' - Update references: Live URL updated in
README.md,CLAUDE.md,AboutPanel.tsx - Verify:
https://markdown-feedback.comloads correctly, SSL valid via Cloudflare - HTTPS enforcement: GitHub Pages
https_enforced: true, Let's Encrypt cert provisioned (required temporarily disabling Cloudflare proxy)
- 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 vialg:flex lg:gap-4 - Responsive toolbar — extracted
Toolbar.tsxwith info icon, title, Import (icon-only on< md:), Export, panel toggle with badge - Responsive padding —
p-4 lg:p-6on 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 belowmd:
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-1to 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
scrollIntoViewwithbehavior: 'smooth'adjustment - Export dropdown repositioning — on small screens, anchor dropdown to viewport edge or use a bottom sheet instead of absolute
right-0
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
- 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
-
.docxtest files in root (e.g.Untitled document (1).docx,Welcome to Markdown Feedback.docx) — move to atests/fixtures/folder or add to.gitignoreif they shouldn't be tracked - Audit top-level files for anything that should live in
docs/, be renamed, or be deleted
-
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
-
Ordered/unordered list bullets/numbers don't render— fixed: restoredlist-style-type: disc/decimalstripped by Tailwind preflight
-
Backspacing through original text created one— fixed:contenteditable=falsespan per character, forming a cursor dead zonehandleSingleCharDeletenow 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. Seedocs/deletion-span-solutions.mdfor the full analysis.
-
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 withstickypanel to viewport-locked flex layout (h-dvh). Editor and panel scroll independently within their columns.
-
— fixed: addedhandleKeyDownin TrackChanges plugin does not reliably fire on iOS virtual keyboard for Backspace/DeletehandleDOMEvents.beforeinputhandler that catches all deletioninputTypes (deleteContentBackward,deleteContentForward, word/line/cut/drag deletions). On desktop,handleKeyDownreturnstrue→preventDefault()stopsbeforeinputfrom firing. On iOS (wherekeydownfires withkey: "Unidentified"),handleKeyDownreturnsfalseandbeforeinputtakes over. A 50ms timestamp guard prevents double-processing edge cases. - Full analysis:
docs/desktop-app.md§8.1,docs/ios-app.md§4
- 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(handleKeyDown→trackchanges:tab-to-commentcustom event) and the listener is inEditor.tsx. Needs investigation — Phase 10B changes did not modify either file, so this may be a pre-existing regression.
- .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) →criticMarkupToHTML→setContent. Needs investigation — may be a parsing regression or a broken handoff between parse result and editor import.
- The serializer's
padTableColumnsfunction 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. TheserializeCriticMarkupfunction detects consecutive table-row paragraphs (viablockNode.attrs.tableRow === true) and passes them topadTableColumns, but the padding is not taking effect. Likely cause: thetableRowattribute is not being set on paragraph nodes when content is imported — the attribute is parsed fromclass="table-row"in the HTML, but may be stripped or not round-tripping correctly through TipTap'ssetContent. Needs investigation: verify thattableRow: trueis present on paragraph nodes in the ProseMirror doc after import.
- 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## Headinginto<h2>nodes; when decorations are toggled off, the content is re-parsed withdecorationsEnabled: false, but the existing heading nodes in the ProseMirror doc are already block-levelheadingnodes, 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.
-
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 —importDocumentcallededitor.commands.setContent(html)on theeditorreference stored in Zustand, which TipTap 3'suseEditorhad silently replaced with a new instance. ThesetContentwent to the stale editor. Fix:importDocumentnow accepts aneditorparameter instead of readingget().editor. Recovery paths (resumeSession,checkForRecoveryautoLoad) set apendingImportfield in the store, and an effect inEditor.tsxprocesses it with the liveuseEditorinstance. ImportModal passes the live editor directly.
-
When saving a comment, all changes with comments disappeared from the Changes Panel.Root cause:addComment/editComment/deleteCommentcalledextractChanges(editor.state.doc, ...)but theeditorreference in the Zustand store was stale — TipTap 3'suseEditorsilently replaced the editor instance during React re-renders, soeditor.state.docwas empty while the visual editor still showed content. Fix: Comment actions no longer readeditor.state.doc. They update the existingchangesarray in-place by merging comment data into the matching entry. TherawMarkupresyncs on the nexthandleEditorChangecall fromonUpdate.
-
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:addCommentserializedrawMarkupby callingserializeCriticMarkup(editor.state.doc, updatedComments)on the store'seditorreference, which was destroyed (isDestroyed: true) due touseEditorsilently replacing the instance. The destroyed editor's empty doc producedrawMarkup: "", which was saved to localStorage, overwriting the correct markup. Fix: (1) AddeduseEffectinEditor.tsxto keep the store's editor ref in sync wheneveruseEditorreturns a new instance. (2) Added!editor.isDestroyedsafety guards inaddComment,editComment, anddeleteComment— if the editor is destroyed, the existing rawMarkup is preserved instead of re-serializing from an empty doc.
-
After typing a comment and pressing Enter or Tab, the cursor did not return to the text editor.Root cause:onReturnToEditorcallededitor.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: Wrappededitor.commands.focus()inrequestAnimationFrame()to defer focus until after React unmounts the textarea.
- 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
-
hardBreaknodes within a paragraph are not emitted as\n -
No handling of— implemented in Phase 5{>>comment<<}yet
-
— updated to "Markdown Feedback"docs/prd.mdanddocs/project-context.mdstill reference "CriticMark Editor"
- 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
- 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
- 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
.docxfiles (Google Docs track-changes includes formatting changes alongside text edits)
- 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
- 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
- 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" option in the Export dropdown menu, visible only when
navigator.shareis 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
.mdfile attachment vianavigator.share({ files })for platforms that support it
- 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
coordsAtPosor the browser Selection APIgetBoundingClientRect - Complements the existing Cmd+Shift+H shortcut for users who don't know the keyboard shortcut
- 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 individual changes to produce a clean document
- Accept all / Reject all bulk operations
- Pre-defined tags:
[tone],[clarity],[structure],[grammar],[concision],[accuracy] - Keyboard shortcuts or buttons for quick tagging
- Serialize as prefixed comments:
{>>[tone] comment text<<}
- 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
- 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
beforeinputfix required as iOS; lower priority - iOS — Tauri 2 (shared
src-tauri/with macOS); requiresbeforeinputhandler + 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 →
.docxwith 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
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