Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .github/workflows/chromatic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const config: StorybookConfig = {
resolve: {
alias: {
"@": path.join(process.cwd(), "src"),
// Note: VERSION mocking for stable visual testing is handled by overwriting
// src/version.ts in the Chromatic CI workflow, not via alias here
},
},
});
Expand Down
9 changes: 9 additions & 0 deletions .storybook/mocks/version.ts
Original file line number Diff line number Diff line change
@@ -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
};

19 changes: 11 additions & 8 deletions src/App.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, ProjectConfig>;
Expand Down Expand Up @@ -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,
},
});

Expand Down Expand Up @@ -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,
Expand All @@ -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,
},
});

Expand Down Expand Up @@ -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,
Expand All @@ -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,
},
});

Expand Down Expand Up @@ -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,
Expand All @@ -525,7 +528,7 @@ export const ActiveWorkspaceWithChat: Story = {
],
metadata: {
historySequence: 7,
timestamp: Date.now() - 180000,
timestamp: STABLE_TIMESTAMP - 180000,
},
});

Expand Down Expand Up @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion src/components/Messages/AssistantMessage.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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,
});
Expand Down
5 changes: 4 additions & 1 deletion src/components/Messages/ReasoningMessage.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -34,7 +37,7 @@ const createReasoningMessage = (
historySequence: 1,
isStreaming: false,
isPartial: false,
timestamp: Date.now(),
timestamp: STABLE_TIMESTAMP,
...overrides,
});

Expand Down
5 changes: 4 additions & 1 deletion src/components/Messages/StreamErrorMessage.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -35,7 +38,7 @@ const createStreamErrorMessage = (
error,
errorType,
historySequence: 1,
timestamp: Date.now(),
timestamp: STABLE_TIMESTAMP,
...overrides,
});

Expand Down
5 changes: 4 additions & 1 deletion src/components/Messages/UserMessage.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -42,7 +45,7 @@ const createUserMessage = (
historyId: "hist-1",
content,
historySequence: 1,
timestamp: Date.now(),
timestamp: STABLE_TIMESTAMP,
...overrides,
});

Expand Down
8 changes: 4 additions & 4 deletions src/components/TipsCarousel.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
render: () => <TipsCarousel />,
render: () => <TipsCarousel fixedTipIndex={0} />,
};

export const WithExplanation: Story = {
Expand All @@ -28,7 +28,7 @@ export const WithExplanation: Story = {
<div className="text-[13px] text-[#cccccc] font-primary">
Tips rotate automatically based on time. Hover to see the gradient effect:
</div>
<TipsCarousel />
<TipsCarousel fixedTipIndex={0} />
<div className="text-[11px] text-[#808080] font-primary">
Tips change every hour to provide variety and convey UX information.
</div>
Expand All @@ -40,7 +40,7 @@ export const DebugControls: Story = {
render: () => (
<div className="flex flex-col gap-5 p-5 bg-[#1e1e1e] min-w-[500px]">
<div className="text-[13px] text-[#cccccc] font-primary">For debugging, you can use:</div>
<TipsCarousel />
<TipsCarousel fixedTipIndex={1} />
<div className="text-[11px] text-[#808080] font-monospace p-3 bg-[#2d2d30] rounded">
<div>window.setTip(0) // Show first tip</div>
<div>window.setTip(1) // Show second tip</div>
Expand All @@ -65,7 +65,7 @@ export const InContext: Story = {
justifyContent: "center",
}}
>
<TipsCarousel />
<TipsCarousel fixedTipIndex={0} />
</div>
<div className="flex items-center gap-2">
<span className="text-[11px] text-[#808080]">Mode: Plan</span>
Expand Down
9 changes: 7 additions & 2 deletions src/components/TipsCarousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<TipsCarouselProps> = ({ fixedTipIndex }) => {
const [manualTipIndex, setManualTipIndex] = useState<number | null>(null);

// Calculate tip based on hours since epoch
Expand All @@ -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(() => {
Expand Down
Loading