Commit Graph

13 Commits

Author SHA1 Message Date
Joseph Doherty
d6221419c6 fix(template-engine): resolve TemplateEngine-015,016 — cascade-rename nested derived templates, correct composed-script ParentPath 2026-05-17 03:18:41 -04:00
Joseph Doherty
adb5e75ec3 fix(template-engine): resolve TemplateEngine-011,013,014 — remove dead converter, duplicate-id-safe cycle detection, unified deletion logic; TemplateEngine-012 deferred 2026-05-16 22:32:30 -04:00
Joseph Doherty
305b42ea6d feat(template-engine): resolve TemplateEngine-002 — per-slot alarm override for derived templates
Adds IsInherited/LockedInDerived to the TemplateAlarm entity (mirroring the
attribute/script override model), an EF migration, base-alarm copy-on-derive,
inherited-alarm flattening skip, and LockedInDerived override-rejection validation.
2026-05-16 20:12:24 -04:00
Joseph Doherty
74aae53500 fix(template-engine): resolve TemplateEngine-001/003/004/005, re-triage 002 — recursive composed flattening, fixed-field guard, alarm script refs, dead collision query 2026-05-16 19:57:28 -04:00
Joseph Doherty
57f477fd28 fix(templates): cascade delete through nested derived templates
DeleteCompositionAsync only dropped the top-level derived template — the
cascaded inner derived rows (created when composing a composite source)
were left orphaned with dangling OwnerCompositionId references. Any
subsequent attempt to recompose the same source hit the name-collision
guard ('Motor Controller.Pump.TempSensor' already exists).

New CascadeDeleteDerivedAsync walks each composition on the derived
template, recursively removes the slot-owned child derived first, then
the composition row, then the derived itself. Mirrors the recursive
shape of CreateCascadedCompositionAsync.
2026-05-12 10:34:55 -04:00
Joseph Doherty
4f90f952d0 fix(templates): cascade child compositions when composing a composite
When the user composes a template that already has compositions of its
own (e.g. \$Sensor → Probe1 slot), only the outer derived was created
— the source's children weren't replicated. AddCompositionAsync now
walks the source's composition graph and creates a parallel derived for
every slot it encounters, each linked back through ParentTemplateId so
override chains stay intact (\$Probe → \$Sensor.Probe1 → \$Pump.TempSensor.Probe1).

The cascade pre-flights every name it would create — a deep collision
aborts before any rows mutate. Internal helper
CreateCascadedCompositionAsync skips the "base templates only" check
since it operates on the source side which may legitimately reference
derived rows.
2026-05-12 09:57:07 -04:00
Joseph Doherty
5c3dc79b8a feat(templates/ui): manage compositions from the tree
Move composition CRUD off the TemplateEdit page and onto the tree
context menu, matching Aveva's Template Toolbox flow.

- New ComposeIntoDialog: pick a parent template, slot name (defaults
  to the source template's name).
- "Compose into…" on every base template's context menu (kebab + right
  click) opens the dialog and calls AddCompositionAsync.
- "Rename…" on composition leaves opens a prompt and calls
  TemplateService.RenameCompositionAsync. The owning composition row
  AND its owned derived template are renamed atomically; duplicate
  slot names or derived-name collisions abort with a clear error.
- "Delete" on composition leaves confirms + cascade-deletes the
  composition (and its derived template via DeleteCompositionAsync).
- "New Derived Template" menu item renamed to "New Inheriting Template"
  to disambiguate from the new derive-on-compose meaning.

TemplateEdit's Compositions tab, Add Composition form, and
Add/DeleteComposition handlers + state fields are deleted — the tree
is now the single source of truth.
2026-05-12 09:22:55 -04:00
Joseph Doherty
f599809486 feat(templates): phase 4+5 — inherit/override resolution + lock enforcement
FlatteningService now treats IsInherited rows as placeholders: when a
derived template carries an inherited attribute or script, the live base
value resolves through the ParentTemplateId chain instead of the
(possibly stale) copy. An IsInherited=false row is a real override and
wins as before.

ValidateLockedInDerived runs once per chain (main + composed) and returns
a flatten-time failure if a derived template overrides a base row that
the base marked LockedInDerived.

TemplateService.Update{Attribute,Script}Async reject mid-flight when a
derived target tries to override a LockedInDerived base member, and now
persist IsInherited/LockedInDerived from the proposed payload so the UI
can flip override state or set base-locks via the same endpoints.
2026-05-12 08:50:49 -04:00
Joseph Doherty
fa86750717 feat(templates): phase 2 — derive-on-compose for new compositions
AddCompositionAsync creates a derived Template ("<parent>.<slot>") that
inherits from the base via ParentTemplateId. Base attributes and scripts
are copied with IsInherited=true so the derived template carries its own
override-able rows. The composition row points at the derived template,
and the derived's OwnerCompositionId back-refs the composition for cascade
delete.

DeleteCompositionAsync cascade-deletes the owned derived template.
DeleteTemplateAsync blocks direct deletion of derived templates and
distinguishes derivatives from regular children, listing slot owners
("'Pump' (as 'TempSensor')") in the error.

Composing a derived template is rejected — only bases can be composed.
Existing compositions still resolve until phase 3 migrates them.
2026-05-12 08:27:13 -04:00
Joseph Doherty
b4cb7e6f5f feat(templates): lock ParentTemplateId after creation
Template inheritance is set once at create time and immutable on update.
UpdateTemplateAsync now returns "Parent template cannot be changed after
creation." when the caller sends a parent that differs from the stored
value — server-side enforcement covers UI, ManagementService, and CLI.
TemplateEdit renders the parent as static plaintext rather than an
editable dropdown; TemplateCreate's parent picker is unchanged.
2026-05-11 21:29:21 -04:00
Joseph Doherty
fc105acd7c feat(ui/templates): new-folder, new-template, move-template dialogs 2026-05-11 11:18:36 -04:00
Joseph Doherty
72b9f7e66e feat(template-engine): TemplateService.MoveTemplateAsync 2026-05-11 11:02:03 -04:00
Joseph Doherty
faef2d0de6 Phase 2 WP-1–13+23: Template Engine CRUD, composition, overrides, locking, collision detection, acyclicity
- WP-23: ITemplateEngineRepository full EF Core implementation
- WP-1: Template CRUD with deletion constraints (instances, children, compositions)
- WP-2–4: Attribute, alarm, script definitions with lock flags and override granularity
- WP-5: Shared script CRUD with syntax validation
- WP-6–7: Composition with recursive nesting and canonical naming
- WP-8–11: Override granularity, locking rules, inheritance/composition scope
- WP-12: Naming collision detection on canonical names (recursive)
- WP-13: Graph acyclicity (inheritance + composition cycles)
Core services: TemplateService, SharedScriptService, TemplateResolver,
LockEnforcer, CollisionDetector, CycleDetector. 358 tests pass.
2026-03-16 20:10:34 -04:00