From f1d46d595a0e6b0205670ae350a52f46a7481359 Mon Sep 17 00:00:00 2001 From: asher <82265836+bytelazy@users.noreply.github.com> Date: Wed, 2 Sep 2026 11:35:20 +0800 Subject: [PATCH 1/2] fix(desktop): use config-relative glob so desktop stories load on Windows The desktop stories entry was built with resolve(REPO_ROOT, ...), which produces a backslash absolute path on Windows. Glob matchers treat backslashes as escape characters, so the pattern matched nothing and the desktop stories were silently dropped from the index (53 entries instead of 251). The neighboring packages/ui entry is a forward-slash, config-relative glob, which is why only the UI stories kept working. Use the same config-relative form for the desktop entry so the glob matches on all platforms. Fixes #4516 Generated-by: Claude (Claude Code) --- apps/desktop/.storybook/main.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/desktop/.storybook/main.ts b/apps/desktop/.storybook/main.ts index 8aa012a7dd..b1355ffd84 100644 --- a/apps/desktop/.storybook/main.ts +++ b/apps/desktop/.storybook/main.ts @@ -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', From 66d0398e64499fd32ac679036c45fc0e1659c4f6 Mon Sep 17 00:00:00 2001 From: asher <82265836+bytelazy@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:33:23 +0800 Subject: [PATCH 2/2] fix(desktop): treat unavailable collaboration authority as an empty inbox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The turn-request inbox polls every owner Runtime Host on a 2s interval. A Host with no collaboration authority (e.g. the default Local Host) rejects each query with operation_unavailable, and when every Host rejected, collectAvailablePendingTurnRequests threw an AggregateError. The renderer caught it and retried, so Electron logged the same rejected IPC handler call every two seconds after startup — an unbounded stream of identical errors that buries real diagnostics. An unavailable collaboration capability is a valid Host composition, not a failure, so resolve the inbox as empty when no Host answered instead of throwing. The poller then keeps a quiet, empty inbox and repopulates it as soon as a capable Host appears. Mixed-capability setups are unchanged: a Host that still answers keeps contributing its requests. Fixes #4522 Generated-by: Claude (Claude Code) --- ...me-host-turn-request-inbox-preload.test.ts | 20 +++++++++++-------- .../runtime-host-turn-request-inbox.ts | 11 +++++----- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/apps/desktop/src/main/__tests__/runtime-host-turn-request-inbox-preload.test.ts b/apps/desktop/src/main/__tests__/runtime-host-turn-request-inbox-preload.test.ts index 267504c866..b188c23f6a 100644 --- a/apps/desktop/src/main/__tests__/runtime-host-turn-request-inbox-preload.test.ts +++ b/apps/desktop/src/main/__tests__/runtime-host-turn-request-inbox-preload.test.ts @@ -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, []); }); diff --git a/apps/desktop/src/preload/runtime-host-turn-request-inbox.ts b/apps/desktop/src/preload/runtime-host-turn-request-inbox.ts index 86199b9187..6da5ed1667 100644 --- a/apps/desktop/src/preload/runtime-host-turn-request-inbox.ts +++ b/apps/desktop/src/preload/runtime-host-turn-request-inbox.ts @@ -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 + // `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));