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.
185 lines
8.0 KiB
Markdown
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.
|