pql: refetch ticket + decision sidebars when the workspace opens (T-352)

On a desktop launch the daemon's PqlClient boots with workDir set to the
launch CWD (e.g. HOME), not the repo — swapIpcServer only rewires it once
the project opens. The tickets and decisions panes fire their first pql
fetch before that swap, so pql runs in the wrong directory against a
stale/global pql.db and the pane errors (observed:
"ticket_deps.blocker_record_id missing — pql.db is from an earlier
schema"). A manual refresh worked because by then the workspace was open.

This is a wrong-workDir timing issue, not db-busy, so the T-350 retry
doesn't catch it. Both panes now re-fetch on ProjectOpened, which fires
after the IPC server swaps to the project workRoot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 20:38:44 +02:00
co-authored by Claude Opus 4.8
parent 7cea13c5c0
commit a0501b4a0a
8 changed files with 71 additions and 0 deletions
@@ -23,6 +23,7 @@ class _DecisionsViewState extends State<DecisionsView> {
StreamSubscription<Message>? _focusSub;
StreamSubscription<DaemonEvent>? _fileSub;
StreamSubscription<SchedulerTick>? _schedulerSub;
StreamSubscription<ProjectOpened>? _projectSub;
bool _refreshing = false;
bool _pendingRefresh = false;
@@ -37,6 +38,12 @@ class _DecisionsViewState extends State<DecisionsView> {
.where((e) => e.subsystem == 'files' && e.kind == 'files.changed' && _isDecisionPath(e.data['path'] as String? ?? ''))
.listen((_) => _refresh());
_schedulerSub = kernel.events.on<SchedulerTick>().where((e) => e.tier == SchedulerTier.oneMinute).listen((_) => _refresh());
// The first load can fire before the project's workspace is wired into
// the daemon (the boot workDir is the launch CWD, not the repo), so pql
// runs against the wrong/old DB and the list errors. Re-fetch once the
// workspace is actually open — ProjectOpened fires after the IPC server
// swaps to the project workRoot. (T-352)
_projectSub = kernel.events.on<ProjectOpened>().listen((_) => _refresh());
}
if (!_loading || _decisions.isNotEmpty) return;
unawaited(_load());
@@ -88,6 +95,7 @@ class _DecisionsViewState extends State<DecisionsView> {
_focusSub?.cancel();
_fileSub?.cancel();
_schedulerSub?.cancel();
_projectSub?.cancel();
super.dispose();
}
@@ -37,6 +37,7 @@ class _TicketsViewState extends State<TicketsView> {
StreamSubscription<Message>? _focusSub;
StreamSubscription<SchedulerTick>? _schedulerSub;
StreamSubscription<Message>? _changedSub;
StreamSubscription<ProjectOpened>? _projectSub;
bool _refreshing = false;
bool _pendingRefresh = false;
@@ -111,6 +112,12 @@ class _TicketsViewState extends State<TicketsView> {
}));
});
_schedulerSub = kernel.events.on<SchedulerTick>().where((e) => e.tier == SchedulerTier.oneMinute).listen((_) => _refresh());
// The first load can fire before the project's workspace is wired into
// the daemon (the boot workDir is the launch CWD, not the repo), so pql
// runs against the wrong/old DB and the list errors. Re-fetch once the
// workspace is actually open — the daemon's pql workDir is correct by
// then (ProjectOpened fires after the IPC server swaps). (T-352)
_projectSub = kernel.events.on<ProjectOpened>().listen((_) => _refresh());
}
if (!_loading || _tickets.isNotEmpty) return;
unawaited(_load());
@@ -121,6 +128,7 @@ class _TicketsViewState extends State<TicketsView> {
_focusSub?.cancel();
_changedSub?.cancel();
_schedulerSub?.cancel();
_projectSub?.cancel();
super.dispose();
}