chore(claude-do): merge Bind-Churn: DetailsIsland.BindAsync feuert ~1900x/Tag, 91-96

ClaudeDo-Task: 351cacc4-3e11-4b3e-a89e-4efc4bd0448e
This commit is contained in:
Mika Kuns
2026-08-13 09:00:32 +02:00
4 changed files with 46 additions and 2 deletions
+1 -1
View File
@@ -89,7 +89,7 @@ new editor boilerplate there rather than copying it a third time.
- **IPrimeScheduleApi** — prime-schedule CRUD.
- **UpdateCheckService** — polls releases; `LastCheckStatus`/`LatestVersion`/`CheckNowAsync` feed the shell's update banner.
- **InheritanceResolver** — resolves the task → list → global override chain to `(value, source)` for the inherited badges.
- **OperationTiming** — NDJSON sink (`~/.todo-app/logs/operation-timing.ndjson`, one rolled `.1` at 4 MB) behind every hub invoke and bulk DB path; each line carries `pid` because app restarts interleave in one file. Successful calls under 25 ms are dropped, **failed/cancelled ones always land** — a 1 ms cancelled `BindAsync` is churn signal, not noise. `DetailsIsland.BindAsync:<source>` carries the selection trigger (`TasksIslandViewModel.SelectionSource`, set via `SelectFrom` — never assign `SelectedTask` directly), because the first day of data showed ~1000 binds in 3.5 h, 91% cancelled, with an unexplained trigger.
- **OperationTiming** — NDJSON sink (`~/.todo-app/logs/operation-timing.ndjson`, one rolled `.1` at 4 MB) behind every hub invoke and bulk DB path; each line carries `pid` because app restarts interleave in one file. Successful calls under 25 ms are dropped, **failed/cancelled ones always land** — a 1 ms cancelled `BindAsync` is churn signal, not noise. `DetailsIsland.BindAsync:<source>` carries the selection trigger (`TasksIslandViewModel.SelectionSource`, set via `SelectFrom` — never assign `SelectedTask` directly). `Shared` is a settable static singleton **on purpose**`Ui.Tests`/`Worker.Tests` each carry a `TestSetup` module initializer that redirects it to a temp file before any test runs, because both projects construct real ViewModels (`DetailsIslandViewModel`, `TasksIslandViewModel`) that call `Shared.Record` directly; without the redirect a single test run appends 50-100 lines straight into the live app's log (a day of ~1900 `BindAsync` lines, 91-96% cancelled, turned out to be 13 such test runs plus pre-fix-binary artifacts — real user-driven binds that day: 4).
- **RepoScanner**, **InstallArtifactLocator**/**InstallerLocator**/**WorkerLocator**, **ForegroundHelper** (Win32 foreground before launching a terminal), **FocusClearing**.
## Converters
+6 -1
View File
@@ -21,7 +21,12 @@ public sealed class OperationTiming
public static string DefaultPath =>
Path.Combine(Paths.AppDataRoot(), "logs", "operation-timing.ndjson");
public static OperationTiming Shared { get; } = new(DefaultPath);
// Settable, not just get-only: xUnit constructs real ViewModels (DetailsIslandViewModel,
// TasksIslandViewModel, ...) that call Shared.Record directly, so without a way to redirect
// it every test run appended its churn straight into the live app's NDJSON file at the real
// AppData path — see Ui.Tests' TestSetup module initializer, which points this at a temp file
// for the whole test process before any test runs.
public static OperationTiming Shared { get; set; } = new(DefaultPath);
private static readonly int ProcessId = Environment.ProcessId;
+20
View File
@@ -0,0 +1,20 @@
using System.Runtime.CompilerServices;
using ClaudeDo.Ui.Services;
namespace ClaudeDo.Ui.Tests;
internal static class TestSetup
{
// Runs once when this test assembly loads, before any test executes. Without it, every test
// that constructs a real DetailsIslandViewModel/TasksIslandViewModel/WorkerClient and calls a
// method instrumented with OperationTiming.Shared.Record writes straight into the live app's
// ~/.todo-app/logs/operation-timing.ndjson — a single Ui.Tests run added 50-100 cancelled
// "DetailsIsland.BindAsync:?" lines to that file, dwarfing real usage and making the churn
// look like a UI bug.
[ModuleInitializer]
internal static void RedirectOperationTimingAwayFromRealAppData()
{
var path = Path.Combine(Path.GetTempPath(), "claudedo-ui-tests", $"operation-timing-{Environment.ProcessId}.ndjson");
OperationTiming.Shared = new OperationTiming(path);
}
}
+19
View File
@@ -0,0 +1,19 @@
using System.Runtime.CompilerServices;
using ClaudeDo.Ui.Services;
namespace ClaudeDo.Worker.Tests;
internal static class TestSetup
{
// Runs once when this test assembly loads, before any test executes. UiVm/*Tests.cs construct
// real TasksIslandViewModel instances, whose LoadForListAsync/ReorderAsync/ReconcileTickAsync
// call OperationTiming.Shared.Record directly — without a redirect those writes land in the
// live app's ~/.todo-app/logs/operation-timing.ndjson (see ClaudeDo.Ui.Tests' TestSetup for
// the same trap on the Ui.Tests side).
[ModuleInitializer]
internal static void RedirectOperationTimingAwayFromRealAppData()
{
var path = Path.Combine(Path.GetTempPath(), "claudedo-worker-tests", $"operation-timing-{Environment.ProcessId}.ndjson");
OperationTiming.Shared = new OperationTiming(path);
}
}