Repository navigation
refactor(views): the node-surface view layer leaves the platform for a module - #2678
Conversation
Eight layout-area files (~4.4k lines) move out of MeshWeaver.Graph into the
MeshWeaver.Graph.Views module: access assignments, API tokens, groups, group
memberships, mesh data sources, notifications, releases and the partition-sync
admin tab. A view is the most-changed and least-load-bearing thing in the
platform, and it was pinned where only a platform release could ship a fix.
What stays is the CONTRACT, per the split you asked for: the node types, their
content records, the area-name consts and the NodeMenuItemDefinition
descriptors. Two content helpers — AccessControlLayoutArea.DeserializeAssignment
and GroupsLayoutArea.DeserializeMembership — become public rather than internal:
the deserialization is contract, the rendering that consumed it is not.
The seam is the one the collaboration carve-out introduced, now KEYED BY NODE
TYPE. That is not cosmetic. Every Add{X}Views() sets its type's default area
with WithDefaultArea, so contributing them globally would make the last
registration the default for EVERY node type — each type would open on some
other type's page. Each platform node type now calls
ApplyNodeHubContributions(NodeType); without the module that call is a no-op.
Seven files stay for the next increment because they need SPLITTING, not
moving: Pin/Copy/Move/StopSync/Version carry NodeMenuItemDefinition descriptors
the platform menu builds from, and AccessControl/Groups are called from
MeshNodeLayoutAreas' forwarders.
Verified: full solution, Release, -warnaserror — 0 warnings, 0 errors,
88 projects emitted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGrR8eBykEAKSBgtFJo3Hx
First of the seven files that need SPLITTING rather than moving. The platform keeps StopSyncLayoutArea: the area name and the NodeMenuItemDefinition the node menu is assembled from platform-side. Only the render method and its private helper move to StopSyncViews in the module. The registration follows the pattern core already documents two lines above it — the OgCard module 'registers on every per-node hub' — so no new seam was needed for a node-wide view. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HGrR8eBykEAKSBgtFJo3Hx
…g moves Same cut as StopSync: the platform keeps each area's name consts and the NodeMenuItemDefinition the node menu is assembled from; the render methods and their private helpers move to Copy/Move/PinViews in the module, registered on every per-node hub the way OgCard already is. Four of the seven split-needing files are done. The remaining three are not mechanical: VersionLayoutArea interleaves two views with HandleRollbackNodeRequest / HandleUndoActivityRequest and stream helpers that are NOT views, and AccessControl / Groups keep their deserializers plus ShowAddAssignmentDialog, which UserNodeType calls. Verified: full solution, Release, -warnaserror — 0 warnings, 0 errors, 88 projects emitted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HGrR8eBykEAKSBgtFJo3Hx
…ndering moves
Six of the seven split-needing files done. These two were not a line cut: their
base members sit BETWEEN the view and its helpers, so the extraction was by
member rather than by offset.
Core keeps, and three of them had to open up because both halves call them:
- AccessControlLayoutArea: DeserializeAssignment, DeleteAssignment,
ShowAddAssignmentDialog (UserNodeType calls it) and ShowValidationError,
which the kept dialog AND the moved views both use;
- GroupsLayoutArea: DeserializeMembership.
Two forwarders in MeshNodeLayoutAreas are gone with them, and SettingsLayoutArea
no longer CALLS the AccessControl view — it embeds the area by name, the same
shape the inline comments section uses. That is the whole point: the settings
tab knows an area name, not a view implementation.
Verified: full solution, Release, -warnaserror — 0 warnings, 0 errors,
88 projects emitted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HGrR8eBykEAKSBgtFJo3Hx
Both views (Versions, VersionDiff) and their thirteen render helpers move. The platform keeps what is NOT a view: the menu descriptor, the compare-stream ids, the content helpers, and — the reason this file needed care — the RollbackNodeRequest / UndoActivityRequest MESSAGE HANDLERS, which are registered here and would have been silently relocated by a naive line cut. PluginSurfaceProbe becomes public: the view modules probe for an installed package the same way MarkdownOverviewLayoutArea's approvals section does, and a probe only the platform can call forces every module to hand-roll its own. All seven split-needing files are now done; the view layer in the module is 15 files. Verified: full solution, Release, -warnaserror — 0 warnings, 0 errors, 88 projects emitted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HGrR8eBykEAKSBgtFJo3Hx
CodeLayoutAreas keeps its seventeen area-name constants — NodeTypeLayoutAreas links them — and every view moves. Two private data-ids moved WITH the views rather than staying behind as unreachable privates, and ActivityLayoutAreas. StatusColor became public because the code cell's toolbar chip renders the same chip and must not fork the palette. CodeNodeType and ScopeNodeType now admit the code views as a keyed contribution instead of calling AddCodeViews() directly. Five suites move with their subject (ActivityProgressView, CodeCellEditor, CodeCellOwnRun, CodeCellStaleness, ImportLayoutArea). They drive internal view helpers, so the module grants InternalsVisibleTo to its test project — the same grant MeshWeaver.Graph.Test had. Verified: full solution, Release, -warnaserror — 0 warnings, 0 errors, 88 projects; module suite 64 passed, 0 failed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HGrR8eBykEAKSBgtFJo3Hx
… moves ActivityLayoutAreas had exactly ONE core consumer (NodeTypeLayoutAreas reads ProgressArea), so the platform keeps the five area-name constants and all fifteen members — the four views plus the header/log/status building blocks — move to ActivityViews. Measuring that took care: grepping 'ActivityLayoutAreas.' also matches 'UserActivityLayoutAreas.', which made the coupling look 8-consumers deep when it is one. The precise count is what made this a clean cut instead of a member-by-member sort. CodeViews already rendered the same status chip, so its calls follow the helpers into the module — which also retires the StatusColor I made public last commit; it is an internal detail of the view layer again, on the right side of the line. ActivityNodeType admits the views as a keyed contribution. Two more suites travel with their subject. Verified: full solution, Release, -warnaserror — 0 warnings, 0 errors, 88 projects emitted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HGrR8eBykEAKSBgtFJo3Hx
…tions SpaceLayoutAreas (779 loc) had exactly two real code references, both .WithView registrations in SpaceNodeType; the other three matches were doc prose. So the whole file moves and SpaceNodeType admits the views as a keyed contribution, carrying the rule that editing a Space edits its markdown body rather than a property form. Three shared view helpers open up because the module renders through them: MeshNodeLayoutAreas.BuildAccessDenied and OverviewLayoutArea's two StripLeadingTitle overloads. Three Space suites travel with their subject. Re-measuring first is what made this a wholesale move rather than a split — the same lesson as Activity, where a sloppy grep had reported eight consumers for a file that has one. Verified: full solution, Release, -warnaserror — 0 warnings, 0 errors, 88 projects; module suite 104 passed, 0 failed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HGrR8eBykEAKSBgtFJo3Hx
…hree DeleteLayoutArea keeps its menu descriptor and the QueriesParam const; the view and its twenty-six helpers move. MaxListedPaths travels with them — it is a rendering detail, and leaving it behind would have made it an unreachable private. The platform registered this same view in FOUR places: the default chain, MarkdownLayoutAreas, RedirectNodeType and — found only by building the whole solution — MeshWeaver.PluginCatalog's CatalogLayoutAreas. One node-wide registration in the module replaces all of them, which is a small simplification the move surfaced rather than caused. Three suites travel with their subject, including one from MeshWeaver.Hosting.Monolith.Test that drove Delete's ancestor-resolution helpers. Verified: full solution, Release, -warnaserror — 0 warnings, 0 errors, 88 projects; module suite 124 passed, 0 failed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HGrR8eBykEAKSBgtFJo3Hx
There was a problem hiding this comment.
Pull request overview
This PR refactors the MeshWeaver.Graph “node-surface” view layer to leave the core platform and be provided by the MeshWeaver.Graph.Views module, keeping platform-side contracts (area-name constants, menu definitions, shared content helpers, and handlers) while removing the rendering implementations and per-type view registrations.
Changes:
- Removed numerous layout-area view implementations from
MeshWeaver.Graphand adjusted hub/layout registration to rely on module-provided registrations viaNodeHubContributions. - Promoted a small set of shared helpers (e.g., deserializers, string-strippers, probes) to
publicso the out-of-tree views can reuse them without duplicating logic. - Deleted/relocated view-specific unit/integration tests from core (expected to live with the moved view module).
Reviewed changes
Copilot reviewed 52 out of 52 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/MeshWeaver.Hosting.Monolith.Test/DeleteRedirectToExistingParentTest.cs | Removes e2e coverage tied to Delete view implementation now expected to live in the views module. |
| test/MeshWeaver.Graph.Test/SpaceResolveSpaceTest.cs | Removes Space view-specific unit tests that move with the view implementation. |
| test/MeshWeaver.Graph.Test/SpaceLayoutAreasLogoTest.cs | Removes Space logo rendering tests (view behavior moved to module). |
| test/MeshWeaver.Graph.Test/SpaceDefaultCatalogTest.cs | Removes Space default-catalog behavior tests (view behavior moved to module). |
| test/MeshWeaver.Graph.Test/ImportLayoutAreaTest.cs | Removes Import layout-area view tests as Import view leaves core. |
| test/MeshWeaver.Graph.Test/DeleteQuerySetTest.cs | Removes Delete view URL/pure helper coverage that is now module-owned. |
| test/MeshWeaver.Graph.Test/CodeCellStalenessTest.cs | Removes Code view behavior tests moving with module-provided Code views. |
| test/MeshWeaver.Graph.Test/CodeCellOwnRunTest.cs | Removes Code view behavior tests moving with module-provided Code views. |
| test/MeshWeaver.Graph.Test/CodeCellEditorTest.cs | Removes Code editor view tests moving with module-provided Code views. |
| test/MeshWeaver.Graph.Test/ActivityProgressViewTest.cs | Removes Activity view rendering tests moving with module-provided views. |
| test/MeshWeaver.Graph.Test/ActivityCancelVisibilityTest.cs | Removes Activity cancel-visibility view tests moving with module-provided views. |
| test/MeshWeaver.Graph.Test/AccessAssignmentLayoutAreasTest.cs | Removes AccessAssignment view tests moving with module-provided views. |
| src/MeshWeaver.PluginCatalog/CatalogLayoutAreas.cs | Stops re-registering Create/Delete views here; relies on module registrations instead. |
| src/MeshWeaver.Graph/StopSyncLayoutArea.cs | Leaves platform-side contract/menu descriptor; removes the StopSync rendering implementation. |
| src/MeshWeaver.Graph/SettingsLayoutArea.cs | Embeds AccessControl by area name (module-provided implementation) instead of calling a platform view method. |
| src/MeshWeaver.Graph/ReleaseLayoutAreas.cs | Removes Release rendering implementation from core. |
| src/MeshWeaver.Graph/PluginSurfaceProbe.cs | Promotes probe API to public for reuse by view modules. |
| src/MeshWeaver.Graph/PinLayoutArea.cs | Removes Pin/Unpin/PinnedThumbnail view implementations from core, leaving menu descriptors/constants. |
| src/MeshWeaver.Graph/OverviewLayoutArea.cs | Promotes title-stripping helpers to public for reuse by module views. |
| src/MeshWeaver.Graph/NotificationLayoutAreas.cs | Removes Notification view implementations from core. |
| src/MeshWeaver.Graph/MoveLayoutArea.cs | Removes Move rendering implementation from core, leaving menu descriptor/constants. |
| src/MeshWeaver.Graph/MeshNodeLayoutAreas.cs | Removes platform registrations for views that now come from the module; promotes BuildAccessDenied to public for module reuse. |
| src/MeshWeaver.Graph/MeshDataSourceLayoutAreas.cs | Removes MeshDataSource view implementations from core. |
| src/MeshWeaver.Graph/MarkdownLayoutAreas.cs | Stops re-registering Delete in Markdown views; relies on module registrations. |
| src/MeshWeaver.Graph/IPartitionSyncSourceProvider.cs | Updates doc comment to reference module-provided GUI surface. |
| src/MeshWeaver.Graph/ImportLayoutArea.cs | Removes Import view implementation from core, leaving the menu descriptor. |
| src/MeshWeaver.Graph/GroupsLayoutArea.cs | Keeps only the platform-side content helper (membership deserialization) for module views. |
| src/MeshWeaver.Graph/GroupMembershipLayoutAreas.cs | Removes GroupMembership view implementations from core. |
| src/MeshWeaver.Graph/GroupLayoutAreas.cs | Removes Group view implementations from core. |
| src/MeshWeaver.Graph/CopyLayoutArea.cs | Removes Copy rendering implementation from core, leaving menu descriptor/constants. |
| src/MeshWeaver.Graph/Configuration/UserNodeType.cs | Switches to .ApplyNodeHubContributions(NodeType) to pick up module-provided views. |
| src/MeshWeaver.Graph/Configuration/SpaceNodeType.cs | Switches to .ApplyNodeHubContributions(NodeType) for Space views. |
| src/MeshWeaver.Graph/Configuration/ScopeNodeType.cs | Switches to .ApplyNodeHubContributions(NodeType) for Code/Scope views. |
| src/MeshWeaver.Graph/Configuration/ReleaseNodeType.cs | Switches to .ApplyNodeHubContributions(NodeType) for Release views. |
| src/MeshWeaver.Graph/Configuration/RedirectNodeType.cs | Removes Delete registration; relies on module-provided Delete view. |
| src/MeshWeaver.Graph/Configuration/NotificationNodeType.cs | Switches to .ApplyNodeHubContributions(NodeType) for Notification views. |
| src/MeshWeaver.Graph/Configuration/MeshDataSourceNodeType.cs | Switches to .ApplyNodeHubContributions(NodeType) for MeshDataSource views. |
| src/MeshWeaver.Graph/Configuration/GroupNodeType.cs | Switches to .ApplyNodeHubContributions(NodeType) for Group views. |
| src/MeshWeaver.Graph/Configuration/GroupMembershipNodeType.cs | Switches to .ApplyNodeHubContributions(NodeType) for GroupMembership views. |
| src/MeshWeaver.Graph/Configuration/CodeNodeType.cs | Switches to .ApplyNodeHubContributions(NodeType) for Code views. |
| src/MeshWeaver.Graph/Configuration/ApiTokenNodeType.cs | Switches to .ApplyNodeHubContributions(NodeType) for ApiToken views. |
| src/MeshWeaver.Graph/Configuration/ActivityNodeType.cs | Switches to .ApplyNodeHubContributions(NodeType) for Activity views. |
| src/MeshWeaver.Graph/Configuration/AccessAssignmentNodeType.cs | Switches to .ApplyNodeHubContributions(NodeType) for AccessAssignment views. |
| src/MeshWeaver.Graph/ApiTokenLayoutAreas.cs | Removes ApiToken view implementations from core. |
| src/MeshWeaver.Graph/ActivityLayoutAreas.cs | Removes Activity view implementations from core, leaving constants expected by module views. |
| src/MeshWeaver.Graph/AccessControlLayoutArea.cs | Keeps platform-side helpers (deserialization, delete, add-assignment dialog) for module views and adjusts visibility. |
| src/MeshWeaver.Graph/AccessAssignmentLayoutAreas.cs | Removes AccessAssignment view implementations from core. |
Suppressed comments (2)
src/MeshWeaver.Graph/AccessControlLayoutArea.cs:75
- ShowAddAssignmentDialog can currently be invoked with an empty/root nodePath, which allows minting a root-scoped AccessAssignment (the interactive UI should not offer this per AccessAssignmentGuard.CanGrantAt) and also produces an invalid access namespace ("/_Access"). Add an explicit guard up front to block the root context before building the form.
public static void ShowAddAssignmentDialog(UiActionContext ctx, string nodePath)
{
var formId = $"add_assignment_{Guid.NewGuid().AsString()}";
ctx.Host.UpdateData(formId, new Dictionary<string, object?>
{
["accessObject"] = "",
["role"] = ""
});
src/MeshWeaver.Graph/MeshNodeLayoutAreas.cs:315
- BuildAccessDenied includes several user-visible hard-coded English strings (the explanatory paragraph and the confirmation dialog title/body). These should be localized via keys in strings.{en,de}.json to avoid rendering English for German viewers.
public static UiControl BuildAccessDenied(string nodePath, string? locale = null)
{
var nodeName = nodePath.Split('/').LastOrDefault() ?? nodePath;
return Controls.Stack.WithWidth("100%").WithStyle("padding: 48px 24px; align-items: center; text-align: center;")
.WithView(Controls.Icon(FluentIcons.ShieldKeyhole())
.WithStyle("font-size: 64px; color: var(--neutral-foreground-hint); margin-bottom: 16px;"))
.WithView(Controls.H2(LocalizationCatalog.Get("error.accessDenied", locale)).WithStyle("margin: 0;"))
.WithView(Controls.Html(
$"<p style=\"color: var(--neutral-foreground-hint); max-width: 480px;\">" +
$"You do not have permission to view <strong>{System.Web.HttpUtility.HtmlEncode(nodeName)}</strong>. " +
$"Contact the owner to request access.</p>"))
.WithView(Controls.Button(LocalizationCatalog.Get("ui.requestAccess", locale))
.WithAppearance(Appearance.Accent)
.WithIconStart(FluentIcons.PersonAdd())
.WithClickAction(ctx =>
{
// Show a confirmation that the request was noted
var dialog = Controls.Dialog(
Controls.Markdown(
$"Access request for **{nodeName}** has been noted.\n\n" +
"The node owner will be notified."),
"Access Requested"
).WithSize("S").WithClosable(true);
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| public static void ShowValidationError(UiActionContext ctx, string message) | ||
| { | ||
| var errorDialog = Controls.Dialog( | ||
| Controls.Markdown(message), | ||
| "Validation Error" | ||
| ).WithSize("S").WithClosable(true); |
Test Results (shard 3)689 tests 497 ✅ 4m 56s ⏱️ Results for commit 5c3169a. ♻️ This comment has been updated with latest results. |
Test Results (shard 2)941 tests 941 ✅ 5m 11s ⏱️ Results for commit 5c3169a. ♻️ This comment has been updated with latest results. |
Test Results (shard 0)913 tests 913 ✅ 5m 29s ⏱️ Results for commit 5c3169a. ♻️ This comment has been updated with latest results. |
Test Results (shard 4) 10 files 10 suites 4m 59s ⏱️ Results for commit 5c3169a. ♻️ This comment has been updated with latest results. |
Test Results (shard 5)1 528 tests 1 528 ✅ 6m 18s ⏱️ Results for commit 5c3169a. ♻️ This comment has been updated with latest results. |
Test Results (shard 1)788 tests 784 ✅ 7m 37s ⏱️ Results for commit 5c3169a. ♻️ This comment has been updated with latest results. |
Test Results 41 files 41 suites 34m 34s ⏱️ Results for commit 5c3169a. ♻️ This comment has been updated with latest results. |
CI caught what my local verification structurally could not. These suites render areas by NAME, not by type reference, so they compiled cleanly against a core that no longer serves them and failed only at RUNTIME — fifteen tests across three projects (Hosting.Monolith.Test, AccessControl.Test, Content.Test). A full solution build plus the module's unit tests can never see this. Only running core's own suite would have, and I did not — the lesson from the collaboration half that I failed to carry over. Moved to the module's test project, where the mesh composes AddGraphViews(). Verified: full solution Release -warnaserror 0/0; module suite 145 passed, 0 failed (was 138/7). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HGrR8eBykEAKSBgtFJo3Hx
…travels too Shard 5 went red on MeshWeaver.Security.Test with 5 failures, all in AccessControlLayoutAreaTest. The suite renders MeshNodeLayoutAreas.AccessControlArea by NAME, so it compiled clean against core and only failed at runtime once AccessControlViews.AccessControl moved to the MeshWeaver.Graph.Views module. Same class as the five suites already moved, and the sixth of its kind: a name-addressed render is invisible to the compiler, so only a run catches it. Sweeping every core test for the module's full area set (constants AND string literals) turns up no further renderer -- the other 21 matches reference the menu DESCRIPTORS, which stay in core, which is why only this shard reddened. Moves to MeshWeaver.Graph.Views.Test composing .AddGraphViews().
…o moves nothing binds
The only red left on this PR is 'Public surface (binary compatibility)', which Consolidate
folds in ('Fail if the binary-compatibility gate failed'); every test shard is green.
Run with --sibling MeshWeaver.Plugins, the gate itself classifies all nine as PROVEN
CROSS-REPO MOVES into src/MeshWeaver.Graph.Views and states that a forwarder is impossible
here (it would have to live in src/MeshWeaver.Graph and reference an assembly this repo does
not build). Its documented resolution for that case is an allow entry carrying the claim that
no shipped module holds the TypeRef. The evidence for that claim is in the file, gathered
today: org-wide code search, the plugins repo outside the module, four sibling checkouts, and
the fact that in-mesh source addresses areas by NAME — which is exactly why this move compiled
clean and surfaced only in runtime render tests.
Ratchet honoured: these lines are to be DELETED once #2678 ships; a stale entry fails the gate.
…ries type-forwards.allow: retire the nine #2678 entries (stale now that the carve-out shipped)
merged (#2783) `scripts/type-forwards.allow` still lists MeshWeaver.Fixture:MeshWeaver.Fixture.ObservableAwait but #2771 has merged, so that move is IN main. The gate compares against the MERGE BASE, so relative to any branch cut after #2771 nothing of that name moves — and the entry is reported as STALE: STALE ALLOW ENTRY: scripts/type-forwards.allow lists `MeshWeaver.Fixture:MeshWeaver.Fixture.ObservableAwait`, but nothing of that name moved in this diff 🚨 That reddens EVERY subsequent PR, not one. Measured: both currently open PRs fail `Public surface (binary compatibility)` on it — #2782 (a test guard plus an allow file) and #2781 (a documentation-only Homebrew change). Neither can reach a public type; both are blocked by main. This is the failure mode the allow file's own rule names and that #2678 -> #2688 already paid for once: an allow entry goes stale FLEET-WIDE the instant its move merges, so it must be deleted in the VERY NEXT change. It was flagged on #2771 before that merge and landed with it anyway, which is exactly how the previous seven stale entries accumulated. The file now carries no live entries — header only, which is the correct state: nothing is currently exempt. Verified: scripts/check-type-forwards.py --self-test -> All 28 self-tests passed scripts/check-type-forwards.py --base <merge-base> -> OK, no unguarded public-type move Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follows the collaboration carve-out (#2660). 52 files, +105 / −8,713 — the node-surface layout areas move out of
MeshWeaver.Graphinto theMeshWeaver.Graph.Viewsmodule (Systemorph/MeshWeaver.Plugins, declared by the pre-installedDefaultViewspackage).The rule this follows
Contract stays, logic leaves. The platform keeps every area-name constant, every
NodeMenuItemDefinitionthe node menu is assembled from, the content deserializers other callers share, and the message handlers. Only rendering moves.21 view files, ~10.6k lines: access assignments, API tokens, groups, group memberships, mesh data sources, notifications, releases, partition-sync admin, Space, StopSync, Copy, Move, Pin, AccessControl, Groups, Version, Code, Import, Activity, Delete.
Things the compiler found that reading would not have
VersionLayoutAreahid two message handlers.HandleRollbackNodeRequest/HandleUndoActivityRequestare registered platform-side and are not views; a line-cut would have relocated them silently.Deletewas registered in FOUR places — the default chain,MarkdownLayoutAreas,RedirectNodeType, andMeshWeaver.PluginCatalog. Only a whole-solution build surfaced the fourth. One node-wide registration now replaces all four.WithDefaultAreaforced keyed contributions. EveryAdd{X}Views()sets its type's default area, so a global registration would have made the last one applied the default for EVERY node type — each type opening on some other type's page, with nothing failing to compile.What opened up, and why
BuildAccessDenied,StripLeadingTitleHtml/Markdown,DeserializeAssignment,DeserializeMembership,ShowValidationError,PluginSurfaceProbe— each because both halves genuinely call them.ActivityLayoutAreas.StatusColorwent public and then back to internal when its second caller followed it into the module, which is the shape you want.SettingsLayoutAreano longer CALLS the AccessControl view — it embeds the area by name, so the settings tab knows an area name rather than an implementation.Registration
Node-wide views use the pattern core already documents for the OgCard module ("registers on every per-node hub"); per-type view sets arrive as node-type-keyed contributions through
NodeHubContributions, the seam #2660 introduced.Verification
Full solution, Release,
-warnaserror: 0 warnings, 0 errors, 88 projects, after every increment. Module suite 124 passed, 0 failed. Twelve suites travelled with their subject, including ones fromMeshWeaver.Hosting.Monolith.TestandMeshWeaver.PluginCatalog.🚨 Nothing here renders a page. Compilation and unit tests are the whole of the local signal; the in-mesh compile gates are the real check.
🤖 Generated with Claude Code
https://claude.ai/code/session_01HGrR8eBykEAKSBgtFJo3Hx