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
Reusable lanes land a module bundle's DLLs but not its meshweaver/moduleassets/wwwroot — a browser-driven mesh 404s every _content/<Module>/ asset #3514
A module bundle carries its static web assets separately, under meshweaver/moduleassets/wwwroot/… (NuGetPackageWriter.ModuleAssetFolder; ModulePackCommand lays a Razor project's wwwroot/ there, dependencies namespaced under wwwroot/_content/<Dep>/). The portal serves a landed module's assets from modules/<Name>/wwwroot (MeshModuleStaticAssetExtensions) and logs only at Debug when there is nothing there — so a module landed this way loads fine and then 404s every asset in the browser. The runtime consumer (ServedModuleBytes) handles the prefix correctly; production is unaffected (both portals serve …/_content/MeshWeaver.Markdown.Collaboration/Components/collaborativeMarkdownView.js with 200).
Where it costs today. For the lanes' own use the gap is latent: the bake is mesh-free and the gate mesh runs no browser. It bites the moment a browser renders against a mesh whose modules were landed by this pattern. MeshWeaver.Education's e2e/mesh/fetch-upstream.sh (a local copy of this landing block) is exactly that: since MeshWeaver.Plugins#1268 moved the collaboration views INTO MeshWeaver.Markdown.Collaboration (2026-09-04 12:48Z), every Education mesh run has thrown
Unhandled exception rendering component: Failed to fetch dynamically imported module:
http://localhost:8323/_content/MeshWeaver.Markdown.Collaboration/Components/collaborativeMarkdownView.js
at MeshWeaver.Blazor.Components.CollaborativeMarkdownView.OnAfterRenderAsync
and every exercise page rendered its brief and then a bare Code node card where the workbench belongs (Playwright screenshot in run 34070110529's e2e-install-report) — 4 × 180 s per course. essentials.module.nupkg ships the JS; the landing never copied it. The last green Education run (09-04 12:16Z) predates #1268 by thirty minutes.
Fix pattern (Education #274, fetch-upstream.sh), for the two lane sites:
if [ -d "$u/meshweaver/moduleassets/wwwroot" ]; then
mkdir -p "$EXT/$name/wwwroot"
cp -R "$u"/meshweaver/moduleassets/wwwroot/. "$EXT/$name/wwwroot/"
# fail CLOSED: shipped vs landed asset counts must match
fi
Simulated against the run's own set: Markdown.Collaboration lands 270 asset(s) (its JS/CSS plus the namespaced dependency assets), the EntityViews/Graph view packs their scoped CSS. Low urgency for the lanes as they stand; filed so the next browser-driven consumer of these blocks (or the next satellite that copies them) does not re-derive it. Related: #2384 (the same class for the OpenStreetMap module's .razor.js), Plugins#808 (scoped CSS shipped in neither image nor module).
Fixed in MeshWeaver#3535 (merged as d2a564c7c278dcb73f8c8231bb784d695a3bf470).
Which half was wrong: the lane. The premise holds — a module legitimately carries static web
assets and every hop of the platform's lane is deliberate (ModulePackCommand → meshweaver/moduleassets/ → ModuleLandingService writes them module-relative beside the entry
assembly → MeshModuleStaticAssetExtensions serves them at _content/<Name>/…). Nothing is
producing assets a module should not have; the two lane sites were simply landing half the bundle.
The recorded "modules cannot carry native or static assets" constraint is a different claim wearing
the same words: native payloads genuinely cannot ride (#1728), static web assets can, and
that half was corrected twice already (most recently #2221).
Two refinements over the pattern in the description, both from reading the packer's contract
rather than today's output:
the copy is meshweaver/moduleassets/. → <module dir>/, module-relative, not moduleassets/wwwroot/. → <module dir>/wwwroot/. wwwroot/ is the only root the packer emits
today, but ModuleAssetEntryPathFor/ValidateAssetPath are specified on a relative path, so
the wwwroot-specific form is correct now and silently lossy the day a second root appears;
the check prints a denominator, not just a count: 30 composed, 6 of them carrying static web assets, 294 asset file(s) landed. 24 of 30 real modules legitimately ship none, so an uncounted
zero looked exactly like the healthy case — which is what let this ride.
Denominator, on real bundles. The extracted lane step run against all 30 module bundles of
MeshWeaver.Plugins run 34086016940: 6 carry
assets (Blazor.Chat 283, Blazor.OpenStreetMap 7, Blazor.Analysis/.EntityViews/.Graph/ .Radzen 1 each), 294 asset files shipped, 294 landed byte-identical. Pre-fix: 0 of 294, at
exit code 0.
Both falsification arms, watched. Real bundle → the extracted lane step → a real WebApplication with AddMeshModuleStaticAssets()/UseMeshModuleStaticAssets() over the landed
tree → an actual fetch:
request
with the fix
pre-fix
_content/MeshWeaver.Markdown.Collaboration/Components/collaborativeMarkdownView.js — this issue's literal stack trace
200 + Content-Encoding: br, byte-identical to the landed .br sibling
404
…/does-not-exist.js — negative control
404
404
Plus, in CI: .github/scripts/test-module-asset-landing.py extracts BOTH lanes' step by id: and
executes it, reads the verdict off the landed bytes' sha256, and carries two arms that mutate the
real step (pre-fix body ⇒ the positive case reds; cp removed ⇒ the lane's own fail-closed check
must fire). Removing the id: reds it too.
Two corrections to the description's premise:
Education feat(mcp): Space instance-sync MCP tool #274 is OPEN, not landed. Its branch does carry the moduleassets copy, but the
PR is a different change (ci: fetch the upstream once, gate the bake in parallel…) and is mergeable_state: blocked; MeshWeaver.Education's main still has the unfixed block, so its
mesh e2e is still 404ing. That half stays theirs.
There is a third copy, not two: MeshWeaver.Plugins/scripts/mesh-test.py hands the mesh --module …/meshweaver/modules/<Name>.dll, so the assets it needs at …/meshweaver/modules/wwwroot sit at …/meshweaver/moduleassets/wwwroot. Same shape, latent
only because nothing points a browser at it — filed as Systemorph/MeshWeaver.Plugins#1440.
One nuance worth pinning, because a fix written to the description's wording would miss the lane:
the portal serves a landed module's assets from the directory of the assembly it actually
loaded, not from modules/<Name>/wwwroot — that path is only the legacy fallback for a module
riding the app closure (ModuleWwwrootPath, #2509/#2562). For the lanes the directory is /ext/<Name>/, which is why the copy has to target the module directory rather than a modules/-shaped path.
Work product: Doc/Architecture/ModuleStaticAssets — the four hops, the landing invariant, every
site that lands a module and what each one owes, why the failure is invisible to review, CI and
production, and both measurements above.
Activity
fix(ci): the reusable lanes land a module's static web assets, not on…
fix(ci): the reusable lanes land a module's static web assets, not on…
rbuergi commentedon Sep 7, 2026
Fixed in MeshWeaver#3535 (merged as
d2a564c7c278dcb73f8c8231bb784d695a3bf470).Which half was wrong: the lane. The premise holds — a module legitimately carries static web
assets and every hop of the platform's lane is deliberate (
ModulePackCommand→meshweaver/moduleassets/→ModuleLandingServicewrites them module-relative beside the entryassembly →
MeshModuleStaticAssetExtensionsserves them at_content/<Name>/…). Nothing isproducing assets a module should not have; the two lane sites were simply landing half the bundle.
The recorded "modules cannot carry native or static assets" constraint is a different claim wearing
the same words: native payloads genuinely cannot ride (#1728), static web assets can, and
that half was corrected twice already (most recently #2221).
Two refinements over the pattern in the description, both from reading the packer's contract
rather than today's output:
meshweaver/moduleassets/.→<module dir>/, module-relative, notmoduleassets/wwwroot/.→<module dir>/wwwroot/.wwwroot/is the only root the packer emitstoday, but
ModuleAssetEntryPathFor/ValidateAssetPathare specified on a relative path, sothe
wwwroot-specific form is correct now and silently lossy the day a second root appears;30 composed, 6 of them carrying static web assets, 294 asset file(s) landed. 24 of 30 real modules legitimately ship none, so an uncountedzero looked exactly like the healthy case — which is what let this ride.
Denominator, on real bundles. The extracted lane step run against all 30 module bundles of
MeshWeaver.Plugins run
34086016940: 6 carry
assets (
Blazor.Chat283,Blazor.OpenStreetMap7,Blazor.Analysis/.EntityViews/.Graph/.Radzen1 each), 294 asset files shipped, 294 landed byte-identical. Pre-fix: 0 of 294, atexit code 0.
Both falsification arms, watched. Real bundle → the extracted lane step → a real
WebApplicationwithAddMeshModuleStaticAssets()/UseMeshModuleStaticAssets()over the landedtree → an actual fetch:
_content/MeshWeaver.Markdown.Collaboration/Components/collaborativeMarkdownView.js— this issue's literal stack tracetext/javascript, 12,391 B, byte-identical_content/MeshWeaver.Blazor.Chat/chatResizer.js_content/MeshWeaver.Blazor.Chat/ChatMessageList.razor.jsAccept-Encoding: brContent-Encoding: br, byte-identical to the landed.brsibling…/does-not-exist.js— negative controlPlus, in CI:
.github/scripts/test-module-asset-landing.pyextracts BOTH lanes' step byid:andexecutes it, reads the verdict off the landed bytes' sha256, and carries two arms that mutate the
real step (pre-fix body ⇒ the positive case reds;
cpremoved ⇒ the lane's own fail-closed checkmust fire). Removing the
id:reds it too.Two corrections to the description's premise:
moduleassetscopy, but thePR is a different change (
ci: fetch the upstream once, gate the bake in parallel…) and ismergeable_state: blocked; MeshWeaver.Education'smainstill has the unfixed block, so itsmesh e2e is still 404ing. That half stays theirs.
MeshWeaver.Plugins/scripts/mesh-test.pyhands the mesh--module …/meshweaver/modules/<Name>.dll, so the assets it needs at…/meshweaver/modules/wwwrootsit at…/meshweaver/moduleassets/wwwroot. Same shape, latentonly because nothing points a browser at it — filed as Systemorph/MeshWeaver.Plugins#1440.
One nuance worth pinning, because a fix written to the description's wording would miss the lane:
the portal serves a landed module's assets from the directory of the assembly it actually
loaded, not from
modules/<Name>/wwwroot— that path is only the legacy fallback for a moduleriding the app closure (
ModuleWwwrootPath, #2509/#2562). For the lanes the directory is/ext/<Name>/, which is why the copy has to target the module directory rather than amodules/-shaped path.Work product:
Doc/Architecture/ModuleStaticAssets— the four hops, the landing invariant, everysite that lands a module and what each one owes, why the failure is invisible to review, CI and
production, and both measurements above.