Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/desktop/renderer-architecture.json
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,7 @@
"useNewTaskChoice": 1,
"useOnboardingSnapshot": 1,
"usePlanModeState": 1,
"useRef": 14,
"useRef": 13,
"useSessionEventHealthPolling": 1,
"useSessionNavigationReads": 1,
"useSessionSettingIntent": 1,
Expand Down Expand Up @@ -786,7 +786,7 @@
"react": 1
},
"importSpecifiers": 92,
"nonTriviaTokens": 12392
"nonTriviaTokens": 12385
},
"src/renderer/use-app-shell-session-list.ts": {
"importDeclarations": 4,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ describe('bootstrap selection lease', () => {
assert.equal(state.activeId(), undefined);
});

it('skips an archived bootstrap-owned selection', () => {
const state = harness('archived');
assert.equal(
state.lease.reconcile([session('archived', 2, true), session('active', 1)]),
true,
);
assert.equal(state.activeId(), 'active');
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 — This case already passes on the merge base: the archived-candidate filter landed in #4980 (bootstrap-selection-lease.ts:45), and this PR does not touch that file. Harmless extra coverage, but it doesn't satisfy "Tests cover the change and fail without it". Consider dropping it or noting in the description that it only backfills coverage.


it('does not reconcile after release', () => {
const state = harness();
state.lease.release();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type { SessionSummary } from '@maka/core/session';
import type { PlanControlIpcResult } from '../../shared/plan-mode-ipc.js';
import { AstryxLocaleProvider, LocaleProvider, ToastProvider } from '@maka/ui';
import { usePlanModeState, type PlanModeState } from '../../renderer/plan-mode-panel.js';
import { createSessionCatalogController } from '../../renderer/application/contracts/session-catalog/session-catalog-state.js';

const originalGlobals = {
document: globalThis.document,
Expand Down Expand Up @@ -96,9 +97,10 @@ test('plan controls stay pending until the bridge promise settles', async () =>
const root = createRoot(container);
mountedRoot = root;

const catalog = createSessionCatalogController();
let controller: PlanModeState | undefined;
function Harness() {
controller = usePlanModeState({ id: 'session-1' } as SessionSummary);
controller = usePlanModeState({ id: 'session-1' } as SessionSummary, catalog);
return null;
}
await act(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ function ports(
): SessionNavigationPorts {
return {
sessionsRef: { current: sessions },
pendingSessionRowActionsRef: { current: new Set<string>() },
acquireAutomaticQueryBlock: () => ({ release: () => undefined }),
activateSession: (sessionId) => calls.push(`activate:${sessionId ?? 'none'}`),
clearSessionRendererState: (sessionId) => calls.push(`clear:${sessionId}`),
refreshSessions: async () => sessions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ function installService(
function createActions(h: Harness, service: SessionNavigationSessionService) {
return createSessionNavigationRowActions({
uiLocale: 'en',
acquireAutomaticQueryBlock: () => ({ release: () => undefined }),
clearSessionRendererState: () => undefined,
pendingSessionRowActionsRef: { current: new Set<string>() },
refreshSessions: async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,16 @@ describe('revision-family session row actions', () => {
const branch = summary('branch', { parentSessionId: 'root', branchOfTurnId: 'turn-1' });
const actions = createSessionNavigationRowActions({
uiLocale: 'en',
acquireAutomaticQueryBlock: (ids) => {
calls.push(`acquire:${ids.join(',')}`);
return { release: () => calls.push('release') };
},
clearSessionRendererState: (id) => { cleared.push(id); },
pendingSessionRowActionsRef: { current: new Set<string>() },
refreshSessions: async () => [root, version, branch],
refreshSessions: async () => {
calls.push('refresh');
return [root, version, branch];
},
service: createService(calls),
sessionsRef: { current: [root, version, branch] },
toastApi: {
Expand All @@ -113,16 +120,98 @@ describe('revision-family session row actions', () => {

assert.deepEqual(calls, [
'flag:version:true:true',
'refresh',
'rename:branch:Independent branch:true',
'refresh',
'acquire:root,version',
'archive:version:true',
'refresh',
'release',
// The delete asks the Host how many subtasks it would archive before the
// confirm, then removes.
'preview:root',
'acquire:root,version',
// `root` is not archived, so the delete states no archived premise —
// requiring one would refuse every delete from the rail.
'remove:root:true:false',
'refresh',
'release',
]);
assert.deepEqual(cleared, ['root', 'version', 'root', 'version']);

const service = createService(calls);
service.archive = async () => { throw new Error('archive failed'); };
const failingActions = createSessionNavigationRowActions({
uiLocale: 'en',
acquireAutomaticQueryBlock: (ids) => {
calls.push(`acquire:${ids.join(',')}`);
return { release: () => calls.push('release') };
},
clearSessionRendererState: () => undefined,
pendingSessionRowActionsRef: { current: new Set<string>() },
refreshSessions: async () => [],
service,
sessionsRef: { current: [root, version] },
toastApi: {
success: () => undefined,
error: () => undefined,
confirm: async () => true,
},
});
await failingActions.archiveSession('root');
assert.deepEqual(calls.slice(-2), ['acquire:root,version', 'release']);
});

it('holds one query block through a bulk archive refresh', async () => {
const calls: string[] = [];
const root = summary('root');
const version = summary('version', {
revisionRootSessionId: 'root',
revisionParentSessionId: 'root',
});
const other = summary('other');
let rejectRefresh = false;
const actions = createSessionNavigationRowActions({
uiLocale: 'en',
acquireAutomaticQueryBlock: (ids) => {
calls.push(`acquire:${ids.join(',')}`);
return { release: () => calls.push('release') };
},
clearSessionRendererState: () => undefined,
pendingSessionRowActionsRef: { current: new Set<string>() },
refreshSessions: async () => {
calls.push('refresh');
if (rejectRefresh) throw new Error('refresh failed');
return [];
},
service: createService(calls),
sessionsRef: { current: [root, version, other] },
toastApi: {
success: () => undefined,
error: () => undefined,
confirm: async () => true,
},
});

await actions.archiveSelected(['version', 'other']);

assert.deepEqual(calls, [
'acquire:root,version,other',
'archive:version:true',
'archive:other:true',
'refresh',
'release',
]);

calls.length = 0;
rejectRefresh = true;
await assert.rejects(actions.archiveSelected(['other']), /refresh failed/);
assert.deepEqual(calls, [
'acquire:other',
'archive:other:true',
'refresh',
'release',
]);
});
});

Expand All @@ -135,8 +224,12 @@ function deleteHarness(
const calls: string[] = [];
const confirms: Array<{ title: string; description: string }> = [];
const successes: Array<{ title: string; description?: string }> = [];
let leaseReleased = false;
const actions = createSessionNavigationRowActions({
uiLocale: 'en',
acquireAutomaticQueryBlock: () => ({
release: () => { leaseReleased = true; },
}),
clearSessionRendererState: () => undefined,
pendingSessionRowActionsRef: { current: new Set<string>() },
refreshSessions: async () => [...sessions],
Expand All @@ -148,7 +241,7 @@ function deleteHarness(
confirm: async (options) => { confirms.push({ title: options.title, description: options.description }); return true; },
},
});
return { actions, calls, confirms, successes };
return { actions, calls, confirms, successes, wasLeaseReleased: () => leaseReleased };
}

describe('delete confirm warns off the Host preview, toast reports the Host count', () => {
Expand Down Expand Up @@ -213,7 +306,7 @@ describe('delete confirm warns off the Host preview, toast reports the Host coun
});

it('stays silent on the toast when a concurrent restore calls the delete off', async () => {
const { actions, confirms, successes } = deleteHarness(
const { actions, confirms, successes, wasLeaseReleased } = deleteHarness(
[summary('parent', { name: 'hi' })],
'restored',
0,
Expand All @@ -226,5 +319,6 @@ describe('delete confirm warns off the Host preview, toast reports the Host coun
assert.match(confirms[0].description, /kept and moved to Archived/);
// But nothing was deleted, so nothing moved to the archive.
assert.deepEqual(successes, [{ title: 'hi was restored, so it was kept', description: undefined }]);
assert.equal(wasLeaseReleased(), true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ function createActions(input: {
}) {
return createSessionNavigationRowActions({
uiLocale: 'en',
acquireAutomaticQueryBlock: () => ({ release: () => undefined }),
clearSessionRendererState: (id) => {
input.harness.cleared.push(id);
},
Expand Down
Loading