You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Modularize the export layer: node types declare their export composers — no compiled type gates #1576
The export subsystem is hard-coded per node type, in compiled code:
DeckExportMenuProvider / MarkdownExportMenuProvider are separate providers, each gating on a NodeType comparison (Deck / Markdown) — a new exportable type means a new compiled provider.
ExportPdf.csx / ExportHtml.csx branch if (isDeck) to pick the composition strategy (slide chapters vs single document).
ExportDocumentLayoutArea decides pixel-fidelity availability off the same compiled type check.
The suffix-aware DeckNodeType.Matches/SlideNodeType.Matches gates (follow-up to the Publish-pack extraction) let plugin-typed decks/slides through these compiled gates — but they are a bridge, not the architecture.
Direction (composition-first, same pattern as layout areas)
Exportability becomes a DECLARATION on the node type's hub configuration, contributed by the type's own configuration lambda (which lives in plugin content for plugin types):
A compiled public surface config.WithExportComposer(descriptor) — format(s), menu label key, fidelity options, and the composition strategy (how this type's content becomes an export document: e.g. deck → one chapter per slide in deck order).
ONE generic export menu provider that renders entries from the declarations on the node's hub config — DeckExportMenuProvider and MarkdownExportMenuProvider collapse into it and are deleted.
The export templates dispatch through the declared composer instead of if (isDeck) — the deck branch moves into the Publish pack's declaration.
The suffix gates from the bridge PR are then deleted.
Design constraint: the hub config freezes at hub build, so declaration changes ride the existing generation-bump → hub-recycle mechanism (same edge as the WS7 menu/composition design — solve once, use for both).
Part of the composition-first program (WS7): a deployment = base runtime + plugin composition; the export if-chains are one of the last compiled per-type switches.
Verdict: PARTLY DONE on main → finished by the two PRs below
Verified against origin/main (not PR titles). Phase 1 (#1594) landed the declaration and the one generic menu provider. Three compiled type gates survived it, and the acceptance list's item 4 was still open:
item
on main today
1. config.WithExportComposer(descriptor)
shipped as WithExport(ExportDeclaration) — src/MeshWeaver.Graph/ExportDeclaration.cs
✅
2. ONE generic menu provider; per-type providers deleted
3. templates dispatch through the declared composer
❌ ExportPdf.csx:74 / ExportHtml.csx:67 still DeckNodeType.Matches(rootNode.NodeType)
✗
4. the bridge's suffix gates deleted
❌ ExportDeclaration.Resolve still carried the DeckNodeType.Matches fallback; Publish/Deck declared nothing, so the fallback was load-bearing
✗
Note the export engine LEFT this repo in 0b3b4266f ("Fourteen modules leave the platform") — MeshWeaver.Markdown.Export now lives in MeshWeaver.Plugins, so items 3 and 4 are a two-repo change.
PRs
Systemorph/MeshWeaver.Plugins#553 — items 3 + the declaration. Publish/Deck DECLARES .WithExport(ExportDeclaration.SlideDeck); ExportMenuProvider and ExportDocumentLayoutArea read Configuration.Get<ExportDeclaration>(); the templates dispatch on a composition input. Green.
The design note said "put the composition on the export request, stamped by ExportDocumentLayoutArea". It turned out simpler: ExportDocumentHandler runs ON the source node's own hub — every caller targets new Address(sourcePath) — so it reads the declaration off hub.Configuration, the same read the menu provider does, and stamps it into the script inputs. No contract change, no client change, and the layout area never has to hand a value forward.
The gate's failure mode, reproduced
A plugin type that composes like a deck but is not NAMED one fell through to the document branch and exported the root node's own body. A deck-shaped node has no body, so the export succeeded and produced an empty file. Control run of the new test against the pre-fix module code:
activity status=Succeeded
[Information] Rendered 414 bytes of email-safe HTML
=== TEST FAILED: Expected "<!doctype html>…<div style="max-width:840px;…"></div></body></html>"
to contain "ALPHAWIDGET" …
Failed: 1
One gate deliberately NOT removed
DeckSelection.ResolveDeckSelection keeps SlideNodeType.Matches for the DEFAULT slide query. That is not an export composer — it is which children a deck with no manifest treats as slides, and it is already overridable as data (DeckContent.Query, which turns the filter off entirely). Leaving it does not let a name gate back into the export path.
Merge order: Plugins#553 first, then #1973 — the fallback is what keeps a deck exportable on an install whose Publish pack has not yet synced its declaration.
Fixed on origin/main — verified rather than assumed.
ExportDeclaration (src/MeshWeaver.Graph/ExportDeclaration.cs:41) is the declaration, set via config.WithExport(...). Commit e2cba48723 collapsed DeckExportMenuProvider + MarkdownExportMenuProvider into a single ExportMenuProvider (now MeshWeaver.Plugins:src/MeshWeaver.Markdown.Export/Layout/ExportMenuProvider.cs, registered in MarkdownExportExtensions.cs), and d50050cb13 deleted the last compiled type gate — item 4 — including the DeckNodeType.Matches transition fallback. isDeck has zero occurrences tree-wide; templates now dispatch on the composition stamped by ExportDocumentHandler.
Crucially — extracting is not using, so the consumer is named and pinned: Publish/Deck declares WithExport(ExportDeclaration.SlideDeck), asserted by Plugins:Publish/Deck/Test/DeckTests.cs and DeclaredCompositionExportTest.
The residual SlideNodeType.Matches/DeckNodeType.Matches callers (DeckSlidesCache.cs:204, MarkdownFileParser.cs:270) are deck ordering and the persistence parser — #1580's scope, not the export layer's.
Activity
rbuergi commentedon Aug 21, 2026
Verdict: PARTLY DONE on main → finished by the two PRs below
Verified against
origin/main(not PR titles). Phase 1 (#1594) landed the declaration and the one generic menu provider. Three compiled type gates survived it, and the acceptance list's item 4 was still open:config.WithExportComposer(descriptor)WithExport(ExportDeclaration)—src/MeshWeaver.Graph/ExportDeclaration.csExportMenuProvider;MarkdownExportMenuProvider/DeckExportMenuProvidergoneExportPdf.csx:74/ExportHtml.csx:67stillDeckNodeType.Matches(rootNode.NodeType)ExportDeclaration.Resolvestill carried theDeckNodeType.Matchesfallback;Publish/Deckdeclared nothing, so the fallback was load-bearingNote the export engine LEFT this repo in
0b3b4266f("Fourteen modules leave the platform") —MeshWeaver.Markdown.Exportnow lives in MeshWeaver.Plugins, so items 3 and 4 are a two-repo change.PRs
Publish/DeckDECLARES.WithExport(ExportDeclaration.SlideDeck);ExportMenuProviderandExportDocumentLayoutAreareadConfiguration.Get<ExportDeclaration>(); the templates dispatch on acompositioninput. Green.ExportDeclaration.Resolvedeleted (no callers left), plus the doc and a contract test.How the declaration reaches a template
The design note said "put the composition on the export request, stamped by
ExportDocumentLayoutArea". It turned out simpler:ExportDocumentHandlerruns ON the source node's own hub — every caller targetsnew Address(sourcePath)— so it reads the declaration offhub.Configuration, the same read the menu provider does, and stamps it into the script inputs. No contract change, no client change, and the layout area never has to hand a value forward.The gate's failure mode, reproduced
A plugin type that composes like a deck but is not NAMED one fell through to the document branch and exported the root node's own body. A deck-shaped node has no body, so the export succeeded and produced an empty file. Control run of the new test against the pre-fix module code:
One gate deliberately NOT removed
DeckSelection.ResolveDeckSelectionkeepsSlideNodeType.Matchesfor the DEFAULT slide query. That is not an export composer — it is which children a deck with no manifest treats as slides, and it is already overridable as data (DeckContent.Query, which turns the filter off entirely). Leaving it does not let a name gate back into the export path.Merge order: Plugins#553 first, then #1973 — the fallback is what keeps a deck exportable on an install whose Publish pack has not yet synced its declaration.
Merge pull request #1973 from Systemorph/chore/ws7-finish
rbuergi commentedon Aug 25, 2026
Fixed on
origin/main— verified rather than assumed.ExportDeclaration(src/MeshWeaver.Graph/ExportDeclaration.cs:41) is the declaration, set viaconfig.WithExport(...). Commite2cba48723collapsedDeckExportMenuProvider+MarkdownExportMenuProviderinto a singleExportMenuProvider(nowMeshWeaver.Plugins:src/MeshWeaver.Markdown.Export/Layout/ExportMenuProvider.cs, registered inMarkdownExportExtensions.cs), andd50050cb13deleted the last compiled type gate — item 4 — including theDeckNodeType.Matchestransition fallback.isDeckhas zero occurrences tree-wide; templates now dispatch on thecompositionstamped byExportDocumentHandler.Crucially — extracting is not using, so the consumer is named and pinned:
Publish/DeckdeclaresWithExport(ExportDeclaration.SlideDeck), asserted byPlugins:Publish/Deck/Test/DeckTests.csandDeclaredCompositionExportTest.The residual
SlideNodeType.Matches/DeckNodeType.Matchescallers (DeckSlidesCache.cs:204,MarkdownFileParser.cs:270) are deck ordering and the persistence parser — #1580's scope, not the export layer's.