Skip to content
Draft
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
18 changes: 18 additions & 0 deletions apps/desktop/src/main/__tests__/about-settings-page.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { AstryxLocaleProvider, LocaleProvider, ToastProvider } from '@maka/ui';
import { AboutSettingsPage } from '../../renderer/settings/about-settings-page.js';

test('keeps manual diagnostics available while About metadata is pending', () => {
const page = createElement(AboutSettingsPage, {});
const withToasts = createElement(ToastProvider, { children: page });
const withAstryxLocale = createElement(AstryxLocaleProvider, { children: withToasts });
const markup = renderToStaticMarkup(
createElement(LocaleProvider, { locale: 'en', children: withAstryxLocale }),
);

assert.match(markup, />Copy diagnostics</);
assert.match(markup, /role="status"[^>]*aria-busy="true"/);
});
44 changes: 44 additions & 0 deletions apps/desktop/src/main/__tests__/app-shell-command-actions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { resolveManualDiagnosticTarget } from '../../renderer/app-shell-command-actions.js';

test('targets manual diagnostics to the current task or new-task Host profile', () => {
assert.deepEqual(
resolveManualDiagnosticTarget(
{ navSection: 'sessions', sessionId: '["remote-host","session-1"]' },
'new-task-profile',
),
{ kind: 'session', sessionId: '["remote-host","session-1"]' },
);
assert.deepEqual(
resolveManualDiagnosticTarget(
{ navSection: 'sessions', sessionId: undefined },
'new-task-profile',
),
{ kind: 'profile', profileId: 'new-task-profile' },
);
assert.equal(
resolveManualDiagnosticTarget(
{ navSection: 'extensions', sessionId: undefined },
'new-task-profile',
),
undefined,
);
assert.deepEqual(
resolveManualDiagnosticTarget(
{ navSection: 'sessions', sessionId: '["hidden-host","hidden-session"]' },
'hidden-new-task-profile',
true,
'settings-profile',
),
{ kind: 'profile', profileId: 'settings-profile' },
);
assert.equal(
resolveManualDiagnosticTarget(
{ navSection: 'sessions', sessionId: '["hidden-host","hidden-session"]' },
'hidden-new-task-profile',
true,
),
undefined,
);
});
53 changes: 53 additions & 0 deletions apps/desktop/src/main/__tests__/app-shell-project-actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,59 @@ test('remote Project capabilities do not dispatch Client-local actions', async (
}
});

test('Project errors preserve the Host authority of the failed operation', async () => {
const actionsModule = await importProjectActions();
const previousWindow = globalThis.window;
const diagnosticTargets: unknown[] = [];
globalThis.window = {
maka: {
app: {
openPath: async () => {
throw new Error('unavailable');
},
},
},
} as unknown as Window & typeof globalThis;

try {
const actions = actionsModule.createAppShellProjectActions({
uiLocale: 'en',
projectPickerPendingRef: { current: false },
projectPickerRequestRef: { current: 0 },
rendererMountedRef: { current: true },
setProjectPickerPending: () => {},
refreshDefaultProjectState: async () => [],
selectedProjectId: null,
projects: [],
projectCapabilities: {
chooseClientDirectory: false,
chooseHostDirectory: false,
selectNoProject: false,
setLocalDefault: false,
viewClientPath: false,
},
sessionId: 'session-key',
onProjectSelected: () => {},
toastApi: {
success: () => {},
error: (_title, _description, _details, target) => {
diagnosticTargets.push(target);
},
},
});

await actions.openWorkspaceFolder();
await actions.openProjectFolder();

assert.deepEqual(diagnosticTargets, [
undefined,
{ sessionId: 'session-key' },
]);
} finally {
globalThis.window = previousWindow;
}
});

async function importProjectActions(): Promise<typeof ProjectActions> {
const outdir = await mkdtemp(resolve(REPO_ROOT, 'apps/desktop/dist/main/__tests__/project-actions-'));
const outfile = resolve(outdir, 'app-shell-project-actions.mjs');
Expand Down
29 changes: 26 additions & 3 deletions apps/desktop/src/main/__tests__/app-shell-session-purge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ function installWindow(
harness: SweepHarness,
options: {
rejectIds?: readonly string[];
rejectWithUndefinedIds?: readonly string[];
surviving?: readonly SessionSummary[];
/** Runs after each accepted removal, to model what another client did meanwhile. */
onRemove?: (sessionId: string) => void;
Expand All @@ -67,6 +68,9 @@ function installWindow(
sessions: {
remove: async (id: string, removeOptions?: { requireArchived?: boolean }) => {
harness.removeOptions.push([id, removeOptions?.requireArchived === true]);
if (options.rejectWithUndefinedIds?.includes(id)) {
return Promise.reject(undefined);
}
if (options.rejectIds?.includes(id)) throw new Error(`busy:${id}`);
const target = options.catalog?.find((session) => session.id === id);
if (removeOptions?.requireArchived && target && !target.isArchived) return 'restored';
Expand Down Expand Up @@ -151,7 +155,7 @@ describe('purgeSessions', () => {
remaining: [],
restored: [],
verified: true,
firstError: undefined,
firstFailure: undefined,
});
// Every delete in a sweep carries the archived premise the confirm named.
assert.deepEqual(h.removeOptions, [
Expand Down Expand Up @@ -226,7 +230,7 @@ describe('purgeSessions', () => {
const outcome = await actions.purgeSessions(['first', 'rescued']).finally(restore);

assert.deepEqual(outcome.restored, ['rescued']);
assert.equal(outcome.firstError, undefined);
assert.equal(outcome.firstFailure, undefined);
// A task that is still there keeps its renderer state, including being the
// open one.
assert.deepEqual(h.cleared, ['first']);
Expand Down Expand Up @@ -296,7 +300,26 @@ describe('purgeSessions', () => {
assert.equal(h.listCalls, 1);
assert.deepEqual(outcome.remaining, ['survivor']);
assert.equal(outcome.removed, 1);
assert.equal((outcome.firstError as Error).message, 'busy:committed');
assert.ok(outcome.firstFailure);
assert.equal((outcome.firstFailure.error as Error).message, 'busy:committed');
assert.equal(outcome.firstFailure.sessionId, 'committed');
});

it('retains the first failing Session even when the rejection value is undefined', async () => {
const h = harness();
const sessions = [summary('first'), summary('second')];
const restore = installWindow(h, {
rejectWithUndefinedIds: ['first'],
rejectIds: ['second'],
surviving: sessions,
});
const actions = createActions({ harness: h, sessions, activeIdRef: { current: undefined } });

const outcome = await actions.purgeSessions(['first', 'second']).finally(restore);

assert.ok(outcome.firstFailure);
assert.equal(outcome.firstFailure.sessionId, 'first');
assert.equal(outcome.firstFailure.error, undefined);
});

it('claims nothing when the catalog cannot be read back', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ function createHarness(options: {
const permissionCalls: string[] = [];
const thinkingCalls: string[] = [];
const errors: string[] = [];
const errorTargets: Array<{ sessionId: string } | undefined> = [];
const successes: Array<{ title: string; description?: string }> = [];
const newTaskPermissionModes: string[] = [];
const modelResult = deferred<DesktopSessionSummary>();
Expand Down Expand Up @@ -99,7 +100,10 @@ function createHarness(options: {
},
toastApi: {
success: (title, description) => successes.push({ title, description }),
error: (title) => errors.push(title),
error: (title, _description, _details, target) => {
errors.push(title);
errorTargets.push(target);
},
confirm: options.confirm ?? (async () => true),
},
});
Expand All @@ -108,6 +112,7 @@ function createHarness(options: {
actions,
activeIdRef,
errors,
errorTargets,
modelCalls,
modelResult,
newTaskPermissionModes,
Expand Down Expand Up @@ -275,6 +280,7 @@ describe('AppShell session settings actions', () => {
assert.equal(harness.pending.has('session-a'), false);
assert.equal(harness.pendingBySession['session-a'], undefined);
assert.equal(harness.errors.length, 1);
assert.deepEqual(harness.errorTargets, [{ sessionId: 'session-a' }]);

const modelChange = harness.actions.setSessionModel({
llmConnectionSlug: 'e2e',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { diagnosticInputForErrorToast } from '../../renderer/app-shell-toast-diagnostics.js';

test('preserves stable Host targets independently from optional execution evidence', () => {
assert.deepEqual(
diagnosticInputForErrorToast({
title: 'Client failure',
}),
{
surface: 'toast',
title: 'Client failure',
},
);
assert.deepEqual(
diagnosticInputForErrorToast({
title: 'Task operation failed',
diagnosticTarget: { sessionId: '["remote-host","session-1"]' },
}),
{
surface: 'toast',
title: 'Task operation failed',
target: { kind: 'session', sessionId: '["remote-host","session-1"]' },
},
);
assert.deepEqual(
diagnosticInputForErrorToast({
title: 'New task failed',
diagnosticTarget: { profileId: 'remote-profile' },
}),
{
surface: 'toast',
title: 'New task failed',
target: { kind: 'profile', profileId: 'remote-profile' },
},
);
assert.deepEqual(
diagnosticInputForErrorToast({
title: 'Turn failed',
diagnosticTarget: {
sessionId: '["remote-host","session-1"]',
turnId: 'turn-1',
eventId: 'event-1',
},
}),
{
surface: 'toast',
title: 'Turn failed',
execution: {
sessionId: '["remote-host","session-1"]',
turnId: 'turn-1',
eventId: 'event-1',
},
},
);
});
Loading