fix(core,builder): one owner for fenced-code detection and reserved directive names - #105
Merged
Merged
Conversation
Slide-split, directive tokenization, and serialize-escape each had their
own fence regex and disagreed:
- MarkdownParser matched backticks only, so ```dart {.hero} did not open
a fence and tilde fences never matched at all.
- TagTokenizer matched backtick or tilde but required column 0.
- SlideSerializer let a ~~~ line close a ``` fence.
Add fencedCodeRanges/isInsideFencedCode in core and route all three
through it. The rule follows what SuperDeck already renders via
package:markdown: 3+ backticks or tildes, backtick openers reject
backticks in the info string, a closer matches the opening character at
>= the opening length, and an unclosed fence runs to EOF.
Closers keep their info string. demo/slides.md closes a fence with
```{.code}; treating that as "not a closer" swallowed the following ---
and merged two slides.
Tests drive the shipped APIs (MarkdownParser, TagTokenizer,
SlideSerializer) rather than a reimplemented scanner, and
fence_agreement_test pins split and tokenize to the same snippets.
The authoring reserved set lived twice: BlockParser rejected a raw 'column' string, and SlideSerializer._reservedTags duplicated the model discriminators as string literals. Dropping 'column' from the serialize copy would emit @column, which parse then rejects. Add directive_names.dart deriving the set from SectionBlock.key, ContentBlock.key, and WidgetBlock.key plus the rejected column alias, and use it from both parse and serialize. Semantics are unchanged; only the owner of the fact moved. It lives in builder because directive grammar is a parse concern and column is not a block type in the core model. The serializer test now loops over reservedDirectiveNames, so a name added to the set cannot silently lose its @widget escaping.
Both builder consumers are line-based and were each rebuilding character
offsets from their own line list:
offset += lines[i].length + 1;
That is one rule with two owners, and it only worked because
MarkdownParser fed fencedCodeRanges the joined lines rather than the
original content; passing the original would have desynced silently on
CRLF input.
Derive both views from one span walker and add fencedCodeLines, which
returns the line indices (addressing text.split('\n')) that sit inside a
fence. Slide splitting and serialize-escape now test membership directly.
fencedCodeLines is the only fence function exported from the barrel; the
offset-based range API has one in-package consumer, TagTokenizer. Neither
was in a released version, so narrowing the export breaks nothing.
Also record why the rule keeps a closer's info string and accepts any
leading whitespace, since both depart from package:markdown deliberately.
|
Visit the preview URL for this PR (updated for commit d96be79): https://superdeck-dev--pr105-fix-parser-fence-and-mkf69akj.web.app (expires Sat, 12 Sep 2026 16:01:41 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: bd68fc230762285849207e7e120aaf87cd4ca2f9 |
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.
Intent
Two parse-phase rules each had multiple owners that disagreed. This gives each one a single owner, with no intended change to authoring semantics.
Seam 1 — fenced code
Three owners had their own fence regex:
MarkdownParser(slide split)```dart {.hero}did not open a fence, tildes never matchedTagTokenizer(directives)SlideSerializer(escape)~~~line could close a```fenceAll three now resolve fences through
fencedCodeLinesin core. The rule follows what SuperDeck already renders viapackage:markdown(codeFencePattern), with two deliberate departures recorded in the source:demo/slides.mdcloses a fence with```{.code}. Treating that as "not a closer" leaves the fence open, swallows the next---, and merges two slides.package:markdown's 0–3 spaces). Over-hiding is safe — a more deeply indented fence is an indented code block to the renderer either way — while under-hiding would split a slide mid-code-sample.Seam 2 — reserved directive names
BlockParserrejected a raw'column'string whileSlideSerializer._reservedTagsduplicated the model discriminators as literals. Droppingcolumnfrom the serialize copy would emit@column, which parse then rejects. Both now usereservedDirectiveNames, derived fromSectionBlock.key/ContentBlock.key/WidgetBlock.keyplus the rejectedcolumnalias.Impacted packages
superdeck_core— newfencedCodeLines(the only fence function exported);TagTokenizernow recognizes indented fences and longer-than-opener closerssuperdeck_builder—MarkdownParser,SlideSerializer,BlockParser, newdirective_names.dartVerification
melos run analyze(dart + DCM, all packages)melos run test --no-select(all packages)packages/coretestspackages/buildertestsdart formatdemo/superdeck_cli buildsuperdeck.json/superdeck_full.jsonbyte-identical tomainThat last row is the behavior-preservation evidence: the real deck compiles to the same artifact, and only the
build_status.jsontimestamp moved (reverted).Tests drive the shipped APIs rather than a reimplemented scanner, and
fence_agreement_testpinsMarkdownParserandTagTokenizerto the same snippets so the two can't drift apart again.