diff --git a/docs/plans/2026-08-21-template-picker.md b/docs/plans/2026-08-21-template-picker.md index f17127b5..3ad8d826 100644 --- a/docs/plans/2026-08-21-template-picker.md +++ b/docs/plans/2026-08-21-template-picker.md @@ -732,4 +732,67 @@ Content: one paragraph under the Create-Instance bullet — template chosen thro ## Status -_(filled in by the executing session)_ +**DONE — all 8 tasks complete, 2026-08-21.** Branch `feat/template-picker` (worktree +`~/Desktop/ScadaBridge-template-picker`), 8 commits. + +| # | Task | Result | +|---|------|--------| +| 1 | `TemplatePathFormatter` | done — 4 tests | +| 2 | `TemplateFolderTree.RevealKeys` | done — 3 tests; `TreeViewTests` / `TemplatesPageTests` / `TransportExportPageTests` unaffected | +| 3 | `TemplatePickerDialog` | done — 7 tests | +| 4 | Create page picker field + `?templateId=` | done — 5 tests (first bUnit render of `InstanceCreate`) | +| 5 | Templates tree "New Instance…" | done — 2 tests (Deployer shows, Designer-only hides) | +| 6 | Topology Disable confirm pin | done — 3 tests, **PASS on first run** (no production change; `0db253b5` intact) | +| 7 | Docs | done — `Component-CentralUI.md` (Create-Instance + Templates-page + dialog-label bullets), this design doc → implemented, bindings design §3.2 superseded note | +| 8 | Build + sweep + live rig | **PASS** — see below | + +### Deviations from the plan, and why + +1. **Selection is tracked inside `TemplatePickerDialog`, not through `SelectedKeys`/`SelectedKeysChanged`.** + In `TreeViewSelectionMode.Single` the inner `TreeView` uses the *singular* `SelectedKey` / + `SelectedKeyChanged` pair; `TemplateFolderTree` forwards only the plural (Checkbox-mode) pair, + which `TreeView` reads for tri-state checkbox aggregation. The plan's wiring would therefore + never have highlighted a row and `OnSelectedKeysChanged` would never have fired. The dialog's own + `NodeContent` click/dblclick handlers own selection and the `text-primary` highlight — no change + to the shared tree's public surface. +2. **`[SupplyParameterFromQuery]` cannot be set as an ordinary bUnit parameter** ("only accepts + cascading values"). The Task-4 tests seed `BunitNavigationManager.NavigateTo(".../create?templateId=7")` + instead, matching `Pages/QueryStringDrillInTests.cs`. +3. **`…` in a Razor *attribute* is emitted literally** — the placeholder uses the real `…`. +4. **The Task-3 `Open()` helper needs a block-bodied `InvokeAsync` lambda.** An expression body + returning the pending `Task` binds to `InvokeAsync(Func)` and awaits the dialog itself; + every test hung until it was changed (comment left in the file). + +### Task 8 — build, sweep, live rig gate + +- `dotnet build ZB.MOM.WW.ScadaBridge.slnx -v q` → **0 warnings, 0 errors**. +- `dotnet test tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests` → **1154 / 1154 PASS**. +- Rig: `docker/build.sh` run from the worktree (tags `scadabridge:latest`), then + `docker compose -f docker/docker-compose.yml up -d --force-recreate` from the **primary + checkout** — deploying `deploy.sh` wholesale from a worktree would have rehomed every node's + bind-mounted appsettings to the worktree copy. `/health/ready` Healthy on all 8 nodes. +- Live check at `http://localhost:9000` as `multi-role`, **all PASS**: + 1. `/design/templates` → right-click **Motor Controller** (inside folder `Test`) → the menu shows + **New Instance…** between its own dividers → lands on + `/deployment/instances/create?templateId=4` with the field reading **"Test / Motor Controller"**. + 2. Site = *Test Plant A* → Connection Bindings card appears (30 attributes, 4 groups) — the + `?templateId` preselect does feed `OnTemplateOrSiteChangedAsync`. + 3. **Choose…** → picker opens **expanded to and highlighting** Motor Controller, footer + "Selected: Test / Motor Controller", Select enabled. Search "Tank" narrows to one row. + **Cancel** left the field, the site and the card untouched. + 4. Re-open → **double-click "Tank Monitor"** → dialog closes, field reads "Tank Monitor" + (root template ⇒ no folder prefix), card re-previews to 7 attributes / 2 groups. + 5. `/deployment/instances/create` with no query → field blank with the placeholder, picker opens + **collapsed**, footer "Select a template", **Select disabled**; clicking a *folder* label leaves + it disabled. + 6. `/deployment/topology` → right-click `rig-probe-1` (Enabled) → **Disable** → confirm button reads + **Disable** in red, body "Disable instance 'rig-probe-1'? …". Cancelled; instance still Enabled. + +### Known limitation (pre-existing, not introduced here) + +`TreeView.InitiallyExpanded` is applied **once** (`_initialExpansionApplied`). So in the picker, if a +preselected template reveals on first render and the operator *then* types a search, the filter's own +ancestor-expand does not re-apply — matches under a still-collapsed folder stay hidden until the +folder is opened by hand. Opening the picker with no preselect (the common case) is unaffected, +because nothing consumed the one-shot. Fixing it means resetting `_initialExpansionApplied` when +`InitiallyExpanded` changes identity, which touches every `TreeView` caller — out of scope here. diff --git a/docs/plans/2026-08-21-template-picker.md.tasks.json b/docs/plans/2026-08-21-template-picker.md.tasks.json index 54bbc60d..ca023348 100644 --- a/docs/plans/2026-08-21-template-picker.md.tasks.json +++ b/docs/plans/2026-08-21-template-picker.md.tasks.json @@ -41,7 +41,7 @@ { "id": 7, "subject": "Task 7: Docs", - "status": "pending", + "status": "completed", "blockedBy": [ 4, 5, @@ -51,7 +51,7 @@ { "id": 8, "subject": "Task 8: Solution build + CentralUI test sweep + live rig check", - "status": "pending", + "status": "completed", "blockedBy": [ 7 ]