feat: Add ability to show/hide tracked changes#970
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
All contributors have signed the CLA ✍️ ✅ Posted by the CLA bot. |
Greptile SummaryThis PR adds Microsoft Word-style tracked-changes display modes (
Confidence Score: 4/5Safe to merge after addressing the hand-written changeset; the logic changes are well-tested and React/Vue parity is maintained. The filtering and merging logic in
|
| Filename | Overview |
|---|---|
| .changeset/simple-markup-change-bars.md | Hand-written changeset file; CLAUDE.md requires bun changeset to generate it — hand-writing risks a typo'd package name that crashes the Release workflow. |
| packages/core/src/layout-bridge/toFlowBlocks.ts | Core filtering logic for all four markup modes; mode normalization ternary duplicated 6×; internal _pPrDel/_pPrIns state smuggled via any casts; hasParagraphRevision set in mergeParagraphBlocks is always false and unused by the painter. |
| packages/core/src/prosemirror/editor.css | Adds ~145 lines of [data-markup-mode] UI-chrome CSS; these rules belong in packages/core/src/styles/editor.css (the CLAUDE.md single source of truth for editor chrome CSS), not in this Document/PM CSS file. |
| packages/react/src/components/DocxEditor.tsx | Adds showTrackedChangesMarkup prop and sidebar filtering; fontFamilies JSDoc block accidentally deleted, causing API snapshot drift on next extract. |
| packages/core/src/layout-painter/renderParagraph.ts | Extends change-bar rendering to cover paragraphs with inline edits (hasInlineEdit); logic is straightforward and correctly falls back to layout-revision-del when inlineEditType is absent. |
| packages/core/src/layout-bridge/tests/toFlowBlocks-tracked-changes.test.ts | Good new test coverage for all four modes, paragraph merging, table row skipping, and Simple Markup change-bar preservation; missing a test for paragraph merging in simple mode specifically. |
| packages/vue/src/composables/useDocxEditor.ts | Mirrors React changes; passes showTrackedChangesMarkup into computeLayout and triggers re-layout via watch; parity is maintained. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["DocxEditor prop\nshowTrackedChangesMarkup"] --> B["DocxEditorShell\nnormalize to data-markup-mode attr"]
A --> C["useLayoutPipeline / useDocxEditor\npass to computeLayout"]
C --> D["toFlowBlocks with mode"]
D --> E{Resolve mode}
E -->|true/undefined| F[all: keep all runs and attrs]
E -->|false| G[none: skip deletions, merge deleted breaks]
E -->|simple| H[simple: skip deletions, keep change bar attrs]
E -->|original| I[original: skip insertions, merge inserted breaks]
F --> J[renderParagraph: change bar via layout-revision-pmark]
H --> J
B --> K[CSS data-markup-mode overrides]
J --> K
A --> L[Sidebar: show trackedChanges only for all/true]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A["DocxEditor prop\nshowTrackedChangesMarkup"] --> B["DocxEditorShell\nnormalize to data-markup-mode attr"]
A --> C["useLayoutPipeline / useDocxEditor\npass to computeLayout"]
C --> D["toFlowBlocks with mode"]
D --> E{Resolve mode}
E -->|true/undefined| F[all: keep all runs and attrs]
E -->|false| G[none: skip deletions, merge deleted breaks]
E -->|simple| H[simple: skip deletions, keep change bar attrs]
E -->|original| I[original: skip insertions, merge inserted breaks]
F --> J[renderParagraph: change bar via layout-revision-pmark]
H --> J
B --> K[CSS data-markup-mode overrides]
J --> K
A --> L[Sidebar: show trackedChanges only for all/true]
Comments Outside Diff (1)
-
packages/core/src/layout-bridge/toFlowBlocks.ts, line 955-1000 (link)hasParagraphRevisioninmergeParagraphBlocksis alwaysfalseand is never consumedAfter the
whileloop,(paraBlock as any)[prop]has been set to the last merged paragraph's_pPrDel/_pPrInsvalue — which is alwaysundefined(falsy) for the terminal paragraph that ended the chain. At that pointhasParagraphRevision = !!(paraBlock as any)._pPrDel || !!(paraBlock as any)._pPrInsevaluates tofalsefor every successfully merged block. Additionally, the painter (renderParagraph.ts) never readsblock.hasParagraphRevision— it only checksattrs.pPrIns,attrs.pPrDel, andattrs.hasInlineEdit. The property write and the surrounding comment are dead code that can be removed.
Reviews (1): Last reviewed commit: "chore: add api extractor" | Re-trigger Greptile
| --- | ||
| '@eigenpal/docx-editor-core': patch | ||
| '@eigenpal/docx-editor-react': patch | ||
| '@eigenpal/docx-editor-vue': patch | ||
| --- | ||
|
|
||
| Add ability to show/hide tracked changes in different Tracked Changes markup modes (Simple Markup, All Markup, No Markup, and Original), and fix change bars rendering in Simple Markup mode to show for paragraphs containing inline tracked changes. |
There was a problem hiding this comment.
Changeset file was hand-written
CLAUDE.md explicitly forbids hand-writing .changeset/*.md files: "Generate the changeset with bun changeset — never hand-write the .changeset/*.md file. The interactive prompt picks the correct package name and bump and writes the right frontmatter. Hand-writing risks a wrong/typo'd package name, which crashes the post-merge Release workflow." This file was not produced by bun changeset — please delete it, run bun changeset, and commit the generated file instead.
Context Used: CLAUDE.md (source)
| } | ||
|
|
||
| .layout-sdt-repeat-btn:hover { | ||
| background: #e8f0fe; | ||
| } | ||
|
|
||
| /* ============================================================================ | ||
| DYNAMIC TRACKED CHANGES MARKUP MODES | ||
| ============================================================================ */ | ||
|
|
||
| /* ── ALL MARKUP (default, no data-markup-mode attribute) ───────────────────── | ||
| Base .docx-insertion and .docx-deletion classes provide the full original | ||
| visual treatment (green dashed-border + bg / red strikethrough + bg). | ||
| Images use outline — handled separately below. | ||
| ──────────────────────────────────────────────────────────────────────────── */ | ||
|
|
||
| /* ── NONE MODE: Accept all changes — show final text, hide all markers ────── */ | ||
| [data-markup-mode='none'] .docx-deletion, | ||
| [data-markup-mode='none'] .ep-revision-pmark.ep-revision-del, | ||
| [data-markup-mode='none'] .ep-revision-row.ep-revision-del, | ||
| [data-markup-mode='none'] .ep-revision-cell.ep-revision-del { | ||
| display: none !important; | ||
| } | ||
|
|
||
| /* Insertions: display as plain final text — strip all insertion markers */ | ||
| [data-markup-mode='none'] .docx-insertion { | ||
| background-color: transparent !important; | ||
| color: inherit !important; | ||
| text-decoration: none !important; | ||
| text-decoration-line: none !important; | ||
| border: none !important; | ||
| outline: none !important; | ||
| padding-bottom: 0 !important; | ||
| } | ||
|
|
||
| /* Hide paragraph/table change bar indicators */ | ||
| [data-markup-mode='none'] .layout-revision-pmark::before, | ||
| [data-markup-mode='none'] .layout-table-row.ep-revision-row::before, | ||
| [data-markup-mode='none'] .layout-table.ep-revision-table::before { | ||
| display: none !important; | ||
| } | ||
|
|
||
| /* Hide pilcrow glyphs */ | ||
| [data-markup-mode='none'] .layout-revision-pmark-glyph.layout-revision-del { | ||
| display: none !important; | ||
| } | ||
|
|
||
| [data-markup-mode='none'] .layout-revision-pmark-glyph.layout-revision-ins { | ||
| color: inherit !important; | ||
| text-decoration: none !important; | ||
| } | ||
|
|
||
| /* Hide pmark structures themselves */ | ||
| [data-markup-mode='none'] .ep-revision-pmark { | ||
| box-shadow: none !important; | ||
| } | ||
|
|
||
| [data-markup-mode='none'] .ep-revision-prop-change { | ||
| box-shadow: none !important; | ||
| } | ||
|
|
||
| /* ── SIMPLE MODE: Show final result + change bars, hide red/green styling ─── */ | ||
| [data-markup-mode='simple'] .docx-deletion, | ||
| [data-markup-mode='simple'] .ep-revision-pmark.ep-revision-del, | ||
| [data-markup-mode='simple'] .ep-revision-row.ep-revision-del, | ||
| [data-markup-mode='simple'] .ep-revision-cell.ep-revision-del { | ||
| display: none !important; | ||
| } | ||
|
|
||
| /* Insertions: show as plain text, strip insertion-specific decoration */ | ||
| [data-markup-mode='simple'] .docx-insertion { | ||
| background-color: transparent !important; | ||
| color: inherit !important; | ||
| text-decoration: none !important; | ||
| text-decoration-line: none !important; | ||
| border: none !important; | ||
| outline: none !important; | ||
| padding-bottom: 0 !important; | ||
| } | ||
|
|
||
| /* Keep change bars (::before on pmark) for simple mode — intentionally NOT hidden */ | ||
| [data-markup-mode='simple'] .layout-revision-pmark::before { | ||
| background-color: #c62828 !important; | ||
| } | ||
|
|
||
| /* Hide pilcrow glyphs in simple mode */ | ||
| [data-markup-mode='simple'] .layout-revision-pmark-glyph.layout-revision-del { | ||
| display: none !important; | ||
| } | ||
|
|
||
| [data-markup-mode='simple'] .layout-revision-pmark-glyph.layout-revision-ins { | ||
| color: inherit !important; |
There was a problem hiding this comment.
Markup-mode CSS should live in
styles/editor.css, not prosemirror/editor.css
CLAUDE.md defines two CSS files with distinct roles: prosemirror/editor.css is for Document/PM CSS, while packages/core/src/styles/editor.css is the single source of truth for UI chrome CSS and color tokens. The new [data-markup-mode='...'] selectors anchor on the editor shell's root element (.ep-root.docx-editor), which is UI chrome behavior — they should go in packages/core/src/styles/editor.css. Adding them here breaks the single-source-of-truth contract and bypasses the check:adapter-css-thin enforcement.
Context Used: CLAUDE.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| showOutline?: boolean; | ||
| /** Whether to show the floating outline toggle button (default: true) */ | ||
| showOutlineButton?: boolean; | ||
| /** | ||
| * Custom list of fonts shown in the toolbar's font-family dropdown. | ||
| * Strings render in the "Other" group; pass `FontOption[]` for category | ||
| * grouping and CSS fallback chains. Omit to use the built-in 12-font | ||
| * default. An empty array renders an empty (but enabled) dropdown. | ||
| * | ||
| * Pass a stable reference (memoized or module-level) — inline arrays | ||
| * create a new identity per render and invalidate the picker's memo. | ||
| * | ||
| * @example fontFamilies={['Arial', 'Roboto']} | ||
| * @example fontFamilies={[{ name: 'Roboto', fontFamily: 'Roboto, sans-serif', category: 'sans-serif' }]} | ||
| */ | ||
| fontFamilies?: ReadonlyArray<string | FontOption>; | ||
| /** | ||
| * Custom font faces to register with the browser before the editor measures |
There was a problem hiding this comment.
fontFamilies TSDoc block was accidentally deleted
The full JSDoc comment for fontFamilies (including the important "pass a stable reference" warning and both @example entries) was removed as part of this diff. This is a @public prop on the exported DocxEditorProps interface; losing its TSDoc causes API Extractor to strip the description from the generated snapshot on the next bun run api:extract, silently regressing the published docs. The two changes (adding showTrackedChangesMarkup, removing the existing comment) appear to be an accidental over-selection during editing.
| defaultTabStopTwips?: number, | ||
| showTrackedChangesMarkup?: boolean | 'all' | 'simple' | 'none' | 'original' | ||
| ): ParagraphAttrs { | ||
| const attrs: ParagraphAttrs = {}; | ||
| const mode = | ||
| showTrackedChangesMarkup === true || showTrackedChangesMarkup === undefined | ||
| ? 'all' | ||
| : showTrackedChangesMarkup === false | ||
| ? 'none' |
There was a problem hiding this comment.
Mode-normalization ternary is duplicated six times across this file alone
The same three-way ternary that maps true/undefined → 'all', false → 'none', string → string is copy-pasted in convertParagraphAttrs, convertParagraph, convertTableCell, convertTableRow, convertTable, and the tail of toFlowBlocks. A shared helper like resolveMarkupMode(showTrackedChangesMarkup) would reduce the blast radius of any future change to the default-value semantics and make each call site easier to read at a glance.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Description
Implements the full set of Microsoft Word-style tracked changes modes (
All Markup,Simple Markup,No Markup, andOriginal) across core, React, and Vue packages. It also resolves a bug in Simple Markup mode where inline revisions did not render margin indicators by recursively detecting inline edits and displaying red left margin change bars.Changes
hasInlineEditandinlineEditTypetoParagraphAttrs, implemented recursive inline revision scanning intoFlowBlocks, added the revision mark styling classes inrenderParagraph, and forced margin lines to render red in Simple Markup mode.Closes: #918
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.