diff --git a/CHANGELOG.md b/CHANGELOG.md index 30dbefcd..44750eb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,18 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. ## [Unreleased] +### Fixed + +- **Ticket and decision sidebars reliably load on first open (real fix).** The + 2.3.1 re-fetch-on-open helped only when a project is picked *after* the window + is up; with sticky-startup the project opens during boot, before the panes + mount, so they never saw the event. The underlying cause was a race: the boot + IPC-server swap (to the launch CWD) and the project-open swap (to the repo) + ran concurrently, and the late-finishing boot swap could clobber the repo + bind — leaving the daemon's pql/git/files pointed at the launch directory + (HOME) and the sidebars erroring on a stale/global pql.db. Swaps are now + serialized so the repo bind always wins. (T-352) + ## [2.3.1] — 2026-06-10 ### Fixed diff --git a/lib/main.dart b/lib/main.dart index 5a57f695..da24b6e4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -116,7 +116,11 @@ Future main() async { McpServer? mcpServer; final ipcLog = Logger(); - Future swapIpcServer(DaemonDispatcher dispatcher, Directory workRoot) async { + // IPC-server swaps must run one-at-a-time — see the swapIpcServer wrapper + // below doSwapIpcServer for why. (T-352) + Future swapChain = Future.value(); + + Future doSwapIpcServer(DaemonDispatcher dispatcher, Directory workRoot) async { if (kIsWeb) return; // Already serving this exact workspace? Reuse the live server. // The startup factory binds the launch CWD, then the project-open @@ -173,6 +177,22 @@ Future main() async { } } + // Serialize IPC-server swaps. The boot factory fires a swap to the launch + // CWD with unawaited(); the project-open flow then fires another to the + // real repo. Unserialized, the two interleave and the late-finishing boot + // swap can clobber the repo bind — reconnecting the daemon client to the + // launch-CWD (HOME) socket, so pql/git/files run against the wrong + // workspace. That surfaced as the ticket/decision sidebars failing on first + // load (stale/global pql.db) yet working after a manual refresh. Chaining + // every swap makes them apply in call order; the repo swap is issued last + // and therefore wins. (T-352) + Future swapIpcServer(DaemonDispatcher dispatcher, Directory workRoot) { + final next = swapChain.then((_) => doSwapIpcServer(dispatcher, workRoot)); + // A failed swap must not break the chain for the next one. + swapChain = next.catchError((Object _) {}); + return next; + } + DaemonDispatcher buildDispatcher( DaemonBus events, Toolchain tc,