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
@@ -546,6 +546,29 @@ void main() {
});
});
group('DecisionsView — workspace open triggers reload (T-352)', () {
testWidgets('ProjectOpened triggers _refresh', (tester) async {
// The first load can fire before the daemon's pql workDir is the repo; a
// ProjectOpened (fired after the IPC server swaps) must re-fetch.
int listCallCount = 0;
f.ipc.stub('pql.decisions.sync', (_) async => _ok(const {}));
f.ipc.stub('pql.decisions.list', (_) async {
listCallCount++;
return _ok({
'decisions': [_decision(id: 'D-$listCallCount', title: 'open $listCallCount')],
});
});
await pumpView(tester);
expect(listCallCount, 1);
f.services.events.emit(const ProjectOpened(path: '/repo'));
await pumpAsync(tester);
expect(listCallCount, greaterThanOrEqualTo(2));
});
});
group('DecisionsView — concurrent refresh guard', () {
testWidgets('second refresh while one is running sets _pendingRefresh', (tester) async {
final Completer<IpcResponse> firstListCompleter = Completer();
@@ -137,6 +137,21 @@ void main() {
expect(calls, greaterThan(before));
});
testWidgets('refetches when the workspace opens (T-352)', (tester) async {
// The first load can fire before the daemon's pql workDir is the repo; a
// ProjectOpened (fired after the IPC server swaps) must re-fetch.
var calls = 0;
f.ipc.stub('pql.tickets.list', (_) async {
calls++;
return _list([_t('T-1', 'Thing', 'backlog')]);
});
await pumpView(tester);
final before = calls;
f.services.events.emit(const ProjectOpened(path: '/repo'));
await pumpAsync(tester);
expect(calls, greaterThan(before));
});
// -- Type-filter chips (T-343) ------------------------------------------
// The chips own one GestureDetector for both onTap (toggle) and onDoubleTap
// (solo), so a single tap's onTap only fires after the ~300ms double-tap