docs(plans): instance picker — node shape + orphan rules as shipped; task 1 complete

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
Joseph Doherty
2026-09-08 09:26:41 -04:00
parent 8e6b748029
commit 8f8100c7a8
2 changed files with 3 additions and 3 deletions
@@ -46,7 +46,7 @@ Alternatives rejected:
A parent that changes `Areas`/`Instances` (site change) resets the field text and closes the panel; a parent that changes `SelectedInstanceId` (URL / localStorage restore) updates the field text.
**Pure builder.** `InstanceTreeBuilder.Build(areas, instances, filter)` → `IReadOnlyList<InstanceTreeNode>` (`Components/Shared/InstanceTreeNode.cs`, `InstanceTreeBuilder.cs`; record `InstanceTreeNode(string Key, string Label, Area? Area, Instance? Instance, List<InstanceTreeNode> Children)`, keys `a:{id}` / `i:{id}`, `IsInstance => Instance != null`). Orphaned areas (parent id pointing nowhere) are treated as roots so a bad row can never hide a subtree. `Topology.razor` keeps its own richer `TopoNode` — not refactored (YAGNI).
**Pure builder.** `InstanceTreeBuilder.Build(areas, instances, filter)` → `IReadOnlyList<InstanceTreeNode>` (`Components/Shared/InstanceTreeNode.cs`, `InstanceTreeBuilder.cs`; record `InstanceTreeNode(string Key, string Label, Area? Area, Instance? Instance)` with a read-only `Children` property the builder fills, keys `a:{id}` / `i:{id}`, `IsInstance => Instance != null`). Orphaned areas (parent id pointing nowhere) and orphaned instances (area id pointing nowhere) are treated as roots so a bad row can never hide a subtree; a `ParentAreaId` cycle is unreachable from any root and simply does not render (guarded against looping). `Topology.razor` keeps its own richer `TopoNode` — not refactored (YAGNI).
## 3. Page wiring
@@ -4,12 +4,12 @@
{
"id": 1,
"subject": "Task 1: InstanceTreeNode + InstanceTreeBuilder (pure) with tests",
"status": "pending"
"status": "completed"
},
{
"id": 2,
"subject": "Task 2: InstancePicker component \u2014 field, panel, select, outside-click",
"status": "pending",
"status": "in_progress",
"blockedBy": [
1
]