From 1045e7966d9dac227e398c79f703dae004454f42 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 17 Jun 2026 14:14:58 -0400 Subject: [PATCH] docs(plans): implementation plan for Debug View tabs + hierarchy trees 7 tasks (DV-1..DV-7): additive AlarmStateChanged native-binding contract chain, site snapshot native placeholders, DebugTreeNode + pure builder (attribute + alarm trees with roll-up/filter), DebugView tabs reusing TreeView, docs, and integration (build + docker + Playwright). --- docs/plans/2026-06-17-debugview-tabs-trees.md | 336 ++++++++++++++++++ ...6-06-17-debugview-tabs-trees.md.tasks.json | 20 ++ 2 files changed, 356 insertions(+) create mode 100644 docs/plans/2026-06-17-debugview-tabs-trees.md create mode 100644 docs/plans/2026-06-17-debugview-tabs-trees.md.tasks.json diff --git a/docs/plans/2026-06-17-debugview-tabs-trees.md b/docs/plans/2026-06-17-debugview-tabs-trees.md new file mode 100644 index 00000000..da4fffaa --- /dev/null +++ b/docs/plans/2026-06-17-debugview-tabs-trees.md @@ -0,0 +1,336 @@ +# Debug View — Tabs + Hierarchy Trees Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task. + +**Goal:** Turn the Central UI Debug View into a tabbed page (Attributes / Alarms) where each tab is a collapsible composition tree showing every configured attribute and alarm with current status, including idle native alarm sources. + +**Architecture:** Reuse the existing generic `TreeView` Blazor component. A pure `DebugTreeBuilder` transforms the flat snapshot/stream lists (whose path-qualified canonical names encode the composition hierarchy) into node forests with branch-level status roll-up. A small additive change to the `AlarmStateChanged` contract carries the native source-binding canonical name so live native conditions can nest under their binding, and the site snapshot emits placeholder rows for configured-but-quiet native sources. + +**Tech Stack:** C#/.NET 10, Blazor Server, Akka.NET (InstanceActor, StreamRelayActor), gRPC (sitestream.proto), Newtonsoft cross-process serializer, xUnit + bUnit, Playwright. + +**Design doc:** `docs/plans/2026-06-17-debugview-tabs-trees-design.md` (committed 811d722). +**Base:** branch `debugview-tabs-trees` off `origin/main` (670b607). + +--- + +## Execution notes (read before starting) + +- **No worktrees in implementers.** This plan already runs in the `debugview-tabs-trees` worktree. Implementer subagents must NOT create their own worktree. +- **Pathspec commits only.** Commit with `git commit -- ` (never `git add -A`/`-a`). Retry once on `index.lock`. +- **Concurrency.** Keep ≤2–3 concurrent committers per wave; after each parallel wave, verify every task's commit is on `HEAD` (`git log --oneline -`), recover any orphan via cherry-pick. +- **Targeted builds/tests per task** (`dotnet build `, `dotnet test --filter ...`). Full-solution build + docker rebuild only in the final integration task. +- **Proto codegen:** `sitestream.proto` regenerates `Sitestream.cs` on build of `ZB.MOM.WW.ScadaBridge.Communication` — no manual codegen step. + +## Suggested waves (for subagent-driven execution) + +- **Wave 1:** DV-1 (high-risk; serial spec→code review). +- **Wave 2:** DV-2 ∥ DV-3 (disjoint files; both need DV-1's new fields). +- **Wave 3:** DV-4 (alarm-tree builder; needs DV-3's model). +- **Wave 4:** DV-5 ∥ DV-6 (DebugView page ∥ docs; disjoint). +- **Wave 5:** DV-7 (integration). + +--- + +### Task DV-1: Native-binding linkage — additive `AlarmStateChanged` contract chain + +**Classification:** high-risk +**Estimated implement time:** ~5 min +**Parallelizable with:** none + +**Why high-risk:** touches a cross-process message contract (Newtonsoft + gRPC proto), the native alarm actor, and both relay directions. Additive-only, but verify nothing existing breaks. + +**Files:** +- Modify: `src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Streaming/AlarmStateChanged.cs:73` (add two init properties before the closing brace) +- Modify: `src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/NativeAlarmActor.cs:316-334` (set `NativeSourceCanonicalName` in the `Emit` initializer) +- Modify: `src/ZB.MOM.WW.ScadaBridge.Communication/Protos/sitestream.proto:86` (add fields 22 + 23 to `AlarmStateUpdate`) +- Modify: `src/ZB.MOM.WW.ScadaBridge.Communication/Actors/StreamRelayActor.cs:62-78` (pack the two fields) +- Modify: `src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcClient.cs:231-249` (unpack the two fields) +- Test: `tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/Grpc/StreamRelayActorTests.cs` (or `ProtoRoundtripTests.cs` / `SiteStreamGrpcClientTests.cs` — match where alarm round-trip is already asserted) +- Test: `tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Actors/NativeAlarmActorTests.cs` + +**Step 1: Write failing tests** + +In the Communication tests, extend the existing alarm round-trip test (find the test that packs an `AlarmStateChanged` → `AlarmStateUpdate` → back) to set `NativeSourceCanonicalName = "Motor1.MotorAlarms"` and `IsConfiguredPlaceholder = true` on the input and assert both survive the round-trip. In `NativeAlarmActorTests`, assert that an emitted `AlarmStateChanged` (the one told to the parent on a live transition) has `NativeSourceCanonicalName == _source.CanonicalName`. + +**Step 2: Run, expect fail** (`NativeSourceCanonicalName`/`IsConfiguredPlaceholder` don't exist yet → compile error). + +**Step 3: Implement** + +`AlarmStateChanged.cs` — add after `LimitValue` (line 73): + +```csharp +/// +/// Canonical name of the native alarm SOURCE BINDING this condition belongs to +/// (e.g. "Motor1.MotorAlarms"). Lets the Debug View nest live native conditions +/// under their configured binding node. Empty for computed alarms. Additive. +/// +public string NativeSourceCanonicalName { get; init; } = string.Empty; + +/// +/// True when this row is a placeholder emitted for a CONFIGURED native source +/// binding that currently has no active conditions, so the Debug View tree can +/// show the binding node as "no active conditions". Additive; default false. +/// +public bool IsConfiguredPlaceholder { get; init; } +``` + +`NativeAlarmActor.cs` — in the `Emit` object initializer (after `LimitValue = t.LimitValue`): + +```csharp +NativeSourceCanonicalName = _source.CanonicalName, +``` + +`sitestream.proto` — append to `message AlarmStateUpdate` (after line 86): + +```proto + string native_source_canonical_name = 22; // native binding canonical name; empty for computed + bool is_configured_placeholder = 23; // true for a quiet-binding placeholder row +``` + +`StreamRelayActor.cs` — in the `AlarmStateUpdate` initializer (alongside `SourceReference`): + +```csharp +NativeSourceCanonicalName = msg.NativeSourceCanonicalName ?? string.Empty, +IsConfiguredPlaceholder = msg.IsConfiguredPlaceholder, +``` + +`SiteStreamGrpcClient.cs` — in the `new AlarmStateChanged(...) { ... }` initializer (alongside `SourceReference`): + +```csharp +NativeSourceCanonicalName = evt.AlarmChanged.NativeSourceCanonicalName ?? string.Empty, +IsConfiguredPlaceholder = evt.AlarmChanged.IsConfiguredPlaceholder, +``` + +**Step 4: Run tests** + +``` +dotnet build src/ZB.MOM.WW.ScadaBridge.Communication/ZB.MOM.WW.ScadaBridge.Communication.csproj +dotnet test tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/ZB.MOM.WW.ScadaBridge.Communication.Tests.csproj --filter "FullyQualifiedName~StreamRelayActor|FullyQualifiedName~ProtoRoundtrip|FullyQualifiedName~SiteStreamGrpcClient" +dotnet test tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.csproj --filter "FullyQualifiedName~NativeAlarmActor" +``` +Expected: PASS. + +**Step 5: Commit** `git commit -- src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Streaming/AlarmStateChanged.cs src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/NativeAlarmActor.cs src/ZB.MOM.WW.ScadaBridge.Communication/Protos/sitestream.proto src/ZB.MOM.WW.ScadaBridge.Communication/Actors/StreamRelayActor.cs src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcClient.cs tests/...` + +--- + +### Task DV-2: Site snapshot — placeholder rows for idle native source bindings + +**Classification:** standard +**Estimated implement time:** ~4 min +**Parallelizable with:** DV-3 +**Depends on:** DV-1 + +**Files:** +- Modify: `src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/InstanceActor.cs:53-54` (add `_nativeAlarmKinds` dict), `:1447-1470` (record kind at native-actor creation), `:1094-1114` (`BuildAlarmStatesSnapshot` native placeholder loop) +- Test: `tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Actors/InstanceActorNativeAlarmTests.cs` + +**Step 1: Write failing tests** + +Two tests against an InstanceActor configured with a native alarm source binding: +1. `BuildAlarmStatesSnapshot_QuietNativeBinding_EmitsPlaceholder` — with a configured native binding and NO live condition for it, the debug snapshot contains exactly one alarm row for that binding canonical name with `IsConfiguredPlaceholder == true`, `State == Normal`, `NativeSourceCanonicalName == `. +2. `BuildAlarmStatesSnapshot_NativeBindingWithLiveCondition_NoPlaceholder` — after the InstanceActor receives an `AlarmStateChanged` (Kind native, `NativeSourceCanonicalName == `), the snapshot contains the live condition and **no** placeholder row for that binding. + +Match the existing construction pattern in `InstanceActorNativeAlarmTests.cs` (TestKit + a `FlattenedConfiguration` with `NativeAlarmSources`). Reuse the debug-snapshot ask already exercised there if present (`SubscribeDebugViewRequest` / `DebugSnapshotRequest`). + +**Step 2: Run, expect fail** (no placeholder emitted today). + +**Step 3: Implement** + +Add field near the other actor dictionaries (line ~54): +```csharp +private readonly Dictionary _nativeAlarmKinds = new(); +``` +At native-actor creation (~1447-1470), record the kind per binding using the SAME derivation `NativeAlarmActor` uses for its `_nativeKind` (read `NativeAlarmActor` ctor to see how kind is derived from the source/connection): +```csharp +_nativeAlarmKinds[nativeSource.CanonicalName] = ; +``` +In `BuildAlarmStatesSnapshot()`, after the existing computed fallback loop and before `return states;`: +```csharp +// Native source bindings with no live condition: emit a placeholder so the +// Debug View tree shows the configured binding node even when quiet. +var liveBindings = _latestAlarmEvents.Values + .Where(e => !string.IsNullOrEmpty(e.NativeSourceCanonicalName)) + .Select(e => e.NativeSourceCanonicalName) + .ToHashSet(); + +foreach (var binding in _nativeAlarmActors.Keys) +{ + if (liveBindings.Contains(binding)) continue; + + states.Add(new AlarmStateChanged( + _instanceUniqueName, binding, AlarmState.Normal, 0, DateTimeOffset.UtcNow) + { + Kind = _nativeAlarmKinds.GetValueOrDefault(binding, AlarmKind.NativeOpcUa), + NativeSourceCanonicalName = binding, + IsConfiguredPlaceholder = true + }); +} +``` + +**Step 4: Run** `dotnet test tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.csproj --filter "FullyQualifiedName~InstanceActorNativeAlarm"` → PASS. + +**Step 5: Commit** pathspec (`InstanceActor.cs` + the test). + +--- + +### Task DV-3: `DebugTreeNode` model + attribute-tree builder (pure, filter, roll-up) + +**Classification:** standard +**Estimated implement time:** ~5 min +**Parallelizable with:** DV-2 +**Depends on:** DV-1 (uses the new `AlarmStateChanged` fields in the shared model file) + +**Files:** +- Create: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugTreeNode.cs` +- Create: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugTreeBuilder.cs` +- Test: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/DebugTreeBuilderTests.cs` + +**Step 1: Write failing tests** (attribute tree only in this task): +- Root-level attributes (`"Speed"`) become direct leaves; `"Motor1.Speed"` + `"Motor1.Temp"` nest under a `Motor1` branch; `"Motor1.Compressor.Pump"` nests two deep. +- Branch roll-up: a branch with any descendant whose `Quality != "Good"` has `HasBadQuality == true`. +- Filter prune: `BuildAttributeTree(attrs, "temp")` keeps only matching leaves plus their ancestor branches; empty/whitespace filter returns the full forest. +- Leaf key is the full canonical name; children sorted by segment. + +**Step 2: Run, expect fail** (types don't exist). + +**Step 3: Implement** + +`DebugTreeNode.cs`: +```csharp +namespace ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Deployment; + +public sealed class DebugTreeNode +{ + public required string Key { get; init; } // full canonical path — stable TreeView key + public required string Segment { get; init; } // display label (last path segment) + public List Children { get; } = new(); + + // Leaf payloads — exactly one is set on a leaf; both null on a pure branch. + public AttributeValueChanged? Attribute { get; init; } + public AlarmStateChanged? Alarm { get; init; } // computed leaf, native condition, or placeholder + + public bool IsNativeBinding { get; init; } // branch grouping native conditions + + // Roll-up (set by the builder for branch nodes). + public AlarmState WorstState { get; set; } = AlarmState.Normal; + public int ActiveCount { get; set; } + public bool HasBadQuality { get; set; } + + public bool HasChildren => Children.Count > 0; +} +``` + +`DebugTreeBuilder.cs` — implement `BuildAttributeTree(IEnumerable attrs, string? filter)`: +- Optionally filter the input: keep an attribute if `AttributeName` contains `filter` (OrdinalIgnoreCase). +- Split each `AttributeName` on `'.'`; walk/create branch nodes (Key = accumulated prefix), attach the attribute as a leaf at the terminal segment. +- Sort children by `Segment`. +- Post-order roll-up: `HasBadQuality` true if the node's own attribute is off-Good or any child has `HasBadQuality`. +- (Leave `BuildAlarmTree` as a `// DV-4` stub or omit; DV-4 adds it to the same class.) + +**Step 4: Run** `dotnet test tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests.csproj --filter "FullyQualifiedName~DebugTreeBuilder"` → PASS. + +**Step 5: Commit** pathspec (the two new src files + the test). + +--- + +### Task DV-4: Alarm-tree builder — computed leaves + native binding grouping + roll-up + +**Classification:** standard +**Estimated implement time:** ~5 min +**Parallelizable with:** none +**Depends on:** DV-3 (shares `DebugTreeNode` + `DebugTreeBuilder`), DV-1 (native fields) + +**Files:** +- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugTreeBuilder.cs` (add `BuildAlarmTree`) +- Test: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/DebugTreeBuilderTests.cs` (add alarm cases) + +**Step 1: Write failing tests:** +- Computed alarm `"Motor1.HighTemp"` → leaf under `Motor1`. +- Native condition (`Kind = NativeOpcUa`, `NativeSourceCanonicalName = "Tank1.Levels"`, `SourceReference = "Tank1.Level.HiHi"`) → nests as a condition child **under a `Tank1.Levels` binding branch** (binding placed by its canonical path under `Tank1`). Two conditions for the same binding → two children of that binding node. +- Placeholder row (`IsConfiguredPlaceholder = true`, `NativeSourceCanonicalName = "Tank1.Levels"`) → the binding node renders as present with zero condition children (builder marks `IsNativeBinding` and adds no condition child). +- Roll-up: a branch with any descendant `Alarm.State == Active` has `WorstState == Active` and `ActiveCount` = number of active descendants; placeholder rows never count as active. +- Filter prune keeps native bindings whose name OR a condition matches. + +**Step 2: Run, expect fail.** + +**Step 3: Implement** `BuildAlarmTree`: +- Partition input by `Kind`. **Computed:** leaf at the canonical path from `AlarmName`. +- **Native:** group by `NativeSourceCanonicalName`. For each binding, create/walk a branch node at the binding's canonical path with `IsNativeBinding = true`. For each non-placeholder condition under it, add a child node (Key = `binding + "::" + SourceReference`, Segment = `SourceReference`, `Alarm = the event`). Placeholder rows add no child (the empty binding node renders "no active conditions" in the page). +- Sort; post-order roll-up of `WorstState`/`ActiveCount` (Active dominates; count leaves/conditions with `State == Active && !IsConfiguredPlaceholder`). + +**Step 4: Run** `--filter "FullyQualifiedName~DebugTreeBuilder"` → PASS. + +**Step 5: Commit** pathspec. + +--- + +### Task DV-5: DebugView page — tabs + two TreeViews + in-place updates + +**Classification:** standard +**Estimated implement time:** ~5 min +**Parallelizable with:** DV-6 +**Depends on:** DV-4 (builder), DV-2 (placeholders flow through the snapshot at runtime; not a compile dep) + +**Files:** +- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugView.razor` +- Modify: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/DebugViewAlarmTableTests.cs` (rework table assertions → tree assertions) + +**Step 1: Write/adjust failing tests** — render the connected page (reuse the harness in `DebugViewAlarmTableTests`) and assert: an Attributes tab and an Alarms tab exist (custom `nav-tabs`); switching to Alarms shows configured alarms grouped under their module branch (a `Motor1` branch containing `HighTemp`); a branch header shows the roll-up badge when a descendant alarm is Active. Preserve the dispose/stream-race coverage in `DebugViewDisposalTests.cs` / `DebugViewStreamRaceTests.cs` (those exercise CentralUI-009/021 — the marshalling is unchanged, so they should keep passing; run them, don't rewrite). + +**Step 2: Run, expect fail.** + +**Step 3: Implement** +- Replace the two side-by-side `
` cards with a Bootstrap `nav-tabs` header + tab panes (custom markup, no third-party lib). Keep the status strip, site/instance selectors, live/snapshot badges, `Connect`/`Disconnect`, localStorage auto-reconnect, and site-scope checks exactly as-is. +- Each tab body hosts `` with an `EmptyContent` hint for an empty instance. +- `NodeContent` fragments: **attribute leaf** → name / `ValueFormatter.FormatDisplayValue` / quality badge (`GetQualityBadge`) / timestamp; **attribute branch** → segment + bad-quality indicator when `HasBadQuality`. **Alarm computed leaf / native condition** → reuse `GetAlarmStateBadge`, `GetKindBadge`, `FormatKind`, `GetAlarmLevelBadge`, `FormatLevel`, `BuildAlarmTooltip`; **native binding branch** → segment + roll-up badge (worst state + `ActiveCount`); a binding with no children renders "no active conditions"; **alarm branch** → segment + worst-state/active-count roll-up badge. +- Keep `_attributeValues` / `_alarmStates` dictionaries as the latest-per-name source of truth but **remove the `MaxRows` cap**, auto-scroll, and Clear controls. Replace `FilteredAttributeValues`/`FilteredAlarmStates` with computed forests: `DebugTreeBuilder.BuildAttributeTree(_attributeValues.Values, _attrFilter)` and `DebugTreeBuilder.BuildAlarmTree(_alarmStates.Values, _alarmFilter)`. Keep the per-tab filter ``; when a filter is non-empty, call the TreeView's `ExpandAll()` via `@ref` so matches are visible. +- Live-event handling: keep `HandleStreamEvent` + `SafeInvokeAsync` marshalling and `UpsertWithCap` → rename to a plain upsert (no cap). Rebuilding the forest in the computed property on each `StateHasChanged` is sufficient (pure + cheap for typical instance sizes). + +**Step 4: Run** `dotnet test tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests.csproj --filter "FullyQualifiedName~DebugView"` → PASS (DebugViewAlarmTable reworked; Disposal + StreamRace still green). + +**Step 5: Commit** pathspec. + +--- + +### Task DV-6: Documentation + +**Classification:** small +**Estimated implement time:** ~3 min +**Parallelizable with:** DV-5 +**Depends on:** DV-4 + +**Files:** +- Modify: `docs/requirements/Component-CentralUI.md` (Debug View section — tabs + trees, branch roll-up, native binding nodes, idle-source placeholders) +- Modify: `docs/requirements/Component-SiteRuntime.md` (snapshot enrichment: placeholder rows for quiet native bindings; `_nativeAlarmKinds`) +- Modify: `docs/requirements/Component-Commons.md` *(if present; else note in CentralUI doc)* — streaming contract note: additive `NativeSourceCanonicalName` + `IsConfiguredPlaceholder` on `AlarmStateChanged` (and proto fields 22/23), additive-only evolution. + +**Steps:** Update the prose to match the shipped behaviour; no README/CLAUDE component-table change (no new component, stays at 25). Commit pathspec. (No tests.) + +--- + +### Task DV-7: Integration — full build, docker rebuild, Playwright, smoke + +**Classification:** high-risk +**Estimated implement time:** ~5 min (+ build/deploy wall-time) +**Parallelizable with:** none +**Depends on:** DV-5, DV-6, DV-2 + +**Files:** +- Create: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/DebugViewTreeTests.cs` + +**Steps:** +1. Full-solution build: `dotnet build ZB.MOM.WW.ScadaBridge.slnx` → 0 errors. +2. Run the affected unit suites green: Communication.Tests (Grpc filter), SiteRuntime.Tests (InstanceActorNativeAlarm), CentralUI.Tests (DebugView + DebugTreeBuilder + TreeView). +3. Rebuild the local cluster: `bash docker/deploy.sh`. +4. One Playwright test (`DebugViewTreeTests`): log in (`multi-role`/`password` via `localhost:9000`), navigate to `/deployment/debug-view`, select a site + instance with composition + a configured alarm (seed via CLI in the fixture, mirroring `TemplateCrudTests`/`InstanceConfigure*` fixtures), Connect, switch to the Alarms tab, assert the configured alarm appears under its module branch and a branch roll-up badge renders. Reuse `PlaywrightFixture`. +5. Manual smoke (note results in the task comment): connect to a real instance, confirm Attributes + Alarms tabs render trees, idle native source shows a "no active conditions" binding node, a fired alarm rolls up to its branch. +6. Commit pathspec (the Playwright test). Then hand off to `finishing-a-development-branch`. + +--- + +## Risks / watch-items + +- **Existing DebugView tests** (`DebugViewAlarmTableTests`) assert the flat-table DOM and WILL break — DV-5 reworks them. The concurrency tests (`DebugViewDisposalTests`, `DebugViewStreamRaceTests`) should stay green because the stream-callback marshalling is unchanged; if they fail, the merge logic diverged from the original `SafeInvokeAsync`/dispose-guard contract — fix the merge, don't weaken the tests. +- **Native kind for placeholders** (DV-2): derive it the same way `NativeAlarmActor` does; if the derivation is awkward to reach from `InstanceActor`, defaulting the placeholder badge to a generic native kind is acceptable (cosmetic only) — note the choice in the commit. +- **Forest rebuild on every event** (DV-5) is intentional (pure + cheap). If profiling ever shows it hot for very large instances, switch to incremental node patching — out of scope here. diff --git a/docs/plans/2026-06-17-debugview-tabs-trees.md.tasks.json b/docs/plans/2026-06-17-debugview-tabs-trees.md.tasks.json new file mode 100644 index 00000000..3d58ea3c --- /dev/null +++ b/docs/plans/2026-06-17-debugview-tabs-trees.md.tasks.json @@ -0,0 +1,20 @@ +{ + "planPath": "docs/plans/2026-06-17-debugview-tabs-trees.md", + "tasks": [ + {"id": 170, "subject": "DV-1: Native-binding linkage — additive AlarmStateChanged contract chain", "classification": "high-risk", "status": "pending"}, + {"id": 171, "subject": "DV-2: Site snapshot — placeholder rows for idle native source bindings", "classification": "standard", "status": "pending", "blockedBy": [170]}, + {"id": 172, "subject": "DV-3: DebugTreeNode model + attribute-tree builder", "classification": "standard", "status": "pending", "blockedBy": [170]}, + {"id": 173, "subject": "DV-4: Alarm-tree builder — computed leaves + native binding grouping + roll-up", "classification": "standard", "status": "pending", "blockedBy": [172, 170]}, + {"id": 174, "subject": "DV-5: DebugView page — tabs + two TreeViews + in-place updates", "classification": "standard", "status": "pending", "blockedBy": [173]}, + {"id": 175, "subject": "DV-6: Documentation — CentralUI + SiteRuntime + streaming contract", "classification": "small", "status": "pending", "blockedBy": [173]}, + {"id": 176, "subject": "DV-7: Integration — full build, docker rebuild, Playwright, smoke", "classification": "high-risk", "status": "pending", "blockedBy": [174, 175, 171]} + ], + "waves": [ + {"wave": 1, "tasks": [170]}, + {"wave": 2, "tasks": [171, 172]}, + {"wave": 3, "tasks": [173]}, + {"wave": 4, "tasks": [174, 175]}, + {"wave": 5, "tasks": [176]} + ], + "lastUpdated": "2026-06-17" +}