From f0871e83dc721e7ba9405dda2125e3bcc8e8265d Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Wed, 22 Oct 2025 22:36:16 -0400 Subject: [PATCH 1/8] =?UTF-8?q?=F0=9F=A4=96=20Use=20stable=20timestamps=20?= =?UTF-8?q?and=20version=20for=20visual=20testing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace Date.now() with stable timestamp in all story files - Mock VERSION in Storybook with stable values for Chromatic - Use Apple's iconic demo time: January 24, 2024, 9:41 AM PST - Ensures consistent visual snapshots across Chromatic runs --- .storybook/main.ts | 2 + .storybook/mocks/version.ts | 9 +++++ src/App.stories.tsx | 19 ++++++---- .../Messages/AssistantMessage.stories.tsx | 5 ++- .../Messages/ReasoningMessage.stories.tsx | 5 ++- .../Messages/StreamErrorMessage.stories.tsx | 5 ++- .../Messages/UserMessage.stories.tsx | 5 ++- src/components/TitleBar.stories.tsx | 38 +++++++++++++++++++ 8 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 .storybook/mocks/version.ts create mode 100644 src/components/TitleBar.stories.tsx diff --git a/.storybook/main.ts b/.storybook/main.ts index db423e756..d31e1ce17 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -19,6 +19,8 @@ const config: StorybookConfig = { resolve: { alias: { "@": path.join(process.cwd(), "src"), + // Mock version module for stable visual testing in Storybook + "@/version": path.join(process.cwd(), ".storybook/mocks/version.ts"), }, }, }); diff --git a/.storybook/mocks/version.ts b/.storybook/mocks/version.ts new file mode 100644 index 000000000..0caeade75 --- /dev/null +++ b/.storybook/mocks/version.ts @@ -0,0 +1,9 @@ +// Mock version for Storybook visual testing +// This ensures consistent snapshots in Chromatic +// Apple's classic demo time: 9:41 AM on January 24, 2024 +export const VERSION = { + git_commit: 'abc1234', + git_describe: 'v1.0.0', + buildTime: '2024-01-24T17:41:00Z', // 9:41 AM PST +}; + diff --git a/src/App.stories.tsx b/src/App.stories.tsx index 96a2b7c0b..05137c5ee 100644 --- a/src/App.stories.tsx +++ b/src/App.stories.tsx @@ -5,6 +5,9 @@ import type { ProjectConfig } from "./config"; import type { FrontendWorkspaceMetadata } from "./types/workspace"; import type { IPCApi } from "./types/ipc"; +// Stable timestamp for visual testing (Apple demo time: Jan 24, 2024, 9:41 AM PST) +const STABLE_TIMESTAMP = new Date('2024-01-24T09:41:00-08:00').getTime(); + // Mock window.api for App component function setupMockAPI(options: { projects?: Map; @@ -374,7 +377,7 @@ export const ActiveWorkspaceWithChat: Story = { parts: [{ type: "text", text: "Add authentication to the user API endpoint" }], metadata: { historySequence: 1, - timestamp: Date.now() - 300000, + timestamp: STABLE_TIMESTAMP - 300000, }, }); @@ -402,7 +405,7 @@ export const ActiveWorkspaceWithChat: Story = { ], metadata: { historySequence: 2, - timestamp: Date.now() - 290000, + timestamp: STABLE_TIMESTAMP - 290000, model: "claude-sonnet-4-20250514", usage: { inputTokens: 1250, @@ -420,7 +423,7 @@ export const ActiveWorkspaceWithChat: Story = { parts: [{ type: "text", text: "Yes, add JWT token validation" }], metadata: { historySequence: 3, - timestamp: Date.now() - 280000, + timestamp: STABLE_TIMESTAMP - 280000, }, }); @@ -452,7 +455,7 @@ export const ActiveWorkspaceWithChat: Story = { ], metadata: { historySequence: 4, - timestamp: Date.now() - 270000, + timestamp: STABLE_TIMESTAMP - 270000, model: "claude-sonnet-4-20250514", usage: { inputTokens: 2100, @@ -470,7 +473,7 @@ export const ActiveWorkspaceWithChat: Story = { parts: [{ type: "text", text: "Can you run the tests to make sure it works?" }], metadata: { historySequence: 5, - timestamp: Date.now() - 240000, + timestamp: STABLE_TIMESTAMP - 240000, }, }); @@ -502,7 +505,7 @@ export const ActiveWorkspaceWithChat: Story = { ], metadata: { historySequence: 6, - timestamp: Date.now() - 230000, + timestamp: STABLE_TIMESTAMP - 230000, model: "claude-sonnet-4-20250514", usage: { inputTokens: 2800, @@ -525,7 +528,7 @@ export const ActiveWorkspaceWithChat: Story = { ], metadata: { historySequence: 7, - timestamp: Date.now() - 180000, + timestamp: STABLE_TIMESTAMP - 180000, }, }); @@ -562,7 +565,7 @@ export const ActiveWorkspaceWithChat: Story = { ], metadata: { historySequence: 8, - timestamp: Date.now() - 170000, + timestamp: STABLE_TIMESTAMP - 170000, model: "claude-sonnet-4-20250514", usage: { inputTokens: 3500, diff --git a/src/components/Messages/AssistantMessage.stories.tsx b/src/components/Messages/AssistantMessage.stories.tsx index c22db4446..802c04bf4 100644 --- a/src/components/Messages/AssistantMessage.stories.tsx +++ b/src/components/Messages/AssistantMessage.stories.tsx @@ -3,6 +3,9 @@ import { AssistantMessage } from "./AssistantMessage"; import type { DisplayedMessage } from "@/types/message"; import { action } from "@storybook/addon-actions"; +// Stable timestamp for visual testing (Apple demo time: Jan 24, 2024, 9:41 AM PST) +const STABLE_TIMESTAMP = new Date('2024-01-24T09:41:00-08:00').getTime(); + const clipboardWriteText = (data: string) => { action("copy-text")(data); return Promise.resolve(); @@ -52,7 +55,7 @@ const createAssistantMessage = ( isStreaming: false, isPartial: false, isCompacted: false, - timestamp: Date.now(), + timestamp: STABLE_TIMESTAMP, model: "anthropic:claude-sonnet-4-5", ...overrides, }); diff --git a/src/components/Messages/ReasoningMessage.stories.tsx b/src/components/Messages/ReasoningMessage.stories.tsx index b8e3d13e9..a1177b2b1 100644 --- a/src/components/Messages/ReasoningMessage.stories.tsx +++ b/src/components/Messages/ReasoningMessage.stories.tsx @@ -2,6 +2,9 @@ import type { Meta, StoryObj } from "@storybook/react"; import { ReasoningMessage } from "./ReasoningMessage"; import type { DisplayedMessage } from "@/types/message"; +// Stable timestamp for visual testing (Apple demo time: Jan 24, 2024, 9:41 AM PST) +const STABLE_TIMESTAMP = new Date('2024-01-24T09:41:00-08:00').getTime(); + const meta = { title: "Messages/ReasoningMessage", component: ReasoningMessage, @@ -34,7 +37,7 @@ const createReasoningMessage = ( historySequence: 1, isStreaming: false, isPartial: false, - timestamp: Date.now(), + timestamp: STABLE_TIMESTAMP, ...overrides, }); diff --git a/src/components/Messages/StreamErrorMessage.stories.tsx b/src/components/Messages/StreamErrorMessage.stories.tsx index ec86cb9ef..bcfdbdb74 100644 --- a/src/components/Messages/StreamErrorMessage.stories.tsx +++ b/src/components/Messages/StreamErrorMessage.stories.tsx @@ -3,6 +3,9 @@ import { StreamErrorMessage } from "./StreamErrorMessage"; import type { DisplayedMessage } from "@/types/message"; import type { StreamErrorType } from "@/types/errors"; +// Stable timestamp for visual testing (Apple demo time: Jan 24, 2024, 9:41 AM PST) +const STABLE_TIMESTAMP = new Date('2024-01-24T09:41:00-08:00').getTime(); + const meta = { title: "Messages/StreamErrorMessage", component: StreamErrorMessage, @@ -35,7 +38,7 @@ const createStreamErrorMessage = ( error, errorType, historySequence: 1, - timestamp: Date.now(), + timestamp: STABLE_TIMESTAMP, ...overrides, }); diff --git a/src/components/Messages/UserMessage.stories.tsx b/src/components/Messages/UserMessage.stories.tsx index a74b9dc01..bd7a74c14 100644 --- a/src/components/Messages/UserMessage.stories.tsx +++ b/src/components/Messages/UserMessage.stories.tsx @@ -3,6 +3,9 @@ import { action } from "@storybook/addon-actions"; import { UserMessage } from "./UserMessage"; import type { DisplayedMessage } from "@/types/message"; +// Stable timestamp for visual testing (Apple demo time: Jan 24, 2024, 9:41 AM PST) +const STABLE_TIMESTAMP = new Date('2024-01-24T09:41:00-08:00').getTime(); + const clipboardWriteText = (data: string) => { action("copy-text")(data); return Promise.resolve(); @@ -42,7 +45,7 @@ const createUserMessage = ( historyId: "hist-1", content, historySequence: 1, - timestamp: Date.now(), + timestamp: STABLE_TIMESTAMP, ...overrides, }); diff --git a/src/components/TitleBar.stories.tsx b/src/components/TitleBar.stories.tsx new file mode 100644 index 000000000..ad45ee567 --- /dev/null +++ b/src/components/TitleBar.stories.tsx @@ -0,0 +1,38 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { TitleBar } from "./TitleBar"; + +const meta = { + title: "Components/TitleBar", + component: TitleBar, + parameters: { + layout: "fullscreen", + backgrounds: { + default: "dark", + values: [{ name: "dark", value: "#1e1e1e" }], + }, + }, + tags: ["autodocs"], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +/** + * The TitleBar shows the cmux version and build date. + * + * Note: The version displayed is generated at build time from git information. + * In Storybook, you'll see the current development version. + */ +export const Default: Story = {}; + +export const WithTelemetryDisabled: Story = { + parameters: { + docs: { + description: { + story: + "When telemetry is disabled, the update indicator shows a disabled state (⊘) and hovering explains updates are disabled.", + }, + }, + }, +}; + From 0005c12b3ce9d2b1e8825b43e093a1badefafa54 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Wed, 22 Oct 2025 22:38:53 -0400 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=A4=96=20Fix=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .storybook/mocks/version.ts | 6 +++--- src/App.stories.tsx | 2 +- src/components/Messages/AssistantMessage.stories.tsx | 2 +- src/components/Messages/ReasoningMessage.stories.tsx | 2 +- src/components/Messages/StreamErrorMessage.stories.tsx | 2 +- src/components/Messages/UserMessage.stories.tsx | 2 +- src/components/TitleBar.stories.tsx | 3 +-- 7 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.storybook/mocks/version.ts b/.storybook/mocks/version.ts index 0caeade75..04e48ceb0 100644 --- a/.storybook/mocks/version.ts +++ b/.storybook/mocks/version.ts @@ -2,8 +2,8 @@ // This ensures consistent snapshots in Chromatic // Apple's classic demo time: 9:41 AM on January 24, 2024 export const VERSION = { - git_commit: 'abc1234', - git_describe: 'v1.0.0', - buildTime: '2024-01-24T17:41:00Z', // 9:41 AM PST + git_commit: "abc1234", + git_describe: "v1.0.0", + buildTime: "2024-01-24T17:41:00Z", // 9:41 AM PST }; diff --git a/src/App.stories.tsx b/src/App.stories.tsx index 05137c5ee..0a44d052f 100644 --- a/src/App.stories.tsx +++ b/src/App.stories.tsx @@ -6,7 +6,7 @@ import type { FrontendWorkspaceMetadata } from "./types/workspace"; import type { IPCApi } from "./types/ipc"; // Stable timestamp for visual testing (Apple demo time: Jan 24, 2024, 9:41 AM PST) -const STABLE_TIMESTAMP = new Date('2024-01-24T09:41:00-08:00').getTime(); +const STABLE_TIMESTAMP = new Date("2024-01-24T09:41:00-08:00").getTime(); // Mock window.api for App component function setupMockAPI(options: { diff --git a/src/components/Messages/AssistantMessage.stories.tsx b/src/components/Messages/AssistantMessage.stories.tsx index 802c04bf4..0ffe8151f 100644 --- a/src/components/Messages/AssistantMessage.stories.tsx +++ b/src/components/Messages/AssistantMessage.stories.tsx @@ -4,7 +4,7 @@ import type { DisplayedMessage } from "@/types/message"; import { action } from "@storybook/addon-actions"; // Stable timestamp for visual testing (Apple demo time: Jan 24, 2024, 9:41 AM PST) -const STABLE_TIMESTAMP = new Date('2024-01-24T09:41:00-08:00').getTime(); +const STABLE_TIMESTAMP = new Date("2024-01-24T09:41:00-08:00").getTime(); const clipboardWriteText = (data: string) => { action("copy-text")(data); diff --git a/src/components/Messages/ReasoningMessage.stories.tsx b/src/components/Messages/ReasoningMessage.stories.tsx index a1177b2b1..e92d8c97f 100644 --- a/src/components/Messages/ReasoningMessage.stories.tsx +++ b/src/components/Messages/ReasoningMessage.stories.tsx @@ -3,7 +3,7 @@ import { ReasoningMessage } from "./ReasoningMessage"; import type { DisplayedMessage } from "@/types/message"; // Stable timestamp for visual testing (Apple demo time: Jan 24, 2024, 9:41 AM PST) -const STABLE_TIMESTAMP = new Date('2024-01-24T09:41:00-08:00').getTime(); +const STABLE_TIMESTAMP = new Date("2024-01-24T09:41:00-08:00").getTime(); const meta = { title: "Messages/ReasoningMessage", diff --git a/src/components/Messages/StreamErrorMessage.stories.tsx b/src/components/Messages/StreamErrorMessage.stories.tsx index bcfdbdb74..c26fd97e5 100644 --- a/src/components/Messages/StreamErrorMessage.stories.tsx +++ b/src/components/Messages/StreamErrorMessage.stories.tsx @@ -4,7 +4,7 @@ import type { DisplayedMessage } from "@/types/message"; import type { StreamErrorType } from "@/types/errors"; // Stable timestamp for visual testing (Apple demo time: Jan 24, 2024, 9:41 AM PST) -const STABLE_TIMESTAMP = new Date('2024-01-24T09:41:00-08:00').getTime(); +const STABLE_TIMESTAMP = new Date("2024-01-24T09:41:00-08:00").getTime(); const meta = { title: "Messages/StreamErrorMessage", diff --git a/src/components/Messages/UserMessage.stories.tsx b/src/components/Messages/UserMessage.stories.tsx index bd7a74c14..f876d69bc 100644 --- a/src/components/Messages/UserMessage.stories.tsx +++ b/src/components/Messages/UserMessage.stories.tsx @@ -4,7 +4,7 @@ import { UserMessage } from "./UserMessage"; import type { DisplayedMessage } from "@/types/message"; // Stable timestamp for visual testing (Apple demo time: Jan 24, 2024, 9:41 AM PST) -const STABLE_TIMESTAMP = new Date('2024-01-24T09:41:00-08:00').getTime(); +const STABLE_TIMESTAMP = new Date("2024-01-24T09:41:00-08:00").getTime(); const clipboardWriteText = (data: string) => { action("copy-text")(data); diff --git a/src/components/TitleBar.stories.tsx b/src/components/TitleBar.stories.tsx index ad45ee567..1272aa520 100644 --- a/src/components/TitleBar.stories.tsx +++ b/src/components/TitleBar.stories.tsx @@ -19,7 +19,7 @@ type Story = StoryObj; /** * The TitleBar shows the cmux version and build date. - * + * * Note: The version displayed is generated at build time from git information. * In Storybook, you'll see the current development version. */ @@ -35,4 +35,3 @@ export const WithTelemetryDisabled: Story = { }, }, }; - From a31b35cdfe4a0d9b9d53c6af7205d962d2f428c7 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Wed, 22 Oct 2025 22:41:34 -0400 Subject: [PATCH 3/8] =?UTF-8?q?=F0=9F=A4=96=20Remove=20TitleBar=20story?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/TitleBar.stories.tsx | 37 ----------------------------- 1 file changed, 37 deletions(-) delete mode 100644 src/components/TitleBar.stories.tsx diff --git a/src/components/TitleBar.stories.tsx b/src/components/TitleBar.stories.tsx deleted file mode 100644 index 1272aa520..000000000 --- a/src/components/TitleBar.stories.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { TitleBar } from "./TitleBar"; - -const meta = { - title: "Components/TitleBar", - component: TitleBar, - parameters: { - layout: "fullscreen", - backgrounds: { - default: "dark", - values: [{ name: "dark", value: "#1e1e1e" }], - }, - }, - tags: ["autodocs"], -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -/** - * The TitleBar shows the cmux version and build date. - * - * Note: The version displayed is generated at build time from git information. - * In Storybook, you'll see the current development version. - */ -export const Default: Story = {}; - -export const WithTelemetryDisabled: Story = { - parameters: { - docs: { - description: { - story: - "When telemetry is disabled, the update indicator shows a disabled state (⊘) and hovering explains updates are disabled.", - }, - }, - }, -}; From cddc7b43789d81e72d99213bc44fe596f5bf6519 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Wed, 22 Oct 2025 22:47:06 -0400 Subject: [PATCH 4/8] =?UTF-8?q?=F0=9F=A4=96=20Fix=20version=20mock=20alias?= =?UTF-8?q?=20order?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .storybook/main.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.storybook/main.ts b/.storybook/main.ts index d31e1ce17..75c271975 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -18,9 +18,10 @@ const config: StorybookConfig = { // Inherit project aliases resolve: { alias: { - "@": path.join(process.cwd(), "src"), // Mock version module for stable visual testing in Storybook + // MUST be before @ alias to take precedence "@/version": path.join(process.cwd(), ".storybook/mocks/version.ts"), + "@": path.join(process.cwd(), "src"), }, }, }); From fb23bf2b385d8f83330d2e15f9fdd7295b6dfeee Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Wed, 22 Oct 2025 22:51:13 -0400 Subject: [PATCH 5/8] =?UTF-8?q?=F0=9F=A4=96=20Try=20exact=20match=20for=20?= =?UTF-8?q?version=20alias?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .storybook/main.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.storybook/main.ts b/.storybook/main.ts index 75c271975..5b5428f87 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -18,10 +18,10 @@ const config: StorybookConfig = { // Inherit project aliases resolve: { alias: { - // Mock version module for stable visual testing in Storybook - // MUST be before @ alias to take precedence - "@/version": path.join(process.cwd(), ".storybook/mocks/version.ts"), "@": path.join(process.cwd(), "src"), + // Override version.ts with mock for stable visual testing + // Use absolute path with $ to ensure exact match + "@/version$": path.resolve(process.cwd(), ".storybook/mocks/version.ts"), }, }, }); From eea24f80dca084ce989c76804e0f26ec0ff2b67f Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Wed, 22 Oct 2025 22:51:51 -0400 Subject: [PATCH 6/8] =?UTF-8?q?=F0=9F=A4=96=20Mock=20version=20by=20overwr?= =?UTF-8?q?iting=20generated=20file=20in=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/chromatic.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index f925e615d..be4d583f9 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -24,6 +24,9 @@ jobs: - name: Generate version file run: ./scripts/generate-version.sh + - name: Mock version for stable visual testing + run: cp .storybook/mocks/version.ts src/version.ts + - name: Build Storybook run: bun x storybook build --stats-json From 0ebb50bd12d18ca25b849e53b1e0e667c67e4067 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Wed, 22 Oct 2025 22:59:34 -0400 Subject: [PATCH 7/8] =?UTF-8?q?=F0=9F=A4=96=20Remove=20unused=20version=20?= =?UTF-8?q?alias=20and=20document=20CI=20approach?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .storybook/main.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.storybook/main.ts b/.storybook/main.ts index 5b5428f87..9ffcdc644 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -19,9 +19,8 @@ const config: StorybookConfig = { resolve: { alias: { "@": path.join(process.cwd(), "src"), - // Override version.ts with mock for stable visual testing - // Use absolute path with $ to ensure exact match - "@/version$": path.resolve(process.cwd(), ".storybook/mocks/version.ts"), + // Note: VERSION mocking for stable visual testing is handled by overwriting + // src/version.ts in the Chromatic CI workflow, not via alias here }, }, }); From 9463c12b208ecdd70635270e0e82aead2c46509b Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Wed, 22 Oct 2025 23:05:23 -0400 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=A4=96=20Fix=20TipsCarousel=20rotatio?= =?UTF-8?q?n=20for=20stable=20visual=20testing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add fixedTipIndex prop to TipsCarousel to override automatic rotation in Storybook stories, ensuring consistent visual snapshots. --- src/components/TipsCarousel.stories.tsx | 8 ++++---- src/components/TipsCarousel.tsx | 9 +++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/components/TipsCarousel.stories.tsx b/src/components/TipsCarousel.stories.tsx index 148133b60..b68972a70 100644 --- a/src/components/TipsCarousel.stories.tsx +++ b/src/components/TipsCarousel.stories.tsx @@ -19,7 +19,7 @@ export default meta; type Story = StoryObj; export const Default: Story = { - render: () => , + render: () => , }; export const WithExplanation: Story = { @@ -28,7 +28,7 @@ export const WithExplanation: Story = {
Tips rotate automatically based on time. Hover to see the gradient effect:
- +
Tips change every hour to provide variety and convey UX information.
@@ -40,7 +40,7 @@ export const DebugControls: Story = { render: () => (
For debugging, you can use:
- +
window.setTip(0) // Show first tip
window.setTip(1) // Show second tip
@@ -65,7 +65,7 @@ export const InContext: Story = { justifyContent: "center", }} > - +
Mode: Plan diff --git a/src/components/TipsCarousel.tsx b/src/components/TipsCarousel.tsx index 8979c551a..647d18950 100644 --- a/src/components/TipsCarousel.tsx +++ b/src/components/TipsCarousel.tsx @@ -37,7 +37,12 @@ const TIPS = [ }, ]; -export const TipsCarousel: React.FC = () => { +interface TipsCarouselProps { + /** Override the automatic tip rotation (for testing/Storybook) */ + fixedTipIndex?: number; +} + +export const TipsCarousel: React.FC = ({ fixedTipIndex }) => { const [manualTipIndex, setManualTipIndex] = useState(null); // Calculate tip based on hours since epoch @@ -48,7 +53,7 @@ export const TipsCarousel: React.FC = () => { return hoursSinceEpoch % TIPS.length; }; - const currentTipIndex = manualTipIndex ?? calculateTipIndex(); + const currentTipIndex = fixedTipIndex ?? manualTipIndex ?? calculateTipIndex(); // Expose setTip to window for debugging useEffect(() => {