test: assert corpus round-trip convergence for supported syntax - #249
Merged
Conversation
Twelve of the sixteen scoped files are rewritten on first open, so byte identity is not assertable today. The dominant cause is the serializer escaping characters that would not parse as syntax in place, which is the open question in #245; the remainder is delimiter and structural form the document model does not retain. Convergence is an interim property chosen for that reason. It holds the editor to a stable document without blessing any particular normalization, and it is what catches the structural loss fixed in #247. Byte identity stays the target, and this assertion should tighten toward it as the escaping question is settled. The other corpus subtrees stay manual. The byte fixtures exist to pin CR, CRLF, BOM, NUL, and a missing final newline, which is precisely what reading and serializing normalizes away; the environment and extension trees assert behavior this test cannot reach.
Azganoth
force-pushed
the
spike/corpus-round-trip
branch
from
August 16, 2026 07:49
7f9372f to
98a299b
Compare
4 tasks
Azganoth
marked this pull request as ready for review
August 16, 2026 07:53
This was referenced Aug 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Delivers the spike outcome from #135: an automated round-trip guard over the corpus subtrees whose syntax Leafdown supports.
src/features/editor/tests/corpusRoundTrip.test.tsx, asserting thatserialize(parse(source))and a second pass over that result produce the same string for 16 files acrosscorpus/commonmark/,corpus/gfm/, andcorpus/isolated/end-of-file/. The editor may normalize on first open; it must not keep changing the document afterwards.CONTRIBUTING.mdthat those three directories now carry automated convergence coverage, so the manual corpus pass over them is for rendering, interaction, and navigator behavior. The remaining subtrees stay fully manual.Why convergence, and why it is interim
Byte identity is the property worth wanting here: open a file, save it, the file is unchanged. It is not assertable today. Measured over the scoped set, 12 of the 16 files are rewritten on first open, several by around 100 lines. Three causes:
garden_sensor_name→garden\_sensor\_name(intraword_is never emphasis),*opening-only→\*opening-only(an unpaired*is already literal),name@example→name\@example. This is not forced by the document model, and it is the open question in How a Markdown escape should be represented in the editor #245.-bullets →*,~strike~→~~strike~~,_em_→*em*. Fixable per construct, as feat: preserve the authored autolink form on save #243 did for autolinks.>, tight lists are written loose.The 4 files that already round-trip byte-identically do so because they contain almost no inline constructs to escape, not because anything preserves authored form. So there is no meaningful identity-holding subset to pin, and no useful hybrid between the two assertions.
Convergence is therefore chosen as an interim: it holds the editor to a stable document without blessing any particular normalization, needs no baseline or deviation list, and is what catches the #247 class of defect. It should tighten toward identity as #245 settles. A green run here does not mean the corpus saves unchanged, and this file should not be read as endorsing the current normalization.
Nothing new was built to support this.
setupMilkdownEditorMount,createMarkdownReferenceContext, andmockTauriApiCommandalready cover it, and reading corpus files from disk followseditorPresentation.test.tsx.Related Issue
Closes #135
Refs #247
Verification
corpusRoundTrip.test.tsxproves the property directly: all 16 scoped files converge. The same test at the spike's baseline failed on three of them (commonmark/lists-and-blockquotes.md,commonmark/text-and-breaks.md,gfm/tables.md), which is what surfaced #247; those three pass here on top of its fix, so the file is a real guard rather than a tautology.Ran
pnpm check:frontendfor this frontend-only change.Not verified: rendering, interaction, and navigator behavior, which convergence says nothing about. This test never types, undoes, or copies.
Notes
Scope, so a green run is not over-read:
**onefollowed by**on its own line normalizes to escaped literal text and converges cleanly — correct per CommonMark, since the closing delimiter run follows whitespace, but the point stands that "stops changing" is not "changes correctly".Deliberately excluded, per the analysis in #135:
corpus/boundaries/bytes/, which exists to pin CR, CRLF, BOM, NUL, and a missing final newline — precisely what reading and serializing normalizes away.corpus/environment/, whose subject needs the Rust backend and is already covered by thecreateArticleTreeandcreateFolderContextfactories.corpus/extensions/, which mostly asserts that unsupported syntax survives as escaped literal text, already covered inmarkdownCompatibility.test.tsx.corpus/practical/, whose local image and link references would turn this into per-path resolution mocks.Three counts in the #135 analysis were off and are corrected by this branch and its
Outcomesection:isolated/end-of-file/holds 4 files rather than 3, so the scoped total is 16 rather than 15.commonmark/links-and-images.mdneeds the image resolver mock, not two files.Observed while measuring, not investigated and not claimed as a defect: tight lists are serialized loose (
- A\n- B→* A\n\n* B), which changes rendered output (<li>A</li>versus<li><p>A</p></li>). Whether the schema distinguishes tight from loose at all is unchecked. Recorded in #135 for whoever picks up #245.