From ebcf73f72c5946eb17c865a3cbda2e1bf12073c7 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 5 Aug 2026 03:19:04 -0400 Subject: [PATCH] docs(plan): implementation plan for the raw-modal picker live-config fix --- ...2026-08-05-raw-modal-picker-live-config.md | 555 ++++++++++++++++++ ...raw-modal-picker-live-config.md.tasks.json | 12 + 2 files changed, 567 insertions(+) create mode 100644 docs/plans/2026-08-05-raw-modal-picker-live-config.md create mode 100644 docs/plans/2026-08-05-raw-modal-picker-live-config.md.tasks.json diff --git a/docs/plans/2026-08-05-raw-modal-picker-live-config.md b/docs/plans/2026-08-05-raw-modal-picker-live-config.md new file mode 100644 index 00000000..2f209c08 --- /dev/null +++ b/docs/plans/2026-08-05-raw-modal-picker-live-config.md @@ -0,0 +1,555 @@ +# Raw-modal picker live config — implementation plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task. + +**Goal:** Replace the `() => "{}"` stub `GetDriverConfigJson` in `RawTagModal` and +`RawManualTagEntryModal` with the merged Driver+Device config, so the AbCip/TwinCAT/FOCAS +address pickers' "Browse controller" button captures against the real device. + +**Architecture:** Each modal resolves `IRawTreeService.LoadMergedProbeConfigAsync(deviceId)` once +per open (the same merge Test-connect and `RawBrowseModal` dial), caches the JSON in a private +field, and passes a **live** `Func` that reads the cache with a `"{}"` fallback. Fail-soft: +a failed resolve leaves the stub behaviour (graceful error chip in the picker), never blocks the +modal. Design: `docs/plans/2026-08-05-raw-modal-picker-live-config-design.md`. + +**Tech Stack:** Blazor Server (razor component classes tested directly — no bUnit; the AdminUI +csproj has `InternalsVisibleTo("ZB.MOM.WW.OtOpcUa.AdminUI.Tests")`), xUnit + Shouldly, hand-written +fakes (no mocking library in the test project). + +**Key facts for the implementer (zero-context):** + +- The two stubs are at `RawTagModal.razor` `BuildEditorParameters()` (~line 190) and + `RawManualTagEntryModal.razor` `BuildEditorParameters(RowModel)` (~line 182): + `["GetDriverConfigJson"] = (Func)(() => "{}")`. +- Both modals already `@inject IRawTreeService Svc`. The resolver exists: + `Task<(string DriverType, string MergedConfigJson)?> LoadMergedProbeConfigAsync(string deviceId, CancellationToken ct = default)` + (`src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/IRawTreeService.cs:396`). +- `RawTagModal` learns its device from `RawTagEditDto.DeviceId` (returned by + `Svc.LoadTagForEditAsync(TagId)` in `OnParametersSetAsync`). `RawManualTagEntryModal` takes + `DeviceId` as a `[Parameter]`. +- The `Func` MUST stay a live accessor (read the field at call time, not capture a value): + the picker body (`AbCipAddressPickerBody.OpenBrowseAsync`) calls it at click time, which is what + makes intermediate renders during the async resolve harmless. +- Both modals guard against re-render re-initialisation (`_loadedKey` / `_open`) — the resolve must + ride the same guard so an in-progress edit is never clobbered by a re-resolve. +- Test precedent for reaching into razor component classes: + `tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Raw/RawDriverTypeDialogParityTests.cs` (reflection; + razor components are ordinary classes in `ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Raw`). + `@inject` generates a **private** `Svc` property — set it with reflection + (`BindingFlags.NonPublic | BindingFlags.Instance`). `OnParametersSetAsync` is protected — invoke + via reflection. `[Parameter]` properties are public — set directly. +- Repo conventions: xUnit + Shouldly, sentence-style test names + (`The_editor_receives_the_merged_config`), comments state constraints not narration. + +--- + +### Task 0: FakeRawTreeService test helper + +**Classification:** small +**Estimated implement time:** ~4 min +**Parallelizable with:** none (Tasks 1–2 depend on it) + +**Files:** +- Create: `tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Raw/FakeRawTreeService.cs` +- Read (reference only): `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/IRawTreeService.cs` + +**Step 1: Write the fake** + +A hand-written fake implementing `IRawTreeService` (the repo has no mocking library). Two members +are driven by settable delegates; every other interface member throws +`NotImplementedException` (one-liners — generate them from the interface file): + +```csharp +using ZB.MOM.WW.OtOpcUa.AdminUI.Uns; + +namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Raw; + +/// +/// Minimal fake for component-class tests. Only the members the +/// raw modals' open path touches are configurable; everything else throws so an unexpected +/// call fails the test instead of vacuously passing. +/// +internal sealed class FakeRawTreeService : IRawTreeService +{ + public Func OnLoadTagForEdit { get; set; } = + _ => throw new NotImplementedException(); + + public Func OnLoadMergedProbeConfig { get; set; } = + _ => throw new NotImplementedException(); + + public Task LoadTagForEditAsync(string tagId, CancellationToken ct = default) + => Task.FromResult(OnLoadTagForEdit(tagId)); + + public Task<(string DriverType, string MergedConfigJson)?> LoadMergedProbeConfigAsync( + string deviceId, CancellationToken ct = default) + => Task.FromResult(OnLoadMergedProbeConfig(deviceId)); + + // --- every other IRawTreeService member: throw new NotImplementedException(); --- +} +``` + +Implement the remaining members exactly as declared in `IRawTreeService.cs` (expression-bodied +`=> throw new NotImplementedException();`). Do not add behaviour to any of them. + +**Step 2: Compile the test project** + +Run: `dotnet build tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests` +Expected: 0 errors. + +**Step 3: Commit** + +```bash +git add tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Raw/FakeRawTreeService.cs +git commit -m "test(raw): FakeRawTreeService for raw-modal component tests" +``` + +--- + +### Task 1: RawTagModal — resolve and forward the merged config + +**Classification:** standard +**Estimated implement time:** ~5 min +**Parallelizable with:** Task 2 + +**Files:** +- Modify: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Raw/RawTagModal.razor` (the + `@code` block: `OnParametersSetAsync` ~lines 143–183, `BuildEditorParameters` ~lines 185–191) +- Test: `tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Raw/RawTagModalMergedConfigTests.cs` (create) + +**Step 1: Write the failing tests** + +```csharp +using System.Reflection; +using Shouldly; +using Xunit; +using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Raw; +using ZB.MOM.WW.OtOpcUa.AdminUI.Uns; + +namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Raw; + +/// +/// Pins the #468 follow-up fix: the typed tag editor inside the raw tag edit modal must +/// receive the merged Driver+Device config — not the historical "{}" stub — or the +/// AbCip/TwinCAT/FOCAS pickers' "Browse controller" captures against no endpoint and can +/// never succeed. The forwarding is razor wiring no rendered-output test reaches (no bUnit), +/// so this drives the component class directly, as RawDriverTypeDialogParityTests does. +/// +public sealed class RawTagModalMergedConfigTests +{ + private const string Merged = /*lang=json*/ """{"endpoint":"10.0.0.1:44818","slot":0}"""; + + private static RawTagEditDto Dto(string deviceId) => new( + "tag-1", deviceId, null, "T1", "Float", + default, false, null, "{}", Array.Empty()); + + private static void InjectSvc(RawTagModal modal, IRawTreeService svc) + { + var p = typeof(RawTagModal).GetProperty("Svc", + BindingFlags.NonPublic | BindingFlags.Instance); + p.ShouldNotBeNull("RawTagModal's @inject property was renamed — this test sets it by name."); + p!.SetValue(modal, svc); + } + + private static Task OpenAsync(RawTagModal modal) + { + var m = typeof(RawTagModal).GetMethod("OnParametersSetAsync", + BindingFlags.NonPublic | BindingFlags.Instance); + m.ShouldNotBeNull(); + return (Task)m!.Invoke(modal, null)!; + } + + [Fact] + public async Task The_editor_receives_the_merged_device_config_not_a_stub() + { + var modal = new RawTagModal { Visible = true, TagId = "tag-1", DriverType = "AbCip" }; + InjectSvc(modal, new FakeRawTreeService + { + OnLoadTagForEdit = _ => Dto("dev-1"), + OnLoadMergedProbeConfig = id => id == "dev-1" ? ("AbCip", Merged) : null, + }); + + await OpenAsync(modal); + + var func = (Func)modal.BuildEditorParameters()["GetDriverConfigJson"]; + func().ShouldBe(Merged); + } + + [Fact] + public async Task A_failed_resolve_degrades_to_the_empty_stub_and_does_not_throw() + { + var modal = new RawTagModal { Visible = true, TagId = "tag-1", DriverType = "AbCip" }; + InjectSvc(modal, new FakeRawTreeService + { + OnLoadTagForEdit = _ => Dto("dev-1"), + OnLoadMergedProbeConfig = _ => throw new InvalidOperationException("device store down"), + }); + + await OpenAsync(modal); // must not throw — fail-soft is the design (§3) + + var func = (Func)modal.BuildEditorParameters()["GetDriverConfigJson"]; + func().ShouldBe("{}"); + } + + [Fact] + public async Task The_accessor_is_live_not_a_captured_snapshot() + { + // The picker body reads the Func at click time; intermediate renders during the async + // resolve are only harmless because the accessor reads the field, not a captured value. + var modal = new RawTagModal { Visible = true, TagId = "tag-1", DriverType = "AbCip" }; + var svc = new FakeRawTreeService + { + OnLoadTagForEdit = _ => Dto("dev-1"), + OnLoadMergedProbeConfig = _ => null, // first open resolves nothing + }; + InjectSvc(modal, svc); + await OpenAsync(modal); + var func = (Func)modal.BuildEditorParameters()["GetDriverConfigJson"]; + func().ShouldBe("{}"); + + // Re-open for a different tag with the store now answering. + modal.Visible = false; + await OpenAsync(modal); + modal.Visible = true; + modal.TagId = "tag-2"; + svc.OnLoadTagForEdit = _ => Dto("dev-1") with { TagId = "tag-2" }; + svc.OnLoadMergedProbeConfig = _ => ("AbCip", Merged); + await OpenAsync(modal); + + func = (Func)modal.BuildEditorParameters()["GetDriverConfigJson"]; + func().ShouldBe(Merged); + } +} +``` + +Notes for the implementer: +- `RawTagEditDto` is a `readonly record struct` in `ZB.MOM.WW.OtOpcUa.AdminUI.Uns` — check its + ctor order in `IRawTreeService.cs:20` and the `AccessLevel` parameter's enum type/namespace + (use `default` for it as above if the name differs). +- If `BuildEditorParameters` is private today, Step 3 makes it `internal` — the test compiles + against `InternalsVisibleTo`. + +**Step 2: Run tests to verify they fail** + +Run: `dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests --filter "FullyQualifiedName~RawTagModalMergedConfigTests"` +Expected: FAIL — `BuildEditorParameters` inaccessible (compile) first; after making it internal +(do that now if needed to get a red run), the first and third tests fail with `"{}"` vs merged. +The stub must be observed failing before Step 3's wiring lands. + +**Step 3: Implement** + +In `RawTagModal.razor` `@code`: + +1. Add the cache field next to `_loadedKey`: + +```csharp + // Merged Driver+Device config for the typed editor's address picker — the same blob + // Test-connect and RawBrowseModal dial, resolved once per open. Null (→ "{}") when the + // resolve failed: the picker's browse then degrades to its graceful error chip. + private string? _mergedConfigJson; +``` + +2. In `OnParametersSetAsync`, clear it in the `!Visible` branch (`_mergedConfigJson = null;` next + to `_loadedKey = null;`), and resolve it after the dto load succeeds (after `_rowVersion = d.RowVersion;`): + +```csharp + _mergedConfigJson = null; + try + { + var merged = await Svc.LoadMergedProbeConfigAsync(d.DeviceId); + if (merged is { } m) { _mergedConfigJson = m.MergedConfigJson; } + } + catch + { + // Fail-soft by design: the modal must open and save regardless — only the browse + // affordance inside the picker is degraded, and it reports its own error on use. + } +``` + +3. Make `BuildEditorParameters` **internal** and forward the live accessor: + +```csharp + internal IDictionary BuildEditorParameters() => new Dictionary + { + ["ConfigJson"] = _form.TagConfig, + ["ConfigJsonChanged"] = EventCallback.Factory.Create(this, v => _form.TagConfig = v), + ["DriverType"] = DriverType ?? "", + ["GetDriverConfigJson"] = (Func)(() => _mergedConfigJson ?? "{}"), + }; +``` + +Also update the modal's header comment (lines 1–6) to mention that the editor receives the merged +probe config for browse. + +**Step 4: Run tests to verify they pass** + +Run: `dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests --filter "FullyQualifiedName~RawTagModalMergedConfigTests"` +Expected: 3 passed. + +**Step 5: Commit** + +```bash +git add src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Raw/RawTagModal.razor \ + tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Raw/RawTagModalMergedConfigTests.cs +git commit -m "fix(raw): tag edit modal feeds the picker the merged device config, not '{}' (#468 follow-up)" +``` + +--- + +### Task 2: RawManualTagEntryModal — resolve and forward the merged config + +**Classification:** standard +**Estimated implement time:** ~5 min +**Parallelizable with:** Task 1 + +**Files:** +- Modify: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Raw/RawManualTagEntryModal.razor` + (the `@code` block: `OnParametersSet` ~lines 162–175, `BuildEditorParameters` ~lines 177–183) +- Test: `tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Raw/RawManualTagEntryModalMergedConfigTests.cs` (create) + +**Step 1: Write the failing tests** + +Same shape as Task 1 (reuse its reflection helpers, adapted to this component — duplicate the two +small private helpers rather than building a shared abstraction; two call sites do not earn one): + +```csharp +using System.Reflection; +using Shouldly; +using Xunit; +using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Raw; +using ZB.MOM.WW.OtOpcUa.AdminUI.Uns; + +namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Raw; + +/// +/// Same defect, same pin as , for the bulk +/// manual-entry grid: each row's typed editor must receive the merged Driver+Device config. +/// Unlike the edit modal, the device is a direct parameter here — no tag load precedes it. +/// +public sealed class RawManualTagEntryModalMergedConfigTests +{ + private const string Merged = /*lang=json*/ """{"endpoint":"10.0.0.1:44818","slot":0}"""; + + private static void InjectSvc(RawManualTagEntryModal modal, IRawTreeService svc) + { + var p = typeof(RawManualTagEntryModal).GetProperty("Svc", + BindingFlags.NonPublic | BindingFlags.Instance); + p.ShouldNotBeNull("RawManualTagEntryModal's @inject property was renamed — this test sets it by name."); + p!.SetValue(modal, svc); + } + + private static Task OpenAsync(RawManualTagEntryModal modal) + { + var m = typeof(RawManualTagEntryModal).GetMethod("OnParametersSetAsync", + BindingFlags.NonPublic | BindingFlags.Instance); + m.ShouldNotBeNull("the open path moved off OnParametersSetAsync — retarget this driver."); + return (Task)m!.Invoke(modal, null)!; + } + + private static Func EditorConfigAccessor(RawManualTagEntryModal modal) + { + // BuildEditorParameters takes the private RowModel — go through it by reflection with the + // first seeded row, exactly as the razor markup does per row. + var rows = typeof(RawManualTagEntryModal) + .GetField("_rows", BindingFlags.NonPublic | BindingFlags.Instance)! + .GetValue(modal)!; + var row = ((System.Collections.IEnumerable)rows).Cast().First(); + var build = typeof(RawManualTagEntryModal).GetMethod("BuildEditorParameters", + BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public); + build.ShouldNotBeNull(); + var dict = (IDictionary)build!.Invoke(modal, new[] { row })!; + return (Func)dict["GetDriverConfigJson"]; + } + + [Fact] + public async Task Each_rows_editor_receives_the_merged_device_config_not_a_stub() + { + var modal = new RawManualTagEntryModal { Visible = true, DeviceId = "dev-1", DriverType = "AbCip" }; + InjectSvc(modal, new FakeRawTreeService + { + OnLoadMergedProbeConfig = id => id == "dev-1" ? ("AbCip", Merged) : null, + }); + + await OpenAsync(modal); + + EditorConfigAccessor(modal)().ShouldBe(Merged); + } + + [Fact] + public async Task A_failed_resolve_degrades_to_the_empty_stub_and_does_not_throw() + { + var modal = new RawManualTagEntryModal { Visible = true, DeviceId = "dev-1", DriverType = "AbCip" }; + InjectSvc(modal, new FakeRawTreeService + { + OnLoadMergedProbeConfig = _ => throw new InvalidOperationException("device store down"), + }); + + await OpenAsync(modal); // must not throw — fail-soft is the design (§3) + + EditorConfigAccessor(modal)().ShouldBe("{}"); + } +} +``` + +**Step 2: Run tests to verify they fail** + +Run: `dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests --filter "FullyQualifiedName~RawManualTagEntryModalMergedConfigTests"` +Expected: FAIL — `OnParametersSetAsync` does not exist yet (the component overrides the sync +`OnParametersSet`), so the `OpenAsync` helper's ShouldNotBeNull trips. That IS the red state. + +**Step 3: Implement** + +In `RawManualTagEntryModal.razor` `@code`: + +1. Add the same cache field as Task 1 (same comment). +2. Convert `OnParametersSet` to `OnParametersSetAsync`, preserving the `_open` guard and row + seeding exactly, and appending the resolve: + +```csharp + protected override async Task OnParametersSetAsync() + { + if (!Visible) + { + _open = false; + _mergedConfigJson = null; + return; + } + if (_open) { return; } // same open, re-render → preserve in-progress rows + _open = true; + _rows.Clear(); + _rows.Add(new RowModel()); + _summary = null; + _summaryOk = false; + + _mergedConfigJson = null; + if (!string.IsNullOrEmpty(DeviceId)) + { + try + { + var merged = await Svc.LoadMergedProbeConfigAsync(DeviceId); + if (merged is { } m) { _mergedConfigJson = m.MergedConfigJson; } + } + catch + { + // Fail-soft by design: the grid must stay usable regardless — only the browse + // affordance inside each row's picker is degraded, and it reports its own error. + } + } + } +``` + +3. Forward the live accessor in `BuildEditorParameters(RowModel row)`: + +```csharp + ["GetDriverConfigJson"] = (Func)(() => _mergedConfigJson ?? "{}"), +``` + +Update the header comment as in Task 1. + +**Step 4: Run tests to verify they pass** + +Run: `dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests --filter "FullyQualifiedName~RawManualTagEntryModalMergedConfigTests"` +Expected: 2 passed. + +**Step 5: Commit** + +```bash +git add src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Raw/RawManualTagEntryModal.razor \ + tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Raw/RawManualTagEntryModalMergedConfigTests.cs +git commit -m "fix(raw): manual tag entry feeds each row's picker the merged device config (#468 follow-up)" +``` + +--- + +### Task 3: Full-suite verification + +**Classification:** small +**Estimated implement time:** ~4 min (mostly wall-clock on the suite) +**Parallelizable with:** none (needs Tasks 1–2 merged in the worktree) + +**Files:** none new — verification only. + +**Step 1: Build the full solution** + +Run: `dotnet build ZB.MOM.WW.OtOpcUa.slnx` +Expected: 0 errors. + +**Step 2: Run the AdminUI test project** + +Run: `dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests` +Expected: all passed, 0 failed (baseline before this work: 749+; now +5). + +**Step 3: Commit anything outstanding** + +Nothing should be outstanding; if a fix was needed to get green, commit it with an explanatory +message. Do NOT commit a red state. + +--- + +### Task 4: Documentation bookkeeping + +**Classification:** trivial +**Estimated implement time:** ~3 min +**Parallelizable with:** none + +**Files:** +- Modify: `docs/plans/2026-07-24-driver-expansion-tracking.md` (Wave 0 section, "Residual + follow-up" bullet) +- Modify: `docs/plans/2026-07-15-universal-discovery-browser-implementation.md` ("Follow-ups + discovered during the first gate" item 2) +- Modify: `docs/plans/2026-08-05-raw-modal-picker-live-config-design.md` (Status line) + +**Step 1: Update all three** + +- Tracking doc: rewrite the "Residual follow-up (open)" bullet as closed — the named + `*DriverPage` shells were already deleted by v3 Batch 2; the surviving instance (the raw modals' + `"{}"` stubs) is fixed by this change (name the commits), unit-pinned by + `RawTagModalMergedConfigTests` / `RawManualTagEntryModalMergedConfigTests`, live gate per the + design doc §4. +- Universal-browser implementation plan: mark first-gate follow-up 2 closed with the same summary + (strikethrough + CLOSED note, matching the doc's existing style for items 1/2 of the second gate). +- Design doc: Status → implemented, tests green, live gate pending/PASSED as applicable. + +**Step 2: Commit** + +```bash +git add docs/plans/2026-07-24-driver-expansion-tracking.md \ + docs/plans/2026-07-15-universal-discovery-browser-implementation.md \ + docs/plans/2026-08-05-raw-modal-picker-live-config-design.md +git commit -m "docs(raw): close the #468 follow-up — raw modals now feed pickers live merged config" +``` + +--- + +### Task 5: Live `/run` gate (main session — NOT a subagent task) + +**Classification:** high-risk (it is the class of verification unit tests cannot provide here) +**Estimated implement time:** ~15 min wall-clock +**Parallelizable with:** none + +This is executed by the **main session** after Tasks 0–4 merge, using the Chrome automation tools +against the local docker-dev rig (login disabled — no sign-in needed) and the patched AbCip fixture. + +**Recipe:** + +1. Rig up (OrbStack local): `docker-dev/docker-compose.yml` stack; AdminUI at + `http://localhost:9200`. AbCip fixture on the shared host: `ssh dohertj2@10.100.0.35 'cd /opt/otopcua-abcip && docker compose --profile controllogix up -d'` + (endpoint `10.100.0.35:44818` — the #468 second-gate patched `ab_server` serving `@tags`). +2. In `/raw`, on an AbCip device pointed at the fixture with at least one authored tag: + - **Edit modal leg:** open the tag's edit modal → the AbCip editor's "Browse controller" → + expect the **nine-tag tree** (`Motor_Run` … `TestDINT`), not an error chip. Select a leaf, + confirm the address lands in the Tag-name field. + - **Manual entry leg:** device context menu → Add tags (manual) → expand a row's config editor → + "Browse controller" → same nine-tag tree. + - **Negative leg:** on a device with an unreachable endpoint, the button still renders and the + click yields the graceful error chip; the modal stays usable and can still save. +3. Record the result in the design doc §4 (and the tracking doc bullet from Task 4). + +--- + +## Execution notes + +- Worktree: run via superpowers-extended-cc:subagent-driven-development in a fresh worktree + (Tasks 1 and 2 may dispatch in parallel after Task 0; Tasks 3 → 4 serial after them). +- Subagent model: **opus** (user-specified). +- The `Files:` blocks are the `files_to_edit` contract; needing other files is a plan defect — + surface it. diff --git a/docs/plans/2026-08-05-raw-modal-picker-live-config.md.tasks.json b/docs/plans/2026-08-05-raw-modal-picker-live-config.md.tasks.json new file mode 100644 index 00000000..393b8299 --- /dev/null +++ b/docs/plans/2026-08-05-raw-modal-picker-live-config.md.tasks.json @@ -0,0 +1,12 @@ +{ + "planPath": "docs/plans/2026-08-05-raw-modal-picker-live-config.md", + "tasks": [ + {"id": 0, "subject": "Task 0: FakeRawTreeService test helper", "status": "pending"}, + {"id": 1, "subject": "Task 1: RawTagModal — resolve and forward merged config", "status": "pending", "blockedBy": [0]}, + {"id": 2, "subject": "Task 2: RawManualTagEntryModal — resolve and forward merged config", "status": "pending", "blockedBy": [0]}, + {"id": 3, "subject": "Task 3: Full-suite verification", "status": "pending", "blockedBy": [1, 2]}, + {"id": 4, "subject": "Task 4: Documentation bookkeeping", "status": "pending", "blockedBy": [3]}, + {"id": 5, "subject": "Task 5: Live /run gate (main session)", "status": "pending", "blockedBy": [4]} + ], + "lastUpdated": "2026-08-05T00:00:00Z" +}