From 552c9e406554f49b2c129baebe0d5512bca05200 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 12 May 2026 08:59:19 -0400 Subject: [PATCH] docs(templates): record phase 4-9 completion + verification TODOs All nine derive-on-compose phases are now implemented. The status doc captures what shipped per phase, what was deferred (LockedInDerived override warning toast, SCADA008 base-Parent hint), and the live-DB / UI smoke checks worth running before merge. --- .../2026-05-12-derive-on-compose-status.md | 196 +++++++----------- 1 file changed, 75 insertions(+), 121 deletions(-) diff --git a/docs/plans/2026-05-12-derive-on-compose-status.md b/docs/plans/2026-05-12-derive-on-compose-status.md index 64c1b92..3871cd0 100644 --- a/docs/plans/2026-05-12-derive-on-compose-status.md +++ b/docs/plans/2026-05-12-derive-on-compose-status.md @@ -1,26 +1,23 @@ -# Derive-on-compose: implementation status + remaining plan +# Derive-on-compose: implementation status -> **For Claude resuming after compaction:** This is the single starting point. -> Read it in full. Then read the companion design doc -> `2026-05-12-derive-on-compose-design.md` for the architectural rationale. -> Do NOT make code changes until you've also confirmed which phase the user -> wants next. +> **For Claude resuming later:** All nine phases are implemented. This +> file is the change-record for the work, not a plan. See the companion +> design doc `2026-05-12-derive-on-compose-design.md` for rationale. ## Where we are **Branch**: `feature/templates-folder-hierarchy`. -**Last commit on this feature**: `03a8c4a` — *Phase 3 complete, data -migration in place*. +**Last commit on this feature**: `a965d4a` — *Phase 9 complete, +single-parent editor context*. -**Phases 1–3 done**. Compose now derives; existing compositions migrate -on next `dotnet ef database update`. Everything from Phase 4 onward is -still pending. +**All nine phases done**. Live verification against SQL Server (phase-3 +migration shape) and a UI smoke test are still recommended before merge. **All test suites currently green**: - `tests/ScadaLink.CentralUI.Tests` — 159 passing - `tests/ScadaLink.SiteRuntime.Tests` — 129 passing -- `tests/ScadaLink.TemplateEngine.Tests` — 205 passing (+6 phase 2 tests) +- `tests/ScadaLink.TemplateEngine.Tests` — 212 passing (+13 derive-on-compose tests) ## Design decisions already made (from the brainstorm) @@ -89,138 +86,95 @@ Files touched in `5615f3d`: **No behavior changes**. New fields are never read or written yet. -## Remaining — phases 4 through 9 +## Done — Phase 4+5: Flattening + lock enforcement -### Phase 4: Inherit/override resolution in flattening +Commit: `f599809`. -`src/ScadaLink.TemplateEngine/Flattening/FlatteningService.cs`: +- `FlatteningService.ResolveInheritedAttributes` / `ResolveInheritedScripts` + treat `IsInherited=true` rows as placeholders that don't shadow the + resolved base value. Override (`IsInherited=false`) wins as before. +- `ValidateLockedInDerived` runs once per chain (main + every composed + chain) and returns a flatten-time failure if a derived row overrides + a `LockedInDerived` base member. +- `TemplateService.UpdateAttributeAsync` / `UpdateScriptAsync` reject + derived-side overrides of `LockedInDerived` base members, and now + persist `IsInherited` (on derived) / `LockedInDerived` (on base) from + the proposed payload so the UI can drive override state. -- The service currently walks `Template.ParentTemplateId` chains in - `ResolveInheritedScripts`. That same chain now naturally includes the - base→derived link. No special handling needed for the chain itself. -- BUT when a derived template's attribute is `IsInherited = true`, the - effective value comes from the base (resolved through the chain). If - `IsInherited = false`, the derived's own value is the override and wins. -- Treat `LockedInDerived` as an enforcement check: if a derived row has - `IsInherited = false` (override) AND the base row has `LockedInDerived = - true`, emit a flattening validation error. +## Done — Phase 6: Template tree hides derived -Tests: round-trip flattening with overridden + inherited combinations. +Commit: `f05b03f` (combined with phases 7+8). -### Phase 5: Lock semantics enforcement +`Templates.razor` filters `t.IsDerived` from the main tree. A "Show +derived" form-switch in the page header flips the filter — derived +templates surface in the flat list so users can still reach them. -In `TemplateService.UpdateAttributeAsync` and `UpdateScriptAsync`: +## Done — Phase 7+8: Derived/base TemplateEdit UI -- If the target template has `IsDerived = true`, look up the base via - `ParentTemplateId`. -- If the same-named attribute on the base has `LockedInDerived = true`, - reject the update with a clear error: *"This attribute is locked by the - base template '$Sensor' and cannot be overridden."* -- If the update is being applied on a base template AND there are - derivatives that have `IsInherited = false` for this field (overrides), the - caller may want to know — surface a warning result. Out of scope for now; - flag as TODO. +Commit: `f05b03f`. -Tests: lock-blocked updates fail with the expected message. +- Derived banner: links to base + slot owner / instance name from + `OwnerCompositionId`. +- Attributes / Scripts tables grew a context-aware column: + * Derived: Source badge (Inherited / Override / Local), plus a + "🔒 Base-locked" badge when `LockedInDerived`. + * Base: a form-switch that flips `LockedInDerived` through + `UpdateAttribute` / `UpdateScript`. +- Effective Value / Code resolves from the base when the derived row + carries an inherited (potentially stale) copy — matches the runtime + flatten behavior so the UI doesn't lie. +- Override and Revert-to-base actions live on the row kebab. Delete is + hidden on inherited rows (the base owns those). +- "When a base toggles LockedInDerived while derivatives override the + field, warn via toast" is NOT implemented — kept out of scope; flatten + validation already surfaces it at deploy time. -### Phase 6: Template tree UI — hide derived +## Done — Phase 9: Single-parent editor context -`src/ScadaLink.CentralUI/Components/Pages/Design/Templates.razor`: +Commit: `a965d4a`. -- Filter `_templates` to exclude `t.IsDerived` by default. -- Add a "Show derived templates" checkbox at the top. -- When shown, render derived templates indented under their base - (`t.ParentTemplateId == base.Id && t.IsDerived`). -- Compositions tab on `TemplateEdit` should link each composition row to the - derived template's edit page (the existing link likely points to the base - — needs to follow `composition.ComposedTemplateId` which after phase 3 is - the derived). +- `BuildParentContextsAsync` resolves the editor's `Parent.*` context + to exactly one entry for derived templates (via `OwnerCompositionId`) + and to an empty list for base templates. +- Multi-parent `