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.
This commit is contained in:
@@ -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 `<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.
|
||||
|
||||
### Phase 7: Derived TemplateEdit UI
|
||||
## Still to verify
|
||||
|
||||
`src/ScadaLink.CentralUI/Components/Pages/Design/TemplateEdit.razor`:
|
||||
|
||||
- Top banner when `_selectedTemplate.IsDerived`:
|
||||
*"Derived from `[base.Name]` — composed inside `[parent.Name]` as
|
||||
`[composition.InstanceName]`."*
|
||||
- Attributes table: new column showing **Inherited** or **Overridden** badge.
|
||||
- Locked-from-base attributes (base has `LockedInDerived = true`) render
|
||||
readonly with a 🔒 icon and tooltip *"Locked by base — cannot override."*
|
||||
- Editing an inherited row flips it to override (`IsInherited = false`).
|
||||
- "Revert to base" button per row when `IsInherited = false` — clears the
|
||||
override; row reverts to the base value (`IsInherited = true`, value
|
||||
cleared).
|
||||
- Adding a new attribute/script: creates a row with `IsInherited = false`
|
||||
(it's not from the base).
|
||||
- Removing an inherited row should be blocked (the row exists because it's
|
||||
on the base; can't remove it on the derived). Removing an own-added row
|
||||
deletes normally.
|
||||
|
||||
### Phase 8: Base TemplateEdit UI — lock toggle
|
||||
|
||||
Same `TemplateEdit.razor`, when editing a base template:
|
||||
|
||||
- New column on Attributes table: a 🔒 toggle representing `LockedInDerived`.
|
||||
- Same on Scripts table.
|
||||
- Tooltip on the toggle: *"Lock this against per-slot override in derived
|
||||
templates."*
|
||||
|
||||
When a base template is open and the user toggles `LockedInDerived = true`
|
||||
while derived templates exist with overrides, surface a warning toast:
|
||||
*"N derived templates currently override this. Existing overrides won't be
|
||||
silently reverted but deploy validation will flag them."*
|
||||
|
||||
### Phase 9: Editor metadata simplification
|
||||
|
||||
Now that derived templates have a single owner, `BuildParentContextsAsync`
|
||||
in `TemplateEdit.razor` simplifies:
|
||||
|
||||
- **If the open template `IsDerived`**: parent = the single template that
|
||||
owns the `OwnerCompositionId`. Always one — no picker.
|
||||
- **If the open template is a base** (`!IsDerived`): suppress `Parent.*`
|
||||
assistance entirely. Add a SCADA008 hint diagnostic on `Parent.*` use:
|
||||
*"Parent access on a base template is ambiguous — override this script in
|
||||
a derived template instead."*
|
||||
|
||||
Remove the multi-parent `select` dropdown that was added in commit `0139c9c`
|
||||
(or keep it but guard with a check that should never fire after phase 3).
|
||||
|
||||
`GetTemplatesComposingAsync` (added in `0139c9c`) is still useful elsewhere —
|
||||
keep it.
|
||||
- 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
|
||||
|
||||
After compaction, the future session should:
|
||||
A future session should:
|
||||
|
||||
1. Read this file. Read the design doc
|
||||
`2026-05-12-derive-on-compose-design.md` for context.
|
||||
2. Run `git log --oneline -15` to confirm the branch is at `03a8c4a` or
|
||||
1. Read this file and the design doc.
|
||||
2. Run `git log --oneline -15` to confirm the branch is at `a965d4a` or
|
||||
later.
|
||||
3. Check test status: `dotnet test` across the three suites named above.
|
||||
4. Ask the user which phase to tackle next (or proceed from phase 4 if the
|
||||
user has explicitly said "continue").
|
||||
5. Each phase is its own commit.
|
||||
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).
|
||||
|
||||
## Pre-existing capability worth knowing
|
||||
|
||||
The script-scope editor work (commits `3ed05f0` → `0139c9c`) is already in
|
||||
place. Scripts on derived templates will automatically benefit from the
|
||||
single-parent context simplification in phase 9. The runtime accessors
|
||||
(`Attributes` / `Children` / `Parent` / `Scope`) defined there continue to
|
||||
work unchanged — the canonical-name paths they resolve are stable across
|
||||
the derivation change.
|
||||
|
||||
The multi-parent picker introduced in `0139c9c` becomes mostly dormant
|
||||
after phase 3 (no template should be composed by multiple parents anymore
|
||||
through the new flow). Plan to remove it in phase 9.
|
||||
|
||||
## Quick sanity script (run before phase 4)
|
||||
## Quick sanity script
|
||||
|
||||
```bash
|
||||
git status --short # should be clean
|
||||
git log --oneline -5 # top should include 03a8c4a
|
||||
dotnet build src/ScadaLink.TemplateEngine src/ScadaLink.ConfigurationDatabase
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user