Skip to content

Commit 10e73fe

Browse files
authored
🤖 fix: stabilize Sidebar/MultipleProjects story (#1148)
Fixes Storybook flakiness where **Sidebar > Multiple Projects** would sometimes render a chat input depending on leftover localStorage workspace selection. Changes: - Add an **App/Sidebar**-level decorator that clears the persisted selected workspace before each story render. - Refactor shared Storybook helpers by exporting and reusing `createOnChatAdapter` / `createGitStatusExecutor` (removes duplicated code in App demo/sidebar stories). Validation: - `make static-check` --- _Generated with `mux` • Model: `openai:gpt-5.2` • Thinking: `xhigh`_
1 parent 27aa56e commit 10e73fe

File tree

3 files changed

+29
-55
lines changed

3 files changed

+29
-55
lines changed

src/browser/stories/App.demo.stories.tsx

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,49 +17,22 @@ import {
1717
createStatusTool,
1818
createStaticChatHandler,
1919
createStreamingChatHandler,
20-
createGitStatusOutput,
2120
type GitStatusFixture,
2221
} from "./mockFactory";
23-
import { selectWorkspace, setWorkspaceInput, setWorkspaceModel } from "./storyHelpers";
22+
import {
23+
createGitStatusExecutor,
24+
createOnChatAdapter,
25+
type ChatHandler,
26+
selectWorkspace,
27+
setWorkspaceInput,
28+
setWorkspaceModel,
29+
} from "./storyHelpers";
2430
import { createMockORPCClient } from "../../../.storybook/mocks/orpc";
25-
import type { WorkspaceChatMessage } from "@/common/orpc/types";
26-
2731
export default {
2832
...appMeta,
2933
title: "App/Demo",
3034
};
3135

32-
type ChatHandler = (callback: (event: WorkspaceChatMessage) => void) => () => void;
33-
34-
/** Adapts callback-based chat handlers to ORPC onChat format */
35-
function createOnChatAdapter(chatHandlers: Map<string, ChatHandler>) {
36-
return (workspaceId: string, emit: (msg: WorkspaceChatMessage) => void) => {
37-
const handler = chatHandlers.get(workspaceId);
38-
if (handler) {
39-
return handler(emit);
40-
}
41-
queueMicrotask(() => emit({ type: "caught-up" }));
42-
return undefined;
43-
};
44-
}
45-
46-
/** Creates an executeBash function that returns git status output for workspaces */
47-
function createGitStatusExecutor(gitStatus: Map<string, GitStatusFixture>) {
48-
return (workspaceId: string, script: string) => {
49-
if (script.includes("git status") || script.includes("git show-branch")) {
50-
const status = gitStatus.get(workspaceId) ?? {};
51-
const output = createGitStatusOutput(status);
52-
return Promise.resolve({ success: true as const, output, exitCode: 0, wall_duration_ms: 50 });
53-
}
54-
return Promise.resolve({
55-
success: true as const,
56-
output: "",
57-
exitCode: 0,
58-
wall_duration_ms: 0,
59-
});
60-
};
61-
}
62-
6336
/**
6437
* Comprehensive story showing all sidebar indicators and chat features.
6538
*

src/browser/stories/App.sidebar.stories.tsx

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,33 +15,29 @@ import {
1515
createGitStatusOutput,
1616
type GitStatusFixture,
1717
} from "./mockFactory";
18-
import { expandProjects } from "./storyHelpers";
18+
import {
19+
clearWorkspaceSelection,
20+
createOnChatAdapter,
21+
type ChatHandler,
22+
expandProjects,
23+
} from "./storyHelpers";
1924
import { GIT_STATUS_INDICATOR_MODE_KEY } from "@/common/constants/storage";
2025
import { within, userEvent, waitFor } from "@storybook/test";
2126

2227
import { createMockORPCClient } from "../../../.storybook/mocks/orpc";
2328

24-
import type { WorkspaceChatMessage } from "@/common/orpc/types";
25-
2629
export default {
2730
...appMeta,
2831
title: "App/Sidebar",
32+
decorators: [
33+
(Story: () => JSX.Element) => {
34+
// Sidebar stories are about list organization; keep the main panel unselected.
35+
clearWorkspaceSelection();
36+
return <Story />;
37+
},
38+
],
2939
};
3040

31-
type ChatHandler = (callback: (event: WorkspaceChatMessage) => void) => () => void;
32-
33-
/** Adapts callback-based chat handlers to ORPC onChat format */
34-
function createOnChatAdapter(chatHandlers: Map<string, ChatHandler>) {
35-
return (workspaceId: string, emit: (msg: WorkspaceChatMessage) => void) => {
36-
const handler = chatHandlers.get(workspaceId);
37-
if (handler) {
38-
return handler(emit);
39-
}
40-
queueMicrotask(() => emit({ type: "caught-up" }));
41-
return undefined;
42-
};
43-
}
44-
4541
/**
4642
* Creates an executeBash function that returns deterministic git outputs for Storybook.
4743
*

src/browser/stories/storyHelpers.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ export function selectWorkspace(workspace: FrontendWorkspaceMetadata): void {
4545
);
4646
}
4747

48+
/** Clear workspace selection from localStorage (for sidebar-focused stories) */
49+
export function clearWorkspaceSelection(): void {
50+
localStorage.removeItem(SELECTED_WORKSPACE_KEY);
51+
}
52+
4853
/** Set input text for a workspace */
4954
export function setWorkspaceInput(workspaceId: string, text: string): void {
5055
localStorage.setItem(getInputKey(workspaceId), text);
@@ -98,7 +103,7 @@ export function createReview(
98103
// ═══════════════════════════════════════════════════════════════════════════════
99104

100105
/** Creates an executeBash function that returns git status output for workspaces */
101-
function createGitStatusExecutor(gitStatus?: Map<string, GitStatusFixture>) {
106+
export function createGitStatusExecutor(gitStatus?: Map<string, GitStatusFixture>) {
102107
return (workspaceId: string, script: string) => {
103108
if (script.includes("git status") || script.includes("git show-branch")) {
104109
const status = gitStatus?.get(workspaceId) ?? {};
@@ -118,10 +123,10 @@ function createGitStatusExecutor(gitStatus?: Map<string, GitStatusFixture>) {
118123
// CHAT HANDLER ADAPTER
119124
// ═══════════════════════════════════════════════════════════════════════════════
120125

121-
type ChatHandler = (callback: (event: WorkspaceChatMessage) => void) => () => void;
126+
export type ChatHandler = (callback: (event: WorkspaceChatMessage) => void) => () => void;
122127

123128
/** Adapts callback-based chat handlers to ORPC onChat format */
124-
function createOnChatAdapter(chatHandlers: Map<string, ChatHandler>) {
129+
export function createOnChatAdapter(chatHandlers: Map<string, ChatHandler>) {
125130
return (workspaceId: string, emit: (msg: WorkspaceChatMessage) => void) => {
126131
const handler = chatHandlers.get(workspaceId);
127132
if (handler) {

0 commit comments

Comments
 (0)