Skip to content
Closed
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
6 changes: 5 additions & 1 deletion apps/desktop/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ const STORYBOOK_NODE_CRYPTO_BOUNDARY = resolve(
const config: StorybookConfig = {
stories: [
'../../../packages/ui/stories/**/*.stories.@(ts|tsx)',
resolve(REPO_ROOT, 'apps/desktop/stories/**/*.stories.@(ts|tsx)'),
// Config-relative glob (forward slashes) like the UI entry above. A
// `resolve(REPO_ROOT, ...)` absolute path produces backslashes on Windows,
// which glob matchers treat as escape characters, so no desktop story ever
// matches there.
'../stories/**/*.stories.@(ts|tsx)',
],
framework: {
name: '@storybook/react-vite',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,16 @@ test('keeps available collaboration inboxes when another Owner Host rejects', as
assert.deepEqual(requests.map(({ requestId }) => requestId), ['earlier', 'later']);
});

test('retains the previous inbox projection when every Owner Host rejects', async () => {
await assert.rejects(
collectAvailablePendingTurnRequests([
Promise.reject(new Error('first unavailable')),
Promise.reject(new Error('second unavailable')),
]),
/Every Runtime Host collaboration inbox request failed/,
);
test('returns an empty inbox when every Owner Host rejects', async () => {
// A Host without a collaboration authority (e.g. the default Local Host)
// rejects each query with `operation_unavailable`. That is a valid
// composition, not an error, so the inbox resolves empty rather than
// throwing — the poller keeps the quiet, empty inbox instead of logging an
// IPC failure every interval.
const requests = await collectAvailablePendingTurnRequests([
Promise.reject(new Error('first unavailable')),
Promise.reject(new Error('second unavailable')),
]);

assert.deepEqual(requests, []);
});
11 changes: 5 additions & 6 deletions apps/desktop/src/preload/runtime-host-turn-request-inbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@ export async function collectAvailablePendingTurnRequests(
const available = results.flatMap(
(result) => result.status === 'fulfilled' ? [result.value] : [],
);
if (queries.length > 0 && available.length === 0) {
throw new AggregateError(
results.flatMap((result) => result.status === 'rejected' ? [result.reason] : []),
'Every Runtime Host collaboration inbox request failed',
);
}
// A Host with no collaboration authority rejects every inbox query with

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.

By the time this runs, Electron has already printed Error occurred in handler for ... in the main process for each rejected invoke. The degrade has to happen in the ipcMain.handle callback in runtime-host-collaboration-ipc-main.ts, and only for operation_unavailable; every other rejection should keep throwing so the renderer keeps its last projection.

// `operation_unavailable`. That is a valid composition (e.g. the default
// Local Host), not a failure, so when no Host answered we surface an empty
// inbox instead of throwing — the next poll repopulates it once a capable
// Host appears.
return available
.flat()
.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
Expand Down