fix(web-shell): preserve artifact cards during reconnect (#13714)

Co-authored-by: 钉萁 <[email protected]>
This commit is contained in:
ytahdnand钉萁 authored and GitHub committed 2026-10-09 06:18:07 +00:00
1 parent b69338b143
commit 82ac13cc0e
2 files changed
+61 -1

No files matched your search

@@ -357,6 +357,66 @@ describe('useSessionArtifacts', () => {
]);
});
it('keeps cached artifacts visible and reconciles after reconnect with the same owner', async () => {
const reconcileLoad = deferred<{ artifacts: DaemonSessionArtifact[] }>();
sdkMock.actions.loadArtifacts
.mockResolvedValueOnce({ artifacts: [artifact('cached-artifact')] })
.mockReturnValueOnce(reconcileLoad.promise);
await renderHookHost();
const cachedArtifacts = latestState?.artifacts;
for (const status of ['disconnected', 'connecting']) {
sdkMock.connection.status = status;
await rerenderHookHost();
expect(latestState?.artifacts).toBe(cachedArtifacts);
expect(latestState?.artifactById.has('cached-artifact')).toBe(true);
expect(latestState?.loading).toBe(false);
expect(sdkMock.actions.loadArtifacts).toHaveBeenCalledOnce();
}
sdkMock.connection.status = 'connected';
await rerenderHookHost();
expect(latestState?.artifacts).toBe(cachedArtifacts);
expect(latestState?.hydrated).toBe(true);
expect(sdkMock.actions.loadArtifacts).toHaveBeenCalledTimes(2);
await act(async () => {
reconcileLoad.resolve({ artifacts: [artifact('reconciled-artifact')] });
await reconcileLoad.promise;
});
expect(latestState?.artifacts.map((item) => item.id)).toEqual([
'reconciled-artifact',
]);
expect(latestState?.hydrated).toBe(true);
});
it.each(['session', 'workspace'])(
'does not show another %s artifact cache while disconnected',
async (scope) => {
Object.assign(sdkMock.connection, { workspaceCwd: '/workspace/a' });
sdkMock.actions.loadArtifacts.mockResolvedValueOnce({
artifacts: [artifact('cached-artifact')],
});
await renderHookHost();
expect(latestState?.artifactById.has('cached-artifact')).toBe(true);
sdkMock.connection.status = 'disconnected';
Object.assign(
sdkMock.connection,
scope === 'session'
? { sessionId: 'session-b' }
: { workspaceCwd: '/workspace/b' },
);
sdkMock.ownerVersion += 1;
await rerenderHookHost();
expect(latestState?.artifacts).toEqual([]);
expect(latestState?.artifactById.size).toBe(0);
expect(latestState?.hydrated).toBe(false);
expect(sdkMock.actions.loadArtifacts).toHaveBeenCalledOnce();
},
);
it('keeps an empty result visible while refreshing the same session', async () => {
const initialLoad = deferred<{ artifacts: DaemonSessionArtifact[] }>();
const refreshLoad = deferred<{ artifacts: DaemonSessionArtifact[] }>();
@@ -139,7 +139,7 @@ export function useSessionArtifacts(
}, [artifactsVersion, refresh]);
const visibleArtifacts =
sessionKey && isConnected && supportsArtifacts
sessionKey && supportsArtifacts
? (artifactsBySessionRef.current.get(sessionKey)?.artifacts ??
EMPTY_ARTIFACTS)
: EMPTY_ARTIFACTS;