diff --git a/openspec/changes/archive/2026-08-26-add-multi-change-timeline-view/design.md b/openspec/changes/archive/2026-08-26-add-multi-change-timeline-view/design.md
new file mode 100644
index 0000000..0d64c16
--- /dev/null
+++ b/openspec/changes/archive/2026-08-26-add-multi-change-timeline-view/design.md
@@ -0,0 +1,108 @@
+## Context
+
+The user explicitly asked for both timeline phases to be designed
+together with a shared data layer (delivered in Change 1) and for a
+logarithmic time axis "from the start," without specifying which
+direction. The plan for this change flagged the log-scale direction as
+the one piece worth sanity-checking against real data before writing
+it into a spec, rather than locking in a formula blind.
+
+## Goals / Non-Goals
+
+**Goals:**
+- Several changes' activity is comparable side by side on one shared
+ axis, with a log scale chosen for a concrete, verified reason (not
+ "log scale sounds sophisticated").
+- No new charting library or canvas/SVG rendering — plain CSS
+ positioning (`left: X%`), per the explicit request to avoid heavy
+ chart infrastructure.
+- Reuses Change 1's data layer and Change 2's `ChangeTimeline` type
+ completely unchanged.
+
+**Non-Goals:**
+- Not building collision/overlap avoidance for near-simultaneous points
+ within a lane (e.g. nudging overlapping dots apart). The log-scale
+ fix already addresses the primary practical case (a dense
+ squash-merge cluster) by spreading it out — see the empirical check
+ below. Minor residual overlap for genuinely simultaneous points is
+ accepted for this first version rather than adding a general
+ collision-layout algorithm.
+- Not adding a manual date-range text input to the VS Code extension's
+ Command Palette flow — no native date picker exists in VS Code's own
+ prompt UI, and typed ISO-date entry is worse UX than deriving a
+ sensible default from the data itself (see Decisions).
+
+## Decisions
+
+### Log-scale direction: from the range start, confirmed empirically
+
+Before writing any component code, real timestamps from this
+repository's own archived changes (spanning 2026-08-26, 09:04 through
+20:03, including a dense 8-change cluster between 09:04 and 11:03 —
+this repository's own squash-merge workflow, exactly the case earlier
+discussion in this session flagged as the common outcome) were plotted
+under three candidate scales in a throwaway HTML/Playwright sketch:
+linear, `log1p(elapsed since range start)`, and `log1p(remaining time
+until range end)`. The "from start" direction visibly spread the dense
+morning cluster into distinguishable points; "from end" compressed the
+same cluster *tighter* than even the linear baseline, defeating the
+purpose. `logPosition` (`timeline-scale.ts`) implements the "from
+start" direction:
+
+```
+raw = (log1p(elapsed) / log1p(span)) * 100, clamped to [0, 100]
+```
+
+### Extension global command derives its date range from the data, not user input
+
+`computeDefaultRange` (`commands.ts`) takes the earliest/latest
+determinable date across every selected change's created/task/archived
+dates. This avoids a manual ISO-date-entry prompt (VS Code's
+`showInputBox` has no date-picker affordance) while still producing a
+sensible, data-driven range. The standalone app's web UI *does* offer
+real `` pickers (a browser-native control VS Code's
+prompt API doesn't have), so this asymmetry is a genuine capability
+difference between the two hosts, not an inconsistency to paper over.
+
+### `archivedDate` is anchored to end-of-day, not midnight
+
+Found during this change's own verification (a real-browser smoke test
+against real archived-change data): `archivedDate` is a plain calendar
+date parsed from the archive folder name, with no time-of-day
+information. Anchoring it to midnight (`T00:00:00.000Z`) made it
+plot — and sort into the default range — *before* that same day's
+actual `createdDate`/task timestamps, even though archiving is
+chronologically the *last* thing that happens to a change. Anchored to
+end-of-day (`T23:59:59.999Z`) instead, in both
+`MultiChangeTimelineView.tsx`'s point-building and `commands.ts`'s
+`computeDefaultRange`.
+
+### Timeline CSS was missing entirely — added as part of this change
+
+Also found during verification: neither this change's nor Change 2's
+timeline components had any matching CSS in `shell-ui.ts`. For Change
+2's single-change view this was a cosmetic gap (default HTML element
+styling made it readable regardless). For this change's positioned-dots
+layout it was a functional one — `left: X%` inline styles do nothing
+without `position: relative` on the track and `position: absolute` on
+each point, so the axis did not visually exist at all beforehand. Added
+`.openspec-timeline-*` and `.openspec-multi-timeline-*` rules to
+`shellThemeCss`, matching the existing CSS-variable palette
+(`--surface`, `--surface-2`, `--line`, `--ink`, `--muted`, `--primary`,
+`--danger`).
+
+## Risks / Trade-offs
+
+- **[Risk]** A smoke test against the bundled `dist/timeline.js` in a
+ bare browser page (not a real VS Code webview) showed the new track/
+ point CSS resolving to transparent colors, because `vscodeThemeCss`'s
+ `:root` rules (which come after `shellThemeCss` in the concatenated
+ stylesheet and win) reference `--vscode-*` custom properties that
+ only exist inside a real VS Code webview, not a plain browser page.
+ → **Mitigation**: confirmed via computed styles that `position`/
+ `left`/`transform` (the actual functional positioning) resolved
+ correctly regardless; the missing color is a known, pre-existing
+ limitation of this lightweight smoke-test technique (already true for
+ `AiPanel`'s own styling, not something introduced by this change),
+ not a defect inside a real VS Code window, where `--vscode-*`
+ variables are genuinely defined by the host.
diff --git a/openspec/changes/archive/2026-08-26-add-multi-change-timeline-view/proposal.md b/openspec/changes/archive/2026-08-26-add-multi-change-timeline-view/proposal.md
new file mode 100644
index 0000000..279d8b1
--- /dev/null
+++ b/openspec/changes/archive/2026-08-26-add-multi-change-timeline-view/proposal.md
@@ -0,0 +1,72 @@
+## Why
+
+Third and final change building the "change timeline" feature (see
+`2026-08-26-add-change-timeline-data-layer` and
+`2026-08-26-add-change-timeline-view`). This adds Phase 2: a global
+"compare changes" view showing several changes in parallel lanes on a
+shared, log-scale time axis with a date-range picker — confirmed
+explicitly by the user, including the log-scale requirement, and
+verified empirically against this repository's own real archived-change
+timestamps before implementation (see design.md, "Log-scale direction").
+
+## What Changes
+
+- Add `packages/webui/src/timeline-scale.ts` (`logPosition`): a
+ log1p-from-range-start position function, 0-100, clamped. Validated
+ against real data: this direction spreads a dense cluster of
+ near-simultaneous changes (this project's own squash-merge workflow
+ produces exactly this) into something readable; the opposite
+ direction (log from the range end) made the same cluster *more*
+ overlapped than a plain linear scale.
+- Add `packages/webui/src/components/MultiChangeTimelineView.tsx`: one
+ lane per selected change, plotting created/task/archived points along
+ the shared axis. Reuses Change 1's `getChangeTimelines` batch
+ function and Change 2's `ChangeTimeline` type unchanged — no changes
+ to the data layer.
+- Standalone app: a "Single change" / "Compare changes" mode toggle
+ within the existing Timeline tab (not a new tab) — a date-range
+ picker plus a multi-select change list, calling `loadChangeTimelines`
+ via real REST.
+- VS Code extension: a new global command,
+ `openspec-ui.showAllChangesTimeline` (Command Palette only, no tree
+ item — matching the `openspec-ui.openspecView` precedent), with a
+ multi-select `showQuickPick` over active and archived changes. The
+ date range is derived automatically from the selected changes' own
+ data (earliest/latest determinable date) rather than asking the user
+ to type ISO dates, since VS Code's prompt UI has no native date
+ picker.
+- `TimelineWebviewPanel` gains `showMulti(...)`, reusing the same
+ not-a-singleton, embed-the-data-in-initial-HTML approach as `show()`
+ (Change 2); `timeline-entry.tsx` renders whichever of
+ `window.__OPENSPEC_UI_TIMELINE__` / `__OPENSPEC_UI_MULTI_TIMELINE__`
+ is present.
+- Add CSS for both this change's and Change 2's timeline classes to
+ `packages/webui/src/shell-ui.ts` — a real gap found during this
+ change's own verification: the positioned-dots-on-an-axis layout is
+ the actual point of the feature, and it did not render as a
+ positioned axis at all without `position: relative`/`absolute` rules,
+ which had never been added.
+- Fix: an archived change's `archivedDate` (a plain calendar date, no
+ time-of-day) was anchored to midnight when computing plot positions
+ and default ranges, which could sort it *before* that same day's real
+ created/task timestamps — since archiving is chronologically last,
+ it is now anchored to end-of-day instead.
+
+## Capabilities
+
+### Modified Capabilities
+
+- `vscode-extension`: adds a Requirement for the global comparison
+ command.
+- `standalone-app`: adds a Requirement for the compare-changes mode.
+
+## Impact
+
+- `packages/webui/src/timeline-scale.ts` (new)
+- `packages/webui/src/components/MultiChangeTimelineView.tsx` (new)
+- `packages/webui/src/standalone-entry.tsx`
+- `packages/webui/src/timeline-entry.tsx`
+- `packages/webui/src/shell-ui.ts`
+- `packages/extension/src/webview/timeline-panel.ts`
+- `packages/extension/src/commands.ts`, `package.json`
+- `.changeset/*.md` (new changeset file)
diff --git a/openspec/changes/archive/2026-08-26-add-multi-change-timeline-view/specs/standalone-app/spec.md b/openspec/changes/archive/2026-08-26-add-multi-change-timeline-view/specs/standalone-app/spec.md
new file mode 100644
index 0000000..1f93e7c
--- /dev/null
+++ b/openspec/changes/archive/2026-08-26-add-multi-change-timeline-view/specs/standalone-app/spec.md
@@ -0,0 +1,21 @@
+## ADDED Requirements
+
+### Requirement: The Timeline tab offers a compare-changes mode with a date-range picker
+
+The system SHALL offer, within the existing Timeline tab, a mode that
+lets the user pick a date range and select multiple active and/or
+archived changes, then shows them as parallel lanes on a shared,
+log-scaled time axis.
+
+#### Scenario: User picks a date range and multiple changes
+
+- **WHEN** the user selects a start date, an end date, and multiple
+ changes, then loads the comparison
+- **THEN** the tab shows one lane per selected change within that range
+
+#### Scenario: User has not selected a range or any changes
+
+- **WHEN** the user attempts to load a comparison without a complete
+ date range or without selecting any change
+- **THEN** the system reports what is missing rather than loading an
+ empty or partial comparison
diff --git a/openspec/changes/archive/2026-08-26-add-multi-change-timeline-view/specs/vscode-extension/spec.md b/openspec/changes/archive/2026-08-26-add-multi-change-timeline-view/specs/vscode-extension/spec.md
new file mode 100644
index 0000000..af6fea6
--- /dev/null
+++ b/openspec/changes/archive/2026-08-26-add-multi-change-timeline-view/specs/vscode-extension/spec.md
@@ -0,0 +1,29 @@
+## ADDED Requirements
+
+### Requirement: A global command compares several changes on a shared timeline
+
+The system SHALL offer a Command Palette command, not tied to any
+single tree item, that lets the user select multiple active and/or
+archived changes and shows them as parallel lanes on a shared,
+log-scaled time axis derived from the selected changes' own data. The
+axis SHALL use a logarithmic scale from the range start, chosen because
+it spreads a dense cluster of near-simultaneous changes into readable
+detail rather than compressing it further.
+
+#### Scenario: User selects changes across active and archived
+
+- **WHEN** the user invokes "Show Change Comparison Timeline" and
+ selects both active and archived changes
+- **THEN** a webview opens showing one lane per selected change, points
+ positioned by their best-effort dates
+
+#### Scenario: User selects no changes
+
+- **WHEN** the user cancels the selection without picking any change
+- **THEN** no webview opens
+
+#### Scenario: The comparison computation fails
+
+- **WHEN** fetching the selected changes' timelines throws
+- **THEN** the extension shows an error message and does not open a
+ webview
diff --git a/openspec/changes/archive/2026-08-26-add-multi-change-timeline-view/tasks.md b/openspec/changes/archive/2026-08-26-add-multi-change-timeline-view/tasks.md
new file mode 100644
index 0000000..de6fd10
--- /dev/null
+++ b/openspec/changes/archive/2026-08-26-add-multi-change-timeline-view/tasks.md
@@ -0,0 +1,76 @@
+## 1. Log-scale util
+
+- [x] 1.1 Before writing any component, validate the log-scale
+ direction against real data: a throwaway HTML/Playwright sketch using
+ this repository's own real archived-change timestamps (a dense
+ 8-change cluster plus sparser later changes), comparing linear,
+ "log from range start," and "log from range end." Confirmed "from
+ start" spreads the dense cluster into readable points; "from end"
+ compressed it tighter than linear.
+- [x] 1.2 Add `packages/webui/src/timeline-scale.ts` (`logPosition`)
+ implementing the confirmed direction, clamped to `[0, 100]`.
+- [x] 1.3 Add `timeline-scale.test.ts`.
+
+## 2. Webui: multi-change component
+
+- [x] 2.1 Add `packages/webui/src/components/MultiChangeTimelineView.tsx`:
+ props `{ timelines, rangeStart, rangeEnd }`, one lane per timeline,
+ created/task/archived points plotted via `logPosition`. Archived
+ points anchored to end-of-day (`T23:59:59.999Z`), not midnight (see
+ design.md).
+- [x] 2.2 Add `MultiChangeTimelineView.test.tsx`.
+- [x] 2.3 Add `.openspec-timeline-*`/`.openspec-multi-timeline-*` CSS to
+ `packages/webui/src/shell-ui.ts` — found missing entirely during
+ verification; required for the positioned-axis layout to render at
+ all, not just cosmetic (see design.md).
+
+## 3. Standalone app: compare-changes mode
+
+- [x] 3.1 Add a "Single change" / "Compare changes" mode toggle within
+ the existing Timeline tab in `standalone-entry.tsx`: date-range
+ `` pair, a multi-select change list, and
+ `loadChangeTimelines` via real REST.
+
+## 4. Extension: global comparison command
+
+- [x] 4.1 Add `pickChangesForTimeline` (multi-select `showQuickPick`
+ over active and archived changes) and `computeDefaultRange`
+ (data-derived date range — no manual date entry, see design.md) to
+ `commands.ts`.
+- [x] 4.2 Register `openspec-ui.showAllChangesTimeline` (Command
+ Palette only, no tree item, matching `openspec-ui.openspecView`'s
+ precedent), calling `getChangeTimelines` directly and
+ `timelinePanel.showMulti(...)`.
+- [x] 4.3 Add `showMulti(...)` to `TimelineWebviewPanel`, reusing the
+ same not-a-singleton, embed-in-initial-HTML approach as `show()`.
+- [x] 4.4 Extend `timeline-entry.tsx` to render whichever of
+ `window.__OPENSPEC_UI_TIMELINE__` / `__OPENSPEC_UI_MULTI_TIMELINE__`
+ is present.
+- [x] 4.5 Add `contributes.commands` entry in `package.json`.
+- [x] 4.6 Add tests to `commands.test.ts`: picks across active and
+ archived, computes the expected default range, calls `showMulti`;
+ does nothing when no changes are picked.
+
+## 5. Verification
+
+- [x] 5.1 `npm run typecheck` and `npm run lint` (including
+ `lint:english`) pass workspace-wide.
+- [x] 5.2 `npm run test` passes workspace-wide, including all new test
+ files.
+- [x] 5.3 Rebuild the VSIX (`npm run package --workspace
+ openspec-ui-vscode`) and confirm it packages without error.
+- [x] 5.4 Live smoke test: same pre-existing, environment-specific
+ `@vscode/test-electron` failure reproduces on this machine
+ (documented since `2026-08-26-signal-run-completion`, unrelated to
+ this change). As a substitute, verified the built `dist/timeline.js`
+ bundle in a real Chromium browser (Playwright) loading real
+ `getChangeTimelines` data for 7 of this repository's own archived
+ changes: correct lane count, correct point count, zero console
+ errors, and — after finding and fixing the missing CSS — confirmed
+ via computed styles that `position`/`left`/`transform` resolve
+ correctly on each plotted point.
+- [x] 5.5 Propose a changeset (`npx changeset`) for `openspec-ui-vscode`
+ and `@openspec-ui/webui` (both minor: new capability, no breaking
+ change) instead of hand-editing `version`/`CHANGELOG.md`; apply it
+ via `npx changeset version`.
+- [x] 5.6 Run `openspec change validate --strict add-multi-change-timeline-view`.
diff --git a/openspec/specs/standalone-app/spec.md b/openspec/specs/standalone-app/spec.md
index 2b3d415..d0c43c6 100644
--- a/openspec/specs/standalone-app/spec.md
+++ b/openspec/specs/standalone-app/spec.md
@@ -223,3 +223,23 @@ with pending or undated tasks shown distinctly rather than omitted.
- **THEN** it is shown without a date rather than omitted or given a
misleading date
+### Requirement: The Timeline tab offers a compare-changes mode with a date-range picker
+
+The system SHALL offer, within the existing Timeline tab, a mode that
+lets the user pick a date range and select multiple active and/or
+archived changes, then shows them as parallel lanes on a shared,
+log-scaled time axis.
+
+#### Scenario: User picks a date range and multiple changes
+
+- **WHEN** the user selects a start date, an end date, and multiple
+ changes, then loads the comparison
+- **THEN** the tab shows one lane per selected change within that range
+
+#### Scenario: User has not selected a range or any changes
+
+- **WHEN** the user attempts to load a comparison without a complete
+ date range or without selecting any change
+- **THEN** the system reports what is missing rather than loading an
+ empty or partial comparison
+
diff --git a/openspec/specs/vscode-extension/spec.md b/openspec/specs/vscode-extension/spec.md
index 12b2ad1..59afaa4 100644
--- a/openspec/specs/vscode-extension/spec.md
+++ b/openspec/specs/vscode-extension/spec.md
@@ -341,3 +341,31 @@ SHALL each open in their own tab, not replace one another.
- **THEN** the extension shows an error message and does not open a
webview
+### Requirement: A global command compares several changes on a shared timeline
+
+The system SHALL offer a Command Palette command, not tied to any
+single tree item, that lets the user select multiple active and/or
+archived changes and shows them as parallel lanes on a shared,
+log-scaled time axis derived from the selected changes' own data. The
+axis SHALL use a logarithmic scale from the range start, chosen because
+it spreads a dense cluster of near-simultaneous changes into readable
+detail rather than compressing it further.
+
+#### Scenario: User selects changes across active and archived
+
+- **WHEN** the user invokes "Show Change Comparison Timeline" and
+ selects both active and archived changes
+- **THEN** a webview opens showing one lane per selected change, points
+ positioned by their best-effort dates
+
+#### Scenario: User selects no changes
+
+- **WHEN** the user cancels the selection without picking any change
+- **THEN** no webview opens
+
+#### Scenario: The comparison computation fails
+
+- **WHEN** fetching the selected changes' timelines throws
+- **THEN** the extension shows an error message and does not open a
+ webview
+
diff --git a/packages/extension/CHANGELOG.md b/packages/extension/CHANGELOG.md
index 0473ea3..b36141c 100644
--- a/packages/extension/CHANGELOG.md
+++ b/packages/extension/CHANGELOG.md
@@ -1,5 +1,22 @@
# Changelog
+## 0.24.0
+
+### Minor Changes
+
+- Add a "compare changes" timeline: a new global command
+ (`openspec-ui.showAllChangesTimeline`) and a standalone Timeline-tab
+ mode that show several changes as parallel lanes on a shared,
+ log-scaled time axis (verified against real archived-change data
+ before choosing the log-scale direction). Also adds the CSS the
+ single-change timeline view needed but was missing, and fixes archived
+ dates plotting before same-day created/task timestamps.
+
+### Patch Changes
+
+- Updated dependencies
+ - @openspec-ui/webui@1.13.0
+
## 0.23.0
### Minor Changes
diff --git a/packages/extension/package.json b/packages/extension/package.json
index 15f0ae8..87156a2 100644
--- a/packages/extension/package.json
+++ b/packages/extension/package.json
@@ -4,7 +4,7 @@
"displayName": "OpenSpec Workbench",
"description": "A dashboard + VS Code extension for OpenSpec, with Claude, Copilot, Codex, and Gemini agents built in.",
"publisher": "openspec-ui",
- "version": "0.23.0",
+ "version": "0.24.0",
"icon": "media/icon.png",
"license": "MIT",
"repository": {
@@ -107,6 +107,10 @@
"command": "openspec-ui.openspecView",
"title": "OpenSpec UI: OpenSpec View (CLI)"
},
+ {
+ "command": "openspec-ui.showAllChangesTimeline",
+ "title": "OpenSpec UI: Show Change Comparison Timeline"
+ },
{
"command": "openspec-ui.showChangeDetails",
"title": "OpenSpec UI: Show Change Details"
diff --git a/packages/extension/src/commands.test.ts b/packages/extension/src/commands.test.ts
index 1565255..61cb0ce 100644
--- a/packages/extension/src/commands.test.ts
+++ b/packages/extension/src/commands.test.ts
@@ -12,6 +12,8 @@ const validateChangeMock = vi.fn();
const archiveChangeMock = vi.fn();
const checkChangesetReminderMock = vi.fn();
const getChangeTimelineMock = vi.fn();
+const getChangeTimelinesMock = vi.fn();
+const discoverOpenSpecWorkspaceMock = vi.fn();
const createChangeMock = vi.fn();
const deleteChangeMock = vi.fn();
const unarchiveChangeMock = vi.fn();
@@ -36,7 +38,9 @@ vi.mock("@openspec-ui/core", () => ({
deleteChange: (...args: unknown[]) => deleteChangeMock(...args),
deleteProjectTemplate: (...args: unknown[]) => deleteProjectTemplateMock(...args),
deleteTaskLine: (...args: unknown[]) => deleteTaskLineMock(...args),
+ discoverOpenSpecWorkspace: (...args: unknown[]) => discoverOpenSpecWorkspaceMock(...args),
getChangeTimeline: (...args: unknown[]) => getChangeTimelineMock(...args),
+ getChangeTimelines: (...args: unknown[]) => getChangeTimelinesMock(...args),
initOpenSpec: (...args: unknown[]) => initOpenSpecMock(...args),
listBootstrapProjectTypes: () => [
{ id: "node", label: "Node.js / TypeScript" },
@@ -62,8 +66,12 @@ const openDiffAgainstHeadMock = vi.fn();
vi.mock("./native/diff.js", () => ({ openDiffAgainstHead: (...args: unknown[]) => openDiffAgainstHeadMock(...args) }));
const timelinePanelShowMock = vi.fn();
+const timelinePanelShowMultiMock = vi.fn();
vi.mock("./webview/timeline-panel.js", () => ({
- TimelineWebviewPanel: vi.fn().mockImplementation(() => ({ show: timelinePanelShowMock })),
+ TimelineWebviewPanel: vi.fn().mockImplementation(() => ({
+ show: timelinePanelShowMock,
+ showMulti: timelinePanelShowMultiMock,
+ })),
}));
const { registerCommands } = await import("./commands.js");
@@ -138,6 +146,7 @@ describe("registerCommands", () => {
"openspec-ui.createChange",
"openspec-ui.validateSelectedChange",
"openspec-ui.showChangeTimeline",
+ "openspec-ui.showAllChangesTimeline",
"openspec-ui.archiveChange",
"openspec-ui.unarchiveChange",
"openspec-ui.deleteChange",
@@ -210,6 +219,65 @@ describe("registerCommands", () => {
);
});
+ it("picks changes across active and archived, fetches, and shows a comparison", async () => {
+ discoverOpenSpecWorkspaceMock.mockResolvedValue({
+ changes: [{ name: "active-change" }],
+ archivedChanges: [{ name: "2026-01-01-old-change" }],
+ });
+ vscodeMock.window.showQuickPick.mockResolvedValue([
+ { label: "active-change", description: "active", archived: false },
+ { label: "2026-01-01-old-change", description: "archived", archived: true },
+ ]);
+ const timelines = [
+ {
+ changeName: "active-change",
+ archived: false,
+ createdDate: "2026-01-02T00:00:00.000Z",
+ archivedDate: null,
+ tasks: [],
+ },
+ {
+ changeName: "2026-01-01-old-change",
+ archived: true,
+ createdDate: null,
+ archivedDate: "2026-01-01",
+ tasks: [],
+ },
+ ];
+ getChangeTimelinesMock.mockResolvedValue(timelines);
+ const deps = makeDeps();
+ registerCommands(makeContext() as unknown as import("vscode").ExtensionContext, deps);
+
+ await vscodeMock._registeredCommands.get("openspec-ui.showAllChangesTimeline")?.();
+
+ expect(getChangeTimelinesMock).toHaveBeenCalledWith("/workspace/repo", [
+ { changeName: "active-change", archived: false },
+ { changeName: "2026-01-01-old-change", archived: true },
+ ]);
+ expect(timelinePanelShowMultiMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ timelines,
+ rangeStart: "2026-01-01T23:59:59.999Z",
+ rangeEnd: "2026-01-02T00:00:00.000Z",
+ }),
+ );
+ });
+
+ it("does nothing when no changes are picked for comparison", async () => {
+ discoverOpenSpecWorkspaceMock.mockResolvedValue({
+ changes: [{ name: "active-change" }],
+ archivedChanges: [],
+ });
+ vscodeMock.window.showQuickPick.mockResolvedValue(undefined);
+ const deps = makeDeps();
+ registerCommands(makeContext() as unknown as import("vscode").ExtensionContext, deps);
+
+ await vscodeMock._registeredCommands.get("openspec-ui.showAllChangesTimeline")?.();
+
+ expect(getChangeTimelinesMock).not.toHaveBeenCalled();
+ expect(timelinePanelShowMultiMock).not.toHaveBeenCalled();
+ });
+
it("archives a confirmed active change and refreshes", async () => {
vscodeMock.window.showWarningMessage.mockResolvedValue("Archive");
archiveChangeMock.mockResolvedValue({ ok: true });
diff --git a/packages/extension/src/commands.ts b/packages/extension/src/commands.ts
index 973436c..5f41d41 100644
--- a/packages/extension/src/commands.ts
+++ b/packages/extension/src/commands.ts
@@ -16,7 +16,9 @@ import {
deleteChange,
deleteProjectTemplate,
deleteTaskLine,
+ discoverOpenSpecWorkspace,
getChangeTimeline,
+ getChangeTimelines,
initOpenSpec,
listBootstrapProjectTypes,
listChanges,
@@ -31,6 +33,7 @@ import {
writeSubtypeInstructions,
type StartProcessOptions,
type WorkbenchProcessScheduler,
+ type ChangeTimeline,
type Command,
type OpenSpecShowResult,
type OpenSpecValidateResult,
@@ -243,6 +246,55 @@ async function pickChange(workspaceRoot: string): Promise<{ name: string; change
return { name: pick.label, changeDir: path.join(workspaceRoot, "openspec", "changes", pick.label) };
}
+async function pickChangesForTimeline(
+ workspaceRoot: string,
+): Promise No changes selected.