diff --git a/src/browser/components/BackgroundProcessesBanner/BackgroundProcessesBanner.stories.tsx b/src/browser/components/BackgroundProcessesBanner/BackgroundProcessesBanner.stories.tsx index 5077f51911..0de4b7b801 100644 --- a/src/browser/components/BackgroundProcessesBanner/BackgroundProcessesBanner.stories.tsx +++ b/src/browser/components/BackgroundProcessesBanner/BackgroundProcessesBanner.stories.tsx @@ -3,7 +3,7 @@ import { appMeta, AppWithMocks } from "@/browser/stories/meta.js"; import { setupSimpleChatStory } from "@/browser/stories/helpers/chatSetup"; import { createAssistantMessage, createUserMessage } from "@/browser/stories/mocks/messages"; import { createTerminalTool } from "@/browser/stories/mocks/tools"; -import { STABLE_TIMESTAMP } from "@/browser/stories/mocks/workspaces"; +import { NOW, STABLE_TIMESTAMP } from "@/browser/stories/mocks/workspaces"; const meta = { ...appMeta, @@ -49,7 +49,7 @@ export const BackgroundProcesses: AppStory = { script: "export NODE_ENV=development\nexport PORT=3000\nnpm run dev -- --host 0.0.0.0 --port $PORT", displayName: "Dev Server", - startTime: Date.now() - 45000, // 45 seconds ago + startTime: NOW - 45000, // 45 seconds ago monitor: { filter: "FAILED|ERROR", filter_exclude: false, @@ -67,14 +67,14 @@ export const BackgroundProcesses: AppStory = { pid: 12346, script: "npm test -- --watch", displayName: "Test Runner", - startTime: Date.now() - 30000, // 30 seconds ago + startTime: NOW - 30000, // 30 seconds ago status: "running", }, { id: "bash_3", pid: 12347, script: "tail -f /var/log/app.log", - startTime: Date.now() - 120000, // 2 minutes ago + startTime: NOW - 120000, // 2 minutes ago status: "running", }, ], diff --git a/src/browser/components/LeftSidebar/LeftSidebar.stories.tsx b/src/browser/components/LeftSidebar/LeftSidebar.stories.tsx index 11556c0977..7fb951bdb3 100644 --- a/src/browser/components/LeftSidebar/LeftSidebar.stories.tsx +++ b/src/browser/components/LeftSidebar/LeftSidebar.stories.tsx @@ -72,7 +72,16 @@ interface LeftSidebarStoryOverrides { // Vite re-evaluates this module on story edits; use a fresh token so the shell // can deterministically remount provider state after each hot update. -const MODULE_RENDER_TOKEN = `${Date.now()}-${Math.random().toString(16).slice(2)}`; +const MODULE_RENDER_TOKEN = (() => { + // Vite HMR re-evaluates this module; bump hot.data so provider trees remount + // after edits. Pixel/CI has no import.meta.hot, so the token stays stable. + if (import.meta.hot) { + const nextGeneration = (Number(import.meta.hot.data.renderGeneration) || 0) + 1; + import.meta.hot.data.renderGeneration = nextGeneration; + return `storybook-left-sidebar-${NOW}-hmr-${nextGeneration}`; + } + return `storybook-left-sidebar-${NOW}`; +})(); function LeftSidebarStoryScene(props: { leftSidebarProps?: LeftSidebarStoryOverrides }) { const { userProjects } = useProjectContext(); diff --git a/src/browser/components/ProjectSidebar/ProjectSidebar.stories.tsx b/src/browser/components/ProjectSidebar/ProjectSidebar.stories.tsx index a0bbd9b1c0..1b80888eca 100644 --- a/src/browser/components/ProjectSidebar/ProjectSidebar.stories.tsx +++ b/src/browser/components/ProjectSidebar/ProjectSidebar.stories.tsx @@ -3,7 +3,7 @@ import type { AppStory } from "@/browser/stories/meta.js"; import { PIXEL_DUAL_THEME, appMeta, AppWithMocks } from "@/browser/stories/meta.js"; import { expandProjects } from "@/browser/stories/helpers/uiState"; import { createMockORPCClient } from "@/browser/stories/mocks/orpc"; -import { createWorkspace, groupWorkspacesByProject } from "@/browser/stories/mocks/workspaces"; +import { NOW, createWorkspace, groupWorkspacesByProject } from "@/browser/stories/mocks/workspaces"; const PROJECT_PATH = "/home/user/projects/my-app"; @@ -239,7 +239,7 @@ export const WorkflowRunGroups: AppStory = { reviewRun("wf-claims", "claims", { title: "Extract claims", taskStatus: "reported", - createdAt: new Date(Date.now() - 8 * 60_000).toISOString(), + createdAt: new Date(NOW - 8 * 60_000).toISOString(), }), createWorkspace({ id: "wf-tests", @@ -248,13 +248,13 @@ export const WorkflowRunGroups: AppStory = { title: "Run test matrix", parentWorkspaceId: "ws-main", taskStatus: "running", - createdAt: new Date(Date.now() - 6 * 60_000).toISOString(), + createdAt: new Date(NOW - 6 * 60_000).toISOString(), workflowTask: { runId: "wfr_legacy567890", stepId: "tests" }, }), reviewRun("wf-verify", "verify", { title: "Verify claims", taskStatus: "running", - createdAt: new Date(Date.now() - 5 * 60_000).toISOString(), + createdAt: new Date(NOW - 5 * 60_000).toISOString(), }), createWorkspace({ id: "var-a", diff --git a/src/browser/components/WorkspaceMCPModal/WorkspaceMCPModal.stories.tsx b/src/browser/components/WorkspaceMCPModal/WorkspaceMCPModal.stories.tsx index b581ad9024..6725de3bcf 100644 --- a/src/browser/components/WorkspaceMCPModal/WorkspaceMCPModal.stories.tsx +++ b/src/browser/components/WorkspaceMCPModal/WorkspaceMCPModal.stories.tsx @@ -16,6 +16,7 @@ import { getMCPTestResultsKey } from "@/common/constants/storage"; import type { MCPServerInfo } from "@/common/types/mcp"; import { WorkspaceMCPModal } from "./WorkspaceMCPModal"; +import { NOW } from "@/browser/stories/storyTime"; const PROJECT_PATH = "/Users/test/my-app"; const WORKSPACE_ID = "ws-mcp-test"; @@ -78,7 +79,7 @@ function setupWorkspaceMCPModalStory(options: WorkspaceMCPStoryOptions = {}): AP for (const [serverName, tools] of Object.entries(options.testResults)) { cachedResults[serverName] = { result: { success: true, tools }, - testedAt: Date.now(), + testedAt: NOW, }; } diff --git a/src/browser/features/Messages/TranscriptDensity.stories.tsx b/src/browser/features/Messages/TranscriptDensity.stories.tsx index 2a53ace6d9..6c3fafe745 100644 --- a/src/browser/features/Messages/TranscriptDensity.stories.tsx +++ b/src/browser/features/Messages/TranscriptDensity.stories.tsx @@ -15,7 +15,7 @@ import { createProposePlanTool, createWebSearchTool, } from "@/browser/stories/mocks/tools"; -import { STABLE_TIMESTAMP } from "@/browser/stories/mocks/workspaces"; +import { NOW, STABLE_TIMESTAMP } from "@/browser/stories/mocks/workspaces"; import { TRANSCRIPT_DENSITY_KEY, type TranscriptDensity } from "@/common/constants/storage"; const meta = { ...appMeta, title: "App/Chat/Transcript Density" }; @@ -200,7 +200,9 @@ export const HyperEdgeCaseGallery: AppStory = { setup={() => { collapseLeftSidebar(); setDensity("hyper"); - const activeStartedAt = Date.now() - 39_000; + // Base on NOW (not STABLE_TIMESTAMP) so the active bundle stays ~39s old and + // renders seconds rather than crossing into "1m …" elapsed formatting. + const activeStartedAt = NOW - 39_000; return setupSimpleChatStory({ messages: [ // Scenario 1: critical events remain visible in hyper density. diff --git a/src/browser/features/RightSidebar/GoalTab.stories.tsx b/src/browser/features/RightSidebar/GoalTab.stories.tsx index f865e92430..07ebb4afbf 100644 --- a/src/browser/features/RightSidebar/GoalTab.stories.tsx +++ b/src/browser/features/RightSidebar/GoalTab.stories.tsx @@ -12,6 +12,7 @@ import type { } from "@/common/types/goal"; import { GoalTab } from "./GoalTab"; +import { NOW } from "@/browser/stories/storyTime"; const meta: Meta = { title: "Features/RightSidebar/GoalTab", @@ -64,7 +65,7 @@ const STATE_VARIANTS: GalleryVariant[] = [ costCents: 0, turnsUsed: 0, turnCap: null, - startedAtMs: Date.now(), + startedAtMs: NOW, }, }, { @@ -77,7 +78,7 @@ const STATE_VARIANTS: GalleryVariant[] = [ costCents: 125, turnsUsed: 3, turnCap: 10, - startedAtMs: Date.now() - 90_000, + startedAtMs: NOW - 90_000, }, }, { @@ -90,7 +91,7 @@ const STATE_VARIANTS: GalleryVariant[] = [ costCents: 125, turnsUsed: 3, turnCap: null, - startedAtMs: Date.now(), + startedAtMs: NOW, }, }, { @@ -103,7 +104,7 @@ const STATE_VARIANTS: GalleryVariant[] = [ costCents: 525, turnsUsed: 4, turnCap: 10, - startedAtMs: Date.now() - 120_000, + startedAtMs: NOW - 120_000, }, }, { @@ -117,7 +118,7 @@ const STATE_VARIANTS: GalleryVariant[] = [ turnsUsed: 5, turnCap: null, completionSummary: "The lifecycle controls shipped with persistence and tests.", - startedAtMs: Date.now(), + startedAtMs: NOW, }, }, { @@ -130,7 +131,7 @@ const STATE_VARIANTS: GalleryVariant[] = [ costCents: 150, turnsUsed: 2, turnCap: null, - startedAtMs: Date.now() - 30_000, + startedAtMs: NOW - 30_000, }, }, ]; @@ -167,7 +168,6 @@ export const States: Story = { // ───────────────────────────────────────────────────────────────────────── const STORY_WORKSPACE_ID = "ws-story-goaltab"; -const NOW = Date.UTC(2026, 4, 20, 12, 0, 0); function makeBoardGoal( overrides: Partial & Pick diff --git a/src/browser/features/RightSidebar/Memory/MemoryTab.stories.tsx b/src/browser/features/RightSidebar/Memory/MemoryTab.stories.tsx index 54cb3e3b16..cdfdf4a238 100644 --- a/src/browser/features/RightSidebar/Memory/MemoryTab.stories.tsx +++ b/src/browser/features/RightSidebar/Memory/MemoryTab.stories.tsx @@ -11,6 +11,7 @@ import type { } from "@/common/orpc/schemas/memory"; import { EXPERIMENT_IDS, getExperimentKey } from "@/common/constants/experiments"; +import { NOW } from "@/browser/stories/storyTime"; import { MemoryTab } from "./MemoryTab"; const meta: Meta = { @@ -27,6 +28,8 @@ const STORY_WORKSPACE_ID = "ws-story-memorytab"; // Multi-file fixture covering every row permutation the tree cares about: // all three scopes, root files, a dir with multiple files, two-level nesting, // a pinned file, with/without descriptions, and used/never-used usage stats. +// Use the storybook NOW fixture (preview also stubs Date.now) so relative +// access labels stay stable even if a path bypasses the stub. const MEMORY_FILES: MemoryFileInfo[] = [ { path: "/memories/global/preferences.md", @@ -34,7 +37,7 @@ const MEMORY_FILES: MemoryFileInfo[] = [ description: "Coding style and tooling preferences", pinned: true, accessCount: 12, - lastAccessedAt: Date.now() - 3_600_000, + lastAccessedAt: NOW - 3_600_000, }, { path: "/memories/global/people/reviewers.md", @@ -42,7 +45,7 @@ const MEMORY_FILES: MemoryFileInfo[] = [ description: "Preferred reviewers per code area", pinned: false, accessCount: 3, - lastAccessedAt: Date.now() - 86_400_000, + lastAccessedAt: NOW - 86_400_000, }, { path: "/memories/global/people/teammates.md", @@ -58,7 +61,7 @@ const MEMORY_FILES: MemoryFileInfo[] = [ description: "High-level architecture notes", pinned: false, accessCount: 5, - lastAccessedAt: Date.now() - 600_000, + lastAccessedAt: NOW - 600_000, }, { path: "/memories/project/conventions.md", @@ -66,7 +69,7 @@ const MEMORY_FILES: MemoryFileInfo[] = [ description: "Repo conventions distilled from AGENTS.md", pinned: false, accessCount: 7, - lastAccessedAt: Date.now() - 7_200_000, + lastAccessedAt: NOW - 7_200_000, }, { path: "/memories/project/testing.md", @@ -82,12 +85,12 @@ const MEMORY_FILES: MemoryFileInfo[] = [ description: "Notes for the current branch work", pinned: false, accessCount: 1, - lastAccessedAt: Date.now() - 60_000, + lastAccessedAt: NOW - 60_000, }, ]; const CONSOLIDATION_RECORD: MemoryConsolidationRecordPayload = { - lastRunAt: Date.now() - 30 * 60 * 1000, + lastRunAt: NOW - 30 * 60 * 1000, trigger: "manual", summary: "Merged duplicate project notes", ops: [{ command: "delete", path: "/memories/project/duplicate.md", applied: true }], @@ -107,8 +110,8 @@ function renderTab(width: string) { globalRecord: CONSOLIDATION_RECORD, latestHarvestRecord: { status: "completed", - startedAt: Date.now() - 45 * 60 * 1000, - completedAt: Date.now() - 44 * 60 * 1000, + startedAt: NOW - 45 * 60 * 1000, + completedAt: NOW - 44 * 60 * 1000, attemptCount: 1, boundaryKey: "summary-story", compactionEpoch: 3, diff --git a/src/browser/features/RightSidebar/RightSidebar.stories.tsx b/src/browser/features/RightSidebar/RightSidebar.stories.tsx index b3d8c9f7b7..56a4262187 100644 --- a/src/browser/features/RightSidebar/RightSidebar.stories.tsx +++ b/src/browser/features/RightSidebar/RightSidebar.stories.tsx @@ -48,6 +48,7 @@ import { expect, userEvent, waitFor, within } from "@storybook/test"; import type { ComponentType, ReactNode } from "react"; import { useEffect, useRef } from "react"; import { RightSidebar } from "./RightSidebar.js"; +import { NOW } from "@/browser/stories/storyTime"; const meta: Meta = { title: "Features/RightSidebar/RightSidebar", @@ -637,7 +638,7 @@ export const ReviewTabSortByLastEdit: Story = { // Clear persisted layout to ensure review tab appears in fresh default layout const workspaceId = "ws-review-sort"; localStorage.removeItem(getRightSidebarLayoutKey(workspaceId)); - const now = Date.now(); + const now = NOW; // Set up first-seen timestamps for hunks (oldest to newest: format -> button -> client) // We use placeholder IDs since exact hash depends on content @@ -1330,7 +1331,7 @@ export const CompactionModelWarning: Story = { * Helper to create realistic output log entries with mixed levels and locations. */ function createOutputLogEntries() { - const now = Date.now(); + const now = NOW; return [ { diff --git a/src/browser/features/Settings/Sections/MCPSettingsSection.stories.tsx b/src/browser/features/Settings/Sections/MCPSettingsSection.stories.tsx index 2490c76e0b..0816e107ca 100644 --- a/src/browser/features/Settings/Sections/MCPSettingsSection.stories.tsx +++ b/src/browser/features/Settings/Sections/MCPSettingsSection.stories.tsx @@ -16,6 +16,7 @@ import type { MCPOAuthAuthStatus } from "@/common/types/mcpOauth"; import type { Secret } from "@/common/types/secrets"; import { MCPSettingsSection } from "./MCPSettingsSection"; +import { NOW } from "@/browser/stories/storyTime"; const MOCK_TOOLS = [ "file_read", @@ -72,7 +73,7 @@ function setupMCPSettingsSectionStory(options: MCPSectionStoryOptions = {}): API for (const [serverName, tools] of Object.entries(options.testResults)) { cachedResults[serverName] = { result: { success: true, tools }, - testedAt: Date.now(), + testedAt: NOW, }; } @@ -400,7 +401,7 @@ export const ProjectSettingsOAuthLoggedIn: Story = { serverUrl: "https://example.com/mcp", isLoggedIn: true, hasRefreshToken: true, - updatedAtMs: Date.now() - 60_000, + updatedAtMs: NOW - 60_000, }, ], ]), diff --git a/src/browser/features/Settings/Sections/MemorySection.stories.tsx b/src/browser/features/Settings/Sections/MemorySection.stories.tsx index 14034264aa..1c02587428 100644 --- a/src/browser/features/Settings/Sections/MemorySection.stories.tsx +++ b/src/browser/features/Settings/Sections/MemorySection.stories.tsx @@ -5,6 +5,7 @@ import { createMockORPCClient } from "@/browser/stories/mocks/orpc"; import type { MemoryFileInfo } from "@/common/orpc/schemas/memory"; import { MemorySection } from "./MemorySection.js"; import { SettingsSectionStory } from "./settingsStoryUtils.js"; +import { NOW } from "@/browser/stories/storyTime"; // Settings → Memory only manages the global scope (no workspace context), // so the fixture is global files only — incl. a nested path, a pinned file, @@ -16,7 +17,7 @@ const GLOBAL_MEMORY_FILES: MemoryFileInfo[] = [ description: "Coding style and tooling preferences", pinned: true, accessCount: 12, - lastAccessedAt: Date.now() - 3_600_000, + lastAccessedAt: NOW - 3_600_000, }, { path: "/memories/global/people/reviewers.md", @@ -24,7 +25,7 @@ const GLOBAL_MEMORY_FILES: MemoryFileInfo[] = [ description: "Preferred reviewers per code area", pinned: false, accessCount: 3, - lastAccessedAt: Date.now() - 86_400_000, + lastAccessedAt: NOW - 86_400_000, }, { path: "/memories/global/glossary.md", diff --git a/src/browser/features/Tools/Bash/BashToolCall.stories.tsx b/src/browser/features/Tools/Bash/BashToolCall.stories.tsx index 72b3a12a26..eb2c1971f0 100644 --- a/src/browser/features/Tools/Bash/BashToolCall.stories.tsx +++ b/src/browser/features/Tools/Bash/BashToolCall.stories.tsx @@ -7,6 +7,7 @@ import { BashBackgroundTerminateToolCall } from "@/browser/features/Tools/BashBa import { BashOutputToolCall } from "@/browser/features/Tools/BashOutputToolCall"; import { BashToolCall } from "@/browser/features/Tools/BashToolCall"; import { lightweightMeta } from "@/browser/stories/meta.js"; +import { NOW } from "@/browser/stories/storyTime"; const meta = { ...lightweightMeta, @@ -144,7 +145,7 @@ export const IntentExecutingSummary: Story = { display_name: "Dev server readiness", }} status="executing" - startedAt={Date.now() - 1_000} + startedAt={NOW - 1_000} /> ),