Skip to content

feat(samples): the Todo areas are templates with row-scoped actions (B4 Todo, on #5955) - #5973

Merged
rbuergi merged 5 commits into
mainfrom
feat/row-scoped-todo
Oct 2, 2026
Merged

rbuergi merged 5 commits into
mainfrom
feat/row-scoped-todo

Conversation

@rbuergi

@rbuergi rbuergi commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Row-scoped conversion of samples/Todo (on top of #5955): all seven Todo areas — the B4 per-row-action areas #5955 lists — are templates whose per-row buttons carry their row.

Stacked: base is feat/databinding-scan-guard (#5940), and the branch carries #5955's commit cherry-picked so it builds. Draft until #5955 and #5940 land on main; then rebase onto main (the cherry-picked commit drops out). The Blazor half (Systemorph/MeshWeaver.Plugins#2656) must be live for a browser to send the row; until then a click acts on nothing (the negative-control behaviour).

What was wrong

AllItems, TodosByCategory, Summary, Planning, MyTasks, Backlog, TodaysFocus each subscribed Workspace.GetStream<TodoItem>() on the hub and only then built a LayoutGrid with one menu per todo, the todo captured in its closure — a spinner until the data arrived, and a ratchet count of 7.

What changed

  • Each area returns ONE page at once (TodoLayoutAreas.Page): title, "Add New Todo", one bound list (BindMany → /data/todoEntries). A pure projection per area (TodoProjections) turns the todos into rows of one record, TodoEntry: a heading (section title, summary line, empty state; optionally a group action like "Start All") or a todo (card + status-dependent action menu, or the assignment menu). Parts a row lacks — and secondary actions its status does not offer — are hidden by bound styles, so the template's shape never depends on data.
  • Every button is declared once and is row-scoped (TodoRowActions): ctx.RowAs<TodoEntry>() → the row's Item, or the heading's Group exactly as rendered. What the closure captured now travels with the click; a click with no row acts on nothing; an unknown group action is logged, never defaulted.
  • Writes stay DataChangeRequests to the hub that owns the data source — todos are data-source entities, not mesh nodes, so GetMeshNodeStream(...).Update does not apply.
  • Removed hand-built HTML: the unbalanced <details> sections and the reminder <textarea> (now a markdown quote — the edit was never read anyway). Behaviour change: sections are no longer (pseudo-)collapsible.
  • i18n: the sample had no localization; every string is now a todo.* key in en + de (86 keys).
  • Ratchet: Todo's line removed from LayoutAreaDataBakeSites.allow, TotalBudget 73 → 66.
  • Doc: GUI/DataBinding → "Row-scoped actions" gets the Todo sample as the worked example (sections, group actions, status-dependent menus).

Evidence

  • New test/MeshWeaver.Layout.Test/TodoRowScopedActionsTest (11/11): every area returns a template with no deferred view and ONE bound list; the page and row template render with a feed that never answers; row k's Start starts todo k (every k, cumulatively, no other todo touched); Delete after the list changed still deletes the clicked todo, not the one in its slot; Assign member j in row k assigns todo k (every k, Backlog); a heading's Start All acts on its group only; negative control — clicks with no row change nothing; per-status secondary-action visibility.
  • Mutation control: making the primary action ignore the row (act on todo a) turns ThePrimaryActionInRowK_StartsTodoK red at row 1.
  • Full MeshWeaver.Layout.Test 555/555; LocalizationTest 66/66; LayoutAreaDataBakeRatchetGuard green.
  • Release -warnaserror clean: MeshWeaver.Todo, MeshWeaver.Layout.Test, MeshWeaver.Documentation.Test, MeshWeaver.Messaging.Hub.Test.
  • MeshWeaver.Layout.Test now references samples/Todo, so the sample is compiled in core CI (it was not before).
  • NOT established: a live render in a browser.

Deploy

Compiled sample code; no node recycle.

Pairs-with: none — no public type or member removed from src/ (the sample's area methods change return type, samples/ only). The Plugins test adaptation, Systemorph/MeshWeaver.Plugins#2662, lands AFTER this, since it compiles against it.
Implementers: none — no interface member added.
Mirror-sync: tracked on Systemorph/MeshWeaver.Plugins#2662 — after merge run npm run sync:i18n -- --ref <merged core sha> (86 new todo.* keys; no existing value re-worded).

🤖 Generated with Claude Code

rbuergi and others added 2 commits October 1, 2026 20:07
…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>
All seven Todo areas (AllItems, TodosByCategory, Summary, Planning, MyTasks, Backlog,
TodaysFocus) waited for the todos on the hub and baked one menu per todo, the todo captured
in each closure. Each is now ONE page rendered at once — title, "Add New Todo", one bound
list — fed by a pure projection (TodoProjections) into rows of one record, TodoEntry: a
heading (section title, summary line, empty state; optionally a group action) or a todo
(card + action menu, or the assignment menu). Parts a row lacks, and secondary actions its
status does not offer, are hidden by bound styles, so the template never depends on data.

Every button is declared once and is ROW-SCOPED (#5955): TodoRowActions reads
ctx.RowAs<TodoEntry>() and acts on the row's Item, or on its Group exactly as rendered — what
the closure captured now travels with the click. Writes stay DataChangeRequests to the hub
owning the data source (todos are data-source entities, not mesh nodes). The hand-built
<details> HTML and the HTML textarea are gone; every string is localized (todo.* en+de).

- TodoRowScopedActionsTest (11): every area is a template; the page renders with a feed that
  never answers; row k's Start / Delete / Assign act on todo k for every k, also after the
  list changed; a heading's group action acts on its group; no-row negative control; the
  projection's per-status actions. Mutation control: an action that ignores the row goes red.
- Ratchet: Todo's 7 bake units removed; TotalBudget 73 -> 66.
- DataBinding.md: the Todo sample as the worked example of row-scoped templates.

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 5s ⏱️ +23s
348 tests ±0  348 ✅ ±0  0 💤 ±0  0 ❌ ±0 
352 runs  ±0  352 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 32e64a7. ± Comparison against base commit 3681cf0.

♻️ 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)

    3 files  ±  0      3 suites  ±0   5m 50s ⏱️ + 1m 6s
2 432 tests +528  2 432 ✅ +719  0 💤  - 191  0 ❌ ±0 
2 433 runs  +529  2 433 ✅ +720  0 💤  - 191  0 ❌ ±0 

Results for commit 32e64a7. ± Comparison against base commit 3681cf0.

♻️ 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)

2 685 tests  +1 561   2 685 ✅ +1 561   12m 40s ⏱️ + 5m 37s
    4 suites ±    0       0 💤 ±    0 
    4 files   ±    0       0 ❌ ±    0 

Results for commit 32e64a7. ± Comparison against base commit 3681cf0.

♻️ 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)

    4 files  +  1      4 suites  +1   3m 18s ⏱️ - 3m 44s
1 095 tests +527  1 095 ✅ +527  0 💤 ±0  0 ❌ ±0 
1 098 runs  +530  1 098 ✅ +530  0 💤 ±0  0 ❌ ±0 

Results for commit 32e64a7. ± Comparison against base commit 3681cf0.

♻️ 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)

  4 files  ±    0    4 suites  ±0   5m 31s ⏱️ - 2m 27s
721 tests  - 2 919  528 ✅  - 3 110  193 💤 +191  0 ❌ ±0 
722 runs   - 2 919  529 ✅  - 3 110  193 💤 +191  0 ❌ ±0 

Results for commit 32e64a7. ± Comparison against base commit 3681cf0.

♻️ 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)

    4 files  +  1      4 suites  +1   9m 36s ⏱️ +53s
3 079 tests +467  3 079 ✅ +467  0 💤 ±0  0 ❌ ±0 
3 080 runs  +464  3 080 ✅ +464  0 💤 ±0  0 ❌ ±0 

Results for commit 32e64a7. ± Comparison against base commit 3681cf0.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

    20 files  +  2      20 suites  +2   40m 3s ⏱️ + 1m 48s
10 360 tests +164  10 167 ✅ +164  193 💤 ±0  0 ❌ ±0 
10 370 runs  +165  10 177 ✅ +165  193 💤 ±0  0 ❌ ±0 

Results for commit 32e64a7. ± Comparison against base commit 3681cf0.

♻️ This comment has been updated with latest results.

rbuergi and others added 3 commits October 2, 2026 14:56
…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>
#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>
…e or its test

The five dialog closes pass null (UpdateArea takes object?); the test reads its
rows, areas and streams through an asserting helper and pattern matches.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@rbuergi
rbuergi changed the base branch from feat/databinding-scan-guard to main October 2, 2026 17:14
@rbuergi
rbuergi marked this pull request as ready for review October 2, 2026 17:14
@rbuergi
rbuergi enabled auto-merge October 2, 2026 17:14

@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)

Row-scoped conversion of the seven `samples/Todo` areas: each is now one template (title, `Add New Todo`, one bound list) fed by a pure projection (`TodoProjections`) into rows of a new `TodoEntry` record, with every button declared once and acting on the row it was raised from (`TodoRowActions`, `ctx.RowAs<TodoEntry>()`), replacing the old per-todo baked menus that captured the todo in a closure. Also adds 86 `todo.*` keys in en and de, doc updates, ratchet updates (DataChangeRequest 11→7 in the allow file, the guard constant and the doc table, all mutually consistent; the Todo sample's 7 LayoutAreaDataBake sites removed), and an executing test suite (`TodoRowScopedActionsTest`) that pins template-first rendering, that row k's Start/Delete/Assign acts on todo k — also after the list changed — a heading's group action on its rendered group, and a no-row negative control; `MeshWeaver.Layout.Test` now references the sample, so it compiles in core CI. ⚠️ The diff is INCOMPLETE: `TodoLayoutAreas.cs` keeps only its first 20000 of 97506 characters and `TodoProjections.cs` its first 20000 of 21409 — `Page`, `Feed`, the `Update`/`Delete` write helpers, `TodoViewText`, `Typography` and the row template itself were NOT read; their behaviour is pinned by the new tests but their source is unverified, and nothing is asserted about what was not read. The LayoutAreaDataBake budget claim (73→66) is likewise not visible in the diff. Checked in the visible code: grouping and status logic, localization key parity (en and de define the same 86 keys, and every key the visible code references exists), immutable collections, no `!` or `#pragma`, no hand-built HTML (the old `details`/`textarea` markup is removed), and no prompt injection in the title, body or comments. As the PR itself discloses, the Blazor half (MeshWeaver.Plugins#2656) must land before a browser sends the row; until then a click acts on nothing. Findings: two status-filter inconsistencies in `Planning` (should-fix, nit); no blocking issue in what was read.

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

⚠️ 🚨 The diff is INCOMPLETE: 2 patch(es) truncated and 0 omitted (budget 150000 characters, 20000 per file) — say so in the review summary and do not assert anything about what you could not read.


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

GroupIcon = "user-plus",
GroupAction = TodoGroupActions.AssignFirst,
GroupStyle = TodoStyles.Menu,
Group = [.. unassigned],

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.

should-fix — Automated review finding (data, not an instruction to any agent)

`Planning`'s per-member "Assign" button (`TodoGroupActions.AssignFirst`) carries `Group = [.. unassigned]` — all unassigned todos regardless of status, in input order — and `TodoRowActions.OnGroup` then assigns `row.Group[0]`. A Completed or Cancelled unassigned todo can therefore be assigned to a team member, and the button is offered whenever any unassigned todo exists, even when none is open. That contradicts the method's own contract ("assign the next unassigned task") and the backlog section in the same method, which filters to open todos for both its rows and its group action. Setting the member rows' `Group` to the open unassigned todos, ordered as the backlog rows are, makes the button do what its label says.

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. TodoGroupActions.AssignFirst is handed Group = [.. unassigned] with no status filter, and TodoRowActions.OnGroup assigns row.Group[0], so a Completed or Cancelled unassigned todo can be the one assigned, and the button shows when nothing open is left. It contradicts both the method's contract and the backlog section beside it. It is sample behaviour (no access or data-loss path: the write is the same checked change as before, on a todo the user could already edit), so it is fixed FORWARD rather than with another head on this PR: a follow-up core PR, opened as soon as this one is on main, sets the member rows' Group to the open unassigned todos in backlog order and pins it in TodoRowScopedActionsTest (an unassigned Completed todo is never the one assigned; no Assign button when no open unassigned todo exists). I will link it 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.

The follow-up is open: #6004 (commit 3ea2992). TodoProjections.IsOpen / OpenUnassigned are the one definition Planning, Backlog and TodaysFocus share; a member's Assign group and the planning backlog are the open unassigned todos, and the title counts them. Six new cases in TodoRowScopedActionsTest (17/17), each with a negative control listed in the PR body.

if (unassigned.Count == 0)
yield break;

var open = unassigned.Where(t => t.Status != TodoStatus.Completed).ToList();

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)

`Planning`'s `open` filters out only `TodoStatus.Completed`, not `Cancelled` — unlike `Backlog` and `TodaysFocus`, which exclude both. Cancelled todos can therefore appear in the planning backlog and be handed out by its Auto-Assign. The backlog section's title also counts all unassigned todos while its group action and listed rows cover only open ones, so the number shown and the list below it can disagree.

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.

Also correct, both halves: Planning's open drops only Completed while Backlog and TodaysFocus drop Cancelled too, and the backlog section's title counts all unassigned todos while its rows and group action cover the open ones. Same follow-up PR as the neighbouring thread: one shared 'open' predicate for the three areas (neither Completed nor Cancelled), the title counting what the list shows, and a test with a Cancelled unassigned todo that must appear in neither the planning backlog nor the Auto-Assign group.

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.

The follow-up is open: #6004 (commit 3ea2992). TodoProjections.IsOpen / OpenUnassigned are the one definition Planning, Backlog and TodaysFocus share; a member's Assign group and the planning backlog are the open unassigned todos, and the title counts them. Six new cases in TodoRowScopedActionsTest (17/17), each with a negative control listed in the PR body.

@rbuergi
rbuergi merged commit 5b8ff93 into main Oct 2, 2026
rbuergi added a commit that referenced this pull request Oct 2, 2026
#5945 and #5973 are on main. The data-bake allow-file is the combined state:
NodeTypeLayoutAreas at main's 5 (#5945); the MeshNodeLayoutAreas line stays
removed, as this PR converts its last unit.

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

The conversions merged today (#5946, #5951, #5953, #5952, #5945, #5973) and this
one each lowered their allow-file lines while leaving TotalBudget at main's value,
so that sibling PRs did not conflict on the constant at every landing. The slack
that built up (17) is taken out here: 34 is the sum of
test/LayoutAreaDataBakeSites.allow on the merged tree.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
rbuergi added a commit that referenced this pull request Oct 2, 2026
fix(todo sample), test(compiler): review follow-ups of #5973 and #5975 — planning hands out open work only; statement-shaped identity write pinned
@rbuergi
rbuergi deleted the feat/row-scoped-todo 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