File formats — how a slide file is read

A .md file whose front matter declares a slide NodeType is read by this pack, not by the platform. MeshWeaver.Publish (the pack's compiled half) contributes a SlideFileParser to the shared file-format parser chain, and the chain tries every module-contributed parser before the catch-all MarkdownFileParser — the same lane the AI engine's agent and skill parsers already use.

.md  →  [ contributed:  AgentFileParser · SkillFileParser · SlideFileParser ]  →  MarkdownFileParser
                                                                                  (accepts everything)

This is phase 3 of the declarative import/export programme (Doc/Architecture/DeclarativeImportExport, Systemorph/MeshWeaver#1580, MeshWeaver.Plugins#961). Before it, the platform's catch-all parser carried one compiled if for slides — the last per-type branch left in the import path.

What the parser owns, and what it delegates

It owns exactly one thing: the content shape. The stage body, the Notes (spoken script) and the Background (stage CSS) become a SlideContent instead of the generic MarkdownContent that has nowhere to put the last two.

Everything else — the id and namespace derived from the path, the name, category, description, icon resolution, state, order, the persisted version counter, the pre-rendered HTML, the BOM strip and the defensive front-matter extractor — is delegated to MarkdownFileParser, never copied. Those rules are why a markdown node gets its identity, they change, and a second copy of them would drift in silence: a slide would start deriving a different id, or quietly lose a field, with nothing red anywhere. SlideFileParserTest pins the delegation field by field.

The write direction is deliberately still core's. CanSerialize returns false, so MarkdownFileParser keeps emitting slide files exactly as it does today. Claiming the write would make this pack responsible for reproducing core's front-matter emission byte for byte, and anything short of exact rewrites every slide file in every synced repo on the next sync-back — a diff nobody asked for, from a change whose entire purpose is that nothing changes. The write direction has its own, better answer to build, and it is core's: dispatch on the live content object through an IFrontMatterRoundTrip interface rather than on a type name, which needs no bootstrapping because the instance exists at write time.

🚨 The decision: import ordering is NOT guaranteed, and must never be relied on

The open question this change had to settle (DeclarativeImportExport → "What blocks the import half", condition 1): a cold-boot import — a bake, a first git-sync — can parse a .md before the NodeType that owns it exists. The two candidate answers were make a miss impossible (guarantee types are registered before any content of them is parsed) or accept the loss and make it loud.

Decided: neither. The premise is removed instead.

A file-format parser answers from the FILE ALONE — its front matter and its path. It consults no NodeType registry, no compiled assembly and no hub state. Completing the typing is the read seam's job, not the parser's.

Why

  1. Ordering cannot be guaranteed cheaply, because parsing is how nodes get created. The import that parses Deck/Intro.md is the same import that creates the Publish/Slide NodeType node from Slide/index.json. "Types before content" therefore means a two-pass import with a compile barrier per package, running before storage can answer a single read — the ProvisionPlan machinery, hoisted into the boot path. That trades a narrow typing gap for serialising every partition's compile at startup.

  2. It is not needed, because the parse-time shape does not have to be final. It only has to be lossless and non-contradicting. IMeshContentTypeRegistry.TryRecoverForNodeType re-materializes content into the NodeType's own CLR type on first activation, and its DiscriminatorAdmits guard matches on the short type name — so the compiled SlideContent this parser emits is admitted into the in-mesh SlideContent compiled from Slide/Source, whether or not that type existed when the file was read. The seam already exists and already runs.

  3. A parser that consulted the registry would be nondeterministic. The same file would parse to a different content shape depending on whether the owning type had compiled yet in this process. That is the "same input, different results" failure the discriminator-ambiguity fix (Systemorph/MeshWeaver#1299) already refused once, and it is strictly worse than the branch it would replace.

  4. The move itself closes the gap the question was about. This is the part worth keeping. A compiled branch in core hosting is present on every host whether or not slides are installed, and absent from none — the branch and the node type it serves have independent lifetimes, which is what made "was the type there when the file was parsed?" a real question with a silent wrong answer. A contributed parser ships in the same package as the node type it serves, so the two now have the same lifetime: a host that can hold slide nodes has this parser, and a host without the Publish module has no slide nodes for it to mis-parse. The ordering hazard was a symptom of the branch living in the wrong repository.

What this forbids

What is still true, and where the residual loss is

A .md declaring a NodeType whose package is not installed still reaches the catch-all and imports as a plain Markdown node. That is now the honest answer rather than a gap: the node type is not installed either, so there is nothing that could render the richer shape. It stops being silent the moment the package is installed, because the file is re-read by its own parser on the next sync.

Cross-repo sequence

The parser landed here first, deliberately, and is a no-op until core's branch is deleted:

# Change Repo State
1 SlideFileParser contributed by MeshWeaver.Publish this repo done (#961)
2 Delete MarkdownFileParser.IsSlideNodeType's parse branch (:215, :270) core follows, after this module ships
3 IFrontMatterRoundTrip for the serialize half; delete the :295 read core independent, no design blocker
4 SlideContent / SlideNodeType leave the platform core blocked, and not by the parser

While core still carries its branch, both parsers build the same object, so either order is safe and there is never a window in which a slide is unparseable. TheStageBody_IsTheCatchAllsOwnBody is what keeps that true across the deletion: core answers a slide file with SlideContent today and with MarkdownContent afterwards, and MarkdownContent.Parse assigns its input verbatim, so the body this parser reads is identical either way.

🚨 Row 4 corrects the design doc. It records that after phase 3 "the parser is the last one", which is what would let SlideContent/SlideNodeType leave core. Measured on core main (2026-08-31), it is not: SlideNodeType.Matches is still read by DeckSlidesCache (the export templates' slide resolver), and the SlideContent record is still needed by BundleWriter, FluentIcons and its own typeRegistry.WithType registration — which is also what gives the record its short-name $type and therefore makes the read-seam recovery above work. Those readers have to go first; deleting the parser branch does not unblock the record.

Reconnecting…
The server was updated. Reloading the page to pick up the latest version.