Repository navigation
fix(create): remove CreateTransient — node Create menu broke on Postgres - #317
Conversation
The Create form wrote a Transient placeholder node then navigated to
/{path}/Create. Postgres path-resolution filters state=2 (Active only), so
the transient node was invisible and the router rendered "Area not found —
No renderer is registered for area {id} on hub {parent}". InMemory tests have
no state filter, so CI never caught it. CreateTransient also bypassed the
CreateNodeRequest access-control pipeline (wrote straight to IStorageAdapter).
Create now builds ONE Active node, persists it with a single CreateNode
(through access control), and navigates to /{path}/Edit — the owning hub
knows the content type and materialises the editor. A fresh node has null
Content, so editors materialise a default instance: generic EditNode via
MeshDataSource.CreateContentInstance, AgentView Details/Edit, ModelProvider.
Comment / reply / group create flows carry the draft in-memory and CreateNode
on submit (also fixes comment-create writing to the primary node's path).
CreateTransient removed from IMeshService / MeshService / HubNodePersistence /
MeshServiceExtensions; MeshNodeState.Transient enum kept (persisted contract).
Tests converted to the single-CreateNode flow; new CreateMenuE2ETest guards
Agent + Markdown create → Edit on a real PG portal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes Postgres-backed portal failures in the node Create menu by removing the transient placeholder create flow (CreateTransient) and switching to a single authoritative CreateNode of an Active node followed by navigation to the node’s Edit area. It also updates UI/editor surfaces to gracefully handle newly-created nodes with null content and adds E2E coverage to prevent regressions.
Changes:
- Removed
CreateTransientfromIMeshService/implementation and updated create flows to persist a single Active node viaCreateNode. - Updated layout areas/editors (Create, Edit, Agent, ModelProvider, comments/replies/groups) to support “fresh node has null Content” behavior.
- Added/updated integration and Playwright E2E tests plus documentation updates to reflect the new create flow.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| test/MeshWeaver.Security.Test/NodeCreationAccessTest.cs | Updates access-control tests to cover creating Active nodes directly (no transient confirm/delete). |
| test/MeshWeaver.Portal.E2E.Test/CreateMenuE2ETest.cs | Adds Playwright regression test ensuring Create lands on /Edit (PG-backed portal). |
| test/MeshWeaver.Hosting.Monolith.Test/CreatableTypesIntegrationTest.cs | Updates create→edit integration test to use CreateNode with Active state. |
| test/MeshWeaver.Acme.Test/TodoCreateFlowTest.cs | Updates Todo create-flow tests to use Active CreateNode and post-create edit semantics. |
| src/MeshWeaver.Mesh.Contract/Services/MeshServiceExtensions.cs | Removes CreateTransientAsync task shim. |
| src/MeshWeaver.Mesh.Contract/Services/IMeshService.cs | Removes CreateTransient from the public service contract docs/API. |
| src/MeshWeaver.Hosting/MeshService.cs | Removes the persistence-bypassing CreateTransient implementation. |
| src/MeshWeaver.Hosting/HubNodePersistence.cs | Removes direct-adapter transient persistence helper. |
| src/MeshWeaver.Graph/MeshNodeLayoutAreas.cs | Seeds default content instance when node content is null for Edit rendering. |
| src/MeshWeaver.Graph/GroupsLayoutArea.cs | Switches membership create flow to CreateNode and navigates to Edit. |
| src/MeshWeaver.Graph/CreateLayoutArea.cs | Reworks Create area to persist one Active node via CreateNode then navigate to Edit. |
| src/MeshWeaver.Graph/CommentsExtensions.cs | Removes transient comment placeholders; drafts are in-memory until a single CreateNode on submit. |
| src/MeshWeaver.Graph/CommentLayoutAreas.cs | Removes transient reply placeholders; drafts are in-memory until a single CreateNode on submit. |
| src/MeshWeaver.Documentation/Data/Architecture/SatelliteNodePatterns.md | Updates satellite creation guidance to match Active CreateNode flow. |
| src/MeshWeaver.Documentation/Data/Architecture/DataAccessPatterns.md | Removes transient create from documented access patterns. |
| src/MeshWeaver.Documentation/Data/Architecture/AsynchronousCalls.md | Updates “subscribe is mandatory” and removes transient-create mentions. |
| src/MeshWeaver.AI/ModelProviderLayoutAreas.cs | Handles null content by showing a default configuration for newly-created providers. |
| src/MeshWeaver.AI/AgentView.cs | Handles null content by showing an empty default AgentConfiguration instead of “Loading…”. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var dataSource = host.Workspace.DataContext.DataSources | ||
| .OfType<MeshDataSource>() | ||
| .FirstOrDefault(ds => ds.ContentType != null); | ||
| instance = dataSource?.CreateContentInstance(node); | ||
| if (instance != null) | ||
| host.UpdateData(Layout.Domain.EditLayoutArea.GetDataId(node.Path), instance); |
| nodeFactory.CreateNode(commentNode).Subscribe( | ||
| _ => host.UpdateData(stateId, ""), | ||
| _ => host.UpdateData(stateId, "")); |
| nodeFactory.CreateNode(replyNode).Subscribe( | ||
| _ => host.UpdateData(replyPathStateId, ""), | ||
| _ => host.UpdateData(replyPathStateId, "")); |
| nodeFactory.CreateNode(newNode).Subscribe( | ||
| _ => saveCtx.NavigateTo($"/{path}/{MeshNodeLayoutAreas.EditArea}"), | ||
| _ => { }); |
| if (string.IsNullOrEmpty(commentPath)) | ||
| return (UiControl)Controls.Stack; | ||
|
|
||
| return (UiControl)BuildCommentCreateForm(h, commentPath, newCommentPathStateId); | ||
| return (UiControl)BuildCommentCreateForm(h, commentPath, newCommentPathStateId, nodePath, currentUser); | ||
| }); |
Test Results (shard 4) 10 files ± 0 10 suites ±0 4m 43s ⏱️ +26s Results for commit 970cd50. ± Comparison against base commit 0ab6eb2. This pull request removes 1 and adds 208 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
Test Results (shard 5)787 tests - 83 605 ✅ - 85 3m 7s ⏱️ -56s Results for commit 970cd50. ± Comparison against base commit 0ab6eb2. This pull request removes 86 and adds 3 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
Test Results (shard 1)1 029 tests ±0 1 028 ✅ ±0 3m 41s ⏱️ -16s Results for commit 970cd50. ± Comparison against base commit 0ab6eb2. This pull request removes 1 and adds 1 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
Test Results 56 files ± 0 56 suites ±0 22m 13s ⏱️ -35s Results for commit 970cd50. ± Comparison against base commit 0ab6eb2. This pull request removes 88 and adds 225 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
… per-render /data seed - Comment/reply/group create-failure handlers now log (ILogger<LayoutAreaHost>) instead of silently swallowing, and the comment/reply drafts stay OPEN on failure (don't clear the state) so the user's text isn't lost. - BuildEditNodeContent no longer seeds the /data projection with the default content instance on every render (this method re-runs on the permissions CombineLatest) — the form binds to the node stream, so the seed was redundant and could reset in-progress edits before the first write lands. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @copilot — addressed in 970cd50:
Re: the inline |
The bug
The node Create menu showed "Area not found — No renderer is registered for area `{id}`" on every Postgres-backed portal (reported on an Agent at `/TestSpace/Tom/Create`, but it hit every node type).
Root cause
The old create flow wrote a `Transient` placeholder node — `meshService.CreateTransient`, which also bypassed the `CreateNodeRequest` access-control pipeline (it wrote straight to `IStorageAdapter`) — then navigated to `/{path}/Create`. `PostgreSqlSqlGenerator` hardcodes `n.state = 2` (Active only), so path-resolution could not see the transient node → it treated the node id as an area name on the parent hub. InMemory tests have no state filter, so CI never caught it.
The standard areas were never missing — every node hub already inherits them via `ConfigureDefaultNodeHub → AddDefaultLayoutAreas`.
The fix (remove `CreateTransient` entirely)
Verification
🤖 Generated with Claude Code