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
6 changes: 6 additions & 0 deletions .changeset/tidy-compliance-theme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@openspec-ui/webui": patch
"openspec-ui-vscode": patch
---

Make the Compliance webview follow VS Code light, dark and high-contrast themes.
29 changes: 29 additions & 0 deletions openspec/changes/compliance-vscode-theme/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Design

## Decisions

- Reuse `useEditorDarkTheme`, `metroRootClassName`, `metroCss`,
`metroIconsCss`, `shellThemeCss` and `vscodeThemeCss`, exactly as the
Harness Settings and Pipeline webviews do.
- Put theme ownership in a small shared Compliance webview root component so
the real entrypoint and the theme test exercise the same wrapper.
- Keep `ComplianceView` transport-agnostic and leave the standalone shell's
theme selection untouched.

### Rejected Alternatives

- Duplicating CSS variables or adding fixed light/dark colors is rejected:
ADR-0030 maps colors to the active editor theme, including custom themes.
- Fixing only the panel HTML is rejected: it cannot add the Metro scope class
or react to theme-class changes without duplicating the existing hook.

## Non-Goals

- No change to Compliance checks, policy, persistence, REST, or message bridge.
- No change to the standalone theme, Metro component selection, or npm publish.

## Risks / Trade-offs

- A wrapper regression could affect CSS scope. The focused test asserts both
the Metro root class and light/dark/high-contrast transitions, and the live
VS Code smoke continues to verify the panel opens.
29 changes: 29 additions & 0 deletions openspec/changes/compliance-vscode-theme/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Proposal: Compliance Follows the VS Code Theme

## Why

The Compliance webview is the only VS Code webview that does not mount the
Metro root/theme adapter. It therefore misses the editor's light/dark/high-
contrast state even though ADR-0030 requires every VS Code webview to map its
palette from `--vscode-*` variables and follow theme changes while open.

## What Changes

- Wrap the Compliance webview in the shared VS Code Metro root and theme hook.
- Verify light, dark, high-contrast and live theme-switch behavior in a focused
webview component test and the existing isolated VS Code smoke.

## Capabilities

### New Capabilities

None.

### Modified Capabilities

- `vscode-extension`: the Compliance webview follows the active editor theme.

## Impact

Only the extension's Compliance webview root and its focused tests change.
The standalone Compliance dialog continues to inherit its existing shell theme.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## ADDED Requirements

### Requirement: VS Code webviews follow the editor theme

Every VS Code webview SHALL mount the shared Metro root class and map its
palette from VS Code theme variables. The webview SHALL follow editor light,
dark and high-contrast theme changes while it remains open. It SHALL NOT use
fixed product colors in place of the editor palette.

#### Scenario: Compliance opens in a light editor theme

- **WHEN** the Compliance webview opens with `vscode-light` on the body
- **THEN** its root includes `openspec-metro` and excludes `dark-side`
- **AND** its controls use the mapped VS Code theme variables

#### Scenario: Compliance opens or changes to a dark editor theme

- **WHEN** the Compliance webview opens with `vscode-dark` or the body changes
from light to dark while it is open
- **THEN** its root includes `dark-side` without reloading the panel

#### Scenario: Compliance changes to a light or high-contrast-light theme

- **WHEN** the editor body changes to `vscode-light` or
`vscode-high-contrast-light`
- **THEN** `dark-side` is removed while `openspec-metro` remains

#### Scenario: Compliance changes to high-contrast dark

- **WHEN** the editor body changes to `vscode-high-contrast`
- **THEN** `dark-side` is present and contrast colors remain sourced from
VS Code variables
19 changes: 19 additions & 0 deletions openspec/changes/compliance-vscode-theme/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
## 1. Webview Theme

- [x] 1.1 Add `packages/webui/src/components/ComplianceApp.tsx` to mount the
shared Metro root/theme wrapper; verify it renders `openspec-metro` and the
expected theme class in `ComplianceApp.test.tsx`. Evidence: the focused test
verifies light, dark, high-contrast and high-contrast-light class changes.
- [x] 1.2 Update `packages/webui/src/compliance-entry.tsx` to render
`ComplianceApp`; verify the Compliance browser bundle builds.
- [x] 1.3 Verify live body-class transitions for light, dark, high contrast and
high-contrast-light in `ComplianceApp.test.tsx` without reloading the app.
- [x] 1.4 Run the existing isolated VS Code Compliance smoke and record its
command and result; do not run the full extension suite locally.
Evidence: `node packages/extension/src/test/compliance-run.mjs <Code.exe>`
passed with exit code 0, verifying activation, command, panel tab and disposal
against the built theme-aware bundle.
- [x] 1.5 Run strict OpenSpec validation and affected webui/extension typechecks.
Evidence: 18 focused theme tests, both package typechecks, changeset check and
strict OpenSpec validation passed on 2026-10-11. `complianceWebviewBuildOptions`
also built `packages/extension/dist/compliance.js` successfully.
8 changes: 3 additions & 5 deletions packages/webui/src/compliance-entry.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { createRoot } from "react-dom/client";
import { ComplianceView, type ComplianceApi } from "./components/ComplianceView.js";
import { shellThemeCss, vscodeThemeCss } from "./shell-ui.js";
import { metroCss } from "./metro-css.generated.js";
import { metroIconsCss } from "./metro-icons.generated.js";
import { ComplianceApp } from "./components/ComplianceApp.js";
import type { ComplianceApi } from "./components/ComplianceView.js";
import type { ComplianceReading } from "@openspec-ui/core/browser";

declare function acquireVsCodeApi(): { postMessage: (value: unknown) => void };
Expand All @@ -22,4 +20,4 @@ const api: ComplianceApi = request => new Promise((resolve, reject) => {
});
const root = document.getElementById("root");
if (!root) throw new Error("Compliance root is missing");
createRoot(root).render(<><style>{[metroCss, metroIconsCss, shellThemeCss, vscodeThemeCss].join(" ")}</style><ComplianceView api={api} /></>);
createRoot(root).render(<ComplianceApp api={api} />);
31 changes: 31 additions & 0 deletions packages/webui/src/components/ComplianceApp.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { act, render, screen } from "@testing-library/react";
import { expect, it, vi } from "vitest";
import { ComplianceApp } from "./ComplianceApp.js";
import type { ComplianceReading } from "@openspec-ui/core/browser";

const reading: ComplianceReading = {
generation: 0,
policy: { backgroundEnabled: false, global: {}, topics: {}, rules: {} },
rules: [],
observations: [],
pending: 0,
nextAllowedAtMs: 0,
};

it("mounts the shared Metro root and follows live light, dark and high-contrast themes", async () => {
document.body.className = "vscode-light";
render(<ComplianceApp api={vi.fn(async () => reading)} />);
const root = screen.getByTestId("compliance-webview-root");

expect(root).toHaveClass("openspec-metro");
expect(root).not.toHaveClass("dark-side");

await act(async () => { document.body.className = "vscode-dark"; });
expect(root).toHaveClass("dark-side");

await act(async () => { document.body.className = "vscode-high-contrast"; });
expect(root).toHaveClass("dark-side");

await act(async () => { document.body.className = "vscode-high-contrast vscode-high-contrast-light"; });
expect(root).not.toHaveClass("dark-side");
});
16 changes: 16 additions & 0 deletions packages/webui/src/components/ComplianceApp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useEditorDarkTheme, metroRootClassName } from "../vscode-theme.js";
import { shellThemeCss, vscodeThemeCss } from "../shell-ui.js";
import { metroCss } from "../metro-css.generated.js";
import { metroIconsCss } from "../metro-icons.generated.js";
import { ComplianceView, type ComplianceApi } from "./ComplianceView.js";

export function ComplianceApp({ api }: { api: ComplianceApi }) {
const editorDark = useEditorDarkTheme();

return (
<div className={metroRootClassName("openspec-extension-app", editorDark)} data-testid="compliance-webview-root">
<style>{[metroCss, metroIconsCss, shellThemeCss, vscodeThemeCss].join(" ")}</style>
<ComplianceView api={api} />
</div>
);
}
Loading