Skip to content

The portal image ships no Blazor view pack — a fresh mesh renders every control as escaped JSON, and reports healthy while doing it #2548

Closed

Description

@rbuergi

Symptom

On a mesh that has not installed the view packs from a registry, every layout-area control renders as escaped JSON instead of a view. The page body is literally:

NamedAreaControl { Id = , Style = , Readonly = , Skins = System.Collections.Immutable.ImmutableList`1[MeshWeaver.Layout.Skin],
  Class = , IsClickable = False, DataContext = , PageTitle = , Meta = , ModuleName = MeshWeaver.Layout,
  ApiVersion = 1.0.0, Area = Overview, ShowProgress = , ProgressMessage = , SpinnerType = Ring }

That is character-for-character BlazorViewRegistry.cs:90 — Controls.Html(WebUtility.HtmlEncode(instance.ToString())), the WithFallbackView slot wired at line 60.

The portal boots healthy and the rollout completes. That is the worst part: Modules:Required does not name the view packs, so nothing reports the loss.

Mechanism

db552ffbf (2026-08-25, "Blazor view definitions leave the platform: DefaultViews and GraphViews move out") removed, in one commit:

  1. the two MeshModuleClosure rows from memex/MeshModulesPublish.targets → no modules/<Name> copy in the image;
  2. their Modules:Assemblies entries → nothing activates them.

Removal (2) was deliberate and is the point of the commit — quoting it:

the removal is the operative one: ComputeEffectiveModuleEntries takes baseline entries first and dedupes the persisted store entry by name, so while a baseline entry named these packs it shadowed the installed copy. With it gone, the registry-landed pack is [used].

That reasoning is correct. The gap is that (1) went with it and no host re-added it, so the packs are now in neither place:

  • MeshWeaver main: no Blazor.Views/Blazor.Graph closure row anywhere.
  • MeshWeaver.Plugins hosts: Memex.Portal.Distributed / Memex.Portal.Monolith add closure rows for Cosmos and Snowflake only.
  • Shipped appsettings.json: Modules:Assemblies = [MeshWeaver.AI.dll, MeshWeaver.Hosting.Grpc.dll]; Modules:Required names Radzen/Analysis/EntityViews/GoogleMaps/Speech/Social/AI — neither view pack.
  • Enumerating all 18,399 files in memex-portal-ai@sha256:85a6de78… (3.0.0-rc8.ci.6141): no MeshWeaver.Blazor.Views.dll, no MeshWeaver.Blazor.Graph.dll — not in /app, not under app/modules/.

MeshWeaver.Blazor keeps only the machinery and the fallback slot (its own comment, BlazorViewRegistry.cs:55-59); the sole NamedAreaControl → NamedAreaView map lives in MeshWeaver.Blazor.Views/ViewsExtensions.cs:101.

The commit's rationale — "both prod portals already have the packs installed and enabled" — holds for a portal that already installed them. It does not hold for a mesh that has never talked to a registry. And preInstalled: true cannot cover it either: module install is restart-as-activation, so even pointing a fresh mesh at a registry leaves the first render as raw JSON.

Blast radius

Every fresh mesh: MeshWeaver.Education's disposable e2e, memex-local, and the first boot of any new deployment. Existing prod portals are unaffected — they installed the packs before the flip, which is exactly why this went unnoticed.

Concretely it is what turned MeshWeaver.Education main red: run 33106582408, all four Every course installs… shards plus The install actually installs (blocking) fail identically at e2e/bootstrap.setup.ts:147 waiting for getByText('package(s) available'). The catalog's own text is built from Controls.Stack/H1/Body (CatalogLayoutAreas.cs:262), every child of which is a NamedAreaControl — so that string can never appear, regardless of how many packages the source offers. Zero [bootstrap +Ns] marks were emitted; seeding itself succeeded (Node created at e2e-admin/Packages). It is the render that failed.

Attribution is clean: the signature appears 10× in the #221 merge run and 0× in the five preceding failed runs (whose failures were an unrelated per-course signature), and the packs are present in the previous pin 5a5564e5 and absent in 85a6de78.

Two ways to fix — this is a design call, not an obvious patch

A. Put the floor back in the image. Re-add the two MeshModuleClosure rows on the Plugins hosts and the two Modules:Assemblies + Modules:Required entries. Verified to build and lay out correctly (dotnet publish -c Release -r linux-x64 -p:CIRun=true, exit 0): modules/MeshWeaver.Blazor.Views/…dll plus .staticwebassets.endpoints.json and wwwroot/ (the #1724 static-asset lane intact), app root free of the DLL so MeshWeaverModulesGuardPublishAppRoot stays satisfied.

🚨 But this re-introduces precisely the shadow db552ffbf set out to remove — a baseline entry wins by name over a registry-landed upgrade (ModuleActivation.cs:497-540). It trades "the registry owns these packs" back for "the image ships a floor."

B. Keep the extraction, remove its cost. Make an ENABLED persisted entry override a same-named baseline in ComputeEffectiveModuleEntries (~5 lines + tests), then a baseline floor no longer shadows anything and A becomes safe. This preserves the Phase B2 end-state, but it changes a load-order contract several packs depend on.

The choice turns on whether Phase B2 means "registry only" or "registry wins", which is why this is filed rather than patched.

Whichever is chosen, Modules:Required should name the view packs, so this can never again be silent. On its own that only converts the failure from invisible to Degraded, named — the portal still comes up unusable — so it is a complement to A or B, not a fix.

Also worth correcting

MeshWeaver.Plugins/.github/workflows/ci.yml:203-209 still asserts the invariant this broke:

the source and the image copy move here together in the flip step, exactly so no deployment ever sees the pack in neither place

Three further places assert the image ships them: ViewsViewPackModuleAttribute.cs, BlazorViewRegistry.cs:57-59, Memex.Portal.Monolith.csproj:44-50. All are stale either way and should move with the fix.

Unrelated observation from the same run

The portal reports version=1.0.0 at runtime (host assembly from the Plugins repo) while the image tag is 3.0.0-rc8.ci.6141. The module floor gate reads a core assembly (ModulePlatformFloor.Resolve()), so minMeshVersion: 3.0.0-rc8 is still satisfied and nothing is broken by it — but the reported version is wrong and probably wants its own issue.

Ruled out as non-causal here: [CreateNode] REFUSED Plugins/_Policy (#1209, live work in #2534) — the same refusal shape appears for ApiToken and Admin/_Access/… in this run while both operations nonetheless succeeded, and the artifact shows a rendering fallback, not an access-denied page.

Activity

  1. rbuergi commented on Aug 27, 2026

    @rbuergi
    ContributorAuthor

    Option A is written and verified, but deliberately not opened as a PR

    A patch implementing option A exists on the branch fix/view-packs-return-to-the-image in MeshWeaver.Plugins (committed locally on the maintainer's machine, not pushed — commit bae5ed75). Four files, +90/−22:

    • Memex.Portal.Distributed.csproj and Memex.Portal.Monolith.csproj — the two MeshModuleClosure rows, in the same "host adds its own row after importing the targets" shape already used for Cosmos/Snowflake. Monolith's is gated on Configuration != Debug, because Debug keeps its ships-the-bits ProjectReferences and a closure row plus an app-root copy is exactly what MeshWeaverModulesGuardPublishAppRoot refuses.
    • both appsettings.json — the two DLLs under Modules:Assemblies and Modules:Required.

    Verified with a real publish rather than by reading (dotnet publish -c Release -r linux-x64 -p:CIRun=true, exit 0, no error lines), checking for positive signals:

    Not verified: a booted portal emitting [ModuleLoad] for either pack and painting a real catalog — that needs a running image, and MeshWeaver.Education's disposable mesh is the natural place to confirm it.

    It is left unopened on purpose: as written it re-introduces the baseline-shadows-registry behaviour that db552ffbf removed on purpose. If option B is preferred, that patch should be discarded rather than merged and then reverted.

  2. added a commit that references this issue on Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions