Commit fc6b143
* test: add failing regression test for #437/#440 markdown editor round-trip
Captures 14 failure modes in the Tiptap+tiptap-markdown round-trip used
by the file-preview pane (mountMarkdownEditor in editor.ts). The
autosave loop in controller.ts reads getTiptapMarkdown() and diffs it
against state.fullDocumentContent, then emits edit_block calls for every
hunk. Any drift introduced by parse-and-reserialize gets silently
written to disk.
Failure cases (all 14/14 fail on current main):
- GFM pipe table -> 'AB12'
- '~' -> '\~' (prosemirror-markdown strikethrough escaping)
- Adjacent block elements gain blank-line separators
- Wikilink + heading combo drifts on spacing
- Trailing newline stripped
- Combined #437 fixture
- YAML frontmatter --- parsed as Setext heading (#437 LevionLaurion)
- '[x]' -> '\[x\]' (#440)
- Underscore-rich identifiers drift on trailing newline (#440)
- '~/path' -> '\~/path' (#440)
- Loose lists drift on trailing newline (#440)
- CRLF -> LF + soft-break collapse (related to #97)
- Realistic README-style file: tables collapse + soft breaks merge,
>70%-changed threshold in computeEditBlocks would emit a single
edit_block replacing the entire file with the degraded version
- Realistic doc with table embedded in prose: prose around table
also lost via soft-break merge
Adds jsdom@^24 as a devDependency to mount Tiptap headlessly.
* fix(markdown editor): round-trip safety + edit-diff invariants
Tiptap's parse-and-reserialise round-trip silently mutates user files
(see #437, #440). Pure round-trip safety (untouched file -> identical
output) is too strict; what matters in production is that an edit
produces only the user's actual change, not collateral normalisation.
Two test suites:
- test-markdown-editor-roundtrip.js (existing, 14 cases): strict
round-trip — files survive open->getMarkdown() byte-for-byte.
- test-markdown-editor-edit-diff.js (new, 7 cases): realistic — apply
a small edit, run computeEditBlocks against the original, assert
the diff is bounded (no whole-file rewrite, <=3 hunks, <=20% lines
differing, expected text actually present).
Implementation:
- Centralise Tiptap config in buildTiptapExtensions() so test and
production exercise the same code path. Disable strike to stop \~
escaping. Add Table / TableRow / TableHeader / TableCell extensions
so GFM pipe tables don't collapse to concatenated cell text.
- preprocessForEditor / applyPostProcess wrap the editor with a
RoundTripContext that captures: original EOL (LF vs CRLF), YAML
frontmatter prefix, gap between frontmatter and body, trailing
newline. All re-applied on the way out.
- Post-process repairs: unescapeSafeChars (\[, \], \~ in prose),
restoreTableSeparatorStyle (|---| vs | --- |), restoreSoftBreaks
(Tiptap's space-joined paragraph lines back to original line breaks),
collapseBlockSeparators (Tiptap's spurious blank lines between
adjacent block elements). Order matters: softBreaks must run before
blockSeparators because blockSeparators matches surrounding lines
against pairs from the original.
- Export computeEditBlocks from controller.ts so the edit-diff test
exercises the real autosave decision logic, including the >70%
whole-file rewrite trigger.
All 21 tests pass. Includes the in-the-wild #437 README fixture, the
mixed table+prose+frontmatter case, CRLF preservation, wikilink
round-trip, and the realistic 'edit a paragraph in a doc with
frontmatter + wikilinks + tasks + table' edit-diff case.
* fix(file-preview): prevent unnecessary markdown autosaves
* fix(markdown editor): 4 in-the-wild round-trip failures
Captured from /Users/eduardsruzga/work/best-value-ai/README.md, which
hit 5 distinct corruption hunks on no-edit open in the previous state.
Now: byte-exact round-trip, 0 hunks.
Tests added (test-markdown-editor-roundtrip.js):
testBareUrlNotAutoLinked — 'https://x' stays bare, no <…>
testEmojiPrefixedSoftBreaksRestored — 3 paragraph lines stay 3 lines
testLinkInTableCellSurvivesRoundTrip — [`x`](url) keeps its href
testStarBulletMarkerPreserved — '*' bullets stay '*', not '-'
Fixes:
- linkify: false. The auto-bracketing ('https://x' -> '<https://x>') was
a serializer-side artifact of the parser's URL-detection rule. Disable
the rule; pasted URLs in the editor still become clickable via
Tiptap's built-in Link extension.
- restoreSoftBreaks now tries both ' ' (the common joiner) and '' (when
the boundary is between punctuation like ')' and a non-letter like an
emoji — the case that broke the 3-line emoji-prefixed sequence in
best-value-ai's README).
- restoreBulletMarkers maps output bullet lines onto source bullet lines
positionally, restoring the marker style ('*' / '-' / '+'). New
bullets the user adds keep the editor's default '-'.
- Code-text links ([`x`](url)) are stripped of their URL by
tiptap-markdown's parser when the link text is purely inline code.
Replace with ASCII placeholders during preprocess; restore in
postprocess. Pattern matches the existing wikilink workaround.
- unescapeSafeChars is now line-aligned: it only removes \[, \], \~
escapes when the same source line, stripped of those escapes, also
matches the output line stripped. Means we never touch escapes the
user authored — we only undo escapes Tiptap added.
Also wired the codeLinks placeholder mapping into the RoundTripContext
so production autosave gets the same restore behaviour as the test
harness.
Result: 18/18 strict + 7/7 edit-diff = 25/25 passing. The
best-value-ai/README.md (12977 bytes, 214 lines) round-trips byte-for-
byte with 0 autosave hunks.
* fix(markdown editor): preserve relative-path links
Tiptap's Link extension validates URLs and silently drops links whose
URL doesn't match its scheme/relative-prefix allow-list. Bare relative
paths with subdirectories (`scripts/foo.mjs`, `references/output.md`)
fall through this validation — the parse drops the link, leaving just
the link text.
Most common corruption mode in real skill files (SKILL.md routinely
links to `scripts/` and `references/`).
Generalised the existing code-text-link placeholder workaround into
INLINE_LINK_RE + isFragileLink(). The same regex and placeholder system
now handles both:
1. Code-text links: `[\`x\`](url)`
2. Bare-relative-subpath links: `[X](dir/file)`
URLs we leave alone (Tiptap accepts them):
- Schemes (http://, mailto:, tel:, ftp:, etc.)
- Anchors (#section)
- Single-segment paths (file.md)
- Explicitly-relative paths (./, ../, /)
Tests:
- testRelativePathLinksSurvive captures the failure with skill-files
fixtures (init-skill.mjs, validate-skill.mjs, references/output.md,
section anchors).
- All 19 strict + 7 edit-diff tests still pass.
Skill-files batch test: 4 of 8 SKILL.md files now round-trip byte-exact
(was 1). Remaining failures are different bugs: HTML entity escaping,
trailing-whitespace strip, bold-around-inline-code mis-shifting.
* fix(file-preview): scope markdown autosave edits
* fix(markdown editor): 6 more in-the-wild round-trip failures
Captured by running the SKILL.md files in ~/.desktop-commander/skills/
through the round-trip pipeline. 4 of 8 files broke before this
commit; all 8 now round-trip byte-exact.
Tests added (test-markdown-editor-roundtrip.js):
testLessThanInProseNotEscaped '< $0.01' -> '< $0.01'
testTrailingHardBreakWhitespacePreserved 'foo \n' (CommonMark hard
break) lost the trailing
spaces or rewritten as
'foo\\\n'
testBoldAroundInlineCodePreserved '**`x`**' -> '`x`',
'**`x` + `y`**' ->
'`x` **+** `y`',
'**Key in `x`:**' ->
'**Key in** `x`**:**'
testEscapedPipeInTableCellPreserved '\\|' inside a cell becomes
bare '|', splits the cell
testListItemWithContinuationLine '- item\n cont\n' joins
to '- item cont\n'
Fixes:
- unescapeHtmlEntitiesInProse: undo Tiptap's '<'/'>'/'&' HTML
entity escaping in prose. Line-aligned like unescapeSafeChars; only
removes entities when the corresponding source line, stripped of
those entities, matches the output line. User-authored entities are
preserved.
- restoreTrailingHardBreaks: detect source lines ending in two
trailing spaces and re-add them. Handles both Tiptap serializer
shapes: '\\\n' line continuation (paragraph) and silently-stripped
(list item).
- BOLD_AROUND_CODE_RE + boldCodeRuns: placeholder '**…`code`…**'
spans during preprocess, restore after serialize. Bypasses
ProseMirror's flat-mark schema limitation.
- pipeEscapeCount + PIPE_ESCAPE_TOKEN: placeholder '\|' as an ASCII
token before parse. Tiptap's serializer drops the backslash; the
token round-trips intact.
- restoreSoftBreaks: recognise list-item-with-lazy-continuation pairs
(list-header followed by 2-space-indented prose line) and try the
de-indented form of the continuation when looking for Tiptap's
joined output.
Result: 24/24 strict + 7/7 edit-diff = 31/31. All 8 skill files in
the stress test now byte-exact.
* telemetry: rename tool name property to tool_name
The capture_call_tool send-site was emitting 'name' as the tool-name
property, which collides with GA4's reserved 'name' parameter when
passed verbatim. Rename to 'tool_name' at the call site (server.ts) and
have capture_call_tool's high-volume routing read either field, falling
back to 'name' for backwards compat with any in-flight callers.
No behaviour change; pure rename.
---------
Co-authored-by: edgarssskore <edgars.skore@gmail.com>
1 parent a029d37 commit fc6b143
9 files changed
Lines changed: 2675 additions & 167 deletions
File tree
- src
- ui/file-preview/src
- markdown
- utils
- test
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
89 | 89 | | |
90 | 90 | | |
91 | 91 | | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
92 | 96 | | |
93 | 97 | | |
94 | 98 | | |
| |||
124 | 128 | | |
125 | 129 | | |
126 | 130 | | |
| 131 | + | |
127 | 132 | | |
128 | 133 | | |
129 | 134 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1175 | 1175 | | |
1176 | 1176 | | |
1177 | 1177 | | |
1178 | | - | |
| 1178 | + | |
1179 | 1179 | | |
1180 | 1180 | | |
1181 | 1181 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
41 | 48 | | |
42 | 49 | | |
43 | 50 | | |
| |||
74 | 81 | | |
75 | 82 | | |
76 | 83 | | |
77 | | - | |
78 | | - | |
79 | | - | |
80 | | - | |
81 | 84 | | |
82 | 85 | | |
83 | 86 | | |
| |||
138 | 141 | | |
139 | 142 | | |
140 | 143 | | |
141 | | - | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
142 | 255 | | |
143 | 256 | | |
144 | 257 | | |
145 | 258 | | |
146 | 259 | | |
147 | 260 | | |
148 | | - | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
149 | 264 | | |
150 | 265 | | |
151 | 266 | | |
152 | 267 | | |
153 | 268 | | |
154 | | - | |
155 | | - | |
156 | | - | |
157 | | - | |
158 | | - | |
| 269 | + | |
| 270 | + | |
159 | 271 | | |
160 | | - | |
| 272 | + | |
161 | 273 | | |
162 | 274 | | |
163 | 275 | | |
| |||
170 | 282 | | |
171 | 283 | | |
172 | 284 | | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
173 | 295 | | |
174 | 296 | | |
175 | 297 | | |
| |||
240 | 362 | | |
241 | 363 | | |
242 | 364 | | |
| 365 | + | |
243 | 366 | | |
244 | 367 | | |
245 | 368 | | |
| |||
289 | 412 | | |
290 | 413 | | |
291 | 414 | | |
| 415 | + | |
292 | 416 | | |
293 | 417 | | |
294 | 418 | | |
| |||
657 | 781 | | |
658 | 782 | | |
659 | 783 | | |
| 784 | + | |
660 | 785 | | |
661 | 786 | | |
662 | 787 | | |
| |||
678 | 803 | | |
679 | 804 | | |
680 | 805 | | |
681 | | - | |
| 806 | + | |
682 | 807 | | |
683 | 808 | | |
684 | 809 | | |
685 | 810 | | |
| 811 | + | |
686 | 812 | | |
687 | 813 | | |
688 | 814 | | |
| |||
728 | 854 | | |
729 | 855 | | |
730 | 856 | | |
731 | | - | |
732 | | - | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
733 | 861 | | |
734 | 862 | | |
| 863 | + | |
735 | 864 | | |
736 | 865 | | |
737 | 866 | | |
738 | 867 | | |
739 | 868 | | |
740 | 869 | | |
741 | | - | |
742 | 870 | | |
743 | 871 | | |
744 | 872 | | |
| |||
919 | 1047 | | |
920 | 1048 | | |
921 | 1049 | | |
922 | | - | |
| 1050 | + | |
| 1051 | + | |
| 1052 | + | |
| 1053 | + | |
923 | 1054 | | |
924 | 1055 | | |
| 1056 | + | |
| 1057 | + | |
| 1058 | + | |
| 1059 | + | |
| 1060 | + | |
| 1061 | + | |
| 1062 | + | |
| 1063 | + | |
| 1064 | + | |
| 1065 | + | |
| 1066 | + | |
925 | 1067 | | |
926 | 1068 | | |
927 | 1069 | | |
| |||
949 | 1091 | | |
950 | 1092 | | |
951 | 1093 | | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
952 | 1097 | | |
953 | 1098 | | |
954 | 1099 | | |
| |||
0 commit comments