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
CreateOrUpdateNode cannot change a node's MainNode: IsNoOpUpsert never compares it and UpdateAccordingToSourceNode keeps the stored one #2631
An upsert (IMeshService.CreateOrUpdateNode → HandleCreateOrUpdateNodeRequest) whose only difference from the stored node is MainNode is treated as a no-op and skipped: MeshExtensions.IsNoOpUpsert compares Name / NodeType / Icon / Category / Description / Order / State / PreRenderedHtml / ExcludeFromContext / Content — never MainNode. And when the upsert does take the update branch, UpdateAccordingToSourceNode builds state with { Name, NodeType, Icon, … Content } and keeps the stored MainNode. So a non-satellite node's MainNode can never be moved by an upsert, while GetMeshNodeStream(path).Update(n => n with { MainNode = … }) (what the patch tool uses) persists it fine.
Measured
MeshWeaver.Plugins #839 mirrors an app tile's click target into the record's MainNode and heals existing records through the Store's RefreshAppTiles sweep, which writes with CreateOrUpdateNode. On 2026-08-28 the sweep reported 1390 of 1443 record(s) refreshed on memex (then 839 on a re-run) and 108 of 133 on systemorph — and not one record's mainNode changed (e.g. rbuergi/_App/HomeAssistant stayed at version 1). A direct patch {"mainNode": …} on the same record persisted (v4 → v5).
Expected
Either the upsert carries MainNode (compare it in IsNoOpUpsert, merge it in UpdateAccordingToSourceNode — the satellite normalisation already runs before both), or the contract documents that MainNode is not upsertable and the request is rejected rather than silently reported as processed.
Workaround shipped in MeshWeaver.Plugins #841 (AppTileRefresh.WriteRecord: upsert, then the node stream for MainNode).
Both sites confirmed as described. A third was found while confirming them: PackageInstaller.ScalarsUnchanged is an explicit mirror of the same field list and gates the write before the upsert is asked, so without it a node-repo install still could not move a MainNode.
The self-path trap is real and measured — the naive source.MainNode ?? state.MainNode fix demotes a stored satellite to a main node on both the merge and the compare path (2 of the PR's 5 tests go red under that mutation, while only 1 goes red on unfixed code). The rule shipped is MeshNode.HasExplicitMainNode: apply the source's MainNode only when it names something other than the node itself. Its one limitation — an upsert cannot turn a satellite back into a main node — is documented on the property and in the PR body; no caller needs it today.
Activity
rbuergi commentedon Aug 29, 2026
Fix in #2652.
Both sites confirmed as described. A third was found while confirming them:
PackageInstaller.ScalarsUnchangedis an explicit mirror of the same field list and gates the write before the upsert is asked, so without it a node-repo install still could not move a MainNode.The self-path trap is real and measured — the naive
source.MainNode ?? state.MainNodefix demotes a stored satellite to a main node on both the merge and the compare path (2 of the PR's 5 tests go red under that mutation, while only 1 goes red on unfixed code). The rule shipped isMeshNode.HasExplicitMainNode: apply the source's MainNode only when it names something other than the node itself. Its one limitation — an upsert cannot turn a satellite back into a main node — is documented on the property and in the PR body; no caller needs it today.🤖 Generated with Claude Code