Files
scadalink-design/docs/plans/2026-05-12-derive-on-compose-status.md
Joseph Doherty 552c9e4065 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 08:59:19 -04:00

185 lines
8.0 KiB
Markdown

# Derive-on-compose: implementation status
> **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**: `a965d4a` — *Phase 9 complete,
single-parent editor context*.
**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` — 212 passing (+13 derive-on-compose tests)
## Design decisions already made (from the brainstorm)
User picked the **full Aveva model** with all four customization scopes:
- **Naming**: dot-separated → `Pump.TempSensor`
- **Delete base with derivatives**: block with a list of the dependents
- **Migration of existing compositions**: auto-migrate all on the EF Core
migration step in Phase 3
- **Tree UX**: derived templates hidden by default; toggle to reveal
- **Customization scope**: override attribute values, override script bodies,
add new attrs/scripts per slot, lock fields against override
## Done — Phase 1: Additive schema
Commits: `6854843` (design doc) + `a968cef` (decisions recorded) + `5615f3d`.
## Done — Phase 2: Compose flow change
Commit: `fa86750`.
- `TemplateService.AddCompositionAsync` builds a derived template
(`"<parent>.<slot>"`), copies base attributes/scripts with
`IsInherited=true`, then composes the derived (not the base). Sets
`OwnerCompositionId` back-ref after the composition's Id is known.
- Composing a derived template is rejected — only bases can be composed.
- `DeleteCompositionAsync` cascade-deletes the slot-owned derived
template (`IsDerived=true` and `OwnerCompositionId==compositionId`).
- `DeleteTemplateAsync` blocks direct deletion of derived templates and
splits the inheritor check into regular children vs. derivatives — the
derivative branch labels each by `'OwnerName' (as 'SlotName')`.
- `TemplateDeletionService.CanDeleteTemplateAsync` mirrors the same
derivative-aware checks.
## Done — Phase 3: Migration of existing compositions
Commit: `03a8c4a`. Migration `20260512122746_MigrateCompositionsToDerived`.
- Pre-flight aborts with a descriptive error if any
`<parent>.<slot>` derived name would collide.
- Cursor-walks every `TemplateComposition` whose target is `IsDerived=0`,
inserts a derived template, copies attributes/scripts with
`IsInherited=1`, then repoints `ComposedTemplateId`.
- Idempotent (only touches non-derived targets), so re-runs are safe.
- `Down()` reverses by repointing compositions to `ParentTemplateId` and
dropping the derived templates.
The migration was NOT verified against a live SQL Server in this
session — run `bash docker/deploy.sh` (or `dotnet ef database update`)
once with seeded test data to confirm shape.
Files touched in `5615f3d`:
- `src/ScadaLink.Commons/Entities/Templates/Template.cs`
- Added `IsDerived: bool`
- Added `OwnerCompositionId: int?` (plain int — not an EF nav prop)
- `src/ScadaLink.Commons/Entities/Templates/TemplateAttribute.cs`
- Added `IsInherited: bool`
- Added `LockedInDerived: bool`
- `src/ScadaLink.Commons/Entities/Templates/TemplateScript.cs`
- Same two fields
- `src/ScadaLink.ConfigurationDatabase/Migrations/20260512121446_AddDerivedTemplateFields.cs`
- EF Core migration. Six new columns, all NOT NULL DEFAULT 0 (or nullable
int). No data transform — existing rows get defaults.
- `ScadaLinkDbContextModelSnapshot.cs` regenerated.
**No behavior changes**. New fields are never read or written yet.
## Done — Phase 4+5: Flattening + lock enforcement
Commit: `f599809`.
- `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.
## Done — Phase 6: Template tree hides derived
Commit: `f05b03f` (combined with phases 7+8).
`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.
## Done — Phase 7+8: Derived/base TemplateEdit UI
Commit: `f05b03f`.
- 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.
## Done — Phase 9: Single-parent editor context
Commit: `a965d4a`.
- `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 `<select>` dropdown removed from the Add Script form.
- `_selectedParentIndex` / `OnParentContextChanged` deleted;
`ActiveEditorParent` collapses to `_editorParents.FirstOrDefault()`.
- The SCADA008 hint diagnostic on `Parent.*` use within base templates
was NOT added in this pass — the analyzer simply emits no completions
when the parent context is empty. Add it later if users want a
positive nudge.
## Still to verify
- Apply the Phase-3 migration against a real SQL Server (run
`bash docker/deploy.sh` or `dotnet ef database update`) with seeded
data to confirm `MigrateCompositionsToDerived` produces the right
shape and respects the collision pre-check.
- Smoke-test the UI flows: add a composition, override an attribute,
revert, toggle `LockedInDerived` on a base, edit a script on a
derived template (single-parent context).
## How to resume
A future session should:
1. Read this file and the design doc.
2. Run `git log --oneline -15` to confirm the branch is at `a965d4a` or
later.
3. Run the three test suites named above.
4. Ask the user whether to ship or to address one of the deferred items
("when base toggles LockedInDerived while derivatives override",
SCADA008 base-Parent hint, or the live-DB / UI smoke verifications).
## Quick sanity script
```bash
git status --short # should be clean
git log --oneline -10 # top should include a965d4a
dotnet build src/ScadaLink.CentralUI src/ScadaLink.TemplateEngine src/ScadaLink.ConfigurationDatabase
dotnet test tests/ScadaLink.TemplateEngine.Tests/ScadaLink.TemplateEngine.Tests.csproj
dotnet test tests/ScadaLink.CentralUI.Tests/ScadaLink.CentralUI.Tests.csproj
dotnet test tests/ScadaLink.SiteRuntime.Tests/ScadaLink.SiteRuntime.Tests.csproj
```
Note: the full `dotnet build` of the solution fails with NU1608 in
`ScadaLink.IntegrationTests` and `ScadaLink.Host.Tests` due to a
pre-existing `Microsoft.CodeAnalysis.Common` 4.13 vs 5.0 mismatch — not
related to the derive-on-compose work. Build the three suites listed in
"Where we are" individually.