Skip to content

fix(create): remove CreateTransient — node Create menu broke on Postgres - #317

Merged
rbuergi merged 2 commits into
mainfrom
fix/remove-create-transient
Jul 6, 2026
Merged

rbuergi merged 2 commits into
mainfrom
fix/remove-create-transient

Conversation

@rbuergi

@rbuergi rbuergi commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

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)

  • `CreateLayoutArea` builds ONE `Active` node and persists it with a single `CreateNode` (through access control), then navigates to `/{path}/Edit`.
  • Editors materialise default content when a fresh node has none — generic `EditNode` (via `MeshDataSource.CreateContentInstance`), `AgentView` (Details + Edit), `ModelProvider` — so a new Agent/Model shows an editable form instead of "Loading…".
  • Comment / reply / group create flows carry the draft in-memory and `CreateNode` on submit (also fixes a latent bug where comment-create wrote to the primary node's path).
  • `CreateTransient` removed from `IMeshService`, `MeshService`, `HubNodePersistence`, `MeshServiceExtensions`. `MeshNodeState.Transient` enum value kept (persisted contract). Docs updated.

Verification

  • Full-solution Release `-warnaserror` build (merged with main): green.
  • Tests: `CreatableTypesIntegrationTest`, `NodeCreationAccessTest`, `TodoCreateFlowTest`, `CreateLayoutAreaIntegrationTest`, comment/reply — all pass.
  • Playwright E2E (new `CreateMenuE2ETest`) against a real PG-backed DevLogin portal: Agent and Markdown create → land on `/Edit`, no "Area not found". 2/2 pass.

🤖 Generated with Claude Code

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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 CreateTransient from IMeshService/implementation and updated create flows to persist a single Active node via CreateNode.
  • 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.

Comment on lines +1689 to +1694
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);
Comment on lines 404 to 406
nodeFactory.CreateNode(commentNode).Subscribe(
_ => host.UpdateData(stateId, ""),
_ => host.UpdateData(stateId, ""));
Comment on lines 435 to 437
nodeFactory.CreateNode(replyNode).Subscribe(
_ => host.UpdateData(replyPathStateId, ""),
_ => host.UpdateData(replyPathStateId, ""));
Comment on lines 296 to 298
nodeFactory.CreateNode(newNode).Subscribe(
_ => saveCtx.NavigateTo($"/{path}/{MeshNodeLayoutAreas.EditArea}"),
_ => { });
Comment on lines 284 to 288
if (string.IsNullOrEmpty(commentPath))
return (UiControl)Controls.Stack;

return (UiControl)BuildCommentCreateForm(h, commentPath, newCommentPathStateId);
return (UiControl)BuildCommentCreateForm(h, commentPath, newCommentPathStateId, nodePath, currentUser);
});
@github-actions

github-actions Bot commented Jul 6, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

697 tests  +1   695 ✅ +1   1m 57s ⏱️ +9s
  9 suites ±0     2 💤 ±0 
  9 files   ±0     0 ❌ ±0 

Results for commit 970cd50. ± Comparison against base commit 0ab6eb2.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jul 6, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

   10 files  ±  0     10 suites  ±0   4m 43s ⏱️ +26s
1 359 tests +207  1 359 ✅ +207  0 💤 ±0  0 ❌ ±0 
1 398 runs  +237  1 398 ✅ +237  0 💤 ±0  0 ❌ ±0 

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.
MeshWeaver.Security.Test.NodeCreationAccessTest ‑ CreateTransientNode_PreservesDesiredId
MeshWeaver.Persistence.Test.ApplicationPageResolutionTest ‑ ResolvePathAsync_FutuReSubPaths_ShouldResolve(path: "FutuRe/Analysis", expectedPrefix: "FutuRe/Analysis", expectedRemainder: null)
MeshWeaver.Persistence.Test.ApplicationPageResolutionTest ‑ ResolvePathAsync_FutuReSubPaths_ShouldResolve(path: "FutuRe/EuropeRe", expectedPrefix: "FutuRe/EuropeRe", expectedRemainder: null)
MeshWeaver.Persistence.Test.ApplicationPageResolutionTest ‑ ResolvePathAsync_FutuReSubPaths_ShouldResolve(path: "FutuRe/EuropeRe/LineOfBusiness", expectedPrefix: "FutuRe/EuropeRe/LineOfBusiness", expectedRemainder: null)
MeshWeaver.Persistence.Test.ApplicationPageResolutionTest ‑ ResolvePathAsync_FutuReSubPaths_ShouldResolve(path: "FutuRe/EuropeRe/Overview", expectedPrefix: "FutuRe/EuropeRe", expectedRemainder: "Overview")
MeshWeaver.Persistence.Test.ApplicationPageResolutionTest ‑ ResolvePathAsync_FutuRe_ShouldNotReturnNull
MeshWeaver.Persistence.Test.ApplicationPageResolutionTest ‑ ResolvePathAsync_UnknownDeepPath_ShouldReturnNull
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.AI.AgenticAI.md")
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.AI.ChatCommands.md")
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.AI.ExecuteScript.md")
MeshWeaver.Persistence.Test.DocumentationCodeBlockCompilationTest ‑ ExecutedCsharpBlocks_MustCompile(embeddedResourceName: "MeshWeaver.Documentation.Data.AI.ExecutiveAssistan"···)
…

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jul 6, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

787 tests   - 83   605 ✅  - 85   3m 7s ⏱️ -56s
  9 suites ± 0   182 💤 + 2 
  9 files   ± 0     0 ❌ ± 0 

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.
MeshWeaver.Hosting.Monolith.Test.AgentChatClientNoSuitableAgentTest ‑ AgentChatClient_OnHostedSubHub_DoesNotReturn_NoSuitableAgent
MeshWeaver.Hosting.Monolith.Test.BrokenNodeTypeAccessTest ‑ AccessingInstance_OfNonCompilingNodeType_AnswersTerminalError_NotSilence
MeshWeaver.Hosting.Monolith.Test.CodeEditRecompileTest ‑ CodeEdit_ExplicitRelease_IsUpToDate_RecompilesOnSourceChange
MeshWeaver.Hosting.Monolith.Test.CodeEditRecompileTest ‑ FailedCompile_PreservesErrorLogAndDoesNotCreateRelease
MeshWeaver.Hosting.Monolith.Test.CodeEditRecompileTest ‑ IsDirty_FlipsTrueOnSourceEdit_FalseAfterCompile
MeshWeaver.Hosting.Monolith.Test.CodeEditRecompileTest ‑ NodeType_RequestedReleasePath_PinsToHistoricalRelease
MeshWeaver.Hosting.Monolith.Test.CodeEditRecompileTest ‑ PressingCompileButton_SetsRequestedReleaseAt_AndProducesNewRelease
MeshWeaver.Hosting.Monolith.Test.CopyModifyCopyBackTest ‑ CopyModifyCopyBack_UpdatesOnlyDeltas
MeshWeaver.Hosting.Monolith.Test.CreatableTypesIntegrationTest ‑ CreateTransientNode_ThenRequestEditView_Succeeds
MeshWeaver.Hosting.Monolith.Test.CrossHubWritePersistenceTest ‑ StreamUpdate_CrossHub_NoPriorSubscription_Persists
…
MeshWeaver.Hosting.Monolith.Test.CreatableTypesIntegrationTest ‑ CreateNode_ThenRequestEditView_Succeeds
MeshWeaver.Portal.E2E.CreateMenuE2ETest ‑ Create_LandsOnEditArea_NotAreaNotFound(nodeType: "Agent", nodeName: "Tom", expectedId: "Tom")
MeshWeaver.Portal.E2E.CreateMenuE2ETest ‑ Create_LandsOnEditArea_NotAreaNotFound(nodeType: "Markdown", nodeName: "TomNote", expectedId: "TomNote")

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jul 6, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

819 tests  +9   819 ✅ +9   3m 49s ⏱️ -5s
 10 suites ±0     0 💤 ±0 
 10 files   ±0     0 ❌ ±0 

Results for commit 970cd50. ± Comparison against base commit 0ab6eb2.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jul 6, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

1 029 tests  ±0   1 028 ✅ ±0   3m 41s ⏱️ -16s
    8 suites ±0       1 💤 ±0 
    8 files   ±0       0 ❌ ±0 

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.
MeshWeaver.Acme.Test.TodoCreateFlowTest ‑ CreateFlow_TransientNodeWithoutContent_PreservesContentFieldsAfterConfirm
MeshWeaver.Acme.Test.TodoCreateFlowTest ‑ CreateFlow_NodeWithoutContent_PreservesContentFieldsAfterEdit

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jul 6, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

648 tests  +3   648 ✅ +3   4m 54s ⏱️ +8s
 10 suites ±0     0 💤 ±0 
 10 files   ±0     0 ❌ ±0 

Results for commit 970cd50. ± Comparison against base commit 0ab6eb2.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jul 6, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

   56 files  ±  0     56 suites  ±0   22m 13s ⏱️ -35s
5 339 tests +137  5 154 ✅ +135  185 💤 +2  0 ❌ ±0 
5 378 runs  +167  5 193 ✅ +165  185 💤 +2  0 ❌ ±0 

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.
MeshWeaver.Acme.Test.TodoCreateFlowTest ‑ CreateFlow_TransientNodeWithoutContent_PreservesContentFieldsAfterConfirm
MeshWeaver.Hosting.Monolith.Test.AgentChatClientNoSuitableAgentTest ‑ AgentChatClient_OnHostedSubHub_DoesNotReturn_NoSuitableAgent
MeshWeaver.Hosting.Monolith.Test.BrokenNodeTypeAccessTest ‑ AccessingInstance_OfNonCompilingNodeType_AnswersTerminalError_NotSilence
MeshWeaver.Hosting.Monolith.Test.CodeEditRecompileTest ‑ CodeEdit_ExplicitRelease_IsUpToDate_RecompilesOnSourceChange
MeshWeaver.Hosting.Monolith.Test.CodeEditRecompileTest ‑ FailedCompile_PreservesErrorLogAndDoesNotCreateRelease
MeshWeaver.Hosting.Monolith.Test.CodeEditRecompileTest ‑ IsDirty_FlipsTrueOnSourceEdit_FalseAfterCompile
MeshWeaver.Hosting.Monolith.Test.CodeEditRecompileTest ‑ NodeType_RequestedReleasePath_PinsToHistoricalRelease
MeshWeaver.Hosting.Monolith.Test.CodeEditRecompileTest ‑ PressingCompileButton_SetsRequestedReleaseAt_AndProducesNewRelease
MeshWeaver.Hosting.Monolith.Test.CopyModifyCopyBackTest ‑ CopyModifyCopyBack_UpdatesOnlyDeltas
MeshWeaver.Hosting.Monolith.Test.CreatableTypesIntegrationTest ‑ CreateTransientNode_ThenRequestEditView_Succeeds
…
Memex.Portal.Shared.Test.OpenAICompatibleModelSyncTest ‑ BuildModelNode_CarriesTheProbedToolSupport(supportsTools: False)
Memex.Portal.Shared.Test.OpenAICompatibleModelSyncTest ‑ BuildModelNode_CarriesTheProbedToolSupport(supportsTools: True)
Memex.Portal.Shared.Test.OpenAICompatibleModelSyncTest ‑ BuildModelNode_CarriesTheProbedToolSupport(supportsTools: null)
MeshWeaver.Acme.Test.TodoCreateFlowTest ‑ CreateFlow_NodeWithoutContent_PreservesContentFieldsAfterEdit
MeshWeaver.ContentCollections.Indexing.PostgreSql.Test.PostgreSqlChunkedContentVectorStoreTests ‑ ReplaceAndSearch_RoundTripsPageAndPosition
MeshWeaver.ContentCollections.Indexing.Test.ContentChunkSearchTest ‑ ToJson_IncludesPageAndBbox_WhenChunkCarriesProvenance
MeshWeaver.ContentCollections.Indexing.Test.ContentIndexingServiceTest ‑ ForceReindex_BypassesHashGate_AndReExtractsProvenance
MeshWeaver.ContentCollections.Indexing.Test.ContentIndexingServiceTest ‑ PdfFile_ChunksCarryPageAndPosition
MeshWeaver.ContentCollections.Indexing.Test.TextChunkerTest ‑ ChunkPositioned_AttributesToStartPage_AndUnionsBoxesOnThatPage
MeshWeaver.ContentCollections.Indexing.Test.TextChunkerTest ‑ ChunkPositioned_ChunkStartingOnLaterPage_TakesThatPage
…

♻️ 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>
@rbuergi

rbuergi commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @copilot — addressed in 970cd50:

  • Comment / reply / group create-failure handlers now log via ILogger<LayoutAreaHost> instead of swallowing, and the comment & reply draft forms stay OPEN on failure (no longer clear the state) so the user's text isn't silently lost.
  • BuildEditNodeContent no longer seeds the /data projection with the default content instance on every render — the form binds to the node stream, so the seed was redundant and could reset in-progress edits before the first write lands.

Re: the inline UpdateData(newCommentPathStateId, "") (CommentsExtensions.cs:288) — that lives in the view-factory lambda ((h, _) => { … }), which runs once when the area subscribes, not per stream emission, so it doesn't discard the draft on comment-list updates. Left as-is.

@rbuergi
rbuergi merged commit b8ac911 into main Jul 6, 2026
16 checks passed
@rbuergi
rbuergi deleted the fix/remove-create-transient branch August 5, 2026 10:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants