Skip to content

feat(layout): row-scoped actions — a click inside a bound row template carries its row - #5955

Merged
meshweaver-cloud[bot] merged 2 commits into
mainfrom
feat/row-scoped-actions
Oct 2, 2026
Merged

meshweaver-cloud[bot] merged 2 commits into
mainfrom
feat/row-scoped-actions

Conversation

@rbuergi

@rbuergi rbuergi commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Platform mechanism for row-scoped actions (maintainer decision; Doc/GUI/DataBinding → new section "Row-scoped actions").

The gap

A bound row template — BindMany (ItemTemplateControl) or a DataGridControl TemplateColumnControl — is declared ONCE and rendered by the client once per row. On the owner its controls exist once, at the template's area, so a button in it posted a ClickedEvent naming only that area: every row ran the same action with nothing saying which row. The data-binding batches therefore downgraded per-row buttons to "click the row, then act", or left areas unconverted. For grids it was worse: a template column's control existed only INLINE in the grid, so a click in a cell reached the GRID's area and the button's own action never ran.

The mechanism (core half — lands first)

  • RowContext (Pointer, Index, Value, Path; NodePath()): the row as the client rendered it.
  • ClickedEvent.Row / BlurEvent.Row (init-only, additive); LayoutAreaHost hands it to the action as UiActionContext.Row.
  • ctx.RowAs<T>() (Mesh.Contract, through ObjectAsExtensions.As, never a cast) and ctx.RowPath().
  • 🚨 The owner never re-resolves the row by index: a list that changed between render and click (rows added above / removed) still acts on the row that was clicked.
  • DataGridControl.RenderSelf renders each template column's template into {grid}/Column{i} (DataGridControl.TemplateColumnArea), so the cell's control and its click action are found where the client's event names them.
  • RowContext registered with the layout types. The row is user input; the action authorizes by writing as the clicking user.
  • MeshSearch rows need nothing: each result renders through the node's own item area, whose hub knows its path (documented).

Client half

Systemorph/MeshWeaver.Plugins — BlazorView gets a cascading Row and stamps it on every click and blur; ItemTemplate cascades each row (pointer, index, rendered value); DataGridView cascades the grid row and renders template columns at their area. PR: see the paired Plugins PR (lands AFTER this one; it does not compile against a platform without RowContext).

Evidence

  • test/MeshWeaver.Layout.Test/RowScopedClickActionTest (4, green): N rows — clicking row k acts on row k (value, pointer, node path) for every k; negative control — the same click with no row: the action cannot tell the row; the list changes between render and click — the click still acts on the clicked row while a fresh click acts on the new occupant of the slot; a grid template-column button acts on its grid row.
  • Negative control on the change: with the template-column rendering disabled, ATemplateColumnButtonActsOnItsGridRow goes red (no control at RowGrid/Column1).
  • Plugins RowScopedActionsFromViewsTest (3, green) against this branch; red (2/3) with the Row = Row stamp removed from BlazorView. Full MeshWeaver.Blazor.Views.Test 334/334.
  • Release -warnaserror clean: MeshWeaver.Layout, MeshWeaver.Mesh.Contract, MeshWeaver.Documentation, MeshWeaver.Layout.Test. MeshWeaver.Documentation.Test 657/657.

Reference conversion

The coupon admin list (from #5948) restores its per-row Open button as a row-scoped action — stacked on #5948's branch as a separate PR (feat/row-scoped-coupon-list), since #5940 → #5944 → #5947 → #5948 have not merged.

Remaining affected areas (for follow-up conversion agents)

Each was downgraded to "click the row / select, then act" or left unconverted for want of this mechanism. Convert with a TemplateColumnControl (grid) or a BindMany template whose button reads ctx.RowAs<T>() / ctx.RowPath():

  • B3 (core settings tabs): revoke token (Service identities / API tokens), archive mail (Inbox), revoke invitation (Invitations) — currently the "fed grid + SELECTION" shape. Open coupon — done in the reference PR.
  • Plugin Catalog cards (core MeshWeaver.PluginCatalog/CatalogLayoutAreas.Catalog): install, update, remove, and the per-card update-policy select (a select in a row: its value is bound by pointer; an OnBlur/click action gets ctx.Row too).
  • B6 (MeshWeaver.Plugins, AI): ThreadInbox, thread Header and Changes (per-row Undo; currently the hub-computed MessageChanges slot).
  • B4 (core samples): samples/Todo — 7 areas with per-row action buttons (Plugins' MeshWeaver.Todo.Test asserts the baked LayoutGridControl; pair it).
  • B10 (MeshWeaver.Reinsurance): ILS Ingestion and Tranche Progress, Underwriting ClaimsReview.
  • B5: PartitionSyncAdmin.
  • B9 (MeshWeaver.Plugins, Chess): the board cells (64 cells as a bound template; each click carries its square).
  • Not covered here: the React client (clients/react itemTemplate.tsx posts clicks with no row) — a follow-up so React-rendered rows carry the row too.

Deploy

Compiled platform code; no node recycle. Satellites gain the API on their next platform set.

Pairs-with: none — this change only ADDS public surface (no type or member removed); the Plugins client half lands after it.
Implementers: none — no interface member added (ClickedEvent/BlurEvent/UiActionContext are records; the new members are init properties).
Mirror-sync: none — no localization catalog change.

🤖 Generated with Claude Code

…e carries its row

A bound row template (BindMany / ItemTemplateControl, a DataGrid TemplateColumnControl) exists
ONCE on the owner, so a button in it posted a ClickedEvent naming only the template's area and
its action could not tell which row was clicked. The data-binding batches had to downgrade
per-row buttons to "click the row, then act" or leave areas unconverted.

- RowContext (Pointer, Index, Value, Path; NodePath()) — the row AS THE CLIENT RENDERED IT.
- ClickedEvent.Row / BlurEvent.Row (init, non-breaking); the owner hands it to the action as
  UiActionContext.Row. Never re-resolved by index: a list that changed between render and click
  still acts on the clicked row.
- ctx.RowAs<T>() (Mesh.Contract, via ObjectAsExtensions.As) and ctx.RowPath().
- DataGridControl renders each template column's template into {grid}/Column{i}, so a cell's
  control and its click action are found where the client's event names them (before, the
  template lived only inline and a click reached the GRID's area).
- RowContext registered with the layout types.

Tests: RowScopedClickActionTest — N rows, row k acts on row k; a no-row negative control; the
list changing between render and click; a grid template column (red without the RenderSelf
change). Doc: GUI/DataBinding → "Row-scoped actions".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

  1 files  ±0    1 suites  ±0   3m 1s ⏱️ -7s
348 tests ±0  348 ✅ ±0  0 💤 ±0  0 ❌ ±0 
352 runs  ±0  352 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 8d90eed. ± Comparison against base commit 86c840b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

    2 files   -   1      2 suites   - 1   5m 16s ⏱️ -18s
1 710 tests  - 656  1 710 ✅  - 656  0 💤 ±0  0 ❌ ±0 
1 714 runs   - 656  1 714 ✅  - 656  0 💤 ±0  0 ❌ ±0 

Results for commit 8d90eed. ± Comparison against base commit 86c840b.

This pull request removes 661 and adds 5 tests. Note that renamed tests count towards both.
Memex.Portal.Shared.Test.InstanceIdRulesMatchTheRegistryTest ‑ TheSetupHostAgreesWithTheRegistry(candidate: "3edd478b-3acc-44f6-a1e8-af225c42578d")
MeshWeaver.Documentation.Test.AWebhookIssueSnapshotKeepsItsCloseDecisionTest ‑ ADuplicateCloseSurvivesTheWebhook
MeshWeaver.Documentation.Test.AWebhookIssueSnapshotKeepsItsCloseDecisionTest ‑ AnOpenIssueCarriesNoCloseDecision
MeshWeaver.Documentation.Test.AWebhookIssueSnapshotKeepsItsCloseDecisionTest ‑ TheTwoMappersAgree(wireValue: "completed", expected: Completed)
MeshWeaver.Documentation.Test.AWebhookIssueSnapshotKeepsItsCloseDecisionTest ‑ TheTwoMappersAgree(wireValue: "duplicate", expected: Duplicate)
MeshWeaver.Documentation.Test.AWebhookIssueSnapshotKeepsItsCloseDecisionTest ‑ TheTwoMappersAgree(wireValue: "not_planned", expected: NotPlanned)
MeshWeaver.Documentation.Test.AgentsRuleAndSkillAgreementGuard ‑ BothDetectorsSeeTheDefectTheyWereWrittenFor
MeshWeaver.Documentation.Test.AgentsRuleAndSkillAgreementGuard ‑ EveryPairsRuleIsStillStatedInAgentsMd
MeshWeaver.Documentation.Test.AgentsRuleAndSkillAgreementGuard ‑ NoSkillCarriesASupersededRulesWording
MeshWeaver.Documentation.Test.AgentsRuleAndSkillAgreementGuard ‑ NoSkillMintsAFileUnderAWhatsNewPath
…
Memex.Portal.Shared.Test.InstanceIdRulesMatchTheRegistryTest ‑ TheSetupHostAgreesWithTheRegistry(candidate: "e763337a-fbd9-4d0e-8457-a1624b2b121d")
MeshWeaver.Layout.Test.RowScopedClickActionTest ‑ AClickWithoutARowCannotTellWhichRowItCameFrom
MeshWeaver.Layout.Test.RowScopedClickActionTest ‑ ARowClickedAfterTheListChangedStillActsOnTheRowThatWasClicked
MeshWeaver.Layout.Test.RowScopedClickActionTest ‑ ATemplateColumnButtonActsOnItsGridRow
MeshWeaver.Layout.Test.RowScopedClickActionTest ‑ EachRowsButtonActsOnItsOwnRow

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

1 693 tests  +583   1 693 ✅ +583   4m 7s ⏱️ +13s
    2 suites  -   2       0 💤 ±  0 
    2 files    -   2       0 ❌ ±  0 

Results for commit 8d90eed. ± Comparison against base commit 86c840b.

This pull request removes 80 and adds 661 tests. Note that renamed tests count towards both.
   --- End of inner exception stack trace ---, isDenial: True)
 ---> System.UnauthorizedAccessException: Access denied
Memex.Portal.Shared.Test.SessionDenialIsAnAnswerTest ‑ OnlyAVerdictReadsAsADenial(shape: "the same verdict nested, as a late denial dispatch"···, failure: System.InvalidOperationException: write failed
MeshWeaver.Cli.Test.GateHostCompositionTest ‑ ATesterReference_IsRecognisedAsTheWrongPlatformImage(image: "ghcr.io/systemorph/memex-portal-ai:main", isTester: False)
MeshWeaver.Cli.Test.GateHostCompositionTest ‑ ATesterReference_IsRecognisedAsTheWrongPlatformImage(image: "meshweaver.azurecr.io/MW-Plugin-Test@sha256:abc", isTester: True)
MeshWeaver.Cli.Test.GateHostCompositionTest ‑ ATesterReference_IsRecognisedAsTheWrongPlatformImage(image: "meshweaver.azurecr.io/memex-portal-ai@sha256:abc", isTester: False)
MeshWeaver.Cli.Test.GateHostCompositionTest ‑ ATesterReference_IsRecognisedAsTheWrongPlatformImage(image: "meshweaver.azurecr.io/mw-plugin-test:3.0.0-rc9.ci."···, isTester: True)
MeshWeaver.Cli.Test.GateHostCompositionTest ‑ DiscardTree_OnAnAbsentPath_IsSilent
MeshWeaver.Cli.Test.GateHostCompositionTest ‑ DiscardTree_RemovesTheTree
MeshWeaver.Cli.Test.GateHostCompositionTest ‑ DiscardTree_ThatCannotBeRemoved_ReportsAndDoesNotThrow
…
Memex.Portal.Shared.Test.SessionDenialIsAnAnswerTest ‑ OnlyAVerdictReadsAsADenial(shape: "the same verdict nested, as a late denial dispatch"···, failure: System.InvalidOperationException: write failed
 ---> System.UnauthorizedAccessException: Access denied
   --- End of inner exception stack trace ---, isDenial: True)
MeshWeaver.Documentation.Test.AWebhookIssueSnapshotKeepsItsCloseDecisionTest ‑ ADuplicateCloseSurvivesTheWebhook
MeshWeaver.Documentation.Test.AWebhookIssueSnapshotKeepsItsCloseDecisionTest ‑ AnOpenIssueCarriesNoCloseDecision
MeshWeaver.Documentation.Test.AWebhookIssueSnapshotKeepsItsCloseDecisionTest ‑ TheTwoMappersAgree(wireValue: "completed", expected: Completed)
MeshWeaver.Documentation.Test.AWebhookIssueSnapshotKeepsItsCloseDecisionTest ‑ TheTwoMappersAgree(wireValue: "duplicate", expected: Duplicate)
MeshWeaver.Documentation.Test.AWebhookIssueSnapshotKeepsItsCloseDecisionTest ‑ TheTwoMappersAgree(wireValue: "not_planned", expected: NotPlanned)
MeshWeaver.Documentation.Test.AgentsRuleAndSkillAgreementGuard ‑ BothDetectorsSeeTheDefectTheyWereWrittenFor
MeshWeaver.Documentation.Test.AgentsRuleAndSkillAgreementGuard ‑ EveryPairsRuleIsStillStatedInAgentsMd
MeshWeaver.Documentation.Test.AgentsRuleAndSkillAgreementGuard ‑ NoSkillCarriesASupersededRulesWording
MeshWeaver.Documentation.Test.AgentsRuleAndSkillAgreementGuard ‑ NoSkillMintsAFileUnderAWhatsNewPath
…

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

588 tests   - 179   588 ✅ + 14   2m 7s ⏱️ +6s
  3 suites  -   1     0 💤  - 193 
  3 files    -   1     0 ❌ ±  0 

Results for commit 8d90eed. ± Comparison against base commit 86c840b.

This pull request removes 216 and adds 37 tests. Note that renamed tests count towards both.
MeshWeaver.Portal.E2E.AgentFilesToolingTest ‑ EveryTool_RunsForReal_AndTheFileIsARealMeshNode
MeshWeaver.Portal.E2E.AgentFilesToolingTest ‑ TheWorkingArea_IsScopedToItsOwnThread
MeshWeaver.Portal.E2E.BlazorVsNextCompareTest ‑ Compare_Blazor_Vs_Next_AcrossScreens
MeshWeaver.Portal.E2E.ChatAtAutocompleteTest ‑ TypingAt_SurfacesLocalPartitionNodes_AndSlashExpandsPartitions
MeshWeaver.Portal.E2E.ChatClearSkillNewComposerTest ‑ ClearSkill_InSidePanel_ReplacesThreadWithComposer_AndLeavesMainAlone
MeshWeaver.Portal.E2E.ChatComposerRefocusTest ‑ Composer_AcceptsTyping_AfterClickingAwayAndBack
MeshWeaver.Portal.E2E.ChatComposerSafariFocusReproTest ‑ Composer_AcceptsTyping_AfterFocusStealDuringStream
MeshWeaver.Portal.E2E.ChatComposerStreamingFocusTest ‑ Composer_KeepsFocus_WhileResponseStreams
MeshWeaver.Portal.E2E.ChatComposerSwitchSelectionTest ‑ SlashCommands_OpenPicker_AndSelectionUpdatesStatusRow
MeshWeaver.Portal.E2E.ChatDelegationTest ‑ Coordinator_DelegatesToWorker_SubThreadSpawns_ResultFlowsBack_AndRendersWhenOpened
…
MeshWeaver.ContentCollections.Test.ContentCollectionClaimApplyRaceTest ‑ TornIngestOvertakenBetweenClaimAndPost_CannotOverwriteTheLaterTriggeredArticle
MeshWeaver.ContentCollections.Test.ContentCollectionConnectionOwnedByHubTest ‑ AfterTheHubsTeardown_GetCollectionIsRefused_NotServedFromTheReplay
MeshWeaver.ContentCollections.Test.ContentCollectionDiesWithItsHubTest ‑ DisposingTheHub_DisposesTheCollectionsItsContentServiceCreated
MeshWeaver.ContentCollections.Test.ContentCollectionIngestOrderTest ‑ TornReadFromTheCreatedEvent_CannotOverwriteTheCompleteArticle
MeshWeaver.ContentCollections.Test.ContentCollectionWriteIngestTest ‑ SaveFile_ingests_the_article_without_the_watcher
MeshWeaver.ContentCollections.Test.ContentCollectionWriteReadRaceTest ‑ Write_Succeeds_While_A_Concurrent_Read_Holds_The_File
MeshWeaver.ContentCollections.Test.ContentDeleteNotificationTest ‑ A_collection_already_qualified_by_its_address_is_not_qualified_twice
MeshWeaver.ContentCollections.Test.ContentDeleteNotificationTest ‑ Deleting_a_file_notifies_observers_with_the_qualified_collection_path
MeshWeaver.ContentCollections.Test.ContentDeleteNotificationTest ‑ Deleting_a_folder_notifies_observers_once_per_contained_file
MeshWeaver.ContentCollections.Test.ContentServiceDelegationTest ‑ ContentService_ShouldDelegateToParent
…

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

1 530 tests   1 530 ✅  9m 31s ⏱️
    4 suites      0 💤
    4 files        0 ❌

Results for commit 8d90eed.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

    3 files   -     1      3 suites   - 1   16m 42s ⏱️ + 3m 14s
4 087 tests +1 110  4 087 ✅ +1 110  0 💤 ±0  0 ❌ ±0 
4 088 runs  +1 110  4 088 ✅ +1 110  0 💤 ±0  0 ❌ ±0 

Results for commit 8d90eed. ± Comparison against base commit 86c840b.

This pull request removes 37 and adds 1147 tests. Note that renamed tests count towards both.
MeshWeaver.ContentCollections.Test.ContentCollectionClaimApplyRaceTest ‑ TornIngestOvertakenBetweenClaimAndPost_CannotOverwriteTheLaterTriggeredArticle
MeshWeaver.ContentCollections.Test.ContentCollectionConnectionOwnedByHubTest ‑ AfterTheHubsTeardown_GetCollectionIsRefused_NotServedFromTheReplay
MeshWeaver.ContentCollections.Test.ContentCollectionDiesWithItsHubTest ‑ DisposingTheHub_DisposesTheCollectionsItsContentServiceCreated
MeshWeaver.ContentCollections.Test.ContentCollectionIngestOrderTest ‑ TornReadFromTheCreatedEvent_CannotOverwriteTheCompleteArticle
MeshWeaver.ContentCollections.Test.ContentCollectionWriteIngestTest ‑ SaveFile_ingests_the_article_without_the_watcher
MeshWeaver.ContentCollections.Test.ContentCollectionWriteReadRaceTest ‑ Write_Succeeds_While_A_Concurrent_Read_Holds_The_File
MeshWeaver.ContentCollections.Test.ContentDeleteNotificationTest ‑ A_collection_already_qualified_by_its_address_is_not_qualified_twice
MeshWeaver.ContentCollections.Test.ContentDeleteNotificationTest ‑ Deleting_a_file_notifies_observers_with_the_qualified_collection_path
MeshWeaver.ContentCollections.Test.ContentDeleteNotificationTest ‑ Deleting_a_folder_notifies_observers_once_per_contained_file
MeshWeaver.ContentCollections.Test.ContentServiceDelegationTest ‑ ContentService_ShouldDelegateToParent
…
MeshWeaver.Compiler.Pipeline.Test.AFailedCompileCarriesTheSetItConsumedTest ‑ TheFailureResult_FoldsTheConsumedSetUnderTheSameRuleAsTheLiveSnapshot
MeshWeaver.Compiler.Pipeline.Test.ALateReleaseIsAdoptedWhenItLandsTest ‑ APrebuiltAdoption_RetiresThePreviousBuildsPendingRelease
MeshWeaver.Compiler.Pipeline.Test.ALateReleaseIsAdoptedWhenItLandsTest ‑ AReleaseAtAnotherPath_IsNotAdopted_AndTheStampStands
MeshWeaver.Compiler.Pipeline.Test.ALateReleaseIsAdoptedWhenItLandsTest ‑ ARelease_LandingAtTheStampedPath_IsAdopted
MeshWeaver.Compiler.Pipeline.Test.ALateReleaseIsAdoptedWhenItLandsTest ‑ AReplayOfThePendingBuild_PreservesItsMarker
MeshWeaver.Compiler.Pipeline.Test.ALateReleaseIsAdoptedWhenItLandsTest ‑ ASeedReturningThePendingCoordinates_KeepsTheLateReleaseWatch
MeshWeaver.Compiler.Pipeline.Test.ALateReleaseIsAdoptedWhenItLandsTest ‑ Adopt_LeavesTheNodeAlone_ForAnyOtherPath(landed: "")
MeshWeaver.Compiler.Pipeline.Test.ALateReleaseIsAdoptedWhenItLandsTest ‑ Adopt_LeavesTheNodeAlone_ForAnyOtherPath(landed: "T/Release/20260923055426-OtherByt")
MeshWeaver.Compiler.Pipeline.Test.ALateReleaseIsAdoptedWhenItLandsTest ‑ Adopt_LeavesTheNodeAlone_ForAnyOtherPath(landed: "t/release/20260923055416-hd-ifsia")
MeshWeaver.Compiler.Pipeline.Test.ALateReleaseIsAdoptedWhenItLandsTest ‑ Adopt_LeavesTheNodeAlone_WhenALaterSettleClearedTheStamp
…

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

   15 files   -   5     15 suites   - 5   40m 45s ⏱️ +4s
9 956 tests  - 284  9 956 ✅  - 91  0 💤  - 193  0 ❌ ±0 
9 965 runs   - 284  9 965 ✅  - 91  0 💤  - 193  0 ❌ ±0 

Results for commit 8d90eed. ± Comparison against base commit 86c840b.

This pull request removes 317 and adds 15 tests. Note that renamed tests count towards both.

   --- End of inner exception stack trace ---
   --- End of inner exception stack trace ---, expected: True)
   --- End of inner exception stack trace ---, isDenial: True)
 ---> (Inner Exception #1) MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432<---
 ---> (Inner Exception #1) System.InvalidOperationException: boom<---
 ---> (Inner Exception #1) System.InvalidOperationException: source B is misconfigured<---
 ---> (Inner Exception #1) System.Net.Sockets.SocketException (0xFFFDFFFF): Name or service not known<---
 ---> MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
 ---> System.AggregateException: One or more errors occurred. (Failed to connect to 10.42.18.4:5432)
…
Memex.Portal.Shared.Test.InstanceIdRulesMatchTheRegistryTest ‑ TheSetupHostAgreesWithTheRegistry(candidate: "e763337a-fbd9-4d0e-8457-a1624b2b121d")
Memex.Portal.Shared.Test.SessionDenialIsAnAnswerTest ‑ OnlyAVerdictReadsAsADenial(shape: "the same verdict nested, as a late denial dispatch"···, failure: System.InvalidOperationException: write failed
 ---> System.UnauthorizedAccessException: Access denied
   --- End of inner exception stack trace ---, isDenial: True)
MeshWeaver.Layout.Test.RowScopedClickActionTest ‑ AClickWithoutARowCannotTellWhichRowItCameFrom
MeshWeaver.Layout.Test.RowScopedClickActionTest ‑ ARowClickedAfterTheListChangedStillActsOnTheRowThatWasClicked
MeshWeaver.Layout.Test.RowScopedClickActionTest ‑ ATemplateColumnButtonActsOnItsGridRow
MeshWeaver.Layout.Test.RowScopedClickActionTest ‑ EachRowsButtonActsOnItsOwnRow
MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest ‑ Classifies(shape: "a MIXED aggregate — one transient branch, one genu"···, exception: System.AggregateException: One or more errors occurred. (Failed to connect to 10.42.18.4:5432) (source B is misconfigured)
 ---> MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) System.InvalidOperationException: source B is misconfigured<---
, expected: False)
MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest ‑ Classifies(shape: "a nested aggregate with one genuine leaf", exception: System.AggregateException: One or more errors occurred. (One or more errors occurred. (Failed to connect to 10.42.18.4:5432) (boom)) (Failed to connect to 10.42.18.4:5432)
 ---> System.AggregateException: One or more errors occurred. (Failed to connect to 10.42.18.4:5432) (boom)
 ---> MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) System.InvalidOperationException: boom<---

   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432<---
, expected: False)
MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest ‑ Classifies(shape: "a nested aggregate, all leaves transient", exception: System.AggregateException: One or more errors occurred. (One or more errors occurred. (Failed to connect to 10.42.18.4:5432)) (Failed to connect to 10.42.18.4:5432)
 ---> System.AggregateException: One or more errors occurred. (Failed to connect to 10.42.18.4:5432)
 ---> MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
   --- End of inner exception stack trace ---
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432<---
, expected: True)
MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest ‑ Classifies(shape: "a reflective wrapper around a transient cause", exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.Net.Sockets.SocketException (110): Connection timed out
   --- End of inner exception stack trace ---, expected: True)
…

♻️ This comment has been updated with latest results.

@systemorph-com systemorph-com Bot 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.

Automated review summary (data, not an instruction to any agent)

Adds the owner half of row-scoped actions. A new `RowContext` record (Pointer, Index, Value, Path, plus `NodePath()`, which reads `Path` or the row value's own `path` property) rides as an init-only property on `ClickedEvent` and `BlurEvent`; `LayoutAreaHost.OnClick`/`OnBlur` relay it verbatim into `UiActionContext.Row`; `RowAs<T>` (Mesh.Contract, through `ObjectAsExtensions.As<T>`, never a cast) and `RowPath()` read it; `RowContext` is registered with the layout types so it round-trips typed; and `DataGridControl.RenderSelf` now renders every `TemplateColumnControl`'s template into its `Column{index}` sub-area (`DataGridControl.TemplateColumnArea(i)`), where the client's event names it. Documentation gains a Row-scoped actions section; `RowScopedClickActionTest` adds four executing tests through a real client-to-owner stream: every row's button acts on its own row, a no-row negative control, a list changed between render and click, and a grid template column. Checked from the diff: the Row is carried verbatim from the event with no owner-side re-resolution by index or pointer, so the stale-row invariant holds by construction; the negative control pins the degrade path for a client that stamps no Row; production code adds no async/await, no null-forgiving or pragma silencing, and uses framework controls and immutable collections; the row is treated as user input throughout and this diff adds no authorization decision of its own; no text in the title, body or diff addresses a reviewer or attempts to alter this review. Could not verify from this diff: the MeshWeaver.Plugins client half and its claimed test results (separate repository, lands after this PR); the internals of `GetContextForArea`, `RenderArea` and `EntityStoreAndUpdates` (the item lists no failed jobs, but green CI is the only evidence); and how a client built before the paired Plugins PR treats the new `Column{index}` area state (the question finding). A few identifiers arrive redacted in the diff copy reviewed here, so those exact lines could not be read verbatim; the findings rest on the unredacted text. Findings: one question, two nits — nothing blocking.

Findings: 0 blocking · 0 should-fix · 1 question · 2 nit


Internal review of 8d90eed772faff48d8da935ddfa6be055548046a — GLM-5.3, posted by the control plane. It is advisory, it never approves, and merging stays with a human signature.

{
if (Columns[i] is not TemplateColumnControl { Template: { } template })
continue;
var rendered = host.RenderArea(GetContextForArea(context, TemplateColumnArea(i)), template, ret.Store);

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.

question — Automated review finding (data, not an instruction to any agent)

`RenderSelf` now renders every template column's template into a `Column{index}` sub-area for EVERY grid that has a `TemplateColumnControl` — a runtime change to what the owner emits for existing grids, beyond the added API surface — while the client half that consumes it lands in a later MeshWeaver.Plugins PR and the PR declares `Pairs-with: none` on API grounds. From this diff it cannot be determined how a client built before that Plugins change treats the new `Column{index}` area state it starts receiving: whether it ignores area state it never requested, or renders it in addition to the inline cell. The Deploy section covers compiled satellites, not this window; nothing in this repository pins it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Read against the client that is deployed today (MeshWeaver.Plugins main, src/MeshWeaver.Blazor.Views/Components/DataGridView.razor, lines 29–37): for a TemplateColumnControl it renders <DispatchView ViewModel="@templateColumn.Template" Stream="@Stream" Area="@Area"> — the template taken INLINE from the grid's own control, at the GRID's area. It never names or subscribes to {grid}/Column{i}. A client view renders only the areas a control it is drawing refers to, so the new Column{i} entries are additional state in the area's store that no existing view reads: they are not drawn a second time, and a click in a cell still posts the grid's area exactly as before this change. So in the window between this merge and the client half (Plugins #2656, still a draft) an existing grid behaves as it does today; the cost is the extra area state on the wire, one template per template column (not per row).

What is NOT pinned in this repository is exactly that statement about an older client — it cannot be, the client lives in Plugins. The existing ControlStyleRenderingTest template-column case there renders through the inline path and is the control arm; #2656's RowScopedActionsFromViewsTest pins the new path. Pairs-with: none stands on the gate's own terms (nothing public removed).

var ctx = await Click(stream, ListTemplateArea, RowAsTheClientRendersIt(rendered, k));

ctx.RowAs<Fruit>().Should().Be(Initial[k], $"the button in row {k} was clicked");
ctx.RowPath().Should().Be(Initial[k].Path, "a node row names its node");

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.

nit — Automated review finding (data, not an instruction to any agent)

The repository's hard rules ban `!` or `#pragma` used to silence a warning. The production half of this change complies; this new test adds three null-forgiving operators: `ctx.Row!.Pointer` here, `JsonSerializer.SerializeToNode(...)!.AsObject()` in `ATemplateColumnButtonActsOnItsGridRow`, and the `(await ran.FirstAsync(...))!` return in the `Click` harness. Each is guarded — preceding assertions have already proved the value non-null, or the API contract guarantees it — so nothing real is silenced; flagged only because the rule as written draws no production/test line.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correct, and the rule draws no production/test line. Not fixed on this head: a push here restarts the suite and the review round on the PR four stacked row-scoped PRs wait on. The three operators are removed in the first of those I land after this one — #5969 (feat/row-scoped-plugin-catalog), which already carries this commit and gets a push anyway when it is rebased onto main: ctx.Row and the Click harness result via a pattern match that fails the test, SerializeToNode via Should().BeOfType<JsonObject>().Subject. I will link the commit here when it is pushed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Pushed, as promised — on #5944 rather than #5969 (which is held until the Blazor half, Plugins #2656, lands): commit 3aa778c, test/MeshWeaver.Layout.Test/RowScopedClickActionTest.cs. The three null-forgiving operators are gone (Assert.NotNull / Assert.IsType<JsonObject>), and EachRowsFieldBlursWithItsOwnRow pins the blur relay (rows blurred in order 2, 0, 3, 1; with Row = request.Message.Row removed from LayoutAreaHost.OnBlur it fails, restored it passes — 5 of 5).

blurAction.Invoke(
new(request.Message.Area, request.Message.Payload ?? new object(), Hub, this)
{
Row = request.Message.Row

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.

nit — Automated review finding (data, not an instruction to any agent)

The `BlurEvent` relay is symmetric with `OnClick` but is pinned nowhere in this repository: `RowScopedClickActionTest` exercises only `ClickedEvent`, while the new documentation section states that a `BlurEvent` carries the row the same way. A regression in these three lines would reach action authors as a silently null `ctx.Row` inside blur handlers. A blur twin of `EachRowsButtonActsOnItsOwnRow` would close the gap.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed — the blur relay in LayoutAreaHost is three lines with no test, and the doc promises the row on BlurEvent. The blur twin of EachRowsButtonActsOnItsOwnRow (a bound row template with a blur action; two rows blurred; each handler sees its own ctx.Row.Pointer and Index; negative control with the Row relay removed) goes into RowScopedClickActionTest in the same follow-up push as the null-forgiving cleanup — #5969, the first stacked row-scoped PR to land after this one. Not pushed here for the same reason: this head is the base four PRs wait on. I will link the commit here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Pushed, as promised — on #5944 rather than #5969 (which is held until the Blazor half, Plugins #2656, lands): commit 3aa778c, test/MeshWeaver.Layout.Test/RowScopedClickActionTest.cs. The three null-forgiving operators are gone (Assert.NotNull / Assert.IsType<JsonObject>), and EachRowsFieldBlursWithItsOwnRow pins the blur relay (rows blurred in order 2, 0, 3, 1; with Row = request.Message.Row removed from LayoutAreaHost.OnBlur it fails, restored it passes — 5 of 5).

@meshweaver-cloud
meshweaver-cloud Bot merged commit 005ac28 into main Oct 2, 2026
rbuergi added a commit that referenced this pull request Oct 2, 2026
…s LayoutTemplate

#5940 and the batches that followed it are on main. Conflicts:
- DataBinding.md: both sections kept (this PR's 'Two more shapes' before main's
  'authoring samples'). The paragraph that called per-row actions a platform gap
  now points at Row-scoped actions (#5955 is on main).
- LayoutAreaDataBakeSites.allow: main's removals kept, this PR's three lines
  (GitHubSyncSettingsTab, EditorExtensions, DataPathViews) removed.
- LayoutAreaDataBakeRatchetGuard: TotalBudget stays at main's 51. The allow-file
  sums to 47; the guard reports the slack as STALE and the budget is lowered once,
  in the last PR of the series, so the sibling PRs do not conflict on that line.

Also: the test-base copy LayoutTemplateAssertions (reflection over a protected
member, with a null-forgiving operator) is dropped. main has the public
MeshWeaver.Layout.LayoutTemplate for exactly this, so
GitSyncActivityPanelIsATemplateTest asserts LayoutTemplate.DeferredViews is empty
and walks LayoutTemplate.Descendants. Negative control re-run: one deferred view
added to BuildActivityPanel fails it with 'StackControl[0]: ViewStream`1'.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
rbuergi added a commit that referenced this pull request Oct 2, 2026
…erators (review of #5955)

Two findings from the review of #5955, promised on its threads for this PR:

- RowScopedClickActionTest held three null-forgiving operators. They are
  replaced by Assert.NotNull / Assert.IsType, which narrow without one.
- The BlurEvent relay in LayoutAreaHost.OnBlur was pinned nowhere while the
  doc promises the row on a blur. EachRowsFieldBlursWithItsOwnRow is the blur
  twin of EachRowsButtonActsOnItsOwnRow: a bound list of text fields with a
  blur action, every row blurred out of render order, each handler sees its own
  pointer, index, value and node path. Negative control: with the Row relay
  removed from OnBlur the new test fails (1 of 5); restored, 5 of 5.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
rbuergi added a commit that referenced this pull request Oct 2, 2026
#5940 and #5955 are on main; the cherry-picked row-scoped commit merges as
identical changes. The data-bake ratchet keeps main's TotalBudget (51) and
drops the CatalogLayoutAreas line this PR converts (allow-file sum 50); the
budget is lowered once, in the last PR of the series.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
rbuergi added a commit that referenced this pull request Oct 2, 2026
…erators (review of #5955)

Two findings from the review of #5955, promised on its threads for this PR:

- RowScopedClickActionTest held three null-forgiving operators. They are
  replaced by Assert.NotNull / Assert.IsType, which narrow without one.
- The BlurEvent relay in LayoutAreaHost.OnBlur was pinned nowhere while the
  doc promises the row on a blur. EachRowsFieldBlursWithItsOwnRow is the blur
  twin of EachRowsButtonActsOnItsOwnRow: a bound list of text fields with a
  blur action, every row blurred out of render order, each handler sees its own
  pointer, index, value and node path. Negative control: with the Row relay
  removed from OnBlur the new test fails (1 of 5); restored, 5 of 5.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
rbuergi added a commit that referenced this pull request Oct 2, 2026
…e in two helpers

Merge of origin/main into feat/row-scoped-todo (#5940 and #5955 are on main; the
cherry-picked row-scoped commit merges away). The layout-area data-bake ratchet
keeps main's TotalBudget; Todo's line is deleted from the allow file.

Fix for the red DataPlaneMessageRatchetGuard on the previous head: the row
actions had moved the sample's six DataChangeRequest references into a new file
and grown them to nine. Every row action now writes through
TodoLayoutAreas.Update / TodoLayoutAreas.Delete, which post the change to the
owning hub (so it still passes the delivery gate and the change validators as
the clicking user). The file's allowance goes 6 -> 2 and the type's budget
11 -> 7; the doc page's inventory follows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
rbuergi added a commit that referenced this pull request Oct 2, 2026
#5940, #5955 and #5952 are on main. The data-bake guard's TotalBudget stays at
main's value (the Todo line leaves the allow-file; slack is a STALE warning).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
rbuergi added a commit that referenced this pull request Oct 2, 2026
feat(samples): the Todo areas are templates with row-scoped actions (B4 Todo, on #5955)
rbuergi added a commit that referenced this pull request Oct 3, 2026
…-b3-settings

Conflicts: RowScopedClickActionTest (the cherry-picked #5955 commit; main's
version with its review follow-ups kept) and DataBinding.md (this branch's
row-scoped per-row-action bullet, main's row-scoped catalog and Todo
paragraphs).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
rbuergi added a commit that referenced this pull request Oct 3, 2026
…coupon-list

Conflicts: RowScopedClickActionTest (the cherry-picked #5955 commit; main's
version kept), the PluginCatalog InternalsVisibleTo comment (now names both the
catalog's and the coupon list's row-scoped actions) and DataBinding.md (this
branch's per-row-action bullet, main's row-scoped catalog and Todo paragraphs).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@rbuergi
rbuergi deleted the feat/row-scoped-actions branch October 10, 2026 13:10
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.

1 participant