From 4eaac5d2b448cc11af6d260273534fb3ce606555 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 13 Apr 2026 13:38:25 -0700 Subject: [PATCH 01/24] Add theme spec for real VSCode theme support. Documents the two-layer CSS variable system, the conversion pipeline from VSCode theme JSON to --vscode-* CSS variables, build-time bundling from OpenVSX, runtime theme installation, and localStorage persistence. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/specs/theme.md | 215 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 docs/specs/theme.md diff --git a/docs/specs/theme.md b/docs/specs/theme.md new file mode 100644 index 00000000..c7089ff4 --- /dev/null +++ b/docs/specs/theme.md @@ -0,0 +1,215 @@ +# Theme Spec + +MouseTerm uses real VSCode themes in standalone and website mode. Bundled themes are extracted from actual VSCode theme extensions at build time. Users can also install additional themes from [OpenVSX](https://open-vsx.org/) at runtime. + +## How it works + +MouseTerm has a two-layer CSS variable theme system: + +1. **`--vscode-*`** — the theme data. In VSCode extension mode, the editor injects these automatically. In standalone/website mode, `applyTheme()` sets them on `document.body`. +2. **`@theme --color-*`** — Tailwind tokens with fallbacks. Defined in `theme.css` as `--color-surface: var(--vscode-editor-background, #1e1e1e)`. Powers utility classes like `bg-surface`, `text-foreground`, `border-border`. + +``` +VSCode theme JSON CSS variables Tailwind +───────────────── ────────────── ──────── +colors: { --vscode-editor-background @theme --color-surface + "editor.background": "#282a36" → set on body.style → → bg-surface + "terminal.ansiRed": "#ff5555" --vscode-terminal-ansiRed (read by getTerminalTheme()) + ... ... +} +``` + +Two consumers read `--vscode-*` variables: +- **`@theme` fallbacks** in `theme.css` — for UI colors (surfaces, tabs, badges, buttons, text) +- **`getTerminalTheme()`** in `terminal-registry.ts` — reads ANSI colors, cursor, and selection directly as `--vscode-*` for xterm.js + +A MutationObserver on `document.body` (in `terminal-registry.ts`) detects style changes and re-reads the theme for all xterm.js terminals. Dockview overrides and Tailwind classes update automatically because they reference `--color-*`. + +### Cleanup from previous `--mt-*` layer + +The old three-layer system (`--vscode-*` → `--mt-*` → `--color-*`) had a redundant middle layer. The `--mt-*` variables were a pure passthrough — every one was immediately re-exported as `--color-*` with no transformation. The cleanup: + +- **Collapsed `--mt-*` into `@theme`**: `--color-surface: var(--vscode-editor-background, #1e1e1e)` directly, no intermediate variable. +- **Deleted 38 dead variables** (114 lines of CSS): `--mt-ansi-*` (32 colors), `--mt-terminal-cursor`, `--mt-terminal-selection`, `--mt-gutter`, `--mt-gutter-active`, `--mt-editor-font-size`, `--mt-editor-font-family`, `--mt-selection-workspace` were defined 3x each (dark/light/prefers-color-scheme) but never consumed. The ANSI colors are read directly as `--vscode-*` by `getTerminalTheme()`. +- **Eliminated duplicate mappings**: `--vscode-focusBorder` was aliased as `--mt-accent`, `--mt-gutter-active`, and `--mt-selection-terminal` — three names for one token. Now just `--color-accent`. +- **Dropped `testing.iconPassed` mapping**: `--mt-success-fg` stole `testing.iconPassed` as a generic success color. Most themes don't define this key, and it's the wrong semantic. `--color-success` now uses a hardcoded green. +- **Kept defensible cross-domain mappings**: `--color-tab-selected-bg/fg` ← `list.activeSelectionBackground/Foreground` (closest VSCode approximation of our command-mode tab selection). `--color-accent` ← `focusBorder` (most themes treat this as their brand/accent color). + +### Light theme body class + +`applyTheme()` adds `vscode-light` to `document.body.classList` for light themes and removes it for dark themes. `theme.css` has a `body.vscode-light` selector that switches all `--color-*` fallback values to the Light+ palette. Without this class, a light theme that doesn't explicitly define every key would get dark fallbacks for missing keys. + +## Theme data model + +```typescript +interface MouseTermTheme { + id: string; // "GitHub.github-vscode-theme.dark-default" or "builtin.dark-plus" + label: string; // "GitHub Dark Default" + type: 'dark' | 'light'; + swatch: string; // editor.background — used for picker preview + accent: string; // focusBorder — used for picker accent dot + vars: Record; // --vscode-* CSS variable overrides + origin: BundledOrigin | InstalledOrigin; +} + +interface BundledOrigin { kind: 'bundled' } +interface InstalledOrigin { + kind: 'installed'; + extensionId: string; // "dracula-theme/theme-dracula" + installedAt: string; // ISO date +} +``` + +This replaces the current `PlaygroundTheme` interface in `website/src/lib/playground-themes.ts`. + +## Conversion pipeline + +### `CONSUMED_VSCODE_KEYS` + +Not all VSCode theme color keys matter to MouseTerm — only the ~45 keys that are actually read. The conversion function filters to this set and drops the rest. The keys come from two consumers: + +**Read by `@theme` fallbacks** (UI colors, ~25 keys): +- **Surfaces**: `editor.background`, `editorGroupHeader.tabsBackground`, `sideBar.background`, `editorWidget.background` +- **Text**: `editor.foreground`, `descriptionForeground` +- **Accent/borders**: `focusBorder`, `panel.border` +- **Tabs**: `tab.activeBackground`, `tab.inactiveBackground`, `tab.activeForeground`, `tab.inactiveForeground`, `list.activeSelectionBackground`, `list.activeSelectionForeground` +- **Terminal**: `terminal.background`, `terminal.foreground` +- **Status**: `badge.background`, `badge.foreground`, `errorForeground`, `editorWarning.foreground` +- **Inputs**: `input.background`, `input.border` +- **Buttons**: `button.background`, `button.foreground`, `button.hoverBackground` +- **Links**: `textLink.foreground` + +**Read by `getTerminalTheme()` directly** (terminal colors, ~20 keys): +- `terminal.background`, `terminal.foreground` (also in `@theme`) +- `terminalCursor.foreground`, `terminal.selectionBackground` +- `terminal.ansiBlack` through `terminal.ansiBrightWhite` (16 ANSI colors) + +### Conversion rule + +For each key in the VSCode theme's `colors` object: if it's in `CONSUMED_VSCODE_KEYS`, emit `--vscode-${key.replace(/\./g, '-')}` → value. Keys not consumed by MouseTerm are silently dropped. Missing keys fall through to the `@theme` fallbacks in `theme.css` (Dark+ or Light+ defaults), which is the same behavior as VSCode itself. + +## Bundled themes + +Bundled themes are extracted at build time by a Node.js script (`scripts/bundle-themes.mjs`) and written to `lib/src/lib/themes/bundled.json`. This file is checked into git so builds don't require network access. + +### Source extensions + +| Extension | OpenVSX ID | Variants | +|-----------|-----------|----------| +| GitHub VSCode Theme | `GitHub/github-vscode-theme` | Dark Default, Light Default, Dark Dimmed, Dark High Contrast, Light High Contrast, Dark Colorblind, Light Colorblind, etc. | +| Dracula | `dracula-theme/theme-dracula` | Dracula, Dracula Soft | +| VSCode builtins | (hardcoded) | Dark+, Light+ | + +Dark+ and Light+ are VSCode built-in themes not published to OpenVSX. Their values are hardcoded (from the existing `lib/.storybook/themes.ts`). + +### Build script flow + +``` +scripts/bundle-themes.mjs + | + +- for each extension in EXTENSIONS list: + | +- fetch /api/{ns}/{name}/latest from OpenVSX + | +- download VSIX from files.download URL + | +- unzip (Node.js zlib + ZIP reader) + | +- read extension/package.json -> contributes.themes + | +- for each theme variant: + | +- read theme JSON from ZIP (parse with jsonc-parser for comments) + | +- convertVscodeThemeColors(colors) -> vars + | +- emit MouseTermTheme object + | + +- append hardcoded Dark+ and Light+ themes + | + +- write lib/src/lib/themes/bundled.json +``` + +Run manually: `pnpm bundle-themes`. Output is committed. + +## Theme store (localStorage) + +| Key | Value | +|-----|-------| +| `mouseterm:installed-themes` | JSON array of `MouseTermTheme` objects (user-installed only) | +| `mouseterm:active-theme` | Theme ID string | + +The store module provides: +- `getAllThemes()` — bundled themes (from `bundled.json`) + installed themes (from localStorage) +- `getActiveThemeId()` / `setActiveThemeId(id)` — persists choice across sessions +- `addInstalledTheme(theme)` / `removeInstalledTheme(id)` — manages user-installed themes + +## Runtime OpenVSX installer + +Users can browse and install themes from OpenVSX directly in the app. + +### OpenVSX API + +OpenVSX has permissive CORS (`Access-Control-Allow-Origin: *`) — no proxy needed. + +- **Search**: `GET https://open-vsx.org/api/-/search?category=Themes&query=...&size=...&offset=...` +- **Extension details**: `GET https://open-vsx.org/api/{namespace}/{name}/latest` +- **VSIX download**: URL in the response's `files.download` field + +### In-browser extraction + +VSIX files are ZIP archives. Extraction uses `fflate` (~8 KB gzipped) via dynamic import — only loaded when the user opens the theme store, so no impact on initial bundle. + +``` +user searches OpenVSX + | + +- fetch /api/-/search?category=Themes&query=... + +- display results (name, icon, download count) + | + user clicks "Install" on an extension + | + +- fetch /api/{ns}/{name}/latest -> get VSIX download URL + +- fetch VSIX as ArrayBuffer + +- fflate.unzipSync() -> all files + +- read extension/package.json -> contributes.themes + +- for each theme variant: + | +- parse theme JSON (jsonc-parser) + | +- convertVscodeThemeColors(colors) -> MouseTermTheme + | +- addInstalledTheme(theme) -> persists to localStorage + | + +- theme immediately available in picker +``` + +## Files + +| File | Role | +|------|------| +| [`lib/src/lib/themes/types.ts`](../../lib/src/lib/themes/types.ts) | `MouseTermTheme` interface and origin types | +| [`lib/src/lib/themes/convert.ts`](../../lib/src/lib/themes/convert.ts) | `CONSUMED_VSCODE_KEYS`, `convertVscodeThemeColors()`, `uiThemeToType()` | +| [`lib/src/lib/themes/apply.ts`](../../lib/src/lib/themes/apply.ts) | `applyTheme()` — sets CSS vars on body, manages body classes | +| [`lib/src/lib/themes/store.ts`](../../lib/src/lib/themes/store.ts) | Theme registry combining bundled + installed, localStorage persistence | +| [`lib/src/lib/themes/openvsx.ts`](../../lib/src/lib/themes/openvsx.ts) | OpenVSX search API, VSIX download + extraction | +| [`lib/src/lib/themes/bundled.json`](../../lib/src/lib/themes/bundled.json) | Pre-converted bundled themes (generated, checked in) | +| [`lib/src/lib/themes/index.ts`](../../lib/src/lib/themes/index.ts) | Barrel export | +| [`scripts/bundle-themes.mjs`](../../scripts/bundle-themes.mjs) | Build-time script to download and convert themes from OpenVSX | +| [`lib/src/theme.css`](../../lib/src/theme.css) | `@theme` tokens with `var(--vscode-*, fallback)` + light mode overrides | +| [`lib/src/lib/terminal-registry.ts`](../../lib/src/lib/terminal-registry.ts) | MutationObserver + `getTerminalTheme()` — no changes needed | + +## Dependencies + +- `jsonc-parser` — parses JSONC (JSON with comments/trailing commas), already a transitive dependency via Storybook +- `fflate` — ~8 KB gzipped ZIP library for in-browser VSIX extraction, dynamically imported + +## Design decisions + +**Why two layers, not three?** The old `--mt-*` middle layer was a pure passthrough — every variable was immediately re-exported as `--color-*`. Collapsing it into `@theme` eliminates 114 lines of dead CSS and removes a layer of indirection with no loss of functionality. + +**Why keep semantic names (`surface`, `accent`) instead of VSCode names (`editor-background`, `focusBorder`)?** `bg-surface` reads better in JSX than `bg-editor-background`, and some mappings are genuinely semantic (`surface-alt` communicates intent better than `editorGroupHeader-tabsBackground`). The mapping is documented in one place (`theme.css`). + +**Why hardcode `success` instead of mapping to `testing.iconPassed`?** Most VSCode themes don't define `testing.iconPassed` — it's an optional, domain-specific key. Using it as generic "success green" means imported themes either get our fallback (fine) or get a color chosen for test runner icons (wrong semantic). A hardcoded green is more reliable. + +**Why extract from real VSIX files instead of manually copying colors?** Manual copying is error-prone — colors drift, coverage is incomplete, and adding new themes requires hunting through source repos. Extracting from the actual published extension guarantees accuracy and makes adding themes trivial (add one line to the extensions list, re-run the script). + +**Why bundle pre-converted themes instead of fetching at runtime?** Bundled themes work offline, load instantly, and don't depend on OpenVSX availability. The bundled JSON is small (~1 KB per theme). Runtime fetching is an opt-in addition for users who want more themes. + +**Why `fflate` over `JSZip`?** JSZip is ~45 KB gzipped. `fflate` is ~8 KB, tree-shakeable, and faster. We only need to read ZIPs, not create them. + +**Why `localStorage` over IndexedDB?** Theme data is small (~1-2 KB per theme). Even with 50 installed themes, that's well under the 5 MB localStorage limit. The project already uses localStorage for state persistence in both standalone and website. IndexedDB would add complexity with no benefit at this scale. + +**Why filter to `CONSUMED_VSCODE_KEYS` instead of passing all colors through?** VSCode themes can define 500+ color keys. Setting all of them as CSS variables would be wasteful (most are never read) and could cause unexpected interactions if VSCode adds new keys that happen to match future `--color-*` variables. + +**Why set the `vscode-light` body class?** `theme.css` uses `body.vscode-light` to switch all `--color-*` fallback values to the Light+ palette. Without this class, a light theme that doesn't explicitly define every key would get dark fallbacks for the missing ones, creating a broken mixed appearance. + +**Why not use OpenVSX's direct file access instead of downloading the full VSIX?** OpenVSX doesn't expose individual theme files via API — you have to download the full VSIX. However, theme-only extensions are typically small (50-200 KB), so this is fine. The build script and runtime installer share the same extraction logic. From b419ee1af720782041d6e94910235587673562d4 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 13 Apr 2026 13:38:35 -0700 Subject: [PATCH 02/24] Collapse --mt-* layer into @theme --color-* tokens. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three-layer chain (--vscode-* → --mt-* → --color-*) had a redundant middle layer. Every --mt-* variable was a pure passthrough to --color-*. - Delete 38 dead variables defined 3× each (114 lines): --mt-ansi-*, --mt-terminal-cursor/selection, --mt-gutter*, --mt-editor-font-*, --mt-selection-workspace — none were consumed anywhere - Eliminate duplicate mappings: focusBorder was aliased as --mt-accent, --mt-gutter-active, and --mt-selection-terminal - Drop testing.iconPassed → success mapping (wrong semantic); hardcode green - Move var(--vscode-*, fallback) chains directly into @theme - Update Dockview overrides and inline style refs to use --color-* Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/src/components/Door.tsx | 2 +- lib/src/components/Pond.tsx | 2 +- lib/src/index.css | 24 +- lib/src/stories/SelectionOverlay.stories.tsx | 2 +- lib/src/theme.css | 282 +++++++------------ 5 files changed, 109 insertions(+), 203 deletions(-) diff --git a/lib/src/components/Door.tsx b/lib/src/components/Door.tsx index 95b1b688..9c2a8ad0 100644 --- a/lib/src/components/Door.tsx +++ b/lib/src/components/Door.tsx @@ -40,7 +40,7 @@ export function Door({ 'transition-colors hover:bg-surface-raised', ].join(' ')} style={{ - border: '2px solid var(--mt-border)', + border: '2px solid var(--color-border)', borderBottom: '2px solid transparent', }} onClick={onClick} diff --git a/lib/src/components/Pond.tsx b/lib/src/components/Pond.tsx index 31c695e5..bfd117b7 100644 --- a/lib/src/components/Pond.tsx +++ b/lib/src/components/Pond.tsx @@ -703,7 +703,7 @@ function useWindowFocused(): boolean { } function readSelectionColor() { - return getComputedStyle(document.documentElement).getPropertyValue('--mt-selection-terminal').trim(); + return getComputedStyle(document.documentElement).getPropertyValue('--color-accent').trim(); } function useSelectionColor() { diff --git a/lib/src/index.css b/lib/src/index.css index dd0a8ba9..33022a3d 100644 --- a/lib/src/index.css +++ b/lib/src/index.css @@ -26,17 +26,17 @@ body { .dockview-theme-abyss { --dv-tabs-and-actions-container-height: 30px !important; --dv-tabs-and-actions-container-font-size: var(--mt-font-size) !important; - --dv-group-view-background-color: var(--mt-surface) !important; - --dv-tabs-and-actions-container-background-color: var(--mt-surface-alt) !important; - --dv-activegroup-visiblepanel-tab-background-color: var(--mt-tab-active-bg) !important; - --dv-activegroup-hiddenpanel-tab-background-color: var(--mt-tab-inactive-bg) !important; - --dv-inactivegroup-visiblepanel-tab-background-color: var(--mt-tab-active-bg) !important; - --dv-inactivegroup-hiddenpanel-tab-background-color: var(--mt-tab-inactive-bg) !important; - --dv-activegroup-visiblepanel-tab-color: var(--mt-tab-active-fg) !important; - --dv-activegroup-hiddenpanel-tab-color: var(--mt-tab-inactive-fg) !important; - --dv-inactivegroup-visiblepanel-tab-color: var(--mt-tab-active-fg) !important; - --dv-inactivegroup-hiddenpanel-tab-color: var(--mt-tab-inactive-fg) !important; - --dv-tab-divider-color: var(--mt-border) !important; + --dv-group-view-background-color: var(--color-surface) !important; + --dv-tabs-and-actions-container-background-color: var(--color-surface-alt) !important; + --dv-activegroup-visiblepanel-tab-background-color: var(--color-tab-active-bg) !important; + --dv-activegroup-hiddenpanel-tab-background-color: var(--color-tab-inactive-bg) !important; + --dv-inactivegroup-visiblepanel-tab-background-color: var(--color-tab-active-bg) !important; + --dv-inactivegroup-hiddenpanel-tab-background-color: var(--color-tab-inactive-bg) !important; + --dv-activegroup-visiblepanel-tab-color: var(--color-tab-active-fg) !important; + --dv-activegroup-hiddenpanel-tab-color: var(--color-tab-inactive-fg) !important; + --dv-inactivegroup-visiblepanel-tab-color: var(--color-tab-active-fg) !important; + --dv-inactivegroup-hiddenpanel-tab-color: var(--color-tab-inactive-fg) !important; + --dv-tab-divider-color: var(--color-border) !important; --dv-separator-border: transparent !important; --dv-active-sash-color: transparent !important; } @@ -45,7 +45,7 @@ body { .dockview-theme-abyss .dv-groupview, .dockview-theme-abyss .dv-split-view-container, .dockview-theme-abyss .dv-split-view-container .dv-view-container { - background-color: var(--mt-surface) !important; + background-color: var(--color-surface) !important; } /* Remove tab-bar background/border — the tab itself provides the bg */ diff --git a/lib/src/stories/SelectionOverlay.stories.tsx b/lib/src/stories/SelectionOverlay.stories.tsx index 26f57173..4d2cd948 100644 --- a/lib/src/stories/SelectionOverlay.stories.tsx +++ b/lib/src/stories/SelectionOverlay.stories.tsx @@ -18,7 +18,7 @@ function SelectionOverlayDemo({ initialMode = 'command' as PondMode }) { return () => ro.disconnect(); }, []); - const color = getComputedStyle(document.documentElement).getPropertyValue('--mt-selection-terminal').trim() || '#007fd4'; + const color = getComputedStyle(document.documentElement).getPropertyValue('--color-accent').trim() || '#007fd4'; const overlayStyle: React.CSSProperties = { position: 'absolute', diff --git a/lib/src/theme.css b/lib/src/theme.css index d4eca89b..b360774a 100644 --- a/lib/src/theme.css +++ b/lib/src/theme.css @@ -1,231 +1,137 @@ /* MouseTerm Theme System * * Two-layer CSS variable strategy: - * Tailwind tokens -> --mt-* (our semantic layer) -> var(--vscode-*, ) + * @theme --color-* tokens → var(--vscode-*, ) * * In VSCode: --vscode-* variables are auto-injected and take precedence. - * VSCode adds body classes: vscode-light, vscode-dark, vscode-high-contrast. - * In Neutralino/standalone: fallback values apply, with prefers-color-scheme - * used to pick dark vs light defaults. + * In standalone/website: applyTheme() sets --vscode-* on document.body. + * Also sets body class vscode-light for light themes. + * For standalone without explicit theme: prefers-color-scheme picks + * dark vs light defaults. + * + * Two consumers read --vscode-* variables: + * 1. @theme fallbacks below — for UI colors (surfaces, tabs, etc.) + * 2. getTerminalTheme() in terminal-registry.ts — reads ANSI colors, + * cursor, and selection directly as --vscode-* for xterm.js */ -/* --- Dark mode fallbacks (VSCode Dark+ defaults) --- */ +/* --- Font tokens (not Tailwind colors) --- */ :root { - /* Typography */ --mt-font-size: var(--vscode-font-size, 13px); --mt-font-family: var(--vscode-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif); - --mt-editor-font-size: var(--vscode-editor-font-size, 12px); - --mt-editor-font-family: var(--vscode-editor-font-family, 'SF Mono', Menlo, Monaco, monospace); +} +/* --- Dark mode defaults (VSCode Dark+) --- */ +@theme { /* Surfaces */ - --mt-surface: var(--vscode-editor-background, #1e1e1e); - --mt-surface-alt: var(--vscode-editorGroupHeader-tabsBackground, var(--vscode-sideBar-background, #252526)); - --mt-surface-raised: var(--vscode-editorWidget-background, #252526); + --color-surface: var(--vscode-editor-background, #1e1e1e); + --color-surface-alt: var(--vscode-editorGroupHeader-tabsBackground, var(--vscode-sideBar-background, #252526)); + --color-surface-raised: var(--vscode-editorWidget-background, #252526); /* Text */ - --mt-foreground: var(--vscode-editor-foreground, #cccccc); - --mt-muted: var(--vscode-descriptionForeground, #858585); + --color-foreground: var(--vscode-editor-foreground, #cccccc); + --color-muted: var(--vscode-descriptionForeground, #858585); /* Accent & borders */ - --mt-accent: var(--vscode-focusBorder, #007fd4); - --mt-border: var(--vscode-panel-border, #2b2b2b); - --mt-gutter: var(--vscode-panel-border, #2b2b2b); - --mt-gutter-active: var(--vscode-focusBorder, #007fd4); + --color-accent: var(--vscode-focusBorder, #007fd4); + --color-border: var(--vscode-panel-border, #2b2b2b); /* Tabs */ - --mt-tab-active-bg: var(--vscode-tab-activeBackground, #1e1e1e); - --mt-tab-inactive-bg: var(--vscode-tab-inactiveBackground, #2d2d2d); - --mt-tab-active-fg: var(--vscode-tab-activeForeground, #ffffff); - --mt-tab-inactive-fg: var(--vscode-tab-inactiveForeground, #969696); - --mt-tab-selected-bg: var(--vscode-list-activeSelectionBackground, #094771); - --mt-tab-selected-fg: var(--vscode-list-activeSelectionForeground, #ffffff); + --color-tab-active-bg: var(--vscode-tab-activeBackground, #1e1e1e); + --color-tab-inactive-bg: var(--vscode-tab-inactiveBackground, #2d2d2d); + --color-tab-active-fg: var(--vscode-tab-activeForeground, #ffffff); + --color-tab-inactive-fg: var(--vscode-tab-inactiveForeground, #969696); + --color-tab-selected-bg: var(--vscode-list-activeSelectionBackground, #094771); + --color-tab-selected-fg: var(--vscode-list-activeSelectionForeground, #ffffff); /* Terminal */ - --mt-terminal-bg: var(--vscode-terminal-background, #1e1e1e); - --mt-terminal-fg: var(--vscode-terminal-foreground, #cccccc); + --color-terminal-bg: var(--vscode-terminal-background, #1e1e1e); + --color-terminal-fg: var(--vscode-terminal-foreground, #cccccc); /* Badges */ - --mt-badge-bg: var(--vscode-badge-background, #007acc); - --mt-badge-fg: var(--vscode-badge-foreground, #ffffff); + --color-badge-bg: var(--vscode-badge-background, #007acc); + --color-badge-fg: var(--vscode-badge-foreground, #ffffff); /* Semantic status */ - --mt-error-fg: var(--vscode-errorForeground, #f48771); - --mt-success-fg: var(--vscode-testing-iconPassed, #73c991); - --mt-warning-fg: var(--vscode-editorWarning-foreground, #cca700); + --color-error: var(--vscode-errorForeground, #f48771); + --color-success: #73c991; + --color-warning: var(--vscode-editorWarning-foreground, #cca700); /* Inputs */ - --mt-input-bg: var(--vscode-input-background, #3c3c3c); - --mt-input-border: var(--vscode-input-border, #3c3c3c); + --color-input-bg: var(--vscode-input-background, #3c3c3c); + --color-input-border: var(--vscode-input-border, #3c3c3c); /* Buttons */ - --mt-button-bg: var(--vscode-button-background, #0e639c); - --mt-button-fg: var(--vscode-button-foreground, #ffffff); - --mt-button-hover-bg: var(--vscode-button-hoverBackground, #1177bb); + --color-button-bg: var(--vscode-button-background, #0e639c); + --color-button-fg: var(--vscode-button-foreground, #ffffff); + --color-button-hover-bg: var(--vscode-button-hoverBackground, #1177bb); - /* Selection overlay */ - --mt-selection-terminal: var(--vscode-focusBorder, #007fd4); - --mt-selection-workspace: var(--vscode-textLink-foreground, #3794ff); - - /* Terminal ANSI colors */ - --mt-ansi-black: var(--vscode-terminal-ansiBlack, #000000); - --mt-ansi-red: var(--vscode-terminal-ansiRed, #cd3131); - --mt-ansi-green: var(--vscode-terminal-ansiGreen, #0dbc79); - --mt-ansi-yellow: var(--vscode-terminal-ansiYellow, #e5e510); - --mt-ansi-blue: var(--vscode-terminal-ansiBlue, #2472c8); - --mt-ansi-magenta: var(--vscode-terminal-ansiMagenta, #bc3fbc); - --mt-ansi-cyan: var(--vscode-terminal-ansiCyan, #11a8cd); - --mt-ansi-white: var(--vscode-terminal-ansiWhite, #e5e5e5); - --mt-ansi-bright-black: var(--vscode-terminal-ansiBrightBlack, #666666); - --mt-ansi-bright-red: var(--vscode-terminal-ansiBrightRed, #f14c4c); - --mt-ansi-bright-green: var(--vscode-terminal-ansiBrightGreen, #23d18b); - --mt-ansi-bright-yellow: var(--vscode-terminal-ansiBrightYellow, #f5f543); - --mt-ansi-bright-blue: var(--vscode-terminal-ansiBrightBlue, #3b8eea); - --mt-ansi-bright-magenta: var(--vscode-terminal-ansiBrightMagenta, #d670d6); - --mt-ansi-bright-cyan: var(--vscode-terminal-ansiBrightCyan, #29b8db); - --mt-ansi-bright-white: var(--vscode-terminal-ansiBrightWhite, #e5e5e5); - --mt-terminal-cursor: var(--vscode-terminalCursor-foreground, #aeafad); - --mt-terminal-selection: var(--vscode-terminal-selectionBackground, #264f7840); + /* Animation */ + --animate-alarm-dot: alarm-dot 2s ease-in-out infinite; } -/* --- Light mode fallbacks (VSCode Light+ defaults) --- +/* --- Light mode (VSCode Light+ defaults) --- * VSCode adds body.vscode-light for light themes. - * Also respect prefers-color-scheme for Neutralino/standalone. */ + * applyTheme() also sets this class for imported light themes. */ body.vscode-light { - --mt-surface: var(--vscode-editor-background, #ffffff); - --mt-surface-alt: var(--vscode-editorGroupHeader-tabsBackground, var(--vscode-sideBar-background, #f3f3f3)); - --mt-surface-raised: var(--vscode-editorWidget-background, #f3f3f3); - --mt-foreground: var(--vscode-editor-foreground, #333333); - --mt-muted: var(--vscode-descriptionForeground, #717171); - --mt-accent: var(--vscode-focusBorder, #0090f1); - --mt-border: var(--vscode-panel-border, #e5e5e5); - --mt-gutter: var(--vscode-panel-border, #d9d9d9); - --mt-gutter-active: var(--vscode-focusBorder, #0090f1); - --mt-tab-active-bg: var(--vscode-tab-activeBackground, #ffffff); - --mt-tab-inactive-bg: var(--vscode-tab-inactiveBackground, #ececec); - --mt-tab-active-fg: var(--vscode-tab-activeForeground, #333333); - --mt-tab-inactive-fg: var(--vscode-tab-inactiveForeground, #8e8e8e); - --mt-tab-selected-bg: var(--vscode-list-activeSelectionBackground, #cce6ff); - --mt-tab-selected-fg: var(--vscode-list-activeSelectionForeground, #000000); - --mt-terminal-bg: var(--vscode-terminal-background, #ffffff); - --mt-terminal-fg: var(--vscode-terminal-foreground, #333333); - --mt-badge-bg: var(--vscode-badge-background, #007acc); - --mt-badge-fg: var(--vscode-badge-foreground, #ffffff); - --mt-error-fg: var(--vscode-errorForeground, #a1260d); - --mt-success-fg: var(--vscode-testing-iconPassed, #388a34); - --mt-warning-fg: var(--vscode-editorWarning-foreground, #bf8803); - --mt-input-bg: var(--vscode-input-background, #ffffff); - --mt-input-border: var(--vscode-input-border, #cecece); - --mt-button-bg: var(--vscode-button-background, #007acc); - --mt-button-fg: var(--vscode-button-foreground, #ffffff); - --mt-button-hover-bg: var(--vscode-button-hoverBackground, #0062a3); - --mt-selection-terminal: var(--vscode-focusBorder, #0090f1); - --mt-selection-workspace: var(--vscode-textLink-foreground, #006ab1); - - --mt-ansi-black: var(--vscode-terminal-ansiBlack, #000000); - --mt-ansi-red: var(--vscode-terminal-ansiRed, #cd3131); - --mt-ansi-green: var(--vscode-terminal-ansiGreen, #00bc00); - --mt-ansi-yellow: var(--vscode-terminal-ansiYellow, #949800); - --mt-ansi-blue: var(--vscode-terminal-ansiBlue, #0451a5); - --mt-ansi-magenta: var(--vscode-terminal-ansiMagenta, #bc05bc); - --mt-ansi-cyan: var(--vscode-terminal-ansiCyan, #0598bc); - --mt-ansi-white: var(--vscode-terminal-ansiWhite, #555555); - --mt-ansi-bright-black: var(--vscode-terminal-ansiBrightBlack, #666666); - --mt-ansi-bright-red: var(--vscode-terminal-ansiBrightRed, #cd3131); - --mt-ansi-bright-green: var(--vscode-terminal-ansiBrightGreen, #14ce14); - --mt-ansi-bright-yellow: var(--vscode-terminal-ansiBrightYellow, #b5ba00); - --mt-ansi-bright-blue: var(--vscode-terminal-ansiBrightBlue, #0451a5); - --mt-ansi-bright-magenta: var(--vscode-terminal-ansiBrightMagenta, #bc05bc); - --mt-ansi-bright-cyan: var(--vscode-terminal-ansiBrightCyan, #0598bc); - --mt-ansi-bright-white: var(--vscode-terminal-ansiBrightWhite, #a5a5a5); - --mt-terminal-cursor: var(--vscode-terminalCursor-foreground, #000000); - --mt-terminal-selection: var(--vscode-terminal-selectionBackground, #add6ff80); + --color-surface: var(--vscode-editor-background, #ffffff); + --color-surface-alt: var(--vscode-editorGroupHeader-tabsBackground, var(--vscode-sideBar-background, #f3f3f3)); + --color-surface-raised: var(--vscode-editorWidget-background, #f3f3f3); + --color-foreground: var(--vscode-editor-foreground, #333333); + --color-muted: var(--vscode-descriptionForeground, #717171); + --color-accent: var(--vscode-focusBorder, #0090f1); + --color-border: var(--vscode-panel-border, #e5e5e5); + --color-tab-active-bg: var(--vscode-tab-activeBackground, #ffffff); + --color-tab-inactive-bg: var(--vscode-tab-inactiveBackground, #ececec); + --color-tab-active-fg: var(--vscode-tab-activeForeground, #333333); + --color-tab-inactive-fg: var(--vscode-tab-inactiveForeground, #8e8e8e); + --color-tab-selected-bg: var(--vscode-list-activeSelectionBackground, #cce6ff); + --color-tab-selected-fg: var(--vscode-list-activeSelectionForeground, #000000); + --color-terminal-bg: var(--vscode-terminal-background, #ffffff); + --color-terminal-fg: var(--vscode-terminal-foreground, #333333); + --color-badge-bg: var(--vscode-badge-background, #007acc); + --color-badge-fg: var(--vscode-badge-foreground, #ffffff); + --color-error: var(--vscode-errorForeground, #a1260d); + --color-success: #388a34; + --color-warning: var(--vscode-editorWarning-foreground, #bf8803); + --color-input-bg: var(--vscode-input-background, #ffffff); + --color-input-border: var(--vscode-input-border, #cecece); + --color-button-bg: var(--vscode-button-background, #007acc); + --color-button-fg: var(--vscode-button-foreground, #ffffff); + --color-button-hover-bg: var(--vscode-button-hoverBackground, #0062a3); } -/* Neutralino/standalone: use OS preference when no VSCode body class */ +/* Standalone: use OS preference when no VSCode body class */ @media (prefers-color-scheme: light) { body:not(.vscode-light):not(.vscode-dark) { - --mt-surface: var(--vscode-editor-background, #ffffff); - --mt-surface-alt: var(--vscode-editorGroupHeader-tabsBackground, var(--vscode-sideBar-background, #f3f3f3)); - --mt-surface-raised: var(--vscode-editorWidget-background, #f3f3f3); - --mt-foreground: var(--vscode-editor-foreground, #333333); - --mt-muted: var(--vscode-descriptionForeground, #717171); - --mt-accent: var(--vscode-focusBorder, #0090f1); - --mt-border: var(--vscode-panel-border, #e5e5e5); - --mt-gutter: var(--vscode-panel-border, #d9d9d9); - --mt-gutter-active: var(--vscode-focusBorder, #0090f1); - --mt-tab-active-bg: var(--vscode-tab-activeBackground, #ffffff); - --mt-tab-inactive-bg: var(--vscode-tab-inactiveBackground, #ececec); - --mt-tab-active-fg: var(--vscode-tab-activeForeground, #333333); - --mt-tab-inactive-fg: var(--vscode-tab-inactiveForeground, #8e8e8e); - --mt-tab-selected-bg: var(--vscode-list-activeSelectionBackground, #cce6ff); - --mt-tab-selected-fg: var(--vscode-list-activeSelectionForeground, #000000); - --mt-terminal-bg: var(--vscode-terminal-background, #ffffff); - --mt-terminal-fg: var(--vscode-terminal-foreground, #333333); - --mt-badge-bg: var(--vscode-badge-background, #007acc); - --mt-badge-fg: var(--vscode-badge-foreground, #ffffff); - --mt-error-fg: var(--vscode-errorForeground, #a1260d); - --mt-success-fg: var(--vscode-testing-iconPassed, #388a34); - --mt-warning-fg: var(--vscode-editorWarning-foreground, #bf8803); - --mt-input-bg: var(--vscode-input-background, #ffffff); - --mt-input-border: var(--vscode-input-border, #cecece); - --mt-button-bg: var(--vscode-button-background, #007acc); - --mt-button-fg: var(--vscode-button-foreground, #ffffff); - --mt-button-hover-bg: var(--vscode-button-hoverBackground, #0062a3); - --mt-selection-terminal: var(--vscode-focusBorder, #0090f1); - --mt-selection-workspace: var(--vscode-textLink-foreground, #006ab1); - - --mt-ansi-black: var(--vscode-terminal-ansiBlack, #000000); - --mt-ansi-red: var(--vscode-terminal-ansiRed, #cd3131); - --mt-ansi-green: var(--vscode-terminal-ansiGreen, #00bc00); - --mt-ansi-yellow: var(--vscode-terminal-ansiYellow, #949800); - --mt-ansi-blue: var(--vscode-terminal-ansiBlue, #0451a5); - --mt-ansi-magenta: var(--vscode-terminal-ansiMagenta, #bc05bc); - --mt-ansi-cyan: var(--vscode-terminal-ansiCyan, #0598bc); - --mt-ansi-white: var(--vscode-terminal-ansiWhite, #555555); - --mt-ansi-bright-black: var(--vscode-terminal-ansiBrightBlack, #666666); - --mt-ansi-bright-red: var(--vscode-terminal-ansiBrightRed, #cd3131); - --mt-ansi-bright-green: var(--vscode-terminal-ansiBrightGreen, #14ce14); - --mt-ansi-bright-yellow: var(--vscode-terminal-ansiBrightYellow, #b5ba00); - --mt-ansi-bright-blue: var(--vscode-terminal-ansiBrightBlue, #0451a5); - --mt-ansi-bright-magenta: var(--vscode-terminal-ansiBrightMagenta, #bc05bc); - --mt-ansi-bright-cyan: var(--vscode-terminal-ansiBrightCyan, #0598bc); - --mt-ansi-bright-white: var(--vscode-terminal-ansiBrightWhite, #a5a5a5); - --mt-terminal-cursor: var(--vscode-terminalCursor-foreground, #000000); - --mt-terminal-selection: var(--vscode-terminal-selectionBackground, #add6ff80); + --color-surface: var(--vscode-editor-background, #ffffff); + --color-surface-alt: var(--vscode-editorGroupHeader-tabsBackground, var(--vscode-sideBar-background, #f3f3f3)); + --color-surface-raised: var(--vscode-editorWidget-background, #f3f3f3); + --color-foreground: var(--vscode-editor-foreground, #333333); + --color-muted: var(--vscode-descriptionForeground, #717171); + --color-accent: var(--vscode-focusBorder, #0090f1); + --color-border: var(--vscode-panel-border, #e5e5e5); + --color-tab-active-bg: var(--vscode-tab-activeBackground, #ffffff); + --color-tab-inactive-bg: var(--vscode-tab-inactiveBackground, #ececec); + --color-tab-active-fg: var(--vscode-tab-activeForeground, #333333); + --color-tab-inactive-fg: var(--vscode-tab-inactiveForeground, #8e8e8e); + --color-tab-selected-bg: var(--vscode-list-activeSelectionBackground, #cce6ff); + --color-tab-selected-fg: var(--vscode-list-activeSelectionForeground, #000000); + --color-terminal-bg: var(--vscode-terminal-background, #ffffff); + --color-terminal-fg: var(--vscode-terminal-foreground, #333333); + --color-badge-bg: var(--vscode-badge-background, #007acc); + --color-badge-fg: var(--vscode-badge-foreground, #ffffff); + --color-error: var(--vscode-errorForeground, #a1260d); + --color-success: #388a34; + --color-warning: var(--vscode-editorWarning-foreground, #bf8803); + --color-input-bg: var(--vscode-input-background, #ffffff); + --color-input-border: var(--vscode-input-border, #cecece); + --color-button-bg: var(--vscode-button-background, #007acc); + --color-button-fg: var(--vscode-button-foreground, #ffffff); + --color-button-hover-bg: var(--vscode-button-hoverBackground, #0062a3); } } -/* --- Register semantic tokens with Tailwind v4 --- */ -@theme { - --color-surface: var(--mt-surface); - --color-surface-alt: var(--mt-surface-alt); - --color-surface-raised: var(--mt-surface-raised); - --color-foreground: var(--mt-foreground); - --color-muted: var(--mt-muted); - --color-accent: var(--mt-accent); - --color-border: var(--mt-border); - --color-tab-active-bg: var(--mt-tab-active-bg); - --color-tab-inactive-bg: var(--mt-tab-inactive-bg); - --color-tab-active-fg: var(--mt-tab-active-fg); - --color-tab-inactive-fg: var(--mt-tab-inactive-fg); - --color-tab-selected-bg: var(--mt-tab-selected-bg); - --color-tab-selected-fg: var(--mt-tab-selected-fg); - --color-terminal-bg: var(--mt-terminal-bg); - --color-terminal-fg: var(--mt-terminal-fg); - --color-badge-bg: var(--mt-badge-bg); - --color-badge-fg: var(--mt-badge-fg); - --color-error: var(--mt-error-fg); - --color-success: var(--mt-success-fg); - --color-warning: var(--mt-warning-fg); - --color-input-bg: var(--mt-input-bg); - --color-input-border: var(--mt-input-border); - --color-button-bg: var(--mt-button-bg); - --color-button-fg: var(--mt-button-fg); - --color-button-hover-bg: var(--mt-button-hover-bg); - - --animate-alarm-dot: alarm-dot 2s ease-in-out infinite; -} - @keyframes alarm-dot { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } From 8b6700999e43b778ee118e5c7a907832fb7a594b Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 13 Apr 2026 13:38:46 -0700 Subject: [PATCH 03/24] Import real VSCode themes from OpenVSX. Build-time: scripts/bundle-themes.mjs downloads VSIX files from OpenVSX, extracts theme JSONs, and converts them to --vscode-* CSS variable maps. 13 bundled themes: Dark+, Light+, 9 GitHub variants, 2 Dracula variants. Runtime: users can search and install additional themes from OpenVSX directly in the browser. VSIX extraction uses fflate (~8KB). Installed themes persist in localStorage. Core library in lib/src/lib/themes/: - convert.ts: CONSUMED_VSCODE_KEYS filter + dots-to-hyphens conversion - apply.ts: sets --vscode-* vars on body + manages vscode-light class - store.ts: bundled + installed registry with localStorage persistence - openvsx.ts: search API + in-browser VSIX download/extraction Replaces the 5 hardcoded themes in playground-themes.ts. Storybook themes now derive from bundled.json instead of manual color maps. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/.storybook/preview.ts | 2 +- lib/.storybook/themes.ts | 198 +------ lib/package.json | 2 + lib/scripts/bundle-themes.mjs | 250 +++++++++ lib/src/lib/themes/apply.ts | 37 ++ lib/src/lib/themes/bundled.json | 684 +++++++++++++++++++++++++ lib/src/lib/themes/convert.test.ts | 74 +++ lib/src/lib/themes/convert.ts | 97 ++++ lib/src/lib/themes/index.ts | 15 + lib/src/lib/themes/openvsx.ts | 120 +++++ lib/src/lib/themes/store.ts | 54 ++ lib/src/lib/themes/types.ts | 28 + package.json | 3 +- pnpm-lock.yaml | 11 + pnpm-workspace.yaml | 2 + website/src/components/ThemePicker.tsx | 68 ++- website/src/components/ThemeStore.tsx | 201 ++++++++ website/src/data/dependencies.json | 21 + website/src/lib/playground-themes.ts | 224 -------- 19 files changed, 1664 insertions(+), 427 deletions(-) create mode 100644 lib/scripts/bundle-themes.mjs create mode 100644 lib/src/lib/themes/apply.ts create mode 100644 lib/src/lib/themes/bundled.json create mode 100644 lib/src/lib/themes/convert.test.ts create mode 100644 lib/src/lib/themes/convert.ts create mode 100644 lib/src/lib/themes/index.ts create mode 100644 lib/src/lib/themes/openvsx.ts create mode 100644 lib/src/lib/themes/store.ts create mode 100644 lib/src/lib/themes/types.ts create mode 100644 website/src/components/ThemeStore.tsx delete mode 100644 website/src/lib/playground-themes.ts diff --git a/lib/.storybook/preview.ts b/lib/.storybook/preview.ts index 57f87aeb..b805d202 100644 --- a/lib/.storybook/preview.ts +++ b/lib/.storybook/preview.ts @@ -43,7 +43,7 @@ const preview: Preview = { }, }, initialGlobals: { - theme: 'Dark+', + theme: 'Dark+ (default)', }, decorators: [ // Theme switcher: inject --vscode-* CSS variables diff --git a/lib/.storybook/themes.ts b/lib/.storybook/themes.ts index a953dbc4..51297c93 100644 --- a/lib/.storybook/themes.ts +++ b/lib/.storybook/themes.ts @@ -1,193 +1,13 @@ /** VSCode theme color maps for Storybook theme switcher. - * Each entry maps --vscode-* CSS variable names to their values in that theme. - * When applied, these override the --mt-* fallbacks in theme.css. + * Derived from bundled themes. When applied, these override + * the @theme --color-* fallbacks in theme.css. */ -export const VSCODE_THEMES: Record> = { - 'Dark+': { - '--vscode-editor-background': '#1e1e1e', - '--vscode-editor-foreground': '#cccccc', - '--vscode-sideBar-background': '#252526', - '--vscode-editorWidget-background': '#252526', - '--vscode-descriptionForeground': '#858585', - '--vscode-focusBorder': '#007fd4', - '--vscode-panel-border': '#2b2b2b', - '--vscode-tab-activeBackground': '#1e1e1e', - '--vscode-tab-inactiveBackground': '#2d2d2d', - '--vscode-tab-activeForeground': '#ffffff', - '--vscode-tab-inactiveForeground': '#969696', - '--vscode-list-activeSelectionBackground': '#094771', - '--vscode-list-activeSelectionForeground': '#ffffff', - '--vscode-list-inactiveSelectionBackground': '#2f3f52', - '--vscode-list-inactiveSelectionForeground': '#cccccc', - '--vscode-terminal-background': '#1e1e1e', - '--vscode-terminal-foreground': '#cccccc', - '--vscode-badge-background': '#007acc', - '--vscode-badge-foreground': '#ffffff', - '--vscode-errorForeground': '#f48771', - '--vscode-input-background': '#3c3c3c', - '--vscode-input-border': '#3c3c3c', - '--vscode-button-background': '#0e639c', - '--vscode-button-foreground': '#ffffff', - '--vscode-button-hoverBackground': '#1177bb', - '--vscode-textLink-foreground': '#3794ff', - '--vscode-terminal-ansiBlack': '#000000', - '--vscode-terminal-ansiRed': '#cd3131', - '--vscode-terminal-ansiGreen': '#0dbc79', - '--vscode-terminal-ansiYellow': '#e5e510', - '--vscode-terminal-ansiBlue': '#2472c8', - '--vscode-terminal-ansiMagenta': '#bc3fbc', - '--vscode-terminal-ansiCyan': '#11a8cd', - '--vscode-terminal-ansiWhite': '#e5e5e5', - '--vscode-terminal-ansiBrightBlack': '#666666', - '--vscode-terminal-ansiBrightRed': '#f14c4c', - '--vscode-terminal-ansiBrightGreen': '#23d18b', - '--vscode-terminal-ansiBrightYellow': '#f5f543', - '--vscode-terminal-ansiBrightBlue': '#3b8eea', - '--vscode-terminal-ansiBrightMagenta': '#d670d6', - '--vscode-terminal-ansiBrightCyan': '#29b8db', - '--vscode-terminal-ansiBrightWhite': '#e5e5e5', - '--vscode-terminalCursor-foreground': '#aeafad', - '--vscode-terminal-selectionBackground': '#264f7840', - }, +import _bundled from '../src/lib/themes/bundled.json'; +import type { MouseTermTheme } from '../src/lib/themes/types'; - 'Light+': { - '--vscode-editor-background': '#ffffff', - '--vscode-editor-foreground': '#333333', - '--vscode-sideBar-background': '#f3f3f3', - '--vscode-editorWidget-background': '#f3f3f3', - '--vscode-descriptionForeground': '#717171', - '--vscode-focusBorder': '#0090f1', - '--vscode-panel-border': '#e5e5e5', - '--vscode-tab-activeBackground': '#ffffff', - '--vscode-tab-inactiveBackground': '#ececec', - '--vscode-tab-activeForeground': '#333333', - '--vscode-tab-inactiveForeground': '#8e8e8e', - '--vscode-list-activeSelectionBackground': '#cce6ff', - '--vscode-list-activeSelectionForeground': '#000000', - '--vscode-list-inactiveSelectionBackground': '#d6e5f8', - '--vscode-list-inactiveSelectionForeground': '#333333', - '--vscode-terminal-background': '#ffffff', - '--vscode-terminal-foreground': '#333333', - '--vscode-badge-background': '#007acc', - '--vscode-badge-foreground': '#ffffff', - '--vscode-errorForeground': '#a1260d', - '--vscode-input-background': '#ffffff', - '--vscode-input-border': '#cecece', - '--vscode-button-background': '#007acc', - '--vscode-button-foreground': '#ffffff', - '--vscode-button-hoverBackground': '#0062a3', - '--vscode-textLink-foreground': '#006ab1', - '--vscode-terminal-ansiBlack': '#000000', - '--vscode-terminal-ansiRed': '#cd3131', - '--vscode-terminal-ansiGreen': '#00bc00', - '--vscode-terminal-ansiYellow': '#949800', - '--vscode-terminal-ansiBlue': '#0451a5', - '--vscode-terminal-ansiMagenta': '#bc05bc', - '--vscode-terminal-ansiCyan': '#0598bc', - '--vscode-terminal-ansiWhite': '#555555', - '--vscode-terminal-ansiBrightBlack': '#666666', - '--vscode-terminal-ansiBrightRed': '#cd3131', - '--vscode-terminal-ansiBrightGreen': '#14ce14', - '--vscode-terminal-ansiBrightYellow': '#b5ba00', - '--vscode-terminal-ansiBrightBlue': '#0451a5', - '--vscode-terminal-ansiBrightMagenta': '#bc05bc', - '--vscode-terminal-ansiBrightCyan': '#0598bc', - '--vscode-terminal-ansiBrightWhite': '#a5a5a5', - '--vscode-terminalCursor-foreground': '#000000', - '--vscode-terminal-selectionBackground': '#add6ff80', - }, +const bundled = _bundled as unknown as MouseTermTheme[]; - 'High Contrast Dark': { - '--vscode-editor-background': '#000000', - '--vscode-editor-foreground': '#ffffff', - '--vscode-sideBar-background': '#000000', - '--vscode-editorWidget-background': '#0c141f', - '--vscode-descriptionForeground': '#ffffff', - '--vscode-focusBorder': '#f38518', - '--vscode-panel-border': '#6fc3df', - '--vscode-tab-activeBackground': '#000000', - '--vscode-tab-inactiveBackground': '#000000', - '--vscode-tab-activeForeground': '#ffffff', - '--vscode-tab-inactiveForeground': '#ffffff', - '--vscode-list-activeSelectionBackground': '#000000', - '--vscode-list-activeSelectionForeground': '#ffffff', - '--vscode-list-inactiveSelectionBackground': '#000000', - '--vscode-list-inactiveSelectionForeground': '#ffffff', - '--vscode-terminal-background': '#000000', - '--vscode-terminal-foreground': '#ffffff', - '--vscode-badge-background': '#000000', - '--vscode-badge-foreground': '#ffffff', - '--vscode-errorForeground': '#f48771', - '--vscode-input-background': '#000000', - '--vscode-input-border': '#6fc3df', - '--vscode-button-background': '#000000', - '--vscode-button-foreground': '#ffffff', - '--vscode-button-hoverBackground': '#000000', - '--vscode-textLink-foreground': '#3794ff', - '--vscode-terminal-ansiBlack': '#000000', - '--vscode-terminal-ansiRed': '#cd3131', - '--vscode-terminal-ansiGreen': '#0dbc79', - '--vscode-terminal-ansiYellow': '#e5e510', - '--vscode-terminal-ansiBlue': '#2472c8', - '--vscode-terminal-ansiMagenta': '#bc3fbc', - '--vscode-terminal-ansiCyan': '#11a8cd', - '--vscode-terminal-ansiWhite': '#e5e5e5', - '--vscode-terminal-ansiBrightBlack': '#666666', - '--vscode-terminal-ansiBrightRed': '#f14c4c', - '--vscode-terminal-ansiBrightGreen': '#23d18b', - '--vscode-terminal-ansiBrightYellow': '#f5f543', - '--vscode-terminal-ansiBrightBlue': '#3b8eea', - '--vscode-terminal-ansiBrightMagenta': '#d670d6', - '--vscode-terminal-ansiBrightCyan': '#29b8db', - '--vscode-terminal-ansiBrightWhite': '#e5e5e5', - '--vscode-terminalCursor-foreground': '#ffffff', - '--vscode-terminal-selectionBackground': '#ffffff40', - }, - - 'High Contrast Light': { - '--vscode-editor-background': '#ffffff', - '--vscode-editor-foreground': '#292929', - '--vscode-sideBar-background': '#ffffff', - '--vscode-editorWidget-background': '#f0f0f0', - '--vscode-descriptionForeground': '#292929', - '--vscode-focusBorder': '#0090f1', - '--vscode-panel-border': '#292929', - '--vscode-tab-activeBackground': '#ffffff', - '--vscode-tab-inactiveBackground': '#ffffff', - '--vscode-tab-activeForeground': '#292929', - '--vscode-tab-inactiveForeground': '#292929', - '--vscode-list-activeSelectionBackground': '#0f4a85', - '--vscode-list-activeSelectionForeground': '#ffffff', - '--vscode-list-inactiveSelectionBackground': '#c8ddf3', - '--vscode-list-inactiveSelectionForeground': '#292929', - '--vscode-terminal-background': '#ffffff', - '--vscode-terminal-foreground': '#292929', - '--vscode-badge-background': '#0f4a85', - '--vscode-badge-foreground': '#ffffff', - '--vscode-errorForeground': '#b5200d', - '--vscode-input-background': '#ffffff', - '--vscode-input-border': '#292929', - '--vscode-button-background': '#0f4a85', - '--vscode-button-foreground': '#ffffff', - '--vscode-button-hoverBackground': '#264f78', - '--vscode-textLink-foreground': '#0f4a85', - '--vscode-terminal-ansiBlack': '#292929', - '--vscode-terminal-ansiRed': '#b5200d', - '--vscode-terminal-ansiGreen': '#1a7f37', - '--vscode-terminal-ansiYellow': '#6c6c00', - '--vscode-terminal-ansiBlue': '#0451a5', - '--vscode-terminal-ansiMagenta': '#8b2252', - '--vscode-terminal-ansiCyan': '#0598bc', - '--vscode-terminal-ansiWhite': '#d6d6d6', - '--vscode-terminal-ansiBrightBlack': '#666666', - '--vscode-terminal-ansiBrightRed': '#cd3131', - '--vscode-terminal-ansiBrightGreen': '#14ce14', - '--vscode-terminal-ansiBrightYellow': '#b5ba00', - '--vscode-terminal-ansiBrightBlue': '#0451a5', - '--vscode-terminal-ansiBrightMagenta': '#bc05bc', - '--vscode-terminal-ansiBrightCyan': '#0598bc', - '--vscode-terminal-ansiBrightWhite': '#a5a5a5', - '--vscode-terminalCursor-foreground': '#292929', - '--vscode-terminal-selectionBackground': '#0f4a8540', - }, -}; +export const VSCODE_THEMES: Record> = {}; +for (const theme of bundled) { + VSCODE_THEMES[theme.label] = theme.vars; +} diff --git a/lib/package.json b/lib/package.json index 2680fe33..8f1773ed 100644 --- a/lib/package.json +++ b/lib/package.json @@ -21,6 +21,8 @@ "dockview-react": "^5.1.0", "react": "^19.2.0", "react-dom": "^19.2.0", + "fflate": "0.8.2", + "jsonc-parser": "3.3.1", "tailwind-merge": "^3.5.0", "tailwind-variants": "^3.2.2" }, diff --git a/lib/scripts/bundle-themes.mjs b/lib/scripts/bundle-themes.mjs new file mode 100644 index 00000000..7703ac2b --- /dev/null +++ b/lib/scripts/bundle-themes.mjs @@ -0,0 +1,250 @@ +#!/usr/bin/env node +/** + * Download VSCode theme extensions from OpenVSX, extract theme JSONs, + * and write lib/src/lib/themes/bundled.json. + * + * Usage: node scripts/bundle-themes.mjs + * + * Output is checked into git so builds don't require network access. + */ + +import { writeFileSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { unzipSync } from 'fflate'; +import { parse as parseJsonc } from 'jsonc-parser'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const OUTPUT = resolve(__dirname, '../src/lib/themes/bundled.json'); + +/** Extensions to download from OpenVSX. */ +const EXTENSIONS = [ + { namespace: 'GitHub', name: 'github-vscode-theme' }, + { namespace: 'dracula-theme', name: 'theme-dracula' }, +]; + +/** + * VSCode theme color keys consumed by MouseTerm. + * Keep in sync with lib/src/lib/themes/convert.ts. + */ +const CONSUMED_KEYS = new Set([ + 'editor.background', 'editorGroupHeader.tabsBackground', 'sideBar.background', + 'editorWidget.background', 'editor.foreground', 'descriptionForeground', + 'focusBorder', 'panel.border', + 'tab.activeBackground', 'tab.inactiveBackground', 'tab.activeForeground', + 'tab.inactiveForeground', 'list.activeSelectionBackground', 'list.activeSelectionForeground', + 'terminal.background', 'terminal.foreground', 'badge.background', 'badge.foreground', + 'errorForeground', 'editorWarning.foreground', + 'input.background', 'input.border', + 'button.background', 'button.foreground', 'button.hoverBackground', + 'textLink.foreground', + 'terminalCursor.foreground', 'terminal.selectionBackground', + 'terminal.ansiBlack', 'terminal.ansiRed', 'terminal.ansiGreen', 'terminal.ansiYellow', + 'terminal.ansiBlue', 'terminal.ansiMagenta', 'terminal.ansiCyan', 'terminal.ansiWhite', + 'terminal.ansiBrightBlack', 'terminal.ansiBrightRed', 'terminal.ansiBrightGreen', + 'terminal.ansiBrightYellow', 'terminal.ansiBrightBlue', 'terminal.ansiBrightMagenta', + 'terminal.ansiBrightCyan', 'terminal.ansiBrightWhite', +]); + +function convertColors(colors) { + const vars = {}; + for (const [key, value] of Object.entries(colors)) { + if (CONSUMED_KEYS.has(key)) { + vars[`--vscode-${key.replace(/\./g, '-')}`] = value; + } + } + return vars; +} + +function uiThemeToType(uiTheme) { + return uiTheme === 'vs' || uiTheme === 'hc-light' ? 'light' : 'dark'; +} + +function slugify(label) { + return label.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); +} + +/** Read a UTF-8 file from the unzipped VSIX entries. */ +function readEntry(entries, path) { + // VSIX entries are prefixed with "extension/" + const key = `extension/${path}`; + const data = entries[key]; + if (!data) throw new Error(`Missing ${key} in VSIX`); + return new TextDecoder().decode(data); +} + +async function fetchExtensionThemes(namespace, name) { + console.log(`Fetching ${namespace}/${name} from OpenVSX...`); + + // Get latest version metadata + const metaRes = await fetch(`https://open-vsx.org/api/${namespace}/${name}/latest`); + if (!metaRes.ok) throw new Error(`OpenVSX metadata failed: ${metaRes.status}`); + const meta = await metaRes.json(); + + const downloadUrl = meta.files?.download; + if (!downloadUrl) throw new Error(`No download URL for ${namespace}/${name}`); + + console.log(` Downloading VSIX (v${meta.version})...`); + const vsixRes = await fetch(downloadUrl); + if (!vsixRes.ok) throw new Error(`VSIX download failed: ${vsixRes.status}`); + const vsixBuf = new Uint8Array(await vsixRes.arrayBuffer()); + + console.log(` Extracting...`); + const entries = unzipSync(vsixBuf); + + // Read package.json to find theme contributions + const pkgJson = JSON.parse(readEntry(entries, 'package.json')); + const themeContribs = pkgJson.contributes?.themes ?? []; + + const themes = []; + for (const contrib of themeContribs) { + const themePath = contrib.path.replace(/^\.\//, ''); + console.log(` Converting ${contrib.label} (${themePath})...`); + + const themeJson = parseJsonc(readEntry(entries, themePath)); + const colors = themeJson.colors ?? {}; + const vars = convertColors(colors); + const type = uiThemeToType(contrib.uiTheme ?? themeJson.type ?? 'vs-dark'); + + themes.push({ + id: `${namespace}.${name}.${slugify(contrib.label)}`, + label: contrib.label, + type, + swatch: colors['editor.background'] ?? (type === 'light' ? '#ffffff' : '#1e1e1e'), + accent: colors['focusBorder'] ?? (type === 'light' ? '#0090f1' : '#007fd4'), + vars, + origin: { kind: 'bundled' }, + }); + } + + console.log(` Found ${themes.length} theme(s).`); + return themes; +} + +/** VSCode built-in Dark+ and Light+ (not on OpenVSX). */ +function builtinThemes() { + return [ + { + id: 'builtin.dark-plus', + label: 'Dark+ (default)', + type: 'dark', + swatch: '#1e1e1e', + accent: '#007fd4', + vars: { + '--vscode-editor-background': '#1e1e1e', + '--vscode-editor-foreground': '#cccccc', + '--vscode-sideBar-background': '#252526', + '--vscode-editorWidget-background': '#252526', + '--vscode-descriptionForeground': '#858585', + '--vscode-focusBorder': '#007fd4', + '--vscode-panel-border': '#2b2b2b', + '--vscode-tab-activeBackground': '#1e1e1e', + '--vscode-tab-inactiveBackground': '#2d2d2d', + '--vscode-tab-activeForeground': '#ffffff', + '--vscode-tab-inactiveForeground': '#969696', + '--vscode-list-activeSelectionBackground': '#094771', + '--vscode-list-activeSelectionForeground': '#ffffff', + '--vscode-terminal-background': '#1e1e1e', + '--vscode-terminal-foreground': '#cccccc', + '--vscode-badge-background': '#007acc', + '--vscode-badge-foreground': '#ffffff', + '--vscode-errorForeground': '#f48771', + '--vscode-input-background': '#3c3c3c', + '--vscode-input-border': '#3c3c3c', + '--vscode-button-background': '#0e639c', + '--vscode-button-foreground': '#ffffff', + '--vscode-button-hoverBackground': '#1177bb', + '--vscode-textLink-foreground': '#3794ff', + '--vscode-terminal-ansiBlack': '#000000', + '--vscode-terminal-ansiRed': '#cd3131', + '--vscode-terminal-ansiGreen': '#0dbc79', + '--vscode-terminal-ansiYellow': '#e5e510', + '--vscode-terminal-ansiBlue': '#2472c8', + '--vscode-terminal-ansiMagenta': '#bc3fbc', + '--vscode-terminal-ansiCyan': '#11a8cd', + '--vscode-terminal-ansiWhite': '#e5e5e5', + '--vscode-terminal-ansiBrightBlack': '#666666', + '--vscode-terminal-ansiBrightRed': '#f14c4c', + '--vscode-terminal-ansiBrightGreen': '#23d18b', + '--vscode-terminal-ansiBrightYellow': '#f5f543', + '--vscode-terminal-ansiBrightBlue': '#3b8eea', + '--vscode-terminal-ansiBrightMagenta': '#d670d6', + '--vscode-terminal-ansiBrightCyan': '#29b8db', + '--vscode-terminal-ansiBrightWhite': '#e5e5e5', + '--vscode-terminalCursor-foreground': '#aeafad', + '--vscode-terminal-selectionBackground': '#264f7840', + }, + origin: { kind: 'bundled' }, + }, + { + id: 'builtin.light-plus', + label: 'Light+ (default)', + type: 'light', + swatch: '#ffffff', + accent: '#0090f1', + vars: { + '--vscode-editor-background': '#ffffff', + '--vscode-editor-foreground': '#333333', + '--vscode-sideBar-background': '#f3f3f3', + '--vscode-editorWidget-background': '#f3f3f3', + '--vscode-descriptionForeground': '#717171', + '--vscode-focusBorder': '#0090f1', + '--vscode-panel-border': '#e5e5e5', + '--vscode-tab-activeBackground': '#ffffff', + '--vscode-tab-inactiveBackground': '#ececec', + '--vscode-tab-activeForeground': '#333333', + '--vscode-tab-inactiveForeground': '#8e8e8e', + '--vscode-list-activeSelectionBackground': '#cce6ff', + '--vscode-list-activeSelectionForeground': '#000000', + '--vscode-terminal-background': '#ffffff', + '--vscode-terminal-foreground': '#333333', + '--vscode-badge-background': '#007acc', + '--vscode-badge-foreground': '#ffffff', + '--vscode-errorForeground': '#a1260d', + '--vscode-input-background': '#ffffff', + '--vscode-input-border': '#cecece', + '--vscode-button-background': '#007acc', + '--vscode-button-foreground': '#ffffff', + '--vscode-button-hoverBackground': '#0062a3', + '--vscode-textLink-foreground': '#006ab1', + '--vscode-terminal-ansiBlack': '#000000', + '--vscode-terminal-ansiRed': '#cd3131', + '--vscode-terminal-ansiGreen': '#00bc00', + '--vscode-terminal-ansiYellow': '#949800', + '--vscode-terminal-ansiBlue': '#0451a5', + '--vscode-terminal-ansiMagenta': '#bc05bc', + '--vscode-terminal-ansiCyan': '#0598bc', + '--vscode-terminal-ansiWhite': '#555555', + '--vscode-terminal-ansiBrightBlack': '#666666', + '--vscode-terminal-ansiBrightRed': '#cd3131', + '--vscode-terminal-ansiBrightGreen': '#14ce14', + '--vscode-terminal-ansiBrightYellow': '#b5ba00', + '--vscode-terminal-ansiBrightBlue': '#0451a5', + '--vscode-terminal-ansiBrightMagenta': '#bc05bc', + '--vscode-terminal-ansiBrightCyan': '#0598bc', + '--vscode-terminal-ansiBrightWhite': '#a5a5a5', + '--vscode-terminalCursor-foreground': '#000000', + '--vscode-terminal-selectionBackground': '#add6ff80', + }, + origin: { kind: 'bundled' }, + }, + ]; +} + +async function main() { + const allThemes = [...builtinThemes()]; + + for (const ext of EXTENSIONS) { + const themes = await fetchExtensionThemes(ext.namespace, ext.name); + allThemes.push(...themes); + } + + console.log(`\nWriting ${allThemes.length} themes to ${OUTPUT}`); + writeFileSync(OUTPUT, JSON.stringify(allThemes, null, 2) + '\n'); + console.log('Done.'); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/lib/src/lib/themes/apply.ts b/lib/src/lib/themes/apply.ts new file mode 100644 index 00000000..c720b8f1 --- /dev/null +++ b/lib/src/lib/themes/apply.ts @@ -0,0 +1,37 @@ +import type { MouseTermTheme } from './types'; + +/** Previously applied variable names — tracked for cleanup. */ +let appliedVarNames: string[] = []; + +/** + * Apply a theme by setting --vscode-* CSS variables on document.body. + * + * Also manages body classes (vscode-light / vscode-dark) so that + * theme.css fallback selectors activate correctly. + * + * The MutationObserver in terminal-registry.ts detects the style change + * and re-reads the theme for all xterm.js terminals. + */ +export function applyTheme(theme: MouseTermTheme): void { + if (typeof document === 'undefined') return; + + // Clear previously applied variables + for (const name of appliedVarNames) { + document.body.style.removeProperty(name); + } + + // Apply new variables + appliedVarNames = Object.keys(theme.vars); + for (const [name, value] of Object.entries(theme.vars)) { + document.body.style.setProperty(name, value); + } + + // Set body class for light/dark so theme.css fallbacks work + if (theme.type === 'light') { + document.body.classList.add('vscode-light'); + document.body.classList.remove('vscode-dark'); + } else { + document.body.classList.add('vscode-dark'); + document.body.classList.remove('vscode-light'); + } +} diff --git a/lib/src/lib/themes/bundled.json b/lib/src/lib/themes/bundled.json new file mode 100644 index 00000000..4eda66aa --- /dev/null +++ b/lib/src/lib/themes/bundled.json @@ -0,0 +1,684 @@ +[ + { + "id": "builtin.dark-plus", + "label": "Dark+ (default)", + "type": "dark", + "swatch": "#1e1e1e", + "accent": "#007fd4", + "vars": { + "--vscode-editor-background": "#1e1e1e", + "--vscode-editor-foreground": "#cccccc", + "--vscode-sideBar-background": "#252526", + "--vscode-editorWidget-background": "#252526", + "--vscode-descriptionForeground": "#858585", + "--vscode-focusBorder": "#007fd4", + "--vscode-panel-border": "#2b2b2b", + "--vscode-tab-activeBackground": "#1e1e1e", + "--vscode-tab-inactiveBackground": "#2d2d2d", + "--vscode-tab-activeForeground": "#ffffff", + "--vscode-tab-inactiveForeground": "#969696", + "--vscode-list-activeSelectionBackground": "#094771", + "--vscode-list-activeSelectionForeground": "#ffffff", + "--vscode-terminal-background": "#1e1e1e", + "--vscode-terminal-foreground": "#cccccc", + "--vscode-badge-background": "#007acc", + "--vscode-badge-foreground": "#ffffff", + "--vscode-errorForeground": "#f48771", + "--vscode-input-background": "#3c3c3c", + "--vscode-input-border": "#3c3c3c", + "--vscode-button-background": "#0e639c", + "--vscode-button-foreground": "#ffffff", + "--vscode-button-hoverBackground": "#1177bb", + "--vscode-textLink-foreground": "#3794ff", + "--vscode-terminal-ansiBlack": "#000000", + "--vscode-terminal-ansiRed": "#cd3131", + "--vscode-terminal-ansiGreen": "#0dbc79", + "--vscode-terminal-ansiYellow": "#e5e510", + "--vscode-terminal-ansiBlue": "#2472c8", + "--vscode-terminal-ansiMagenta": "#bc3fbc", + "--vscode-terminal-ansiCyan": "#11a8cd", + "--vscode-terminal-ansiWhite": "#e5e5e5", + "--vscode-terminal-ansiBrightBlack": "#666666", + "--vscode-terminal-ansiBrightRed": "#f14c4c", + "--vscode-terminal-ansiBrightGreen": "#23d18b", + "--vscode-terminal-ansiBrightYellow": "#f5f543", + "--vscode-terminal-ansiBrightBlue": "#3b8eea", + "--vscode-terminal-ansiBrightMagenta": "#d670d6", + "--vscode-terminal-ansiBrightCyan": "#29b8db", + "--vscode-terminal-ansiBrightWhite": "#e5e5e5", + "--vscode-terminalCursor-foreground": "#aeafad", + "--vscode-terminal-selectionBackground": "#264f7840" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "builtin.light-plus", + "label": "Light+ (default)", + "type": "light", + "swatch": "#ffffff", + "accent": "#0090f1", + "vars": { + "--vscode-editor-background": "#ffffff", + "--vscode-editor-foreground": "#333333", + "--vscode-sideBar-background": "#f3f3f3", + "--vscode-editorWidget-background": "#f3f3f3", + "--vscode-descriptionForeground": "#717171", + "--vscode-focusBorder": "#0090f1", + "--vscode-panel-border": "#e5e5e5", + "--vscode-tab-activeBackground": "#ffffff", + "--vscode-tab-inactiveBackground": "#ececec", + "--vscode-tab-activeForeground": "#333333", + "--vscode-tab-inactiveForeground": "#8e8e8e", + "--vscode-list-activeSelectionBackground": "#cce6ff", + "--vscode-list-activeSelectionForeground": "#000000", + "--vscode-terminal-background": "#ffffff", + "--vscode-terminal-foreground": "#333333", + "--vscode-badge-background": "#007acc", + "--vscode-badge-foreground": "#ffffff", + "--vscode-errorForeground": "#a1260d", + "--vscode-input-background": "#ffffff", + "--vscode-input-border": "#cecece", + "--vscode-button-background": "#007acc", + "--vscode-button-foreground": "#ffffff", + "--vscode-button-hoverBackground": "#0062a3", + "--vscode-textLink-foreground": "#006ab1", + "--vscode-terminal-ansiBlack": "#000000", + "--vscode-terminal-ansiRed": "#cd3131", + "--vscode-terminal-ansiGreen": "#00bc00", + "--vscode-terminal-ansiYellow": "#949800", + "--vscode-terminal-ansiBlue": "#0451a5", + "--vscode-terminal-ansiMagenta": "#bc05bc", + "--vscode-terminal-ansiCyan": "#0598bc", + "--vscode-terminal-ansiWhite": "#555555", + "--vscode-terminal-ansiBrightBlack": "#666666", + "--vscode-terminal-ansiBrightRed": "#cd3131", + "--vscode-terminal-ansiBrightGreen": "#14ce14", + "--vscode-terminal-ansiBrightYellow": "#b5ba00", + "--vscode-terminal-ansiBrightBlue": "#0451a5", + "--vscode-terminal-ansiBrightMagenta": "#bc05bc", + "--vscode-terminal-ansiBrightCyan": "#0598bc", + "--vscode-terminal-ansiBrightWhite": "#a5a5a5", + "--vscode-terminalCursor-foreground": "#000000", + "--vscode-terminal-selectionBackground": "#add6ff80" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "GitHub.github-vscode-theme.github-light-default", + "label": "GitHub Light Default", + "type": "light", + "swatch": "#ffffff", + "accent": "#0969da", + "vars": { + "--vscode-focusBorder": "#0969da", + "--vscode-descriptionForeground": "#656d76", + "--vscode-errorForeground": "#cf222e", + "--vscode-textLink-foreground": "#0969da", + "--vscode-button-background": "#1f883d", + "--vscode-button-foreground": "#ffffff", + "--vscode-button-hoverBackground": "#1a7f37", + "--vscode-input-background": "#ffffff", + "--vscode-input-border": "#d0d7de", + "--vscode-badge-foreground": "#ffffff", + "--vscode-badge-background": "#0969da", + "--vscode-sideBar-background": "#f6f8fa", + "--vscode-list-activeSelectionForeground": "#1f2328", + "--vscode-list-activeSelectionBackground": "#afb8c133", + "--vscode-editorGroupHeader-tabsBackground": "#f6f8fa", + "--vscode-tab-activeForeground": "#1f2328", + "--vscode-tab-inactiveForeground": "#656d76", + "--vscode-tab-inactiveBackground": "#f6f8fa", + "--vscode-tab-activeBackground": "#ffffff", + "--vscode-editor-foreground": "#1f2328", + "--vscode-editor-background": "#ffffff", + "--vscode-editorWidget-background": "#ffffff", + "--vscode-panel-border": "#d0d7de", + "--vscode-terminal-foreground": "#1f2328", + "--vscode-terminal-ansiBlack": "#24292f", + "--vscode-terminal-ansiRed": "#cf222e", + "--vscode-terminal-ansiGreen": "#116329", + "--vscode-terminal-ansiYellow": "#4d2d00", + "--vscode-terminal-ansiBlue": "#0969da", + "--vscode-terminal-ansiMagenta": "#8250df", + "--vscode-terminal-ansiCyan": "#1b7c83", + "--vscode-terminal-ansiWhite": "#6e7781", + "--vscode-terminal-ansiBrightBlack": "#57606a", + "--vscode-terminal-ansiBrightRed": "#a40e26", + "--vscode-terminal-ansiBrightGreen": "#1a7f37", + "--vscode-terminal-ansiBrightYellow": "#633c01", + "--vscode-terminal-ansiBrightBlue": "#218bff", + "--vscode-terminal-ansiBrightMagenta": "#a475f9", + "--vscode-terminal-ansiBrightCyan": "#3192aa", + "--vscode-terminal-ansiBrightWhite": "#8c959f" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "GitHub.github-vscode-theme.github-light-high-contrast", + "label": "GitHub Light High Contrast", + "type": "light", + "swatch": "#ffffff", + "accent": "#0349b4", + "vars": { + "--vscode-focusBorder": "#0349b4", + "--vscode-descriptionForeground": "#0e1116", + "--vscode-errorForeground": "#a0111f", + "--vscode-textLink-foreground": "#0349b4", + "--vscode-button-background": "#055d20", + "--vscode-button-foreground": "#ffffff", + "--vscode-button-hoverBackground": "#024c1a", + "--vscode-input-background": "#ffffff", + "--vscode-input-border": "#20252c", + "--vscode-badge-foreground": "#ffffff", + "--vscode-badge-background": "#0349b4", + "--vscode-sideBar-background": "#ffffff", + "--vscode-list-activeSelectionForeground": "#0e1116", + "--vscode-list-activeSelectionBackground": "#acb6c033", + "--vscode-editorGroupHeader-tabsBackground": "#ffffff", + "--vscode-tab-activeForeground": "#0e1116", + "--vscode-tab-inactiveForeground": "#0e1116", + "--vscode-tab-inactiveBackground": "#ffffff", + "--vscode-tab-activeBackground": "#ffffff", + "--vscode-editor-foreground": "#0e1116", + "--vscode-editor-background": "#ffffff", + "--vscode-editorWidget-background": "#ffffff", + "--vscode-panel-border": "#20252c", + "--vscode-terminal-foreground": "#0e1116", + "--vscode-terminal-ansiBlack": "#0e1116", + "--vscode-terminal-ansiRed": "#a0111f", + "--vscode-terminal-ansiGreen": "#024c1a", + "--vscode-terminal-ansiYellow": "#3f2200", + "--vscode-terminal-ansiBlue": "#0349b4", + "--vscode-terminal-ansiMagenta": "#622cbc", + "--vscode-terminal-ansiCyan": "#1b7c83", + "--vscode-terminal-ansiWhite": "#66707b", + "--vscode-terminal-ansiBrightBlack": "#4b535d", + "--vscode-terminal-ansiBrightRed": "#86061d", + "--vscode-terminal-ansiBrightGreen": "#055d20", + "--vscode-terminal-ansiBrightYellow": "#4e2c00", + "--vscode-terminal-ansiBrightBlue": "#1168e3", + "--vscode-terminal-ansiBrightMagenta": "#844ae7", + "--vscode-terminal-ansiBrightCyan": "#3192aa", + "--vscode-terminal-ansiBrightWhite": "#88929d" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "GitHub.github-vscode-theme.github-light-colorblind-beta", + "label": "GitHub Light Colorblind (Beta)", + "type": "light", + "swatch": "#ffffff", + "accent": "#0969da", + "vars": { + "--vscode-focusBorder": "#0969da", + "--vscode-descriptionForeground": "#57606a", + "--vscode-errorForeground": "#b35900", + "--vscode-textLink-foreground": "#0969da", + "--vscode-button-background": "#218bff", + "--vscode-button-foreground": "#ffffff", + "--vscode-button-hoverBackground": "#0969da", + "--vscode-input-background": "#ffffff", + "--vscode-input-border": "#d0d7de", + "--vscode-badge-foreground": "#ffffff", + "--vscode-badge-background": "#0969da", + "--vscode-sideBar-background": "#f6f8fa", + "--vscode-list-activeSelectionForeground": "#24292f", + "--vscode-list-activeSelectionBackground": "#afb8c133", + "--vscode-editorGroupHeader-tabsBackground": "#f6f8fa", + "--vscode-tab-activeForeground": "#24292f", + "--vscode-tab-inactiveForeground": "#57606a", + "--vscode-tab-inactiveBackground": "#f6f8fa", + "--vscode-tab-activeBackground": "#ffffff", + "--vscode-editor-foreground": "#24292f", + "--vscode-editor-background": "#ffffff", + "--vscode-editorWidget-background": "#ffffff", + "--vscode-panel-border": "#d0d7de", + "--vscode-terminal-foreground": "#24292f", + "--vscode-terminal-ansiBlack": "#24292f", + "--vscode-terminal-ansiRed": "#b35900", + "--vscode-terminal-ansiGreen": "#0550ae", + "--vscode-terminal-ansiYellow": "#4d2d00", + "--vscode-terminal-ansiBlue": "#0969da", + "--vscode-terminal-ansiMagenta": "#8250df", + "--vscode-terminal-ansiCyan": "#1b7c83", + "--vscode-terminal-ansiWhite": "#6e7781", + "--vscode-terminal-ansiBrightBlack": "#57606a", + "--vscode-terminal-ansiBrightRed": "#8a4600", + "--vscode-terminal-ansiBrightGreen": "#0969da", + "--vscode-terminal-ansiBrightYellow": "#633c01", + "--vscode-terminal-ansiBrightBlue": "#218bff", + "--vscode-terminal-ansiBrightMagenta": "#a475f9", + "--vscode-terminal-ansiBrightCyan": "#3192aa", + "--vscode-terminal-ansiBrightWhite": "#8c959f" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "GitHub.github-vscode-theme.github-dark-default", + "label": "GitHub Dark Default", + "type": "dark", + "swatch": "#0d1117", + "accent": "#1f6feb", + "vars": { + "--vscode-focusBorder": "#1f6feb", + "--vscode-descriptionForeground": "#7d8590", + "--vscode-errorForeground": "#f85149", + "--vscode-textLink-foreground": "#2f81f7", + "--vscode-button-background": "#238636", + "--vscode-button-foreground": "#ffffff", + "--vscode-button-hoverBackground": "#2ea043", + "--vscode-input-background": "#0d1117", + "--vscode-input-border": "#30363d", + "--vscode-badge-foreground": "#ffffff", + "--vscode-badge-background": "#1f6feb", + "--vscode-sideBar-background": "#010409", + "--vscode-list-activeSelectionForeground": "#e6edf3", + "--vscode-list-activeSelectionBackground": "#6e768166", + "--vscode-editorGroupHeader-tabsBackground": "#010409", + "--vscode-tab-activeForeground": "#e6edf3", + "--vscode-tab-inactiveForeground": "#7d8590", + "--vscode-tab-inactiveBackground": "#010409", + "--vscode-tab-activeBackground": "#0d1117", + "--vscode-editor-foreground": "#e6edf3", + "--vscode-editor-background": "#0d1117", + "--vscode-editorWidget-background": "#161b22", + "--vscode-panel-border": "#30363d", + "--vscode-terminal-foreground": "#e6edf3", + "--vscode-terminal-ansiBlack": "#484f58", + "--vscode-terminal-ansiRed": "#ff7b72", + "--vscode-terminal-ansiGreen": "#3fb950", + "--vscode-terminal-ansiYellow": "#d29922", + "--vscode-terminal-ansiBlue": "#58a6ff", + "--vscode-terminal-ansiMagenta": "#bc8cff", + "--vscode-terminal-ansiCyan": "#39c5cf", + "--vscode-terminal-ansiWhite": "#b1bac4", + "--vscode-terminal-ansiBrightBlack": "#6e7681", + "--vscode-terminal-ansiBrightRed": "#ffa198", + "--vscode-terminal-ansiBrightGreen": "#56d364", + "--vscode-terminal-ansiBrightYellow": "#e3b341", + "--vscode-terminal-ansiBrightBlue": "#79c0ff", + "--vscode-terminal-ansiBrightMagenta": "#d2a8ff", + "--vscode-terminal-ansiBrightCyan": "#56d4dd", + "--vscode-terminal-ansiBrightWhite": "#ffffff" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "GitHub.github-vscode-theme.github-dark-high-contrast", + "label": "GitHub Dark High Contrast", + "type": "dark", + "swatch": "#0a0c10", + "accent": "#409eff", + "vars": { + "--vscode-focusBorder": "#409eff", + "--vscode-descriptionForeground": "#f0f3f6", + "--vscode-errorForeground": "#ff6a69", + "--vscode-textLink-foreground": "#71b7ff", + "--vscode-button-background": "#09b43a", + "--vscode-button-foreground": "#0a0c10", + "--vscode-button-hoverBackground": "#26cd4d", + "--vscode-input-background": "#0a0c10", + "--vscode-input-border": "#7a828e", + "--vscode-badge-foreground": "#0a0c10", + "--vscode-badge-background": "#409eff", + "--vscode-sideBar-background": "#010409", + "--vscode-list-activeSelectionForeground": "#f0f3f6", + "--vscode-list-activeSelectionBackground": "#9ea7b366", + "--vscode-editorGroupHeader-tabsBackground": "#010409", + "--vscode-tab-activeForeground": "#f0f3f6", + "--vscode-tab-inactiveForeground": "#f0f3f6", + "--vscode-tab-inactiveBackground": "#010409", + "--vscode-tab-activeBackground": "#0a0c10", + "--vscode-editor-foreground": "#f0f3f6", + "--vscode-editor-background": "#0a0c10", + "--vscode-editorWidget-background": "#272b33", + "--vscode-panel-border": "#7a828e", + "--vscode-terminal-foreground": "#f0f3f6", + "--vscode-terminal-ansiBlack": "#7a828e", + "--vscode-terminal-ansiRed": "#ff9492", + "--vscode-terminal-ansiGreen": "#26cd4d", + "--vscode-terminal-ansiYellow": "#f0b72f", + "--vscode-terminal-ansiBlue": "#71b7ff", + "--vscode-terminal-ansiMagenta": "#cb9eff", + "--vscode-terminal-ansiCyan": "#39c5cf", + "--vscode-terminal-ansiWhite": "#d9dee3", + "--vscode-terminal-ansiBrightBlack": "#9ea7b3", + "--vscode-terminal-ansiBrightRed": "#ffb1af", + "--vscode-terminal-ansiBrightGreen": "#4ae168", + "--vscode-terminal-ansiBrightYellow": "#f7c843", + "--vscode-terminal-ansiBrightBlue": "#91cbff", + "--vscode-terminal-ansiBrightMagenta": "#dbb7ff", + "--vscode-terminal-ansiBrightCyan": "#56d4dd", + "--vscode-terminal-ansiBrightWhite": "#ffffff" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "GitHub.github-vscode-theme.github-dark-colorblind-beta", + "label": "GitHub Dark Colorblind (Beta)", + "type": "dark", + "swatch": "#0d1117", + "accent": "#1f6feb", + "vars": { + "--vscode-focusBorder": "#1f6feb", + "--vscode-descriptionForeground": "#8b949e", + "--vscode-errorForeground": "#d47616", + "--vscode-textLink-foreground": "#58a6ff", + "--vscode-button-background": "#1f6feb", + "--vscode-button-foreground": "#ffffff", + "--vscode-button-hoverBackground": "#388bfd", + "--vscode-input-background": "#0d1117", + "--vscode-input-border": "#30363d", + "--vscode-badge-foreground": "#ffffff", + "--vscode-badge-background": "#1f6feb", + "--vscode-sideBar-background": "#010409", + "--vscode-list-activeSelectionForeground": "#c9d1d9", + "--vscode-list-activeSelectionBackground": "#6e768166", + "--vscode-editorGroupHeader-tabsBackground": "#010409", + "--vscode-tab-activeForeground": "#c9d1d9", + "--vscode-tab-inactiveForeground": "#8b949e", + "--vscode-tab-inactiveBackground": "#010409", + "--vscode-tab-activeBackground": "#0d1117", + "--vscode-editor-foreground": "#c9d1d9", + "--vscode-editor-background": "#0d1117", + "--vscode-editorWidget-background": "#161b22", + "--vscode-panel-border": "#30363d", + "--vscode-terminal-foreground": "#c9d1d9", + "--vscode-terminal-ansiBlack": "#484f58", + "--vscode-terminal-ansiRed": "#ec8e2c", + "--vscode-terminal-ansiGreen": "#58a6ff", + "--vscode-terminal-ansiYellow": "#d29922", + "--vscode-terminal-ansiBlue": "#58a6ff", + "--vscode-terminal-ansiMagenta": "#bc8cff", + "--vscode-terminal-ansiCyan": "#39c5cf", + "--vscode-terminal-ansiWhite": "#b1bac4", + "--vscode-terminal-ansiBrightBlack": "#6e7681", + "--vscode-terminal-ansiBrightRed": "#fdac54", + "--vscode-terminal-ansiBrightGreen": "#79c0ff", + "--vscode-terminal-ansiBrightYellow": "#e3b341", + "--vscode-terminal-ansiBrightBlue": "#79c0ff", + "--vscode-terminal-ansiBrightMagenta": "#d2a8ff", + "--vscode-terminal-ansiBrightCyan": "#56d4dd", + "--vscode-terminal-ansiBrightWhite": "#ffffff" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "GitHub.github-vscode-theme.github-dark-dimmed", + "label": "GitHub Dark Dimmed", + "type": "dark", + "swatch": "#22272e", + "accent": "#316dca", + "vars": { + "--vscode-focusBorder": "#316dca", + "--vscode-descriptionForeground": "#768390", + "--vscode-errorForeground": "#e5534b", + "--vscode-textLink-foreground": "#539bf5", + "--vscode-button-background": "#347d39", + "--vscode-button-foreground": "#ffffff", + "--vscode-button-hoverBackground": "#46954a", + "--vscode-input-background": "#22272e", + "--vscode-input-border": "#444c56", + "--vscode-badge-foreground": "#cdd9e5", + "--vscode-badge-background": "#316dca", + "--vscode-sideBar-background": "#1c2128", + "--vscode-list-activeSelectionForeground": "#adbac7", + "--vscode-list-activeSelectionBackground": "#636e7b66", + "--vscode-editorGroupHeader-tabsBackground": "#1c2128", + "--vscode-tab-activeForeground": "#adbac7", + "--vscode-tab-inactiveForeground": "#768390", + "--vscode-tab-inactiveBackground": "#1c2128", + "--vscode-tab-activeBackground": "#22272e", + "--vscode-editor-foreground": "#adbac7", + "--vscode-editor-background": "#22272e", + "--vscode-editorWidget-background": "#2d333b", + "--vscode-panel-border": "#444c56", + "--vscode-terminal-foreground": "#adbac7", + "--vscode-terminal-ansiBlack": "#545d68", + "--vscode-terminal-ansiRed": "#f47067", + "--vscode-terminal-ansiGreen": "#57ab5a", + "--vscode-terminal-ansiYellow": "#c69026", + "--vscode-terminal-ansiBlue": "#539bf5", + "--vscode-terminal-ansiMagenta": "#b083f0", + "--vscode-terminal-ansiCyan": "#39c5cf", + "--vscode-terminal-ansiWhite": "#909dab", + "--vscode-terminal-ansiBrightBlack": "#636e7b", + "--vscode-terminal-ansiBrightRed": "#ff938a", + "--vscode-terminal-ansiBrightGreen": "#6bc46d", + "--vscode-terminal-ansiBrightYellow": "#daaa3f", + "--vscode-terminal-ansiBrightBlue": "#6cb6ff", + "--vscode-terminal-ansiBrightMagenta": "#dcbdfb", + "--vscode-terminal-ansiBrightCyan": "#56d4dd", + "--vscode-terminal-ansiBrightWhite": "#cdd9e5" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "GitHub.github-vscode-theme.github-light", + "label": "GitHub Light", + "type": "light", + "swatch": "#fff", + "accent": "#2188ff", + "vars": { + "--vscode-focusBorder": "#2188ff", + "--vscode-descriptionForeground": "#6a737d", + "--vscode-errorForeground": "#cb2431", + "--vscode-textLink-foreground": "#0366d6", + "--vscode-button-background": "#159739", + "--vscode-button-foreground": "#fff", + "--vscode-button-hoverBackground": "#138934", + "--vscode-input-background": "#fafbfc", + "--vscode-input-border": "#e1e4e8", + "--vscode-badge-foreground": "#005cc5", + "--vscode-badge-background": "#dbedff", + "--vscode-sideBar-background": "#f6f8fa", + "--vscode-list-activeSelectionForeground": "#2f363d", + "--vscode-list-activeSelectionBackground": "#e2e5e9", + "--vscode-editorGroupHeader-tabsBackground": "#f6f8fa", + "--vscode-tab-activeForeground": "#2f363d", + "--vscode-tab-inactiveForeground": "#6a737d", + "--vscode-tab-inactiveBackground": "#f6f8fa", + "--vscode-tab-activeBackground": "#fff", + "--vscode-editor-foreground": "#24292e", + "--vscode-editor-background": "#fff", + "--vscode-editorWidget-background": "#f6f8fa", + "--vscode-editorWarning-foreground": "#f9c513", + "--vscode-panel-border": "#e1e4e8", + "--vscode-terminal-foreground": "#586069", + "--vscode-terminalCursor-foreground": "#005cc5", + "--vscode-terminal-ansiBrightWhite": "#d1d5da", + "--vscode-terminal-ansiWhite": "#6a737d", + "--vscode-terminal-ansiBrightBlack": "#959da5", + "--vscode-terminal-ansiBlack": "#24292e", + "--vscode-terminal-ansiBlue": "#0366d6", + "--vscode-terminal-ansiBrightBlue": "#005cc5", + "--vscode-terminal-ansiGreen": "#28a745", + "--vscode-terminal-ansiBrightGreen": "#22863a", + "--vscode-terminal-ansiCyan": "#1b7c83", + "--vscode-terminal-ansiBrightCyan": "#3192aa", + "--vscode-terminal-ansiRed": "#d73a49", + "--vscode-terminal-ansiBrightRed": "#cb2431", + "--vscode-terminal-ansiMagenta": "#5a32a3", + "--vscode-terminal-ansiBrightMagenta": "#5a32a3", + "--vscode-terminal-ansiYellow": "#dbab09", + "--vscode-terminal-ansiBrightYellow": "#b08800" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "GitHub.github-vscode-theme.github-dark", + "label": "GitHub Dark", + "type": "dark", + "swatch": "#24292e", + "accent": "#005cc5", + "vars": { + "--vscode-focusBorder": "#005cc5", + "--vscode-descriptionForeground": "#959da5", + "--vscode-errorForeground": "#f97583", + "--vscode-textLink-foreground": "#79b8ff", + "--vscode-button-background": "#176f2c", + "--vscode-button-foreground": "#dcffe4", + "--vscode-button-hoverBackground": "#22863a", + "--vscode-input-background": "#2f363d", + "--vscode-input-border": "#1b1f23", + "--vscode-badge-foreground": "#c8e1ff", + "--vscode-badge-background": "#044289", + "--vscode-sideBar-background": "#1f2428", + "--vscode-list-activeSelectionForeground": "#e1e4e8", + "--vscode-list-activeSelectionBackground": "#39414a", + "--vscode-editorGroupHeader-tabsBackground": "#1f2428", + "--vscode-tab-activeForeground": "#e1e4e8", + "--vscode-tab-inactiveForeground": "#959da5", + "--vscode-tab-inactiveBackground": "#1f2428", + "--vscode-tab-activeBackground": "#24292e", + "--vscode-editor-foreground": "#e1e4e8", + "--vscode-editor-background": "#24292e", + "--vscode-editorWidget-background": "#1f2428", + "--vscode-editorWarning-foreground": "#ffea7f", + "--vscode-panel-border": "#1b1f23", + "--vscode-terminal-foreground": "#d1d5da", + "--vscode-terminalCursor-foreground": "#79b8ff", + "--vscode-terminal-ansiBrightWhite": "#fafbfc", + "--vscode-terminal-ansiWhite": "#d1d5da", + "--vscode-terminal-ansiBrightBlack": "#959da5", + "--vscode-terminal-ansiBlack": "#586069", + "--vscode-terminal-ansiBlue": "#2188ff", + "--vscode-terminal-ansiBrightBlue": "#79b8ff", + "--vscode-terminal-ansiGreen": "#34d058", + "--vscode-terminal-ansiBrightGreen": "#85e89d", + "--vscode-terminal-ansiCyan": "#39c5cf", + "--vscode-terminal-ansiBrightCyan": "#56d4dd", + "--vscode-terminal-ansiRed": "#ea4a5a", + "--vscode-terminal-ansiBrightRed": "#f97583", + "--vscode-terminal-ansiMagenta": "#b392f0", + "--vscode-terminal-ansiBrightMagenta": "#b392f0", + "--vscode-terminal-ansiYellow": "#ffea7f", + "--vscode-terminal-ansiBrightYellow": "#ffea7f" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "dracula-theme.theme-dracula.dracula-theme", + "label": "Dracula Theme", + "type": "dark", + "swatch": "#282A36", + "accent": "#6272A4", + "vars": { + "--vscode-terminal-background": "#282A36", + "--vscode-terminal-foreground": "#F8F8F2", + "--vscode-terminal-ansiBrightBlack": "#6272A4", + "--vscode-terminal-ansiBrightRed": "#FF6E6E", + "--vscode-terminal-ansiBrightGreen": "#69FF94", + "--vscode-terminal-ansiBrightYellow": "#FFFFA5", + "--vscode-terminal-ansiBrightBlue": "#D6ACFF", + "--vscode-terminal-ansiBrightMagenta": "#FF92DF", + "--vscode-terminal-ansiBrightCyan": "#A4FFFF", + "--vscode-terminal-ansiBrightWhite": "#FFFFFF", + "--vscode-terminal-ansiBlack": "#21222C", + "--vscode-terminal-ansiRed": "#FF5555", + "--vscode-terminal-ansiGreen": "#50FA7B", + "--vscode-terminal-ansiYellow": "#F1FA8C", + "--vscode-terminal-ansiBlue": "#BD93F9", + "--vscode-terminal-ansiMagenta": "#FF79C6", + "--vscode-terminal-ansiCyan": "#8BE9FD", + "--vscode-terminal-ansiWhite": "#F8F8F2", + "--vscode-focusBorder": "#6272A4", + "--vscode-errorForeground": "#FF5555", + "--vscode-button-background": "#44475A", + "--vscode-button-foreground": "#F8F8F2", + "--vscode-input-background": "#282A36", + "--vscode-input-border": "#191A21", + "--vscode-badge-foreground": "#F8F8F2", + "--vscode-badge-background": "#44475A", + "--vscode-list-activeSelectionBackground": "#44475A", + "--vscode-list-activeSelectionForeground": "#F8F8F2", + "--vscode-sideBar-background": "#21222C", + "--vscode-editorGroupHeader-tabsBackground": "#191A21", + "--vscode-tab-activeBackground": "#282A36", + "--vscode-tab-activeForeground": "#F8F8F2", + "--vscode-tab-inactiveBackground": "#21222C", + "--vscode-tab-inactiveForeground": "#6272A4", + "--vscode-editor-foreground": "#F8F8F2", + "--vscode-editor-background": "#282A36", + "--vscode-editorWarning-foreground": "#8BE9FD", + "--vscode-editorWidget-background": "#21222C", + "--vscode-panel-border": "#BD93F9" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "dracula-theme.theme-dracula.dracula-theme-soft", + "label": "Dracula Theme Soft", + "type": "dark", + "swatch": "#282A36", + "accent": "#7b7f8b", + "vars": { + "--vscode-terminal-background": "#282A36", + "--vscode-terminal-foreground": "#f6f6f4", + "--vscode-terminal-ansiBrightBlack": "#7b7f8b", + "--vscode-terminal-ansiBrightRed": "#f07c7c", + "--vscode-terminal-ansiBrightGreen": "#78f09a", + "--vscode-terminal-ansiBrightYellow": "#f6f6ae", + "--vscode-terminal-ansiBrightBlue": "#d6b4f7", + "--vscode-terminal-ansiBrightMagenta": "#f49dda", + "--vscode-terminal-ansiBrightCyan": "#adf6f6", + "--vscode-terminal-ansiBrightWhite": "#ffffff", + "--vscode-terminal-ansiBlack": "#262626", + "--vscode-terminal-ansiRed": "#ee6666", + "--vscode-terminal-ansiGreen": "#62e884", + "--vscode-terminal-ansiYellow": "#e7ee98", + "--vscode-terminal-ansiBlue": "#bf9eee", + "--vscode-terminal-ansiMagenta": "#f286c4", + "--vscode-terminal-ansiCyan": "#97e1f1", + "--vscode-terminal-ansiWhite": "#f6f6f4", + "--vscode-focusBorder": "#7b7f8b", + "--vscode-errorForeground": "#ee6666", + "--vscode-button-background": "#44475A", + "--vscode-button-foreground": "#f6f6f4", + "--vscode-input-background": "#282A36", + "--vscode-input-border": "#191A21", + "--vscode-badge-foreground": "#f6f6f4", + "--vscode-badge-background": "#44475A", + "--vscode-list-activeSelectionBackground": "#44475A", + "--vscode-list-activeSelectionForeground": "#f6f6f4", + "--vscode-sideBar-background": "#262626", + "--vscode-editorGroupHeader-tabsBackground": "#191A21", + "--vscode-tab-activeBackground": "#282A36", + "--vscode-tab-activeForeground": "#f6f6f4", + "--vscode-tab-inactiveBackground": "#262626", + "--vscode-tab-inactiveForeground": "#7b7f8b", + "--vscode-editor-foreground": "#f6f6f4", + "--vscode-editor-background": "#282A36", + "--vscode-editorWarning-foreground": "#97e1f1", + "--vscode-editorWidget-background": "#262626", + "--vscode-panel-border": "#bf9eee" + }, + "origin": { + "kind": "bundled" + } + } +] diff --git a/lib/src/lib/themes/convert.test.ts b/lib/src/lib/themes/convert.test.ts new file mode 100644 index 00000000..56bbe28d --- /dev/null +++ b/lib/src/lib/themes/convert.test.ts @@ -0,0 +1,74 @@ +import { describe, it, expect } from 'vitest'; +import { convertVscodeThemeColors, uiThemeToType, CONSUMED_VSCODE_KEYS } from './convert'; + +describe('convertVscodeThemeColors', () => { + it('converts consumed keys to --vscode-* CSS variables', () => { + const result = convertVscodeThemeColors({ + 'editor.background': '#282a36', + 'editor.foreground': '#f8f8f2', + 'terminal.ansiRed': '#ff5555', + }); + expect(result).toEqual({ + '--vscode-editor-background': '#282a36', + '--vscode-editor-foreground': '#f8f8f2', + '--vscode-terminal-ansiRed': '#ff5555', + }); + }); + + it('drops keys not in CONSUMED_VSCODE_KEYS', () => { + const result = convertVscodeThemeColors({ + 'editor.background': '#282a36', + 'activityBar.background': '#21222c', // not consumed + 'statusBar.background': '#191a21', // not consumed + }); + expect(result).toEqual({ + '--vscode-editor-background': '#282a36', + }); + }); + + it('returns empty object for empty input', () => { + expect(convertVscodeThemeColors({})).toEqual({}); + }); + + it('handles all consumed keys without error', () => { + const colors: Record = {}; + for (const key of CONSUMED_VSCODE_KEYS) { + colors[key] = '#000000'; + } + const result = convertVscodeThemeColors(colors); + expect(Object.keys(result)).toHaveLength(CONSUMED_VSCODE_KEYS.length); + }); + + it('preserves camelCase in key conversion', () => { + const result = convertVscodeThemeColors({ + 'editorGroupHeader.tabsBackground': '#252526', + 'terminal.ansiBrightMagenta': '#d670d6', + }); + expect(result).toEqual({ + '--vscode-editorGroupHeader-tabsBackground': '#252526', + '--vscode-terminal-ansiBrightMagenta': '#d670d6', + }); + }); +}); + +describe('uiThemeToType', () => { + it('maps vs to light', () => { + expect(uiThemeToType('vs')).toBe('light'); + }); + + it('maps hc-light to light', () => { + expect(uiThemeToType('hc-light')).toBe('light'); + }); + + it('maps vs-dark to dark', () => { + expect(uiThemeToType('vs-dark')).toBe('dark'); + }); + + it('maps hc-black to dark', () => { + expect(uiThemeToType('hc-black')).toBe('dark'); + }); + + it('defaults unknown values to dark', () => { + expect(uiThemeToType('something-else')).toBe('dark'); + }); +}); diff --git a/lib/src/lib/themes/convert.ts b/lib/src/lib/themes/convert.ts new file mode 100644 index 00000000..dc0c2081 --- /dev/null +++ b/lib/src/lib/themes/convert.ts @@ -0,0 +1,97 @@ +/** + * Conversion from VSCode theme JSON `colors` to --vscode-* CSS variables. + * + * Two consumers read --vscode-* variables: + * 1. @theme fallbacks in theme.css — UI colors (surfaces, tabs, etc.) + * 2. getTerminalTheme() in terminal-registry.ts — ANSI, cursor, selection + */ + +/** VSCode theme color keys consumed by MouseTerm. Derived from theme.css and terminal-registry.ts. */ +export const CONSUMED_VSCODE_KEYS: readonly string[] = [ + // Surfaces (theme.css @theme) + 'editor.background', + 'editorGroupHeader.tabsBackground', + 'sideBar.background', + 'editorWidget.background', + // Text + 'editor.foreground', + 'descriptionForeground', + // Accent & borders + 'focusBorder', + 'panel.border', + // Tabs + 'tab.activeBackground', + 'tab.inactiveBackground', + 'tab.activeForeground', + 'tab.inactiveForeground', + 'list.activeSelectionBackground', + 'list.activeSelectionForeground', + // Terminal + 'terminal.background', + 'terminal.foreground', + // Badges + 'badge.background', + 'badge.foreground', + // Status + 'errorForeground', + 'editorWarning.foreground', + // Inputs + 'input.background', + 'input.border', + // Buttons + 'button.background', + 'button.foreground', + 'button.hoverBackground', + // Links + 'textLink.foreground', + // Terminal (read directly by getTerminalTheme()) + 'terminalCursor.foreground', + 'terminal.selectionBackground', + 'terminal.ansiBlack', + 'terminal.ansiRed', + 'terminal.ansiGreen', + 'terminal.ansiYellow', + 'terminal.ansiBlue', + 'terminal.ansiMagenta', + 'terminal.ansiCyan', + 'terminal.ansiWhite', + 'terminal.ansiBrightBlack', + 'terminal.ansiBrightRed', + 'terminal.ansiBrightGreen', + 'terminal.ansiBrightYellow', + 'terminal.ansiBrightBlue', + 'terminal.ansiBrightMagenta', + 'terminal.ansiBrightCyan', + 'terminal.ansiBrightWhite', +] as const; + +const consumedSet = new Set(CONSUMED_VSCODE_KEYS); + +/** + * Convert a VSCode theme `colors` object to --vscode-* CSS variable entries. + * Only keys in CONSUMED_VSCODE_KEYS are included; the rest are dropped. + * + * Conversion rule: `editor.background` → `--vscode-editor-background` + */ +export function convertVscodeThemeColors( + colors: Record, +): Record { + const vars: Record = {}; + for (const [key, value] of Object.entries(colors)) { + if (consumedSet.has(key)) { + vars[`--vscode-${key.replace(/\./g, '-')}`] = value; + } + } + return vars; +} + +/** Map package.json contributes.themes[].uiTheme to our type field. */ +export function uiThemeToType(uiTheme: string): 'dark' | 'light' { + switch (uiTheme) { + case 'vs': + case 'hc-light': + return 'light'; + default: + return 'dark'; + } +} diff --git a/lib/src/lib/themes/index.ts b/lib/src/lib/themes/index.ts new file mode 100644 index 00000000..178f3f32 --- /dev/null +++ b/lib/src/lib/themes/index.ts @@ -0,0 +1,15 @@ +export type { MouseTermTheme, BundledOrigin, InstalledOrigin } from './types'; +export { CONSUMED_VSCODE_KEYS, convertVscodeThemeColors, uiThemeToType } from './convert'; +export { applyTheme } from './apply'; +export { + getBundledThemes, + getInstalledThemes, + getAllThemes, + getTheme, + addInstalledTheme, + removeInstalledTheme, + getActiveThemeId, + setActiveThemeId, +} from './store'; +export { searchThemes, fetchExtensionThemes } from './openvsx'; +export type { OpenVSXSearchResult, OpenVSXExtension } from './openvsx'; diff --git a/lib/src/lib/themes/openvsx.ts b/lib/src/lib/themes/openvsx.ts new file mode 100644 index 00000000..8eb712a2 --- /dev/null +++ b/lib/src/lib/themes/openvsx.ts @@ -0,0 +1,120 @@ +/** + * Runtime OpenVSX theme installer. + * + * Searches for theme extensions, downloads VSIX files, extracts theme + * JSONs in the browser, and converts them to MouseTermTheme objects. + * + * fflate is dynamically imported so it doesn't affect initial bundle size. + */ + +import type { MouseTermTheme } from './types'; +import { convertVscodeThemeColors, uiThemeToType } from './convert'; + +const OPENVSX_API = 'https://open-vsx.org/api'; + +export interface OpenVSXSearchResult { + extensions: OpenVSXExtension[]; + totalSize: number; + offset: number; +} + +export interface OpenVSXExtension { + namespace: string; + name: string; + displayName: string; + description: string; + version: string; + averageRating?: number; + downloadCount: number; + files?: { icon?: string }; +} + +export async function searchThemes( + query: string, + offset = 0, + size = 20, +): Promise { + const params = new URLSearchParams({ + category: 'Themes', + query, + size: String(size), + offset: String(offset), + sortBy: 'relevance', + sortOrder: 'desc', + }); + const res = await fetch(`${OPENVSX_API}/-/search?${params}`); + if (!res.ok) throw new Error(`OpenVSX search failed: ${res.status}`); + return res.json(); +} + +function slugify(label: string): string { + return label + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-|-$/g, ''); +} + +/** + * Download a theme extension from OpenVSX and return all theme variants + * as MouseTermTheme objects ready for installation. + */ +export async function fetchExtensionThemes( + namespace: string, + name: string, +): Promise { + // 1. Get latest version metadata + const metaRes = await fetch(`${OPENVSX_API}/${namespace}/${name}/latest`); + if (!metaRes.ok) throw new Error(`OpenVSX metadata failed: ${metaRes.status}`); + const meta = await metaRes.json(); + + const downloadUrl = meta.files?.download; + if (!downloadUrl) throw new Error(`No download URL for ${namespace}/${name}`); + + // 2. Download VSIX + const vsixRes = await fetch(downloadUrl); + if (!vsixRes.ok) throw new Error(`VSIX download failed: ${vsixRes.status}`); + const vsixBuf = new Uint8Array(await vsixRes.arrayBuffer()); + + // 3. Extract (dynamic import — fflate only loaded when needed) + const { unzipSync } = await import('fflate'); + const entries = unzipSync(vsixBuf); + + // 4. Read package.json + const pkgData = entries['extension/package.json']; + if (!pkgData) throw new Error('No package.json in VSIX'); + const pkgJson = JSON.parse(new TextDecoder().decode(pkgData)); + const themeContribs: Array<{ label: string; uiTheme?: string; path: string }> = + pkgJson.contributes?.themes ?? []; + + // 5. Parse JSONC (dynamic import to avoid loading at startup) + const { parse: parseJsonc } = await import('jsonc-parser'); + + // 6. Convert each theme variant + const themes: MouseTermTheme[] = []; + for (const contrib of themeContribs) { + const themePath = `extension/${contrib.path.replace(/^\.\//, '')}`; + const themeData = entries[themePath]; + if (!themeData) continue; + + const themeJson = parseJsonc(new TextDecoder().decode(themeData)); + const colors: Record = themeJson.colors ?? {}; + const vars = convertVscodeThemeColors(colors); + const type = uiThemeToType(contrib.uiTheme ?? themeJson.type ?? 'vs-dark'); + + themes.push({ + id: `${namespace}.${name}.${slugify(contrib.label)}`, + label: contrib.label, + type, + swatch: colors['editor.background'] ?? (type === 'light' ? '#ffffff' : '#1e1e1e'), + accent: colors['focusBorder'] ?? (type === 'light' ? '#0090f1' : '#007fd4'), + vars, + origin: { + kind: 'installed', + extensionId: `${namespace}/${name}`, + installedAt: new Date().toISOString(), + }, + }); + } + + return themes; +} diff --git a/lib/src/lib/themes/store.ts b/lib/src/lib/themes/store.ts new file mode 100644 index 00000000..a088f510 --- /dev/null +++ b/lib/src/lib/themes/store.ts @@ -0,0 +1,54 @@ +import type { MouseTermTheme } from './types'; +// JSON import types are inferred too narrowly — cast at the boundary. +import _bundledThemes from './bundled.json'; +const bundledThemes = _bundledThemes as unknown as MouseTermTheme[]; + +const INSTALLED_KEY = 'mouseterm:installed-themes'; +const ACTIVE_KEY = 'mouseterm:active-theme'; + +const hasStorage = typeof localStorage !== 'undefined'; + +export function getBundledThemes(): MouseTermTheme[] { + return bundledThemes; +} + +export function getInstalledThemes(): MouseTermTheme[] { + if (!hasStorage) return []; + try { + const raw = localStorage.getItem(INSTALLED_KEY); + return raw ? (JSON.parse(raw) as MouseTermTheme[]) : []; + } catch { + return []; + } +} + +export function getAllThemes(): MouseTermTheme[] { + return [...getBundledThemes(), ...getInstalledThemes()]; +} + +export function getTheme(id: string): MouseTermTheme | undefined { + return getAllThemes().find((t) => t.id === id); +} + +export function addInstalledTheme(theme: MouseTermTheme): void { + if (!hasStorage) return; + const installed = getInstalledThemes().filter((t) => t.id !== theme.id); + installed.push(theme); + localStorage.setItem(INSTALLED_KEY, JSON.stringify(installed)); +} + +export function removeInstalledTheme(id: string): void { + if (!hasStorage) return; + const installed = getInstalledThemes().filter((t) => t.id !== id); + localStorage.setItem(INSTALLED_KEY, JSON.stringify(installed)); +} + +export function getActiveThemeId(): string { + if (!hasStorage) return getBundledThemes()[0]?.id ?? ''; + return localStorage.getItem(ACTIVE_KEY) ?? getBundledThemes()[0]?.id ?? ''; +} + +export function setActiveThemeId(id: string): void { + if (!hasStorage) return; + localStorage.setItem(ACTIVE_KEY, id); +} diff --git a/lib/src/lib/themes/types.ts b/lib/src/lib/themes/types.ts new file mode 100644 index 00000000..37893ac1 --- /dev/null +++ b/lib/src/lib/themes/types.ts @@ -0,0 +1,28 @@ +export interface MouseTermTheme { + /** Stable unique ID, e.g. "GitHub.github-vscode-theme.dark-default" or "builtin.dark-plus" */ + id: string; + /** Human-readable label from the VSCode theme */ + label: string; + /** Theme base type */ + type: 'dark' | 'light'; + /** Background color for picker swatch (editor.background) */ + swatch: string; + /** Accent color for picker dot (focusBorder) */ + accent: string; + /** --vscode-* CSS variable overrides */ + vars: Record; + /** Where this theme came from */ + origin: BundledOrigin | InstalledOrigin; +} + +export interface BundledOrigin { + kind: 'bundled'; +} + +export interface InstalledOrigin { + kind: 'installed'; + /** OpenVSX namespace/name, e.g. "dracula-theme/theme-dracula" */ + extensionId: string; + /** ISO date string */ + installedAt: string; +} diff --git a/package.json b/package.json index d2a4c025..34f0ede1 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "build:standalone": "pnpm --filter mouseterm-standalone tauri build", "build:website": "pnpm --filter mouseterm-website build", "dogfood:vscode": "pnpm run build:vscode && pnpm --filter mouseterm dogfood", - "storybook": "pnpm --filter mouseterm-lib storybook" + "storybook": "pnpm --filter mouseterm-lib storybook", + "bundle-themes": "node lib/scripts/bundle-themes.mjs" }, "pnpm": { "onlyBuiltDependencies": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 889448a0..5777c9f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,6 +25,12 @@ importers: dockview-react: specifier: ^5.1.0 version: 5.1.0(react@19.2.4) + fflate: + specifier: 0.8.2 + version: 0.8.2 + jsonc-parser: + specifier: 3.3.1 + version: 3.3.1 react: specifier: ^19.2.0 version: 19.2.4 @@ -2095,6 +2101,9 @@ packages: picomatch: optional: true + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -5175,6 +5184,8 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fflate@0.8.2: {} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index feb5adb3..d9581231 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,3 +5,5 @@ packages: - vscode-ext - website minimumReleaseAge: 20160 +minimumReleaseAgeExclude: + - jsdom diff --git a/website/src/components/ThemePicker.tsx b/website/src/components/ThemePicker.tsx index d5df865b..aee32add 100644 --- a/website/src/components/ThemePicker.tsx +++ b/website/src/components/ThemePicker.tsx @@ -1,8 +1,35 @@ -import { useState } from "react"; -import { THEMES, applyTheme } from "../lib/playground-themes"; +import { useState, useEffect, useCallback } from "react"; +import { + getAllThemes, + getActiveThemeId, + setActiveThemeId, + getTheme, + applyTheme, +} from "mouseterm-lib/lib/themes"; +import { ThemeStore } from "./ThemeStore"; export function ThemePicker() { - const [activeTheme, setActiveTheme] = useState(THEMES[0].name); + const [themes, setThemes] = useState(getAllThemes); + const [activeId, setActiveId] = useState(getActiveThemeId); + const [storeOpen, setStoreOpen] = useState(false); + + // Restore persisted theme on mount + useEffect(() => { + const theme = getTheme(activeId); + if (theme) applyTheme(theme); + }, []); + + const refreshThemes = useCallback(() => { + setThemes(getAllThemes()); + }, []); + + const select = (id: string) => { + const theme = getTheme(id); + if (!theme) return; + setActiveId(id); + setActiveThemeId(id); + applyTheme(theme); + }; return (
@@ -12,18 +39,15 @@ export function ThemePicker() { > Theme -
- {THEMES.map((theme) => { - const isActive = theme.name === activeTheme; +
+ {themes.map((theme) => { + const isActive = theme.id === activeId; return ( ); })} + + {/* Add theme button */} +
+ + setStoreOpen(false)} + onThemesChanged={() => { + refreshThemes(); + // Sync active ID in case install auto-selected a theme + setActiveId(getActiveThemeId()); + }} + />
); } diff --git a/website/src/components/ThemeStore.tsx b/website/src/components/ThemeStore.tsx new file mode 100644 index 00000000..d69f576a --- /dev/null +++ b/website/src/components/ThemeStore.tsx @@ -0,0 +1,201 @@ +import { useState, useCallback, useRef, useEffect } from "react"; +import type { OpenVSXExtension } from "mouseterm-lib/lib/themes"; +import { + searchThemes, + fetchExtensionThemes, + addInstalledTheme, + removeInstalledTheme, + getInstalledThemes, + applyTheme, + setActiveThemeId, +} from "mouseterm-lib/lib/themes"; + +interface ThemeStoreProps { + open: boolean; + onClose: () => void; + /** Called after themes change so the picker can refresh */ + onThemesChanged: () => void; +} + +export function ThemeStore({ open, onClose, onThemesChanged }: ThemeStoreProps) { + const [query, setQuery] = useState(""); + const [results, setResults] = useState([]); + const [loading, setLoading] = useState(false); + const [installing, setInstalling] = useState(null); + const [error, setError] = useState(null); + const debounceRef = useRef>(); + const dialogRef = useRef(null); + + // Manage dialog open/close + useEffect(() => { + const dialog = dialogRef.current; + if (!dialog) return; + if (open && !dialog.open) dialog.showModal(); + else if (!open && dialog.open) dialog.close(); + }, [open]); + + const doSearch = useCallback(async (q: string) => { + if (!q.trim()) { + setResults([]); + return; + } + setLoading(true); + setError(null); + try { + const res = await searchThemes(q, 0, 20); + setResults(res.extensions); + } catch (e) { + setError(e instanceof Error ? e.message : "Search failed"); + } finally { + setLoading(false); + } + }, []); + + const handleInput = (value: string) => { + setQuery(value); + clearTimeout(debounceRef.current); + debounceRef.current = setTimeout(() => doSearch(value), 300); + }; + + const handleInstall = async (ext: OpenVSXExtension) => { + const key = `${ext.namespace}/${ext.name}`; + setInstalling(key); + setError(null); + try { + const themes = await fetchExtensionThemes(ext.namespace, ext.name); + for (const theme of themes) { + addInstalledTheme(theme); + } + // Apply the first variant + if (themes[0]) { + applyTheme(themes[0]); + setActiveThemeId(themes[0].id); + } + onThemesChanged(); + } catch (e) { + setError(e instanceof Error ? e.message : "Install failed"); + } finally { + setInstalling(null); + } + }; + + const handleRemove = (extensionId: string) => { + const installed = getInstalledThemes(); + for (const theme of installed) { + if (theme.origin.kind === "installed" && theme.origin.extensionId === extensionId) { + removeInstalledTheme(theme.id); + } + } + onThemesChanged(); + }; + + const isInstalled = (ext: OpenVSXExtension) => { + const key = `${ext.namespace}/${ext.name}`; + return getInstalledThemes().some( + (t) => t.origin.kind === "installed" && t.origin.extensionId === key, + ); + }; + + if (!open) return null; + + return ( + +
+ {/* Header */} +
+ Install Theme from OpenVSX + +
+ + {/* Search */} +
+ handleInput(e.target.value)} + placeholder="Search themes..." + autoFocus + className="w-full rounded border border-white/10 bg-[#3c3c3c] px-3 py-1.5 text-xs text-[#cccccc] outline-none placeholder:text-[#858585] focus:border-[#007fd4]" + /> +
+ + {/* Results */} +
+ {error && ( +
+ {error} +
+ )} + {loading && ( +
Searching...
+ )} + {!loading && results.length === 0 && query.trim() && ( +
No themes found
+ )} + {!loading && !query.trim() && ( +
+ Search for a VSCode theme to install +
+ )} + {results.map((ext) => { + const key = `${ext.namespace}/${ext.name}`; + const installed = isInstalled(ext); + const isInstallingThis = installing === key; + return ( +
+ {/* Icon */} + {ext.files?.icon ? ( + + ) : ( +
+ 🎨 +
+ )} + + {/* Info */} +
+
+ {ext.displayName || ext.name} +
+
+ {ext.namespace} · {ext.downloadCount.toLocaleString()} downloads +
+
+ + {/* Action */} + {installed ? ( + + ) : ( + + )} +
+ ); + })} +
+
+
+ ); +} diff --git a/website/src/data/dependencies.json b/website/src/data/dependencies.json index c1c5dc76..c2462d60 100644 --- a/website/src/data/dependencies.json +++ b/website/src/data/dependencies.json @@ -27,6 +27,13 @@ "author": null, "homepage": "https://github.com/tauri-apps/plugins-workspace#readme" }, + { + "name": "@tauri-apps/plugin-updater", + "version": "2.10.1", + "license": "MIT OR Apache-2.0", + "author": null, + "homepage": "https://github.com/tauri-apps/plugins-workspace#readme" + }, { "name": "@xterm/addon-fit", "version": "0.11.0", @@ -69,6 +76,20 @@ "author": "https://github.com/mathuo", "homepage": "https://github.com/mathuo/dockview" }, + { + "name": "fflate", + "version": "0.8.2", + "license": "MIT", + "author": "Arjun Barrett", + "homepage": "https://101arrowz.github.io/fflate" + }, + { + "name": "jsonc-parser", + "version": "3.3.1", + "license": "MIT", + "author": "Microsoft Corporation", + "homepage": "https://github.com/microsoft/node-jsonc-parser#readme" + }, { "name": "node-addon-api", "version": "7.1.1", diff --git a/website/src/lib/playground-themes.ts b/website/src/lib/playground-themes.ts deleted file mode 100644 index f9d1047b..00000000 --- a/website/src/lib/playground-themes.ts +++ /dev/null @@ -1,224 +0,0 @@ -/** - * Playground themes — sets of --vscode-* CSS variable overrides. - * - * When applied to document.body, these cascade into: - * 1. --mt-* variables (via var(--vscode-*, fallback) in theme.css) - * 2. xterm.js terminal themes (via getTerminalTheme() in terminal-registry) - * 3. Dockview/Tailwind token colors (via @theme in index.css) - * - * The MutationObserver on body attributes triggers xterm.js re-reads automatically. - */ - -export interface PlaygroundTheme { - name: string; - /** Label shown in the picker */ - label: string; - /** Preview swatch color (the surface/background color) */ - swatch: string; - /** Accent dot color */ - accent: string; - /** CSS variable overrides (--vscode-* keys) */ - vars: Record; -} - -export const THEMES: PlaygroundTheme[] = [ - { - name: 'dark-default', - label: 'Dark', - swatch: '#1e1e1e', - accent: '#007fd4', - vars: {}, // Uses the fallback values from theme.css — no overrides needed - }, - { - name: 'monokai', - label: 'Monokai', - swatch: '#272822', - accent: '#f92672', - vars: { - '--vscode-editor-background': '#272822', - '--vscode-editor-foreground': '#f8f8f2', - '--vscode-editorGroupHeader-tabsBackground': '#1e1f1c', - '--vscode-sideBar-background': '#1e1f1c', - '--vscode-editorWidget-background': '#1e1f1c', - '--vscode-descriptionForeground': '#75715e', - '--vscode-focusBorder': '#f92672', - '--vscode-panel-border': '#3e3d32', - '--vscode-tab-activeBackground': '#272822', - '--vscode-tab-inactiveBackground': '#1e1f1c', - '--vscode-tab-activeForeground': '#f8f8f2', - '--vscode-tab-inactiveForeground': '#75715e', - '--vscode-terminal-background': '#272822', - '--vscode-terminal-foreground': '#f8f8f2', - '--vscode-badge-background': '#f92672', - '--vscode-badge-foreground': '#ffffff', - '--vscode-terminal-ansiBlack': '#272822', - '--vscode-terminal-ansiRed': '#f92672', - '--vscode-terminal-ansiGreen': '#a6e22e', - '--vscode-terminal-ansiYellow': '#f4bf75', - '--vscode-terminal-ansiBlue': '#66d9ef', - '--vscode-terminal-ansiMagenta': '#ae81ff', - '--vscode-terminal-ansiCyan': '#a1efe4', - '--vscode-terminal-ansiWhite': '#f8f8f2', - '--vscode-terminal-ansiBrightBlack': '#75715e', - '--vscode-terminal-ansiBrightRed': '#f92672', - '--vscode-terminal-ansiBrightGreen': '#a6e22e', - '--vscode-terminal-ansiBrightYellow': '#f4bf75', - '--vscode-terminal-ansiBrightBlue': '#66d9ef', - '--vscode-terminal-ansiBrightMagenta': '#ae81ff', - '--vscode-terminal-ansiBrightCyan': '#a1efe4', - '--vscode-terminal-ansiBrightWhite': '#f9f8f5', - '--vscode-terminalCursor-foreground': '#f8f8f0', - '--vscode-terminal-selectionBackground': '#49483e80', - }, - }, - { - name: 'solarized-dark', - label: 'Solarized', - swatch: '#002b36', - accent: '#268bd2', - vars: { - '--vscode-editor-background': '#002b36', - '--vscode-editor-foreground': '#839496', - '--vscode-editorGroupHeader-tabsBackground': '#00212b', - '--vscode-sideBar-background': '#00212b', - '--vscode-editorWidget-background': '#00212b', - '--vscode-descriptionForeground': '#586e75', - '--vscode-focusBorder': '#268bd2', - '--vscode-panel-border': '#073642', - '--vscode-tab-activeBackground': '#002b36', - '--vscode-tab-inactiveBackground': '#00212b', - '--vscode-tab-activeForeground': '#93a1a1', - '--vscode-tab-inactiveForeground': '#586e75', - '--vscode-terminal-background': '#002b36', - '--vscode-terminal-foreground': '#839496', - '--vscode-badge-background': '#268bd2', - '--vscode-badge-foreground': '#ffffff', - '--vscode-terminal-ansiBlack': '#073642', - '--vscode-terminal-ansiRed': '#dc322f', - '--vscode-terminal-ansiGreen': '#859900', - '--vscode-terminal-ansiYellow': '#b58900', - '--vscode-terminal-ansiBlue': '#268bd2', - '--vscode-terminal-ansiMagenta': '#d33682', - '--vscode-terminal-ansiCyan': '#2aa198', - '--vscode-terminal-ansiWhite': '#eee8d5', - '--vscode-terminal-ansiBrightBlack': '#586e75', - '--vscode-terminal-ansiBrightRed': '#cb4b16', - '--vscode-terminal-ansiBrightGreen': '#859900', - '--vscode-terminal-ansiBrightYellow': '#b58900', - '--vscode-terminal-ansiBrightBlue': '#268bd2', - '--vscode-terminal-ansiBrightMagenta': '#6c71c4', - '--vscode-terminal-ansiBrightCyan': '#2aa198', - '--vscode-terminal-ansiBrightWhite': '#fdf6e3', - '--vscode-terminalCursor-foreground': '#839496', - '--vscode-terminal-selectionBackground': '#073642cc', - }, - }, - { - name: 'nord', - label: 'Nord', - swatch: '#2e3440', - accent: '#88c0d0', - vars: { - '--vscode-editor-background': '#2e3440', - '--vscode-editor-foreground': '#d8dee9', - '--vscode-editorGroupHeader-tabsBackground': '#242933', - '--vscode-sideBar-background': '#242933', - '--vscode-editorWidget-background': '#242933', - '--vscode-descriptionForeground': '#616e88', - '--vscode-focusBorder': '#88c0d0', - '--vscode-panel-border': '#3b4252', - '--vscode-tab-activeBackground': '#2e3440', - '--vscode-tab-inactiveBackground': '#242933', - '--vscode-tab-activeForeground': '#eceff4', - '--vscode-tab-inactiveForeground': '#616e88', - '--vscode-terminal-background': '#2e3440', - '--vscode-terminal-foreground': '#d8dee9', - '--vscode-badge-background': '#88c0d0', - '--vscode-badge-foreground': '#2e3440', - '--vscode-terminal-ansiBlack': '#3b4252', - '--vscode-terminal-ansiRed': '#bf616a', - '--vscode-terminal-ansiGreen': '#a3be8c', - '--vscode-terminal-ansiYellow': '#ebcb8b', - '--vscode-terminal-ansiBlue': '#81a1c1', - '--vscode-terminal-ansiMagenta': '#b48ead', - '--vscode-terminal-ansiCyan': '#88c0d0', - '--vscode-terminal-ansiWhite': '#e5e9f0', - '--vscode-terminal-ansiBrightBlack': '#4c566a', - '--vscode-terminal-ansiBrightRed': '#bf616a', - '--vscode-terminal-ansiBrightGreen': '#a3be8c', - '--vscode-terminal-ansiBrightYellow': '#ebcb8b', - '--vscode-terminal-ansiBrightBlue': '#81a1c1', - '--vscode-terminal-ansiBrightMagenta': '#b48ead', - '--vscode-terminal-ansiBrightCyan': '#8fbcbb', - '--vscode-terminal-ansiBrightWhite': '#eceff4', - '--vscode-terminalCursor-foreground': '#d8dee9', - '--vscode-terminal-selectionBackground': '#434c5ecc', - }, - }, - { - name: 'dracula', - label: 'Dracula', - swatch: '#282a36', - accent: '#bd93f9', - vars: { - '--vscode-editor-background': '#282a36', - '--vscode-editor-foreground': '#f8f8f2', - '--vscode-editorGroupHeader-tabsBackground': '#21222c', - '--vscode-sideBar-background': '#21222c', - '--vscode-editorWidget-background': '#21222c', - '--vscode-descriptionForeground': '#6272a4', - '--vscode-focusBorder': '#bd93f9', - '--vscode-panel-border': '#44475a', - '--vscode-tab-activeBackground': '#282a36', - '--vscode-tab-inactiveBackground': '#21222c', - '--vscode-tab-activeForeground': '#f8f8f2', - '--vscode-tab-inactiveForeground': '#6272a4', - '--vscode-terminal-background': '#282a36', - '--vscode-terminal-foreground': '#f8f8f2', - '--vscode-badge-background': '#bd93f9', - '--vscode-badge-foreground': '#282a36', - '--vscode-terminal-ansiBlack': '#21222c', - '--vscode-terminal-ansiRed': '#ff5555', - '--vscode-terminal-ansiGreen': '#50fa7b', - '--vscode-terminal-ansiYellow': '#f1fa8c', - '--vscode-terminal-ansiBlue': '#bd93f9', - '--vscode-terminal-ansiMagenta': '#ff79c6', - '--vscode-terminal-ansiCyan': '#8be9fd', - '--vscode-terminal-ansiWhite': '#f8f8f2', - '--vscode-terminal-ansiBrightBlack': '#6272a4', - '--vscode-terminal-ansiBrightRed': '#ff6e6e', - '--vscode-terminal-ansiBrightGreen': '#69ff94', - '--vscode-terminal-ansiBrightYellow': '#ffffa5', - '--vscode-terminal-ansiBrightBlue': '#d6acff', - '--vscode-terminal-ansiBrightMagenta': '#ff92df', - '--vscode-terminal-ansiBrightCyan': '#a4ffff', - '--vscode-terminal-ansiBrightWhite': '#ffffff', - '--vscode-terminalCursor-foreground': '#f8f8f2', - '--vscode-terminal-selectionBackground': '#44475a80', - }, - }, -]; - -/** Previously applied variable names — tracked for cleanup. */ -let appliedVarNames: string[] = []; - -/** - * Apply a theme by setting --vscode-* CSS variables on document.body. - * The MutationObserver in terminal-registry will detect the style change - * and re-read the theme for all xterm.js terminals. - */ -export function applyTheme(themeName: string): void { - const theme = THEMES.find((t) => t.name === themeName); - if (!theme) return; - - // Clear previously applied variables - for (const name of appliedVarNames) { - document.body.style.removeProperty(name); - } - - // Apply new variables - appliedVarNames = Object.keys(theme.vars); - for (const [name, value] of Object.entries(theme.vars)) { - document.body.style.setProperty(name, value); - } -} From cffc4ed15cbea4672fcf565a2a9b7cbb0a7d935c Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 14 Apr 2026 12:10:49 -0700 Subject: [PATCH 04/24] Fix playground theme picking. --- docs/specs/tutorial.md | 13 ++++++------- website/src/components/PlaygroundToolbar.tsx | 2 +- website/src/components/ThemePicker.tsx | 20 +++++++++----------- website/src/pages/Playground.tsx | 2 +- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/docs/specs/tutorial.md b/docs/specs/tutorial.md index ba398a1a..c0cbab11 100644 --- a/docs/specs/tutorial.md +++ b/docs/specs/tutorial.md @@ -5,14 +5,14 @@ At the `/playground` route on the website. **Status: Implemented** (Epics 14, 15 ## Layout - `SiteHeader` at top (with Playground as active nav item). -- `PlaygroundToolbar` below the header: a dedicated toolbar with a **theme picker** dead center (mouse-based, not keyboard). Visually distinct from the site nav — belongs to the sandbox, not the website. -- Below the toolbar: MouseTerm `Pond` embedded fullscreen using `FakePtyAdapter`. +- `PlaygroundToolbar` below the fixed site header: a dedicated toolbar with a **theme picker** centered in the available width. It sits below the header's hitbox so swatches remain clickable, and the swatch row scrolls horizontally on narrow screens while the install button stays visible. +- Below the toolbar: MouseTerm `Pond` embedded fullscreen using `FakePtyAdapter`. The page-level `
` is a flex container so Pond's `flex-1 min-h-0` root receives a real height. ### Implementation - `website/src/pages/Playground.tsx` — Page component. Dynamically imports Pond (SSR-safe). Initializes `FakePtyAdapter`, `TutorialShell`, and `TutorialDetector`. Passes `onApiReady` to set up the 3-pane layout and `onEvent` for step detection. - `website/src/components/PlaygroundToolbar.tsx` — Toolbar shell with centered slot. -- `website/src/components/ThemePicker.tsx` — Color dot swatches for 5 themes. +- `website/src/components/ThemePicker.tsx` — Color dot swatches for bundled themes plus an OpenVSX install button. - `website/vite.config.ts` — Vite alias `mouseterm-lib` → `../lib/src` for workspace imports. ## Initial State @@ -127,14 +127,14 @@ The sandbox stays fully functional after completion. Running `tut` shows "Tutori Implemented in `website/src/lib/playground-themes.ts` and `website/src/components/ThemePicker.tsx`. -5 themes available: **Dark** (default), **Monokai**, **Solarized**, **Nord**, **Dracula**. +Bundled themes are provided by `mouseterm-lib/lib/themes` and include Dark+, Light+, GitHub variants, and Dracula variants. Users can install additional themes from OpenVSX via the `+` button. -Each theme is defined as a map of `--vscode-*` CSS variable overrides. `applyTheme()` sets these on `document.body`, which: +Each theme is defined as a map of `--vscode-*` CSS variable overrides. `applyTheme()` applies the active theme, which: 1. Cascades into `--mt-*` variables (via `var(--vscode-*, fallback)` in `theme.css`) 2. Triggers the `MutationObserver` in `terminal-registry.ts` to re-read `getTerminalTheme()` for all xterm.js terminals 3. Updates Dockview/Tailwind token colors -The Dark theme uses an empty vars map (relies on the existing CSS fallback values). +The picker restores the persisted active theme on mount and keeps the `+` install action outside the scrollable swatch strip so it remains reachable on phone-width viewports. ## Technical Notes @@ -142,4 +142,3 @@ The Dark theme uses an empty vars map (relies on the existing CSS fallback value - `FakePtyAdapter` extensions: `setInputHandler(id, fn)` routes `writePty` calls to a custom handler; `sendOutput(id, data)` writes to a terminal's output stream. - `Pond` extensions: `initialPaneIds` prop seeds the first pane(s); `onApiReady` callback prop exposes `DockviewApi`; `onEvent` callback prop fires `PondEvent` for mode/zoom/detach/selection/split changes (types: `modeChange`, `zoomChange`, `detachChange`, `split`, `selectionChange`). - `SCENARIO_TUTORIAL_MOTD` scenario added to `lib/src/lib/platform/fake-scenarios.ts`. - diff --git a/website/src/components/PlaygroundToolbar.tsx b/website/src/components/PlaygroundToolbar.tsx index f1262ee6..73c2303b 100644 --- a/website/src/components/PlaygroundToolbar.tsx +++ b/website/src/components/PlaygroundToolbar.tsx @@ -5,7 +5,7 @@ interface PlaygroundToolbarProps { export function PlaygroundToolbar({ children }: PlaygroundToolbarProps) { return (
+
Theme -
+
{themes.map((theme) => { const isActive = theme.id === activeId; return ( @@ -47,10 +47,8 @@ export function ThemePicker() { key={theme.id} onClick={() => select(theme.id)} title={theme.label} - className="group relative flex shrink-0 items-center justify-center rounded-full transition-transform hover:scale-110" + className="group relative flex h-8 w-8 shrink-0 items-center justify-center rounded-full transition-transform hover:scale-105" style={{ - width: 22, - height: 22, outline: isActive ? `2px solid ${theme.accent}` : "2px solid transparent", @@ -61,8 +59,8 @@ export function ThemePicker() { @@ -84,7 +82,7 @@ export function ThemePicker() { ); })} - - {/* Add theme button */} -
+ {/* Add theme button */} + + setStoreOpen(false)} From ea15146203564b24e74445aaf8301f32f8e525c8 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 15 Apr 2026 14:31:55 -0700 Subject: [PATCH 07/24] Theme picker is now a dropdown, styling includes site header. --- docs/specs/theme.md | 10 + docs/specs/tutorial.md | 20 +- website/src/components/PlaygroundToolbar.tsx | 19 -- website/src/components/SiteHeader.tsx | 152 ++++++++--- website/src/components/ThemePicker.tsx | 260 +++++++++++++++---- website/src/components/ThemeStore.tsx | 3 + website/src/pages/Playground.tsx | 14 +- 7 files changed, 343 insertions(+), 135 deletions(-) delete mode 100644 website/src/components/PlaygroundToolbar.tsx diff --git a/docs/specs/theme.md b/docs/specs/theme.md index c7089ff4..317a1577 100644 --- a/docs/specs/theme.md +++ b/docs/specs/theme.md @@ -136,6 +136,14 @@ The store module provides: - `getActiveThemeId()` / `setActiveThemeId(id)` — persists choice across sessions - `addInstalledTheme(theme)` / `removeInstalledTheme(id)` — manages user-installed themes +## Website playground picker + +The website renders the theme picker only on `/playground`. `Playground.tsx` passes it through `SiteHeader`'s playground-only `controls` slot and enables `themeAware`, so the same active `--vscode-*` variables style the header background, border, text, banner, Pond, Dockview, and terminals. + +The picker is labeled `Theme:` and uses a custom dropdown rather than a native ` handleQueryChange(event.target.value)} + placeholder="Search themes..." + autoFocus + className="w-full rounded border border-input-border bg-input-bg px-3 py-1.5 text-xs text-foreground outline-none placeholder:text-muted focus:border-accent" + /> +
+ +
+ {error ? ( +
{error}
+ ) : null} + {loading ? ( +
Searching...
+ ) : null} + {!loading && results.length === 0 && query.trim() ? ( +
No themes found
+ ) : null} + {!loading && !query.trim() ? ( +
+ Search for a VS Code theme to install +
+ ) : null} + {results.map((extension) => { + const key = `${extension.namespace}/${extension.name}`; + const installed = isInstalled(extension); + const isInstallingThis = installing === key; + return ( +
+ {extension.files?.icon ? ( + + ) : ( +
+ VS +
+ )} +
+
+ {extension.displayName || extension.name} +
+
+ {extension.namespace} - {extension.downloadCount.toLocaleString()} downloads +
+
+ {installed ? ( + + ) : ( + + )} +
+ ); + })} +
+
+ + ); +} + +export function StandaloneThemePicker() { + const [themes, setThemes] = useState(getAllThemes); + const [activeId, setActiveId] = useState(() => getAllThemes()[0]?.id ?? ''); + const [open, setOpen] = useState(false); + const [storeOpen, setStoreOpen] = useState(false); + const ref = useRef(null); + + useEffect(() => { + const theme = applyActiveThemeFallback(); + if (theme) setActiveId(theme.id); + setThemes(getAllThemes()); + }, []); + + useEffect(() => { + if (!open) return; + const closeOnPointerDown = (event: PointerEvent) => { + if (ref.current && !ref.current.contains(event.target as Node)) setOpen(false); + }; + const closeOnEscape = (event: KeyboardEvent) => { + if (event.key === 'Escape') setOpen(false); + }; + window.addEventListener('pointerdown', closeOnPointerDown, true); + window.addEventListener('keydown', closeOnEscape); + return () => { + window.removeEventListener('pointerdown', closeOnPointerDown, true); + window.removeEventListener('keydown', closeOnEscape); + }; + }, [open]); + + const refreshThemes = useCallback(() => { + setThemes(getAllThemes()); + const theme = applyActiveThemeFallback(); + if (theme) setActiveId(theme.id); + }, []); + + const selectTheme = (id: string) => { + const theme = getTheme(id); + if (!theme) return; + setActiveThemeId(id); + setActiveId(id); + applyTheme(theme); + setOpen(false); + }; + + const deleteTheme = (theme: MouseTermTheme) => { + if (theme.origin.kind !== 'installed') return; + const confirmed = window.confirm(`Delete "${theme.label}"?`); + if (!confirmed) return; + + removeInstalledTheme(theme.id); + setThemes(getAllThemes()); + + if (theme.id === activeId) { + const fallback = applyActiveThemeFallback(); + if (fallback) setActiveId(fallback.id); + } + }; + + const activeTheme = themes.find((theme) => theme.id === activeId) ?? themes[0]; + + return ( +
+ + + {open ? ( +
+
+ {themes.map((theme) => { + const isActive = theme.id === activeId; + const isInstalled = theme.origin.kind === 'installed'; + return ( +
+ + {isInstalled ? ( + + ) : null} +
+ ); + })} +
+
+ +
+
+ ) : null} + + setStoreOpen(false)} onThemesChanged={refreshThemes} /> +
+ ); +} From 9f25b56e406c4cd3c3eba4b35552567c6d87f0b6 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 15 Apr 2026 16:04:44 -0700 Subject: [PATCH 11/24] Centralize the theme picker. --- AGENTS.md | 2 +- docs/specs/theme.md | 18 +- docs/specs/tutorial.md | 4 +- .../src/components/ThemePicker.tsx | 247 +++++++++++------ standalone/src/AppBar.tsx | 6 +- website/src/components/ThemePicker.tsx | 254 ------------------ website/src/components/ThemeStore.tsx | 204 -------------- website/src/pages/Playground.tsx | 4 +- 8 files changed, 178 insertions(+), 561 deletions(-) rename standalone/src/StandaloneThemePicker.tsx => lib/src/components/ThemePicker.tsx (55%) delete mode 100644 website/src/components/ThemePicker.tsx delete mode 100644 website/src/components/ThemeStore.tsx diff --git a/AGENTS.md b/AGENTS.md index 5b6129f2..bdf4827b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,7 +33,7 @@ The primary job of a spec is to be an accurate reference for the current state o - **`docs/specs/layout.md`** — Tiling layout, pane/door containers, dockview configuration, modes (passthrough/command), keyboard shortcuts, selection overlay, spatial navigation, detach/reattach, inline rename, session lifecycle, session persistence, and theming. Read this when touching: `Pond.tsx`, `Baseboard.tsx`, `Door.tsx`, `TerminalPane.tsx`, `spatial-nav.ts`, `layout-snapshot.ts`, `terminal-registry.ts`, `session-save.ts`, `session-restore.ts`, `reconnect.ts`, `index.css`, `theme.css`, or any keyboard/navigation/mode behavior. - **`docs/specs/alarm.md`** — Activity monitoring state machine, alarm trigger/clearing rules, attention model, TODO lifecycle (soft/hard), bell button visual states and interaction, door alarm indicators, and hardening (a11y, motion, i18n, overflow). Read this when touching: `activity-monitor.ts`, `alarm-manager.ts`, the alarm bell or TODO pill in `Pond.tsx` (TerminalPaneHeader), alarm indicators in `Door.tsx`, or the `a`/`t` keyboard shortcuts. Layout.md defers to this spec for all alarm/TODO behavior. - **`docs/specs/vscode.md`** — VS Code extension architecture: hosting modes (WebviewView + WebviewPanel), PTY lifecycle and buffering, message protocol between webview and extension host, session persistence flow, reconnection protocol, theme integration, CSP, build pipeline, and invariants (save-before-kill ordering, PTY ownership, alarm state merging). Read this when touching: `extension.ts`, `webview-view-provider.ts`, `message-router.ts`, `message-types.ts`, `pty-manager.ts`, `pty-host.js`, `session-state.ts`, `webview-html.ts`, `vscode-adapter.ts`, or `pty-core.js`. -- **`docs/specs/tutorial.md`** — Playground tutorial on the website: 3-pane initial layout, `tut` command and TutorialShell, 6-step progressive tutorial with detection logic, theme picker, FakePtyAdapter extensions, and Pond event hooks. Read this when touching: `website/src/pages/Playground.tsx`, `website/src/lib/tutorial-shell.ts`, `website/src/lib/tutorial-detection.ts`, `website/src/components/ThemePicker.tsx`, `website/src/lib/playground-themes.ts`, `lib/src/lib/platform/fake-scenarios.ts` (tutorial scenarios), or the `onApiReady`/`onEvent`/`initialPaneIds` props on Pond. +- **`docs/specs/tutorial.md`** — Playground tutorial on the website: 3-pane initial layout, `tut` command and TutorialShell, 6-step progressive tutorial with detection logic, theme picker, FakePtyAdapter extensions, and Pond event hooks. Read this when touching: `website/src/pages/Playground.tsx`, `website/src/lib/tutorial-shell.ts`, `website/src/lib/tutorial-detection.ts`, `lib/src/components/ThemePicker.tsx`, `website/src/lib/playground-themes.ts`, `lib/src/lib/platform/fake-scenarios.ts` (tutorial scenarios), or the `onApiReady`/`onEvent`/`initialPaneIds` props on Pond. When updating code covered by a spec, update the spec to match. When the two specs overlap (e.g. pane header elements appear in both), layout.md documents placement and sizing while alarm.md documents behavior and visual states. diff --git a/docs/specs/theme.md b/docs/specs/theme.md index 59d57a2e..56b166fa 100644 --- a/docs/specs/theme.md +++ b/docs/specs/theme.md @@ -136,22 +136,23 @@ The store module provides: - `getActiveThemeId()` / `setActiveThemeId(id)` — persists choice across sessions - `addInstalledTheme(theme)` / `removeInstalledTheme(id)` — manages user-installed themes -## Website playground picker +## Shared theme picker -The website renders the theme picker only on `/playground`. `Playground.tsx` passes it through `SiteHeader`'s playground-only `controls` slot and enables `themeAware`, so the same active `--vscode-*` variables style the header background, border, text, banner, Pond, Dockview, and terminals. +`lib/src/components/ThemePicker.tsx` exports a shared `ThemePicker` with two variants: -The picker is labeled `Theme:` and uses a custom dropdown rather than a native ` handleQueryChange(event.target.value)} + onChange={(event) => handleInput(event.target.value)} placeholder="Search themes..." autoFocus - className="w-full rounded border border-input-border bg-input-bg px-3 py-1.5 text-xs text-foreground outline-none placeholder:text-muted focus:border-accent" + className="w-full rounded border px-3 py-1.5 text-xs outline-none placeholder:opacity-65" + style={styles.trigger(false)} />
{error ? ( -
{error}
- ) : null} - {loading ? ( -
Searching...
+
+ {error} +
) : null} + {loading ?
Searching...
: null} {!loading && results.length === 0 && query.trim() ? ( -
No themes found
+
No themes found
) : null} {!loading && !query.trim() ? ( -
+
Search for a VS Code theme to install
) : null} @@ -161,22 +240,20 @@ function ThemeStoreDialog({ const installed = isInstalled(extension); const isInstallingThis = installing === key; return ( -
+
{extension.files?.icon ? ( ) : ( -
+
VS
)}
-
- {extension.displayName || extension.name} -
-
+
{extension.displayName || extension.name}
+
{extension.namespace} - {extension.downloadCount.toLocaleString()} downloads
@@ -184,7 +261,8 @@ function ThemeStoreDialog({ @@ -193,7 +271,8 @@ function ThemeStoreDialog({ type="button" onClick={() => handleInstall(extension)} disabled={isInstallingThis} - className="shrink-0 rounded bg-button-bg px-2 py-1 text-[10px] text-button-fg transition-colors hover:bg-button-hover-bg disabled:opacity-50" + className="shrink-0 rounded px-2 py-1 text-[10px] transition-opacity hover:opacity-90 disabled:opacity-50" + style={styles.button} > {isInstallingThis ? 'Installing...' : 'Install'} @@ -207,12 +286,17 @@ function ThemeStoreDialog({ ); } -export function StandaloneThemePicker() { +export function ThemePicker({ variant, className = '' }: ThemePickerProps) { + const labelId = useId(); + const currentId = useId(); const [themes, setThemes] = useState(getAllThemes); const [activeId, setActiveId] = useState(() => getAllThemes()[0]?.id ?? ''); const [open, setOpen] = useState(false); const [storeOpen, setStoreOpen] = useState(false); - const ref = useRef(null); + const rootRef = useRef(null); + + const isPlayground = variant === 'playground-header'; + const activeTheme = themes.find((theme) => theme.id === activeId) ?? themes[0]; useEffect(() => { const theme = applyActiveThemeFallback(); @@ -220,21 +304,8 @@ export function StandaloneThemePicker() { setThemes(getAllThemes()); }, []); - useEffect(() => { - if (!open) return; - const closeOnPointerDown = (event: PointerEvent) => { - if (ref.current && !ref.current.contains(event.target as Node)) setOpen(false); - }; - const closeOnEscape = (event: KeyboardEvent) => { - if (event.key === 'Escape') setOpen(false); - }; - window.addEventListener('pointerdown', closeOnPointerDown, true); - window.addEventListener('keydown', closeOnEscape); - return () => { - window.removeEventListener('pointerdown', closeOnPointerDown, true); - window.removeEventListener('keydown', closeOnEscape); - }; - }, [open]); + const closeDropdown = useCallback(() => setOpen(false), []); + useCloseOnOutsideAndEscape(open, rootRef, closeDropdown); const refreshThemes = useCallback(() => { setThemes(getAllThemes()); @@ -265,78 +336,78 @@ export function StandaloneThemePicker() { } }; - const activeTheme = themes.find((theme) => theme.id === activeId) ?? themes[0]; + const rootClass = isPlayground + ? 'relative flex min-w-0 items-center gap-1.5 text-xs' + : 'relative flex items-center'; + const triggerClass = isPlayground + ? 'flex h-8 w-[116px] min-w-0 items-center gap-2 rounded border px-2 text-left text-[12px] transition-colors sm:w-40 md:w-56' + : 'flex h-6 max-w-[190px] items-center gap-1.5 rounded border border-transparent px-2 text-xs transition-colors hover:opacity-85'; + const menuClass = isPlayground + ? 'fixed top-16 right-4 left-4 z-50 overflow-hidden rounded border shadow-2xl md:absolute md:top-full md:right-0 md:left-auto md:mt-2 md:w-[22rem]' + : 'absolute right-0 top-full z-50 mt-1 w-[280px] overflow-hidden rounded border shadow-2xl'; + const rowButtonClass = isPlayground + ? 'flex min-w-0 flex-1 items-center gap-2 px-3 py-2 text-left text-xs' + : 'flex min-w-0 flex-1 items-center gap-2 px-3 py-1.5 text-left text-xs'; + const swatchSize = isPlayground ? 'md' : 'sm'; return ( -
+
+ {isPlayground ? ( + + Theme: + + ) : null} + {open ? ( -
-
+
+
{themes.map((theme) => { const isActive = theme.id === activeId; const isInstalled = theme.origin.kind === 'installed'; return (
{isInstalled ? (
-
+ +
diff --git a/standalone/src/AppBar.tsx b/standalone/src/AppBar.tsx index be25a961..47e5dac8 100644 --- a/standalone/src/AppBar.tsx +++ b/standalone/src/AppBar.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import { getCurrentWindow } from '@tauri-apps/api/window'; import { CaretDownIcon, MinusIcon, CornersOutIcon, CornersInIcon, XIcon, TerminalWindowIcon, PlusIcon } from '@phosphor-icons/react'; -import { StandaloneThemePicker } from './StandaloneThemePicker'; +import { ThemePicker } from '../../lib/src/components/ThemePicker'; export interface ShellEntry { name: string; @@ -210,13 +210,13 @@ export function AppBar({ projectDir, homeDir, shells }: AppBarProps) { {/* Shell dropdown on the right (macOS) or window controls (Windows/Linux) */} {IS_MAC ? (
- +
) : (
- +
diff --git a/website/src/components/ThemePicker.tsx b/website/src/components/ThemePicker.tsx deleted file mode 100644 index cdc0c570..00000000 --- a/website/src/components/ThemePicker.tsx +++ /dev/null @@ -1,254 +0,0 @@ -import { useState, useEffect, useCallback, useRef } from "react"; -import { - getAllThemes, - getActiveThemeId, - setActiveThemeId, - getTheme, - applyTheme, - removeInstalledTheme, -} from "mouseterm-lib/lib/themes"; -import type { MouseTermTheme } from "mouseterm-lib/lib/themes"; -import { ThemeStore } from "./ThemeStore"; - -export function ThemePicker() { - const [themes, setThemes] = useState(getAllThemes); - const [activeId, setActiveId] = useState(() => getAllThemes()[0]?.id ?? ""); - const [open, setOpen] = useState(false); - const [storeOpen, setStoreOpen] = useState(false); - const rootRef = useRef(null); - - // Restore persisted theme on mount. - useEffect(() => { - const allThemes = getAllThemes(); - const theme = getTheme(getActiveThemeId()) ?? allThemes[0]; - setThemes(allThemes); - if (!theme) return; - setActiveId(theme.id); - setActiveThemeId(theme.id); - applyTheme(theme); - }, []); - - useEffect(() => { - if (!open) return; - - const closeOnPointerDown = (event: PointerEvent) => { - if (!rootRef.current?.contains(event.target as Node)) { - setOpen(false); - } - }; - const closeOnEscape = (event: KeyboardEvent) => { - if (event.key === "Escape") setOpen(false); - }; - - window.addEventListener("pointerdown", closeOnPointerDown, true); - window.addEventListener("keydown", closeOnEscape); - return () => { - window.removeEventListener("pointerdown", closeOnPointerDown, true); - window.removeEventListener("keydown", closeOnEscape); - }; - }, [open]); - - const refreshThemes = useCallback(() => { - const allThemes = getAllThemes(); - setThemes(allThemes); - - const current = allThemes.find((theme) => theme.id === getActiveThemeId()); - if (current) { - setActiveId(current.id); - return; - } - - const fallback = allThemes[0]; - if (!fallback) return; - setActiveId(fallback.id); - setActiveThemeId(fallback.id); - applyTheme(fallback); - }, []); - - const select = (id: string) => { - const theme = getTheme(id); - if (!theme) return; - setActiveId(id); - setActiveThemeId(id); - applyTheme(theme); - setOpen(false); - }; - - const deleteTheme = (theme: MouseTermTheme) => { - if (theme.origin.kind !== "installed") return; - const confirmed = window.confirm(`Delete "${theme.label}"?`); - if (!confirmed) return; - - removeInstalledTheme(theme.id); - const allThemes = getAllThemes(); - setThemes(allThemes); - - if (activeId !== theme.id) return; - const fallback = allThemes[0]; - if (!fallback) return; - setActiveId(fallback.id); - setActiveThemeId(fallback.id); - applyTheme(fallback); - }; - - const openStore = () => { - setOpen(false); - setStoreOpen(true); - }; - - const activeTheme = themes.find((theme) => theme.id === activeId) ?? themes[0]; - - return ( -
- - Theme: - - - - - {open ? ( -
-
- {themes.map((theme) => { - const isActive = theme.id === activeId; - const isInstalled = theme.origin.kind === "installed"; - return ( -
- - {isInstalled ? ( - - ) : null} -
- ); - })} -
- -
- -
-
- ) : null} - - setStoreOpen(false)} - onThemesChanged={() => { - refreshThemes(); - // Sync active ID in case install auto-selected a theme - setActiveId(getActiveThemeId()); - }} - /> -
- ); -} diff --git a/website/src/components/ThemeStore.tsx b/website/src/components/ThemeStore.tsx deleted file mode 100644 index ca7e906d..00000000 --- a/website/src/components/ThemeStore.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import { useState, useCallback, useRef, useEffect } from "react"; -import type { OpenVSXExtension } from "mouseterm-lib/lib/themes"; -import { - searchThemes, - fetchExtensionThemes, - addInstalledTheme, - removeInstalledTheme, - getInstalledThemes, - applyTheme, - setActiveThemeId, -} from "mouseterm-lib/lib/themes"; - -interface ThemeStoreProps { - open: boolean; - onClose: () => void; - /** Called after themes change so the picker can refresh */ - onThemesChanged: () => void; -} - -export function ThemeStore({ open, onClose, onThemesChanged }: ThemeStoreProps) { - const [query, setQuery] = useState(""); - const [results, setResults] = useState([]); - const [loading, setLoading] = useState(false); - const [installing, setInstalling] = useState(null); - const [error, setError] = useState(null); - const debounceRef = useRef>(); - const dialogRef = useRef(null); - - // Manage dialog open/close - useEffect(() => { - const dialog = dialogRef.current; - if (!dialog) return; - if (open && !dialog.open) dialog.showModal(); - else if (!open && dialog.open) dialog.close(); - }, [open]); - - const doSearch = useCallback(async (q: string) => { - if (!q.trim()) { - setResults([]); - return; - } - setLoading(true); - setError(null); - try { - const res = await searchThemes(q, 0, 20); - setResults(res.extensions); - } catch (e) { - setError(e instanceof Error ? e.message : "Search failed"); - } finally { - setLoading(false); - } - }, []); - - const handleInput = (value: string) => { - setQuery(value); - clearTimeout(debounceRef.current); - debounceRef.current = setTimeout(() => doSearch(value), 300); - }; - - const handleInstall = async (ext: OpenVSXExtension) => { - const key = `${ext.namespace}/${ext.name}`; - setInstalling(key); - setError(null); - try { - const themes = await fetchExtensionThemes(ext.namespace, ext.name); - for (const theme of themes) { - addInstalledTheme(theme); - } - // Apply the first variant - if (themes[0]) { - applyTheme(themes[0]); - setActiveThemeId(themes[0].id); - } - onThemesChanged(); - } catch (e) { - setError(e instanceof Error ? e.message : "Install failed"); - } finally { - setInstalling(null); - } - }; - - const handleRemove = (extensionId: string) => { - const confirmed = window.confirm(`Remove installed themes from ${extensionId}?`); - if (!confirmed) return; - - const installed = getInstalledThemes(); - for (const theme of installed) { - if (theme.origin.kind === "installed" && theme.origin.extensionId === extensionId) { - removeInstalledTheme(theme.id); - } - } - onThemesChanged(); - }; - - const isInstalled = (ext: OpenVSXExtension) => { - const key = `${ext.namespace}/${ext.name}`; - return getInstalledThemes().some( - (t) => t.origin.kind === "installed" && t.origin.extensionId === key, - ); - }; - - if (!open) return null; - - return ( - -
- {/* Header */} -
- Install Theme from OpenVSX - -
- - {/* Search */} -
- handleInput(e.target.value)} - placeholder="Search themes..." - autoFocus - className="w-full rounded border border-white/10 bg-[#3c3c3c] px-3 py-1.5 text-xs text-[#cccccc] outline-none placeholder:text-[#858585] focus:border-[#007fd4]" - /> -
- - {/* Results */} -
- {error && ( -
- {error} -
- )} - {loading && ( -
Searching...
- )} - {!loading && results.length === 0 && query.trim() && ( -
No themes found
- )} - {!loading && !query.trim() && ( -
- Search for a VSCode theme to install -
- )} - {results.map((ext) => { - const key = `${ext.namespace}/${ext.name}`; - const installed = isInstalled(ext); - const isInstallingThis = installing === key; - return ( -
- {/* Icon */} - {ext.files?.icon ? ( - - ) : ( -
- 🎨 -
- )} - - {/* Info */} -
-
- {ext.displayName || ext.name} -
-
- {ext.namespace} · {ext.downloadCount.toLocaleString()} downloads -
-
- - {/* Action */} - {installed ? ( - - ) : ( - - )} -
- ); - })} -
-
-
- ); -} diff --git a/website/src/pages/Playground.tsx b/website/src/pages/Playground.tsx index 3079b8b7..410e2b2b 100644 --- a/website/src/pages/Playground.tsx +++ b/website/src/pages/Playground.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback, useRef } from "react"; import SiteHeader from "../components/SiteHeader"; -import { ThemePicker } from "../components/ThemePicker"; +import { ThemePicker } from "mouseterm-lib/components/ThemePicker"; import { TutorialShell } from "../lib/tutorial-shell"; import { TutorialDetector } from "../lib/tutorial-detection"; @@ -90,7 +90,7 @@ function Playground() { } + controls={} />
From 303b4ed96c7328691820915d9db5b03f40149300 Mon Sep 17 00:00:00 2001 From: nedtwigg Date: Wed, 15 Apr 2026 23:33:58 +0000 Subject: [PATCH 12/24] Claude Code review R1: fix stale references in specs and AGENTS.md - AGENTS.md: replace deleted website/src/lib/playground-themes.ts with lib/src/lib/themes/, add theme.md spec entry - theme.md: fix bundle-themes.mjs path (scripts/ -> lib/scripts/), update stale "replaces the current" for deleted PlaygroundTheme file - layout.md: update --mt-* references to --color-* (collapsed in theme refactor), fix var(--mt-surface) -> var(--color-surface) --- AGENTS.md | 3 ++- docs/specs/layout.md | 8 ++++---- docs/specs/theme.md | 8 ++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index bdf4827b..f1cd309a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,7 +33,8 @@ The primary job of a spec is to be an accurate reference for the current state o - **`docs/specs/layout.md`** — Tiling layout, pane/door containers, dockview configuration, modes (passthrough/command), keyboard shortcuts, selection overlay, spatial navigation, detach/reattach, inline rename, session lifecycle, session persistence, and theming. Read this when touching: `Pond.tsx`, `Baseboard.tsx`, `Door.tsx`, `TerminalPane.tsx`, `spatial-nav.ts`, `layout-snapshot.ts`, `terminal-registry.ts`, `session-save.ts`, `session-restore.ts`, `reconnect.ts`, `index.css`, `theme.css`, or any keyboard/navigation/mode behavior. - **`docs/specs/alarm.md`** — Activity monitoring state machine, alarm trigger/clearing rules, attention model, TODO lifecycle (soft/hard), bell button visual states and interaction, door alarm indicators, and hardening (a11y, motion, i18n, overflow). Read this when touching: `activity-monitor.ts`, `alarm-manager.ts`, the alarm bell or TODO pill in `Pond.tsx` (TerminalPaneHeader), alarm indicators in `Door.tsx`, or the `a`/`t` keyboard shortcuts. Layout.md defers to this spec for all alarm/TODO behavior. - **`docs/specs/vscode.md`** — VS Code extension architecture: hosting modes (WebviewView + WebviewPanel), PTY lifecycle and buffering, message protocol between webview and extension host, session persistence flow, reconnection protocol, theme integration, CSP, build pipeline, and invariants (save-before-kill ordering, PTY ownership, alarm state merging). Read this when touching: `extension.ts`, `webview-view-provider.ts`, `message-router.ts`, `message-types.ts`, `pty-manager.ts`, `pty-host.js`, `session-state.ts`, `webview-html.ts`, `vscode-adapter.ts`, or `pty-core.js`. -- **`docs/specs/tutorial.md`** — Playground tutorial on the website: 3-pane initial layout, `tut` command and TutorialShell, 6-step progressive tutorial with detection logic, theme picker, FakePtyAdapter extensions, and Pond event hooks. Read this when touching: `website/src/pages/Playground.tsx`, `website/src/lib/tutorial-shell.ts`, `website/src/lib/tutorial-detection.ts`, `lib/src/components/ThemePicker.tsx`, `website/src/lib/playground-themes.ts`, `lib/src/lib/platform/fake-scenarios.ts` (tutorial scenarios), or the `onApiReady`/`onEvent`/`initialPaneIds` props on Pond. +- **`docs/specs/tutorial.md`** — Playground tutorial on the website: 3-pane initial layout, `tut` command and TutorialShell, 6-step progressive tutorial with detection logic, theme picker, FakePtyAdapter extensions, and Pond event hooks. Read this when touching: `website/src/pages/Playground.tsx`, `website/src/lib/tutorial-shell.ts`, `website/src/lib/tutorial-detection.ts`, `lib/src/components/ThemePicker.tsx`, `lib/src/lib/themes/`, `lib/src/lib/platform/fake-scenarios.ts` (tutorial scenarios), or the `onApiReady`/`onEvent`/`initialPaneIds` props on Pond. +- **`docs/specs/theme.md`** — Theme system: two-layer CSS variable strategy, theme data model, conversion pipeline, bundled themes, localStorage store, shared ThemePicker component, standalone AppBar picker, runtime OpenVSX installer. Read this when touching: `lib/src/lib/themes/`, `lib/src/components/ThemePicker.tsx`, `lib/src/theme.css`, `lib/scripts/bundle-themes.mjs`, `standalone/src/AppBar.tsx` (theme picker), `standalone/src/main.tsx` (theme restore), or `website/src/components/SiteHeader.tsx` (themeAware mode). When updating code covered by a spec, update the spec to match. When the two specs overlap (e.g. pane header elements appear in both), layout.md documents placement and sizing while alarm.md documents behavior and visual states. diff --git a/docs/specs/layout.md b/docs/specs/layout.md index f2f2ad80..bc1193a8 100644 --- a/docs/specs/layout.md +++ b/docs/specs/layout.md @@ -68,7 +68,7 @@ The content area is a tiling layout of panes, powered by dockview. Each pane occ ### Pane header -Each pane has a 30px header that doubles as a drag handle. The header uses `cursor-grab` / `active:cursor-grabbing` and `select-none`. Background uses `--mt-tab-*` theme tokens (adapts to VSCode host theme). Dockview's default close button and right-actions container are hidden via CSS. +Each pane has a 30px header that doubles as a drag handle. The header uses `cursor-grab` / `active:cursor-grabbing` and `select-none`. Background uses `--color-tab-*` theme tokens (adapts to VSCode host theme). Dockview's default close button and right-actions container are hidden via CSS. Elements from left to right: @@ -279,9 +279,9 @@ Custom `mousetermTheme` extends dockview's `themeAbyss`: - Pane header height: `--dv-tabs-and-actions-container-height: 30px` - 6px padding around the dockview area (`p-1.5` on wrapper, `inset-1.5` on container) -Colors use a two-layer CSS variable strategy: `--mt-*` semantic tokens → `var(--vscode-*, )`. In VSCode, host theme variables take precedence. In standalone mode, fallback values apply (Dark+ defaults with `prefers-color-scheme: light` overrides). Tailwind v4 `@theme` block registers `--mt-*` tokens as Tailwind colors (e.g., `bg-surface`, `text-foreground`, `border-border`). See `theme.css` for the full token map. +Colors use a two-layer CSS variable strategy: `@theme --color-*` tokens → `var(--vscode-*, )`. In VSCode, host theme variables take precedence. In standalone mode, fallback values apply (Dark+ defaults with `prefers-color-scheme: light` overrides). Tailwind v4 `@theme` block registers `--color-*` tokens as Tailwind colors (e.g., `bg-surface`, `text-foreground`, `border-border`). See `theme.css` for the full token map. -Dockview's separator borders, sash handles, and groupview borders are all set to transparent/none — the 6px gap is the only visual separator between panes. All dockview container backgrounds are flattened to `var(--mt-surface)`. +Dockview's separator borders, sash handles, and groupview borders are all set to transparent/none — the 6px gap is the only visual separator between panes. All dockview container backgrounds are flattened to `var(--color-surface)`. ## Corner cases @@ -315,4 +315,4 @@ Dockview's separator borders, sash handles, and groupview borders are all set to | `lib/src/lib/reconnect.ts` | Priority-based recovery: live PTYs first, then saved session, then empty | | `lib/src/lib/resume-patterns.ts` | Detects resumable commands (`claude --resume`, etc.) in scrollback | | `lib/src/index.css` | Dockview theme overrides — separator/sash/border removal, background flattening | -| `lib/src/theme.css` | Two-layer VSCode theme token system (`--mt-*` → `--vscode-*`) and Tailwind v4 `@theme` integration | +| `lib/src/theme.css` | Two-layer VSCode theme token system (`@theme --color-*` → `--vscode-*`) and Tailwind v4 `@theme` integration | diff --git a/docs/specs/theme.md b/docs/specs/theme.md index 56b166fa..fae448fd 100644 --- a/docs/specs/theme.md +++ b/docs/specs/theme.md @@ -60,7 +60,7 @@ interface InstalledOrigin { } ``` -This replaces the current `PlaygroundTheme` interface in `website/src/lib/playground-themes.ts`. +This replaced the old `PlaygroundTheme` interface (previously in `website/src/lib/playground-themes.ts`, now deleted). ## Conversion pipeline @@ -90,7 +90,7 @@ For each key in the VSCode theme's `colors` object: if it's in `CONSUMED_VSCODE_ ## Bundled themes -Bundled themes are extracted at build time by a Node.js script (`scripts/bundle-themes.mjs`) and written to `lib/src/lib/themes/bundled.json`. This file is checked into git so builds don't require network access. +Bundled themes are extracted at build time by a Node.js script (`lib/scripts/bundle-themes.mjs`) and written to `lib/src/lib/themes/bundled.json`. This file is checked into git so builds don't require network access. ### Source extensions @@ -105,7 +105,7 @@ Dark+ and Light+ are VSCode built-in themes not published to OpenVSX. Their valu ### Build script flow ``` -scripts/bundle-themes.mjs +lib/scripts/bundle-themes.mjs | +- for each extension in EXTENSIONS list: | +- fetch /api/{ns}/{name}/latest from OpenVSX @@ -200,7 +200,7 @@ user searches OpenVSX | [`lib/src/lib/themes/openvsx.ts`](../../lib/src/lib/themes/openvsx.ts) | OpenVSX search API, VSIX download + extraction | | [`lib/src/lib/themes/bundled.json`](../../lib/src/lib/themes/bundled.json) | Pre-converted bundled themes (generated, checked in) | | [`lib/src/lib/themes/index.ts`](../../lib/src/lib/themes/index.ts) | Barrel export | -| [`scripts/bundle-themes.mjs`](../../scripts/bundle-themes.mjs) | Build-time script to download and convert themes from OpenVSX | +| [`lib/scripts/bundle-themes.mjs`](../../lib/scripts/bundle-themes.mjs) | Build-time script to download and convert themes from OpenVSX | | [`lib/src/theme.css`](../../lib/src/theme.css) | `@theme` tokens with `var(--vscode-*, fallback)` + light mode overrides | | [`lib/src/lib/terminal-registry.ts`](../../lib/src/lib/terminal-registry.ts) | MutationObserver + `getTerminalTheme()` — no changes needed | | [`lib/src/components/ThemePicker.tsx`](../../lib/src/components/ThemePicker.tsx) | Shared website/standalone dropdown and OpenVSX dialog for selecting, installing, and deleting themes | From b4ac144b72c8a1baf1eddca40f83e88a4147a825 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 15 Apr 2026 21:20:17 -0700 Subject: [PATCH 13/24] Fix Tauri warning by `com.mouseterm.app` -> `com.mouseterm.standalone` --- standalone/src-tauri/tauri.conf.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standalone/src-tauri/tauri.conf.json b/standalone/src-tauri/tauri.conf.json index f8d6d27c..9e78e3ef 100644 --- a/standalone/src-tauri/tauri.conf.json +++ b/standalone/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "https://schema.tauri.app/config/2", "productName": "MouseTerm", "version": "0.6.2", - "identifier": "com.mouseterm.app", + "identifier": "com.mouseterm.standalone", "build": { "beforeDevCommand": "pnpm dev", "devUrl": "http://localhost:1420", From 6ba459f792c73b734929ef6a82cd8bf56914c7a0 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 15 Apr 2026 21:20:55 -0700 Subject: [PATCH 14/24] Set version we've been missing. --- standalone/src-tauri/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standalone/src-tauri/Cargo.toml b/standalone/src-tauri/Cargo.toml index 6c11a13a..58b89aaf 100644 --- a/standalone/src-tauri/Cargo.toml +++ b/standalone/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mouseterm" -version = "0.1.0" +version = "0.6.2" description = "Mouse-friendly multitasking terminal" authors = ["DiffPlug"] license = "FSL-1.1-MIT" From 338ade072ef651456bd9c26dc16f25f06b8b6e14 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 15 Apr 2026 21:21:48 -0700 Subject: [PATCH 15/24] Fixup dogfooding on mac. --- docs/specs/deploy.md | 1 + standalone/scripts/dogfood.sh | 27 +++++++++++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/docs/specs/deploy.md b/docs/specs/deploy.md index b2546363..331a0573 100644 --- a/docs/specs/deploy.md +++ b/docs/specs/deploy.md @@ -18,6 +18,7 @@ Human-driven steps, in order: 1. **Update dependencies page** — run `node website/scripts/generate-deps.js` and review the diff in `website/src/data/dependencies.json`. Commit if changed. 2. **Finalize changelog** — promote the `[Unreleased]` section in `CHANGELOG.md` to `[X.Y.Z]` with today's date. Write release notes covering both standalone and VSCode changes. 3. **Bump versions** — update `version` in all three places: + - [standalone/src-tauri/Cargo.toml](../../standalone/src-tauri/Cargo.toml) - [standalone/src-tauri/tauri.conf.json](../../standalone/src-tauri/tauri.conf.json) - [vscode-ext/package.json](../../vscode-ext/package.json) - [lib/package.json](../../lib/package.json) diff --git a/standalone/scripts/dogfood.sh b/standalone/scripts/dogfood.sh index 943fac90..e264cd2a 100755 --- a/standalone/scripts/dogfood.sh +++ b/standalone/scripts/dogfood.sh @@ -12,8 +12,8 @@ # # Install mode (--install): # Copies the built files over the system-installed copy, bypassing the slow -# bundling/installer step. Requires a one-time install via the NSIS installer -# so that registry entries, shortcuts, etc. are in place. Currently Windows only. +# installer step. Requires a one-time install first (NSIS installer on Windows, +# DMG on macOS) so that the install location exists. # set -euo pipefail @@ -23,9 +23,14 @@ set -euo pipefail RELEASE_DIR="standalone/src-tauri/target/release" if [[ "${1:-}" == "--install" ]]; then - # Full build with bundling, but disable updater artifact signing + # Full build with bundling, but disable updater artifact signing. + # On macOS, build only the .app bundle (skip DMG creation). + BUNDLE_ARGS=() + case "$(uname -s)" in + Darwin) BUNDLE_ARGS=(--bundles app) ;; + esac pnpm --filter mouseterm-standalone tauri build \ - -c '{"bundle":{"createUpdaterArtifacts":false}}' + -c '{"bundle":{"createUpdaterArtifacts":false}}' "${BUNDLE_ARGS[@]}" else # Fast build: skip bundling entirely since we just need the exe pnpm --filter mouseterm-standalone tauri build --no-bundle @@ -56,6 +61,20 @@ if [[ "${1:-}" == "--install" ]]; then cp -r "$RELEASE_DIR/_up_/" "$INSTALL_DIR/_up_/" echo "✦ Installed to $INSTALL_DIR" ;; + Darwin) + INSTALL_DIR="/Applications/MouseTerm.app" + if [[ ! -d "$INSTALL_DIR" ]]; then + echo "MouseTerm is not installed yet." + echo "Install via the DMG first:" + echo " open $RELEASE_DIR/bundle/dmg/MouseTerm_*.dmg" + echo "" + echo "After that, 'dogfood:standalone --install' will work from then on." + exit 1 + fi + rm -rf "$INSTALL_DIR" + cp -r "$RELEASE_DIR/bundle/macos/MouseTerm.app" "$INSTALL_DIR" + echo "✦ Installed to $INSTALL_DIR" + ;; *) echo "--install is not yet implemented for this platform." exit 1 From cd248fac72e31dcfc35361ea33414fdbb51a08b3 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 16 Apr 2026 11:46:10 -0700 Subject: [PATCH 16/24] Only ship OpenVSX themes. --- docs/specs/layout.md | 2 +- docs/specs/theme.md | 16 +- docs/specs/tutorial.md | 2 +- docs/specs/vscode.md | 2 +- lib/.storybook/preview.ts | 2 +- lib/scripts/bundle-themes.mjs | 125 ++----------- lib/src/lib/themes/bundled.json | 294 +++++-------------------------- lib/src/lib/themes/types.ts | 4 +- lib/src/stories/Pond.stories.tsx | 4 +- lib/src/theme.css | 4 +- 10 files changed, 70 insertions(+), 385 deletions(-) diff --git a/docs/specs/layout.md b/docs/specs/layout.md index bc1193a8..d0345d0f 100644 --- a/docs/specs/layout.md +++ b/docs/specs/layout.md @@ -279,7 +279,7 @@ Custom `mousetermTheme` extends dockview's `themeAbyss`: - Pane header height: `--dv-tabs-and-actions-container-height: 30px` - 6px padding around the dockview area (`p-1.5` on wrapper, `inset-1.5` on container) -Colors use a two-layer CSS variable strategy: `@theme --color-*` tokens → `var(--vscode-*, )`. In VSCode, host theme variables take precedence. In standalone mode, fallback values apply (Dark+ defaults with `prefers-color-scheme: light` overrides). Tailwind v4 `@theme` block registers `--color-*` tokens as Tailwind colors (e.g., `bg-surface`, `text-foreground`, `border-border`). See `theme.css` for the full token map. +Colors use a two-layer CSS variable strategy: `@theme --color-*` tokens → `var(--vscode-*, )`. In VSCode, host theme variables take precedence. In standalone mode, fallback values apply with `prefers-color-scheme: light` overrides. Tailwind v4 `@theme` block registers `--color-*` tokens as Tailwind colors (e.g., `bg-surface`, `text-foreground`, `border-border`). See `theme.css` for the full token map. Dockview's separator borders, sash handles, and groupview borders are all set to transparent/none — the 6px gap is the only visual separator between panes. All dockview container backgrounds are flattened to `var(--color-surface)`. diff --git a/docs/specs/theme.md b/docs/specs/theme.md index fae448fd..d0a93950 100644 --- a/docs/specs/theme.md +++ b/docs/specs/theme.md @@ -37,13 +37,13 @@ The old three-layer system (`--vscode-*` → `--mt-*` → `--color-*`) had a red ### Light theme body class -`applyTheme()` adds `vscode-light` to `document.body.classList` for light themes and removes it for dark themes. `theme.css` has a `body.vscode-light` selector that switches all `--color-*` fallback values to the Light+ palette. Without this class, a light theme that doesn't explicitly define every key would get dark fallbacks for missing keys. +`applyTheme()` adds `vscode-light` to `document.body.classList` for light themes and removes it for dark themes. `theme.css` has a `body.vscode-light` selector that switches all `--color-*` fallback values to the light fallback palette. Without this class, a light theme that doesn't explicitly define every key would get dark fallbacks for missing keys. ## Theme data model ```typescript interface MouseTermTheme { - id: string; // "GitHub.github-vscode-theme.dark-default" or "builtin.dark-plus" + id: string; // "GitHub.github-vscode-theme.github-dark-default" label: string; // "GitHub Dark Default" type: 'dark' | 'light'; swatch: string; // editor.background — used for picker preview @@ -55,7 +55,7 @@ interface MouseTermTheme { interface BundledOrigin { kind: 'bundled' } interface InstalledOrigin { kind: 'installed'; - extensionId: string; // "dracula-theme/theme-dracula" + extensionId: string; // "publisher/theme-extension" installedAt: string; // ISO date } ``` @@ -86,7 +86,7 @@ Not all VSCode theme color keys matter to MouseTerm — only the ~45 keys that a ### Conversion rule -For each key in the VSCode theme's `colors` object: if it's in `CONSUMED_VSCODE_KEYS`, emit `--vscode-${key.replace(/\./g, '-')}` → value. Keys not consumed by MouseTerm are silently dropped. Missing keys fall through to the `@theme` fallbacks in `theme.css` (Dark+ or Light+ defaults), which is the same behavior as VSCode itself. +For each key in the VSCode theme's `colors` object: if it's in `CONSUMED_VSCODE_KEYS`, emit `--vscode-${key.replace(/\./g, '-')}` → value. Keys not consumed by MouseTerm are silently dropped. Missing keys fall through to the `@theme` fallbacks in `theme.css`, which is the same behavior as VSCode itself. ## Bundled themes @@ -97,10 +97,6 @@ Bundled themes are extracted at build time by a Node.js script (`lib/scripts/bun | Extension | OpenVSX ID | Variants | |-----------|-----------|----------| | GitHub VSCode Theme | `GitHub/github-vscode-theme` | Dark Default, Light Default, Dark Dimmed, Dark High Contrast, Light High Contrast, Dark Colorblind, Light Colorblind, etc. | -| Dracula | `dracula-theme/theme-dracula` | Dracula, Dracula Soft | -| VSCode builtins | (hardcoded) | Dark+, Light+ | - -Dark+ and Light+ are VSCode built-in themes not published to OpenVSX. Their values are hardcoded (from the existing `lib/.storybook/themes.ts`). ### Build script flow @@ -117,8 +113,6 @@ lib/scripts/bundle-themes.mjs | +- convertVscodeThemeColors(colors) -> vars | +- emit MouseTermTheme object | - +- append hardcoded Dark+ and Light+ themes - | +- write lib/src/lib/themes/bundled.json ``` @@ -231,6 +225,6 @@ user searches OpenVSX **Why filter to `CONSUMED_VSCODE_KEYS` instead of passing all colors through?** VSCode themes can define 500+ color keys. Setting all of them as CSS variables would be wasteful (most are never read) and could cause unexpected interactions if VSCode adds new keys that happen to match future `--color-*` variables. -**Why set the `vscode-light` body class?** `theme.css` uses `body.vscode-light` to switch all `--color-*` fallback values to the Light+ palette. Without this class, a light theme that doesn't explicitly define every key would get dark fallbacks for the missing ones, creating a broken mixed appearance. +**Why set the `vscode-light` body class?** `theme.css` uses `body.vscode-light` to switch all `--color-*` fallback values to the light fallback palette. Without this class, a light theme that doesn't explicitly define every key would get dark fallbacks for the missing ones, creating a broken mixed appearance. **Why not use OpenVSX's direct file access instead of downloading the full VSIX?** OpenVSX doesn't expose individual theme files via API — you have to download the full VSIX. However, theme-only extensions are typically small (50-200 KB), so this is fine. The build script and runtime installer share the same extraction logic. diff --git a/docs/specs/tutorial.md b/docs/specs/tutorial.md index 91d1d532..628333be 100644 --- a/docs/specs/tutorial.md +++ b/docs/specs/tutorial.md @@ -127,7 +127,7 @@ The sandbox stays fully functional after completion. Running `tut` shows "Tutori Implemented in `mouseterm-lib/lib/themes` and `mouseterm-lib/components/ThemePicker`. -Bundled themes are provided by `mouseterm-lib/lib/themes` and include Dark+, Light+, GitHub variants, and Dracula variants. Users can install additional themes from OpenVSX through the dropdown footer action. +Bundled themes are provided by `mouseterm-lib/lib/themes` and include only GitHub variants. Users can install additional themes from OpenVSX through the dropdown footer action. The picker appears only on `/playground`, inside `SiteHeader`, labeled `Theme:`. The trigger opens a dropdown of bundled and installed themes. The dropdown footer is always `Install theme from OpenVSX`, which opens the theme store dialog. Installed theme rows include an `X` delete control; deletion requires browser confirmation before removing the theme from localStorage. If the active installed theme is deleted, the picker falls back to the first bundled theme and applies it immediately. diff --git a/docs/specs/vscode.md b/docs/specs/vscode.md index d80ec75b..82528d33 100644 --- a/docs/specs/vscode.md +++ b/docs/specs/vscode.md @@ -231,7 +231,7 @@ Example of the pattern: --color-surface: var(--mt-surface); ``` -Full mapping in `lib/src/theme.css` covers: surfaces (3), text (2), accent/borders (4), tabs (6), terminal bg/fg/cursor/selection (4), all 16 ANSI colors + bright variants, badges (2), semantic status (3), inputs (2), buttons (3), and selection (2). Dark mode fallbacks (VS Code Dark+ defaults) are in `:root`; light mode overrides (VS Code Light+ defaults) are in `body.vscode-light`; a standalone fallback uses `@media (prefers-color-scheme: light)` for non-VS Code contexts. +Full mapping in `lib/src/theme.css` covers: surfaces (3), text (2), accent/borders (4), tabs (6), terminal bg/fg/cursor/selection (4), all 16 ANSI colors + bright variants, badges (2), semantic status (3), inputs (2), buttons (3), and selection (2). Dark mode fallbacks are in `:root`; light mode overrides are in `body.vscode-light`; a standalone fallback uses `@media (prefers-color-scheme: light)` for non-VS Code contexts. A `MutationObserver` in `terminal-registry.ts` watches for VS Code theme changes on `body`/`html` (class and style attribute mutations) and live-updates all xterm.js instances. diff --git a/lib/.storybook/preview.ts b/lib/.storybook/preview.ts index b805d202..993bcece 100644 --- a/lib/.storybook/preview.ts +++ b/lib/.storybook/preview.ts @@ -43,7 +43,7 @@ const preview: Preview = { }, }, initialGlobals: { - theme: 'Dark+ (default)', + theme: 'GitHub Dark Default', }, decorators: [ // Theme switcher: inject --vscode-* CSS variables diff --git a/lib/scripts/bundle-themes.mjs b/lib/scripts/bundle-themes.mjs index 7703ac2b..0012afec 100644 --- a/lib/scripts/bundle-themes.mjs +++ b/lib/scripts/bundle-themes.mjs @@ -20,7 +20,10 @@ const OUTPUT = resolve(__dirname, '../src/lib/themes/bundled.json'); /** Extensions to download from OpenVSX. */ const EXTENSIONS = [ { namespace: 'GitHub', name: 'github-vscode-theme' }, - { namespace: 'dracula-theme', name: 'theme-dracula' }, +]; + +const PREFERRED_THEME_ORDER = [ + 'GitHub.github-vscode-theme.github-dark-default', ]; /** @@ -121,123 +124,21 @@ async function fetchExtensionThemes(namespace, name) { return themes; } -/** VSCode built-in Dark+ and Light+ (not on OpenVSX). */ -function builtinThemes() { - return [ - { - id: 'builtin.dark-plus', - label: 'Dark+ (default)', - type: 'dark', - swatch: '#1e1e1e', - accent: '#007fd4', - vars: { - '--vscode-editor-background': '#1e1e1e', - '--vscode-editor-foreground': '#cccccc', - '--vscode-sideBar-background': '#252526', - '--vscode-editorWidget-background': '#252526', - '--vscode-descriptionForeground': '#858585', - '--vscode-focusBorder': '#007fd4', - '--vscode-panel-border': '#2b2b2b', - '--vscode-tab-activeBackground': '#1e1e1e', - '--vscode-tab-inactiveBackground': '#2d2d2d', - '--vscode-tab-activeForeground': '#ffffff', - '--vscode-tab-inactiveForeground': '#969696', - '--vscode-list-activeSelectionBackground': '#094771', - '--vscode-list-activeSelectionForeground': '#ffffff', - '--vscode-terminal-background': '#1e1e1e', - '--vscode-terminal-foreground': '#cccccc', - '--vscode-badge-background': '#007acc', - '--vscode-badge-foreground': '#ffffff', - '--vscode-errorForeground': '#f48771', - '--vscode-input-background': '#3c3c3c', - '--vscode-input-border': '#3c3c3c', - '--vscode-button-background': '#0e639c', - '--vscode-button-foreground': '#ffffff', - '--vscode-button-hoverBackground': '#1177bb', - '--vscode-textLink-foreground': '#3794ff', - '--vscode-terminal-ansiBlack': '#000000', - '--vscode-terminal-ansiRed': '#cd3131', - '--vscode-terminal-ansiGreen': '#0dbc79', - '--vscode-terminal-ansiYellow': '#e5e510', - '--vscode-terminal-ansiBlue': '#2472c8', - '--vscode-terminal-ansiMagenta': '#bc3fbc', - '--vscode-terminal-ansiCyan': '#11a8cd', - '--vscode-terminal-ansiWhite': '#e5e5e5', - '--vscode-terminal-ansiBrightBlack': '#666666', - '--vscode-terminal-ansiBrightRed': '#f14c4c', - '--vscode-terminal-ansiBrightGreen': '#23d18b', - '--vscode-terminal-ansiBrightYellow': '#f5f543', - '--vscode-terminal-ansiBrightBlue': '#3b8eea', - '--vscode-terminal-ansiBrightMagenta': '#d670d6', - '--vscode-terminal-ansiBrightCyan': '#29b8db', - '--vscode-terminal-ansiBrightWhite': '#e5e5e5', - '--vscode-terminalCursor-foreground': '#aeafad', - '--vscode-terminal-selectionBackground': '#264f7840', - }, - origin: { kind: 'bundled' }, - }, - { - id: 'builtin.light-plus', - label: 'Light+ (default)', - type: 'light', - swatch: '#ffffff', - accent: '#0090f1', - vars: { - '--vscode-editor-background': '#ffffff', - '--vscode-editor-foreground': '#333333', - '--vscode-sideBar-background': '#f3f3f3', - '--vscode-editorWidget-background': '#f3f3f3', - '--vscode-descriptionForeground': '#717171', - '--vscode-focusBorder': '#0090f1', - '--vscode-panel-border': '#e5e5e5', - '--vscode-tab-activeBackground': '#ffffff', - '--vscode-tab-inactiveBackground': '#ececec', - '--vscode-tab-activeForeground': '#333333', - '--vscode-tab-inactiveForeground': '#8e8e8e', - '--vscode-list-activeSelectionBackground': '#cce6ff', - '--vscode-list-activeSelectionForeground': '#000000', - '--vscode-terminal-background': '#ffffff', - '--vscode-terminal-foreground': '#333333', - '--vscode-badge-background': '#007acc', - '--vscode-badge-foreground': '#ffffff', - '--vscode-errorForeground': '#a1260d', - '--vscode-input-background': '#ffffff', - '--vscode-input-border': '#cecece', - '--vscode-button-background': '#007acc', - '--vscode-button-foreground': '#ffffff', - '--vscode-button-hoverBackground': '#0062a3', - '--vscode-textLink-foreground': '#006ab1', - '--vscode-terminal-ansiBlack': '#000000', - '--vscode-terminal-ansiRed': '#cd3131', - '--vscode-terminal-ansiGreen': '#00bc00', - '--vscode-terminal-ansiYellow': '#949800', - '--vscode-terminal-ansiBlue': '#0451a5', - '--vscode-terminal-ansiMagenta': '#bc05bc', - '--vscode-terminal-ansiCyan': '#0598bc', - '--vscode-terminal-ansiWhite': '#555555', - '--vscode-terminal-ansiBrightBlack': '#666666', - '--vscode-terminal-ansiBrightRed': '#cd3131', - '--vscode-terminal-ansiBrightGreen': '#14ce14', - '--vscode-terminal-ansiBrightYellow': '#b5ba00', - '--vscode-terminal-ansiBrightBlue': '#0451a5', - '--vscode-terminal-ansiBrightMagenta': '#bc05bc', - '--vscode-terminal-ansiBrightCyan': '#0598bc', - '--vscode-terminal-ansiBrightWhite': '#a5a5a5', - '--vscode-terminalCursor-foreground': '#000000', - '--vscode-terminal-selectionBackground': '#add6ff80', - }, - origin: { kind: 'bundled' }, - }, - ]; -} - async function main() { - const allThemes = [...builtinThemes()]; + const allThemes = []; for (const ext of EXTENSIONS) { const themes = await fetchExtensionThemes(ext.namespace, ext.name); allThemes.push(...themes); } + allThemes.sort((a, b) => { + const aIndex = PREFERRED_THEME_ORDER.indexOf(a.id); + const bIndex = PREFERRED_THEME_ORDER.indexOf(b.id); + if (aIndex === -1 && bIndex === -1) return 0; + if (aIndex === -1) return 1; + if (bIndex === -1) return -1; + return aIndex - bIndex; + }); console.log(`\nWriting ${allThemes.length} themes to ${OUTPUT}`); writeFileSync(OUTPUT, JSON.stringify(allThemes, null, 2) + '\n'); diff --git a/lib/src/lib/themes/bundled.json b/lib/src/lib/themes/bundled.json index 4eda66aa..3c5683e2 100644 --- a/lib/src/lib/themes/bundled.json +++ b/lib/src/lib/themes/bundled.json @@ -1,107 +1,51 @@ [ { - "id": "builtin.dark-plus", - "label": "Dark+ (default)", + "id": "GitHub.github-vscode-theme.github-dark-default", + "label": "GitHub Dark Default", "type": "dark", - "swatch": "#1e1e1e", - "accent": "#007fd4", + "swatch": "#0d1117", + "accent": "#1f6feb", "vars": { - "--vscode-editor-background": "#1e1e1e", - "--vscode-editor-foreground": "#cccccc", - "--vscode-sideBar-background": "#252526", - "--vscode-editorWidget-background": "#252526", - "--vscode-descriptionForeground": "#858585", - "--vscode-focusBorder": "#007fd4", - "--vscode-panel-border": "#2b2b2b", - "--vscode-tab-activeBackground": "#1e1e1e", - "--vscode-tab-inactiveBackground": "#2d2d2d", - "--vscode-tab-activeForeground": "#ffffff", - "--vscode-tab-inactiveForeground": "#969696", - "--vscode-list-activeSelectionBackground": "#094771", - "--vscode-list-activeSelectionForeground": "#ffffff", - "--vscode-terminal-background": "#1e1e1e", - "--vscode-terminal-foreground": "#cccccc", - "--vscode-badge-background": "#007acc", - "--vscode-badge-foreground": "#ffffff", - "--vscode-errorForeground": "#f48771", - "--vscode-input-background": "#3c3c3c", - "--vscode-input-border": "#3c3c3c", - "--vscode-button-background": "#0e639c", + "--vscode-focusBorder": "#1f6feb", + "--vscode-descriptionForeground": "#7d8590", + "--vscode-errorForeground": "#f85149", + "--vscode-textLink-foreground": "#2f81f7", + "--vscode-button-background": "#238636", "--vscode-button-foreground": "#ffffff", - "--vscode-button-hoverBackground": "#1177bb", - "--vscode-textLink-foreground": "#3794ff", - "--vscode-terminal-ansiBlack": "#000000", - "--vscode-terminal-ansiRed": "#cd3131", - "--vscode-terminal-ansiGreen": "#0dbc79", - "--vscode-terminal-ansiYellow": "#e5e510", - "--vscode-terminal-ansiBlue": "#2472c8", - "--vscode-terminal-ansiMagenta": "#bc3fbc", - "--vscode-terminal-ansiCyan": "#11a8cd", - "--vscode-terminal-ansiWhite": "#e5e5e5", - "--vscode-terminal-ansiBrightBlack": "#666666", - "--vscode-terminal-ansiBrightRed": "#f14c4c", - "--vscode-terminal-ansiBrightGreen": "#23d18b", - "--vscode-terminal-ansiBrightYellow": "#f5f543", - "--vscode-terminal-ansiBrightBlue": "#3b8eea", - "--vscode-terminal-ansiBrightMagenta": "#d670d6", - "--vscode-terminal-ansiBrightCyan": "#29b8db", - "--vscode-terminal-ansiBrightWhite": "#e5e5e5", - "--vscode-terminalCursor-foreground": "#aeafad", - "--vscode-terminal-selectionBackground": "#264f7840" - }, - "origin": { - "kind": "bundled" - } - }, - { - "id": "builtin.light-plus", - "label": "Light+ (default)", - "type": "light", - "swatch": "#ffffff", - "accent": "#0090f1", - "vars": { - "--vscode-editor-background": "#ffffff", - "--vscode-editor-foreground": "#333333", - "--vscode-sideBar-background": "#f3f3f3", - "--vscode-editorWidget-background": "#f3f3f3", - "--vscode-descriptionForeground": "#717171", - "--vscode-focusBorder": "#0090f1", - "--vscode-panel-border": "#e5e5e5", - "--vscode-tab-activeBackground": "#ffffff", - "--vscode-tab-inactiveBackground": "#ececec", - "--vscode-tab-activeForeground": "#333333", - "--vscode-tab-inactiveForeground": "#8e8e8e", - "--vscode-list-activeSelectionBackground": "#cce6ff", - "--vscode-list-activeSelectionForeground": "#000000", - "--vscode-terminal-background": "#ffffff", - "--vscode-terminal-foreground": "#333333", - "--vscode-badge-background": "#007acc", + "--vscode-button-hoverBackground": "#2ea043", + "--vscode-input-background": "#0d1117", + "--vscode-input-border": "#30363d", "--vscode-badge-foreground": "#ffffff", - "--vscode-errorForeground": "#a1260d", - "--vscode-input-background": "#ffffff", - "--vscode-input-border": "#cecece", - "--vscode-button-background": "#007acc", - "--vscode-button-foreground": "#ffffff", - "--vscode-button-hoverBackground": "#0062a3", - "--vscode-textLink-foreground": "#006ab1", - "--vscode-terminal-ansiBlack": "#000000", - "--vscode-terminal-ansiRed": "#cd3131", - "--vscode-terminal-ansiGreen": "#00bc00", - "--vscode-terminal-ansiYellow": "#949800", - "--vscode-terminal-ansiBlue": "#0451a5", - "--vscode-terminal-ansiMagenta": "#bc05bc", - "--vscode-terminal-ansiCyan": "#0598bc", - "--vscode-terminal-ansiWhite": "#555555", - "--vscode-terminal-ansiBrightBlack": "#666666", - "--vscode-terminal-ansiBrightRed": "#cd3131", - "--vscode-terminal-ansiBrightGreen": "#14ce14", - "--vscode-terminal-ansiBrightYellow": "#b5ba00", - "--vscode-terminal-ansiBrightBlue": "#0451a5", - "--vscode-terminal-ansiBrightMagenta": "#bc05bc", - "--vscode-terminal-ansiBrightCyan": "#0598bc", - "--vscode-terminal-ansiBrightWhite": "#a5a5a5", - "--vscode-terminalCursor-foreground": "#000000", - "--vscode-terminal-selectionBackground": "#add6ff80" + "--vscode-badge-background": "#1f6feb", + "--vscode-sideBar-background": "#010409", + "--vscode-list-activeSelectionForeground": "#e6edf3", + "--vscode-list-activeSelectionBackground": "#6e768166", + "--vscode-editorGroupHeader-tabsBackground": "#010409", + "--vscode-tab-activeForeground": "#e6edf3", + "--vscode-tab-inactiveForeground": "#7d8590", + "--vscode-tab-inactiveBackground": "#010409", + "--vscode-tab-activeBackground": "#0d1117", + "--vscode-editor-foreground": "#e6edf3", + "--vscode-editor-background": "#0d1117", + "--vscode-editorWidget-background": "#161b22", + "--vscode-panel-border": "#30363d", + "--vscode-terminal-foreground": "#e6edf3", + "--vscode-terminal-ansiBlack": "#484f58", + "--vscode-terminal-ansiRed": "#ff7b72", + "--vscode-terminal-ansiGreen": "#3fb950", + "--vscode-terminal-ansiYellow": "#d29922", + "--vscode-terminal-ansiBlue": "#58a6ff", + "--vscode-terminal-ansiMagenta": "#bc8cff", + "--vscode-terminal-ansiCyan": "#39c5cf", + "--vscode-terminal-ansiWhite": "#b1bac4", + "--vscode-terminal-ansiBrightBlack": "#6e7681", + "--vscode-terminal-ansiBrightRed": "#ffa198", + "--vscode-terminal-ansiBrightGreen": "#56d364", + "--vscode-terminal-ansiBrightYellow": "#e3b341", + "--vscode-terminal-ansiBrightBlue": "#79c0ff", + "--vscode-terminal-ansiBrightMagenta": "#d2a8ff", + "--vscode-terminal-ansiBrightCyan": "#56d4dd", + "--vscode-terminal-ansiBrightWhite": "#ffffff" }, "origin": { "kind": "bundled" @@ -263,58 +207,6 @@ "kind": "bundled" } }, - { - "id": "GitHub.github-vscode-theme.github-dark-default", - "label": "GitHub Dark Default", - "type": "dark", - "swatch": "#0d1117", - "accent": "#1f6feb", - "vars": { - "--vscode-focusBorder": "#1f6feb", - "--vscode-descriptionForeground": "#7d8590", - "--vscode-errorForeground": "#f85149", - "--vscode-textLink-foreground": "#2f81f7", - "--vscode-button-background": "#238636", - "--vscode-button-foreground": "#ffffff", - "--vscode-button-hoverBackground": "#2ea043", - "--vscode-input-background": "#0d1117", - "--vscode-input-border": "#30363d", - "--vscode-badge-foreground": "#ffffff", - "--vscode-badge-background": "#1f6feb", - "--vscode-sideBar-background": "#010409", - "--vscode-list-activeSelectionForeground": "#e6edf3", - "--vscode-list-activeSelectionBackground": "#6e768166", - "--vscode-editorGroupHeader-tabsBackground": "#010409", - "--vscode-tab-activeForeground": "#e6edf3", - "--vscode-tab-inactiveForeground": "#7d8590", - "--vscode-tab-inactiveBackground": "#010409", - "--vscode-tab-activeBackground": "#0d1117", - "--vscode-editor-foreground": "#e6edf3", - "--vscode-editor-background": "#0d1117", - "--vscode-editorWidget-background": "#161b22", - "--vscode-panel-border": "#30363d", - "--vscode-terminal-foreground": "#e6edf3", - "--vscode-terminal-ansiBlack": "#484f58", - "--vscode-terminal-ansiRed": "#ff7b72", - "--vscode-terminal-ansiGreen": "#3fb950", - "--vscode-terminal-ansiYellow": "#d29922", - "--vscode-terminal-ansiBlue": "#58a6ff", - "--vscode-terminal-ansiMagenta": "#bc8cff", - "--vscode-terminal-ansiCyan": "#39c5cf", - "--vscode-terminal-ansiWhite": "#b1bac4", - "--vscode-terminal-ansiBrightBlack": "#6e7681", - "--vscode-terminal-ansiBrightRed": "#ffa198", - "--vscode-terminal-ansiBrightGreen": "#56d364", - "--vscode-terminal-ansiBrightYellow": "#e3b341", - "--vscode-terminal-ansiBrightBlue": "#79c0ff", - "--vscode-terminal-ansiBrightMagenta": "#d2a8ff", - "--vscode-terminal-ansiBrightCyan": "#56d4dd", - "--vscode-terminal-ansiBrightWhite": "#ffffff" - }, - "origin": { - "kind": "bundled" - } - }, { "id": "GitHub.github-vscode-theme.github-dark-high-contrast", "label": "GitHub Dark High Contrast", @@ -578,107 +470,5 @@ "origin": { "kind": "bundled" } - }, - { - "id": "dracula-theme.theme-dracula.dracula-theme", - "label": "Dracula Theme", - "type": "dark", - "swatch": "#282A36", - "accent": "#6272A4", - "vars": { - "--vscode-terminal-background": "#282A36", - "--vscode-terminal-foreground": "#F8F8F2", - "--vscode-terminal-ansiBrightBlack": "#6272A4", - "--vscode-terminal-ansiBrightRed": "#FF6E6E", - "--vscode-terminal-ansiBrightGreen": "#69FF94", - "--vscode-terminal-ansiBrightYellow": "#FFFFA5", - "--vscode-terminal-ansiBrightBlue": "#D6ACFF", - "--vscode-terminal-ansiBrightMagenta": "#FF92DF", - "--vscode-terminal-ansiBrightCyan": "#A4FFFF", - "--vscode-terminal-ansiBrightWhite": "#FFFFFF", - "--vscode-terminal-ansiBlack": "#21222C", - "--vscode-terminal-ansiRed": "#FF5555", - "--vscode-terminal-ansiGreen": "#50FA7B", - "--vscode-terminal-ansiYellow": "#F1FA8C", - "--vscode-terminal-ansiBlue": "#BD93F9", - "--vscode-terminal-ansiMagenta": "#FF79C6", - "--vscode-terminal-ansiCyan": "#8BE9FD", - "--vscode-terminal-ansiWhite": "#F8F8F2", - "--vscode-focusBorder": "#6272A4", - "--vscode-errorForeground": "#FF5555", - "--vscode-button-background": "#44475A", - "--vscode-button-foreground": "#F8F8F2", - "--vscode-input-background": "#282A36", - "--vscode-input-border": "#191A21", - "--vscode-badge-foreground": "#F8F8F2", - "--vscode-badge-background": "#44475A", - "--vscode-list-activeSelectionBackground": "#44475A", - "--vscode-list-activeSelectionForeground": "#F8F8F2", - "--vscode-sideBar-background": "#21222C", - "--vscode-editorGroupHeader-tabsBackground": "#191A21", - "--vscode-tab-activeBackground": "#282A36", - "--vscode-tab-activeForeground": "#F8F8F2", - "--vscode-tab-inactiveBackground": "#21222C", - "--vscode-tab-inactiveForeground": "#6272A4", - "--vscode-editor-foreground": "#F8F8F2", - "--vscode-editor-background": "#282A36", - "--vscode-editorWarning-foreground": "#8BE9FD", - "--vscode-editorWidget-background": "#21222C", - "--vscode-panel-border": "#BD93F9" - }, - "origin": { - "kind": "bundled" - } - }, - { - "id": "dracula-theme.theme-dracula.dracula-theme-soft", - "label": "Dracula Theme Soft", - "type": "dark", - "swatch": "#282A36", - "accent": "#7b7f8b", - "vars": { - "--vscode-terminal-background": "#282A36", - "--vscode-terminal-foreground": "#f6f6f4", - "--vscode-terminal-ansiBrightBlack": "#7b7f8b", - "--vscode-terminal-ansiBrightRed": "#f07c7c", - "--vscode-terminal-ansiBrightGreen": "#78f09a", - "--vscode-terminal-ansiBrightYellow": "#f6f6ae", - "--vscode-terminal-ansiBrightBlue": "#d6b4f7", - "--vscode-terminal-ansiBrightMagenta": "#f49dda", - "--vscode-terminal-ansiBrightCyan": "#adf6f6", - "--vscode-terminal-ansiBrightWhite": "#ffffff", - "--vscode-terminal-ansiBlack": "#262626", - "--vscode-terminal-ansiRed": "#ee6666", - "--vscode-terminal-ansiGreen": "#62e884", - "--vscode-terminal-ansiYellow": "#e7ee98", - "--vscode-terminal-ansiBlue": "#bf9eee", - "--vscode-terminal-ansiMagenta": "#f286c4", - "--vscode-terminal-ansiCyan": "#97e1f1", - "--vscode-terminal-ansiWhite": "#f6f6f4", - "--vscode-focusBorder": "#7b7f8b", - "--vscode-errorForeground": "#ee6666", - "--vscode-button-background": "#44475A", - "--vscode-button-foreground": "#f6f6f4", - "--vscode-input-background": "#282A36", - "--vscode-input-border": "#191A21", - "--vscode-badge-foreground": "#f6f6f4", - "--vscode-badge-background": "#44475A", - "--vscode-list-activeSelectionBackground": "#44475A", - "--vscode-list-activeSelectionForeground": "#f6f6f4", - "--vscode-sideBar-background": "#262626", - "--vscode-editorGroupHeader-tabsBackground": "#191A21", - "--vscode-tab-activeBackground": "#282A36", - "--vscode-tab-activeForeground": "#f6f6f4", - "--vscode-tab-inactiveBackground": "#262626", - "--vscode-tab-inactiveForeground": "#7b7f8b", - "--vscode-editor-foreground": "#f6f6f4", - "--vscode-editor-background": "#282A36", - "--vscode-editorWarning-foreground": "#97e1f1", - "--vscode-editorWidget-background": "#262626", - "--vscode-panel-border": "#bf9eee" - }, - "origin": { - "kind": "bundled" - } } ] diff --git a/lib/src/lib/themes/types.ts b/lib/src/lib/themes/types.ts index 37893ac1..7b4511ee 100644 --- a/lib/src/lib/themes/types.ts +++ b/lib/src/lib/themes/types.ts @@ -1,5 +1,5 @@ export interface MouseTermTheme { - /** Stable unique ID, e.g. "GitHub.github-vscode-theme.dark-default" or "builtin.dark-plus" */ + /** Stable unique ID, e.g. "GitHub.github-vscode-theme.github-dark-default" */ id: string; /** Human-readable label from the VSCode theme */ label: string; @@ -21,7 +21,7 @@ export interface BundledOrigin { export interface InstalledOrigin { kind: 'installed'; - /** OpenVSX namespace/name, e.g. "dracula-theme/theme-dracula" */ + /** OpenVSX namespace/name, e.g. "publisher/theme-extension" */ extensionId: string; /** ISO date string */ installedAt: string; diff --git a/lib/src/stories/Pond.stories.tsx b/lib/src/stories/Pond.stories.tsx index 9525d77e..663984b5 100644 --- a/lib/src/stories/Pond.stories.tsx +++ b/lib/src/stories/Pond.stories.tsx @@ -73,13 +73,13 @@ export const MultiPane: Story = { export const MultiPaneDark: Story = { parameters: { fakePty: { scenario: flattenScenario(SCENARIO_LS_OUTPUT) } }, - globals: { theme: 'Dark+' }, + globals: { theme: 'GitHub Dark Default' }, play: splitPanes, }; export const MultiPaneLight: Story = { parameters: { fakePty: { scenario: flattenScenario(SCENARIO_LS_OUTPUT) } }, - globals: { theme: 'Light+' }, + globals: { theme: 'GitHub Light Default' }, play: splitPanes, }; diff --git a/lib/src/theme.css b/lib/src/theme.css index b360774a..306931a4 100644 --- a/lib/src/theme.css +++ b/lib/src/theme.css @@ -21,7 +21,7 @@ --mt-font-family: var(--vscode-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif); } -/* --- Dark mode defaults (VSCode Dark+) --- */ +/* --- Dark mode fallback defaults --- */ @theme { /* Surfaces */ --color-surface: var(--vscode-editor-background, #1e1e1e); @@ -70,7 +70,7 @@ --animate-alarm-dot: alarm-dot 2s ease-in-out infinite; } -/* --- Light mode (VSCode Light+ defaults) --- +/* --- Light mode fallback defaults --- * VSCode adds body.vscode-light for light themes. * applyTheme() also sets this class for imported light themes. */ body.vscode-light { From a0b5bf02d8925593bb59da8a4ae524b204351323 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 16 Apr 2026 11:46:27 -0700 Subject: [PATCH 17/24] No reason to exclude jsdom from the minimumReleaseAge. --- pnpm-workspace.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d9581231..feb5adb3 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,5 +5,3 @@ packages: - vscode-ext - website minimumReleaseAge: 20160 -minimumReleaseAgeExclude: - - jsdom From 26939260c384d28c454f8f9b2c59b6f7ca774219 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 16 Apr 2026 12:02:23 -0700 Subject: [PATCH 18/24] Add solarized/selenized --- lib/scripts/bundle-themes.mjs | 1 + lib/src/lib/themes/bundled.json | 196 ++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) diff --git a/lib/scripts/bundle-themes.mjs b/lib/scripts/bundle-themes.mjs index 0012afec..c060a645 100644 --- a/lib/scripts/bundle-themes.mjs +++ b/lib/scripts/bundle-themes.mjs @@ -20,6 +20,7 @@ const OUTPUT = resolve(__dirname, '../src/lib/themes/bundled.json'); /** Extensions to download from OpenVSX. */ const EXTENSIONS = [ { namespace: 'GitHub', name: 'github-vscode-theme' }, + { namespace: 'santoso-wijaya', name: 'helios-selene' }, ]; const PREFERRED_THEME_ORDER = [ diff --git a/lib/src/lib/themes/bundled.json b/lib/src/lib/themes/bundled.json index 3c5683e2..059a959f 100644 --- a/lib/src/lib/themes/bundled.json +++ b/lib/src/lib/themes/bundled.json @@ -470,5 +470,201 @@ "origin": { "kind": "bundled" } + }, + { + "id": "santoso-wijaya.helios-selene.selenized-light", + "label": "Selenized Light", + "type": "light", + "swatch": "#fef3da", + "accent": "#c75d2099", + "vars": { + "--vscode-focusBorder": "#c75d2099", + "--vscode-errorForeground": "#d4212b", + "--vscode-button-background": "#b38800", + "--vscode-input-background": "#d6cbb4", + "--vscode-input-border": "#8f9894", + "--vscode-badge-background": "#b38800AA", + "--vscode-list-activeSelectionBackground": "#b3880088", + "--vscode-sideBar-background": "#f0e4cc", + "--vscode-editorGroupHeader-tabsBackground": "#d6cbb4", + "--vscode-tab-activeBackground": "#fef3da", + "--vscode-tab-activeForeground": "#384c52", + "--vscode-tab-inactiveForeground": "#52666d", + "--vscode-tab-inactiveBackground": "#d6cbb4", + "--vscode-editor-background": "#fef3da", + "--vscode-editor-foreground": "#384c52", + "--vscode-editorWidget-background": "#f0e4cc", + "--vscode-panel-border": "#8f9894", + "--vscode-terminal-background": "#fef3da", + "--vscode-terminal-foreground": "#384c52", + "--vscode-terminal-selectionBackground": "#f0e4cc", + "--vscode-terminalCursor-foreground": "#52666d", + "--vscode-terminal-ansiBlack": "#f0e4cc", + "--vscode-terminal-ansiRed": "#d4212b", + "--vscode-terminal-ansiGreen": "#539100", + "--vscode-terminal-ansiYellow": "#b38800", + "--vscode-terminal-ansiBlue": "#0073d2", + "--vscode-terminal-ansiMagenta": "#cb4c99", + "--vscode-terminal-ansiCyan": "#009c8f", + "--vscode-terminal-ansiWhite": "#52666d", + "--vscode-terminal-ansiBrightBlack": "#fef3da", + "--vscode-terminal-ansiBrightRed": "#c75d20", + "--vscode-terminal-ansiBrightGreen": "#539100", + "--vscode-terminal-ansiBrightYellow": "#b38800", + "--vscode-terminal-ansiBrightBlue": "#0073d2", + "--vscode-terminal-ansiBrightMagenta": "#7d64c5", + "--vscode-terminal-ansiBrightCyan": "#0073d2", + "--vscode-terminal-ansiBrightWhite": "#384c52" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "santoso-wijaya.helios-selene.selenized-dark", + "label": "Selenized Dark", + "type": "dark", + "swatch": "#053d48", + "accent": "#39c7b999", + "vars": { + "--vscode-focusBorder": "#39c7b999", + "--vscode-errorForeground": "#fd564e", + "--vscode-button-background": "#0096f5", + "--vscode-input-background": "#275b69", + "--vscode-input-border": "#718b90", + "--vscode-badge-background": "#0096f5AA", + "--vscode-list-activeSelectionBackground": "#0096f588", + "--vscode-sideBar-background": "#0e4956", + "--vscode-editorGroupHeader-tabsBackground": "#275b69", + "--vscode-tab-activeBackground": "#053d48", + "--vscode-tab-activeForeground": "#c8d7d8", + "--vscode-tab-inactiveForeground": "#adbcbc", + "--vscode-tab-inactiveBackground": "#275b69", + "--vscode-editor-background": "#053d48", + "--vscode-editor-foreground": "#c8d7d8", + "--vscode-editorWidget-background": "#0e4956", + "--vscode-panel-border": "#718b90", + "--vscode-terminal-background": "#053d48", + "--vscode-terminal-foreground": "#c8d7d8", + "--vscode-terminal-selectionBackground": "#0e4956", + "--vscode-terminalCursor-foreground": "#adbcbc", + "--vscode-terminal-ansiBlack": "#0e4956", + "--vscode-terminal-ansiRed": "#fd564e", + "--vscode-terminal-ansiGreen": "#80b83c", + "--vscode-terminal-ansiYellow": "#e3b230", + "--vscode-terminal-ansiBlue": "#0096f5", + "--vscode-terminal-ansiMagenta": "#f176bd", + "--vscode-terminal-ansiCyan": "#39c7b9", + "--vscode-terminal-ansiWhite": "#adbcbc", + "--vscode-terminal-ansiBrightBlack": "#053d48", + "--vscode-terminal-ansiBrightRed": "#f38649", + "--vscode-terminal-ansiBrightGreen": "#80b83c", + "--vscode-terminal-ansiBrightYellow": "#e3b230", + "--vscode-terminal-ansiBrightBlue": "#0096f5", + "--vscode-terminal-ansiBrightMagenta": "#a58cec", + "--vscode-terminal-ansiBrightCyan": "#0096f5", + "--vscode-terminal-ansiBrightWhite": "#c8d7d8" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "santoso-wijaya.helios-selene.solarized-light", + "label": "Solarized Light", + "type": "light", + "swatch": "#fff6e3", + "accent": "#cf4b1599", + "vars": { + "--vscode-focusBorder": "#cf4b1599", + "--vscode-errorForeground": "#e0332e", + "--vscode-button-background": "#bb8801", + "--vscode-input-background": "#d7cebc", + "--vscode-input-border": "#92a1a1", + "--vscode-badge-background": "#bb8801AA", + "--vscode-list-activeSelectionBackground": "#bb880188", + "--vscode-sideBar-background": "#f0e7d5", + "--vscode-editorGroupHeader-tabsBackground": "#d7cebc", + "--vscode-tab-activeBackground": "#fff6e3", + "--vscode-tab-activeForeground": "#566e76", + "--vscode-tab-inactiveForeground": "#637b82", + "--vscode-tab-inactiveBackground": "#d7cebc", + "--vscode-editor-background": "#fff6e3", + "--vscode-editor-foreground": "#566e76", + "--vscode-editorWidget-background": "#f0e7d5", + "--vscode-panel-border": "#92a1a1", + "--vscode-terminal-background": "#fff6e3", + "--vscode-terminal-foreground": "#566e76", + "--vscode-terminal-selectionBackground": "#f0e7d5", + "--vscode-terminalCursor-foreground": "#637b82", + "--vscode-terminal-ansiBlack": "#f0e7d5", + "--vscode-terminal-ansiRed": "#e0332e", + "--vscode-terminal-ansiGreen": "#8d9800", + "--vscode-terminal-ansiYellow": "#bb8801", + "--vscode-terminal-ansiBlue": "#008dd1", + "--vscode-terminal-ansiMagenta": "#f2579c", + "--vscode-terminal-ansiCyan": "#1fa198", + "--vscode-terminal-ansiWhite": "#637b82", + "--vscode-terminal-ansiBrightBlack": "#fff6e3", + "--vscode-terminal-ansiBrightRed": "#cf4b15", + "--vscode-terminal-ansiBrightGreen": "#8d9800", + "--vscode-terminal-ansiBrightYellow": "#bb8801", + "--vscode-terminal-ansiBrightBlue": "#008dd1", + "--vscode-terminal-ansiBrightMagenta": "#5c73c4", + "--vscode-terminal-ansiBrightCyan": "#008dd1", + "--vscode-terminal-ansiBrightWhite": "#566e76" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "santoso-wijaya.helios-selene.solarized-dark", + "label": "Solarized Dark", + "type": "dark", + "swatch": "#002b36", + "accent": "#1fa19899", + "vars": { + "--vscode-focusBorder": "#1fa19899", + "--vscode-errorForeground": "#e0332e", + "--vscode-button-background": "#008dd1", + "--vscode-input-background": "#164854", + "--vscode-input-border": "#566e76", + "--vscode-badge-background": "#008dd1AA", + "--vscode-list-activeSelectionBackground": "#008dd188", + "--vscode-sideBar-background": "#003641", + "--vscode-editorGroupHeader-tabsBackground": "#164854", + "--vscode-tab-activeBackground": "#002b36", + "--vscode-tab-activeForeground": "#92a1a1", + "--vscode-tab-inactiveForeground": "#829496", + "--vscode-tab-inactiveBackground": "#164854", + "--vscode-editor-background": "#002b36", + "--vscode-editor-foreground": "#92a1a1", + "--vscode-editorWidget-background": "#003641", + "--vscode-panel-border": "#566e76", + "--vscode-terminal-background": "#002b36", + "--vscode-terminal-foreground": "#92a1a1", + "--vscode-terminal-selectionBackground": "#003641", + "--vscode-terminalCursor-foreground": "#829496", + "--vscode-terminal-ansiBlack": "#003641", + "--vscode-terminal-ansiRed": "#e0332e", + "--vscode-terminal-ansiGreen": "#8d9800", + "--vscode-terminal-ansiYellow": "#bb8801", + "--vscode-terminal-ansiBlue": "#008dd1", + "--vscode-terminal-ansiMagenta": "#f2579c", + "--vscode-terminal-ansiCyan": "#1fa198", + "--vscode-terminal-ansiWhite": "#829496", + "--vscode-terminal-ansiBrightBlack": "#002b36", + "--vscode-terminal-ansiBrightRed": "#cf4b15", + "--vscode-terminal-ansiBrightGreen": "#8d9800", + "--vscode-terminal-ansiBrightYellow": "#bb8801", + "--vscode-terminal-ansiBrightBlue": "#008dd1", + "--vscode-terminal-ansiBrightMagenta": "#5c73c4", + "--vscode-terminal-ansiBrightCyan": "#008dd1", + "--vscode-terminal-ansiBrightWhite": "#92a1a1" + }, + "origin": { + "kind": "bundled" + } } ] From 85cf56bf57b9c170d49aca1d8070dc7d70cd444b Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 16 Apr 2026 12:23:22 -0700 Subject: [PATCH 19/24] Bundle gruvbox and iceberg. --- lib/scripts/bundle-themes.mjs | 2 + lib/src/lib/themes/bundled.json | 422 ++++++++++++++++++++++++++++++++ standalone/src-tauri/Cargo.lock | 2 +- 3 files changed, 425 insertions(+), 1 deletion(-) diff --git a/lib/scripts/bundle-themes.mjs b/lib/scripts/bundle-themes.mjs index c060a645..5902ed21 100644 --- a/lib/scripts/bundle-themes.mjs +++ b/lib/scripts/bundle-themes.mjs @@ -21,6 +21,8 @@ const OUTPUT = resolve(__dirname, '../src/lib/themes/bundled.json'); const EXTENSIONS = [ { namespace: 'GitHub', name: 'github-vscode-theme' }, { namespace: 'santoso-wijaya', name: 'helios-selene' }, + { namespace: 'jdinhlife', name: 'gruvbox' }, + { namespace: 'cocopon', name: 'iceberg-theme' }, ]; const PREFERRED_THEME_ORDER = [ diff --git a/lib/src/lib/themes/bundled.json b/lib/src/lib/themes/bundled.json index 059a959f..110f5adc 100644 --- a/lib/src/lib/themes/bundled.json +++ b/lib/src/lib/themes/bundled.json @@ -666,5 +666,427 @@ "origin": { "kind": "bundled" } + }, + { + "id": "jdinhlife.gruvbox.gruvbox-dark-medium", + "label": "Gruvbox Dark Medium", + "type": "dark", + "swatch": "#282828", + "accent": "#3c3836", + "vars": { + "--vscode-focusBorder": "#3c3836", + "--vscode-errorForeground": "#fb4934", + "--vscode-button-background": "#45858880", + "--vscode-button-foreground": "#ebdbb2", + "--vscode-button-hoverBackground": "#45858860", + "--vscode-input-background": "#282828", + "--vscode-input-border": "#3c3836", + "--vscode-badge-background": "#b16286", + "--vscode-badge-foreground": "#ebdbb2", + "--vscode-list-activeSelectionBackground": "#3c383680", + "--vscode-list-activeSelectionForeground": "#8ec07c", + "--vscode-sideBar-background": "#282828", + "--vscode-editorGroupHeader-tabsBackground": "#282828", + "--vscode-tab-activeBackground": "#3c3836", + "--vscode-tab-activeForeground": "#ebdbb2", + "--vscode-tab-inactiveForeground": "#a89984", + "--vscode-tab-inactiveBackground": "#282828", + "--vscode-editor-background": "#282828", + "--vscode-editor-foreground": "#ebdbb2", + "--vscode-editorWarning-foreground": "#d79921", + "--vscode-editorWidget-background": "#282828", + "--vscode-panel-border": "#3c3836", + "--vscode-terminal-ansiBlack": "#3c3836", + "--vscode-terminal-ansiBrightBlack": "#928374", + "--vscode-terminal-ansiRed": "#cc241d", + "--vscode-terminal-ansiBrightRed": "#fb4934", + "--vscode-terminal-ansiGreen": "#98971a", + "--vscode-terminal-ansiBrightGreen": "#b8bb26", + "--vscode-terminal-ansiYellow": "#d79921", + "--vscode-terminal-ansiBrightYellow": "#fabd2f", + "--vscode-terminal-ansiBlue": "#458588", + "--vscode-terminal-ansiBrightBlue": "#83a598", + "--vscode-terminal-ansiMagenta": "#b16286", + "--vscode-terminal-ansiBrightMagenta": "#d3869b", + "--vscode-terminal-ansiCyan": "#689d6a", + "--vscode-terminal-ansiBrightCyan": "#8ec07c", + "--vscode-terminal-ansiWhite": "#a89984", + "--vscode-terminal-ansiBrightWhite": "#ebdbb2", + "--vscode-terminal-foreground": "#ebdbb2", + "--vscode-terminal-background": "#282828", + "--vscode-textLink-foreground": "#83a598" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "jdinhlife.gruvbox.gruvbox-dark-hard", + "label": "Gruvbox Dark Hard", + "type": "dark", + "swatch": "#1d2021", + "accent": "#3c3836", + "vars": { + "--vscode-focusBorder": "#3c3836", + "--vscode-errorForeground": "#fb4934", + "--vscode-button-background": "#45858880", + "--vscode-button-foreground": "#ebdbb2", + "--vscode-button-hoverBackground": "#45858860", + "--vscode-input-background": "#1d2021", + "--vscode-input-border": "#3c3836", + "--vscode-badge-background": "#b16286", + "--vscode-badge-foreground": "#ebdbb2", + "--vscode-list-activeSelectionBackground": "#3c383680", + "--vscode-list-activeSelectionForeground": "#8ec07c", + "--vscode-sideBar-background": "#1d2021", + "--vscode-editorGroupHeader-tabsBackground": "#1d2021", + "--vscode-tab-activeBackground": "#3c3836", + "--vscode-tab-activeForeground": "#ebdbb2", + "--vscode-tab-inactiveForeground": "#a89984", + "--vscode-tab-inactiveBackground": "#1d2021", + "--vscode-editor-background": "#1d2021", + "--vscode-editor-foreground": "#ebdbb2", + "--vscode-editorWarning-foreground": "#d79921", + "--vscode-editorWidget-background": "#1d2021", + "--vscode-panel-border": "#3c3836", + "--vscode-terminal-ansiBlack": "#3c3836", + "--vscode-terminal-ansiBrightBlack": "#928374", + "--vscode-terminal-ansiRed": "#cc241d", + "--vscode-terminal-ansiBrightRed": "#fb4934", + "--vscode-terminal-ansiGreen": "#98971a", + "--vscode-terminal-ansiBrightGreen": "#b8bb26", + "--vscode-terminal-ansiYellow": "#d79921", + "--vscode-terminal-ansiBrightYellow": "#fabd2f", + "--vscode-terminal-ansiBlue": "#458588", + "--vscode-terminal-ansiBrightBlue": "#83a598", + "--vscode-terminal-ansiMagenta": "#b16286", + "--vscode-terminal-ansiBrightMagenta": "#d3869b", + "--vscode-terminal-ansiCyan": "#689d6a", + "--vscode-terminal-ansiBrightCyan": "#8ec07c", + "--vscode-terminal-ansiWhite": "#a89984", + "--vscode-terminal-ansiBrightWhite": "#ebdbb2", + "--vscode-terminal-foreground": "#ebdbb2", + "--vscode-terminal-background": "#1d2021", + "--vscode-textLink-foreground": "#83a598" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "jdinhlife.gruvbox.gruvbox-dark-soft", + "label": "Gruvbox Dark Soft", + "type": "dark", + "swatch": "#32302f", + "accent": "#3c3836", + "vars": { + "--vscode-focusBorder": "#3c3836", + "--vscode-errorForeground": "#fb4934", + "--vscode-button-background": "#45858880", + "--vscode-button-foreground": "#ebdbb2", + "--vscode-button-hoverBackground": "#45858860", + "--vscode-input-background": "#32302f", + "--vscode-input-border": "#3c3836", + "--vscode-badge-background": "#b16286", + "--vscode-badge-foreground": "#ebdbb2", + "--vscode-list-activeSelectionBackground": "#3c383680", + "--vscode-list-activeSelectionForeground": "#8ec07c", + "--vscode-sideBar-background": "#32302f", + "--vscode-editorGroupHeader-tabsBackground": "#32302f", + "--vscode-tab-activeBackground": "#3c3836", + "--vscode-tab-activeForeground": "#ebdbb2", + "--vscode-tab-inactiveForeground": "#a89984", + "--vscode-tab-inactiveBackground": "#32302f", + "--vscode-editor-background": "#32302f", + "--vscode-editor-foreground": "#ebdbb2", + "--vscode-editorWarning-foreground": "#d79921", + "--vscode-editorWidget-background": "#32302f", + "--vscode-panel-border": "#3c3836", + "--vscode-terminal-ansiBlack": "#3c3836", + "--vscode-terminal-ansiBrightBlack": "#928374", + "--vscode-terminal-ansiRed": "#cc241d", + "--vscode-terminal-ansiBrightRed": "#fb4934", + "--vscode-terminal-ansiGreen": "#98971a", + "--vscode-terminal-ansiBrightGreen": "#b8bb26", + "--vscode-terminal-ansiYellow": "#d79921", + "--vscode-terminal-ansiBrightYellow": "#fabd2f", + "--vscode-terminal-ansiBlue": "#458588", + "--vscode-terminal-ansiBrightBlue": "#83a598", + "--vscode-terminal-ansiMagenta": "#b16286", + "--vscode-terminal-ansiBrightMagenta": "#d3869b", + "--vscode-terminal-ansiCyan": "#689d6a", + "--vscode-terminal-ansiBrightCyan": "#8ec07c", + "--vscode-terminal-ansiWhite": "#a89984", + "--vscode-terminal-ansiBrightWhite": "#ebdbb2", + "--vscode-terminal-foreground": "#ebdbb2", + "--vscode-terminal-background": "#32302f", + "--vscode-textLink-foreground": "#83a598" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "jdinhlife.gruvbox.gruvbox-light-medium", + "label": "Gruvbox Light Medium", + "type": "light", + "swatch": "#fbf1c7", + "accent": "#ebdbb2", + "vars": { + "--vscode-focusBorder": "#ebdbb2", + "--vscode-errorForeground": "#9d0006", + "--vscode-button-background": "#45858880", + "--vscode-button-foreground": "#3c3836", + "--vscode-button-hoverBackground": "#45858860", + "--vscode-input-background": "#fbf1c7", + "--vscode-input-border": "#ebdbb2", + "--vscode-badge-background": "#b16286", + "--vscode-badge-foreground": "#ebdbb2", + "--vscode-list-activeSelectionBackground": "#ebdbb280", + "--vscode-list-activeSelectionForeground": "#427b58", + "--vscode-sideBar-background": "#fbf1c7", + "--vscode-editorGroupHeader-tabsBackground": "#fbf1c7", + "--vscode-tab-activeBackground": "#ebdbb2", + "--vscode-tab-activeForeground": "#3c3836", + "--vscode-tab-inactiveForeground": "#7c6f64", + "--vscode-tab-inactiveBackground": "#fbf1c7", + "--vscode-editor-background": "#fbf1c7", + "--vscode-editor-foreground": "#3c3836", + "--vscode-editorWarning-foreground": "#d79921", + "--vscode-editorWidget-background": "#fbf1c7", + "--vscode-panel-border": "#ebdbb2", + "--vscode-terminal-ansiBlack": "#ebdbb2", + "--vscode-terminal-ansiBrightBlack": "#928374", + "--vscode-terminal-ansiRed": "#cc241d", + "--vscode-terminal-ansiBrightRed": "#9d0006", + "--vscode-terminal-ansiGreen": "#98971a", + "--vscode-terminal-ansiBrightGreen": "#79740e", + "--vscode-terminal-ansiYellow": "#d79921", + "--vscode-terminal-ansiBrightYellow": "#b57614", + "--vscode-terminal-ansiBlue": "#458588", + "--vscode-terminal-ansiBrightBlue": "#076678", + "--vscode-terminal-ansiMagenta": "#b16286", + "--vscode-terminal-ansiBrightMagenta": "#8f3f71", + "--vscode-terminal-ansiCyan": "#689d6a", + "--vscode-terminal-ansiBrightCyan": "#427b58", + "--vscode-terminal-ansiWhite": "#7c6f64", + "--vscode-terminal-ansiBrightWhite": "#3c3836", + "--vscode-terminal-foreground": "#3c3836", + "--vscode-terminal-background": "#fbf1c7", + "--vscode-textLink-foreground": "#076678" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "jdinhlife.gruvbox.gruvbox-light-hard", + "label": "Gruvbox Light Hard", + "type": "light", + "swatch": "#f9f5d7", + "accent": "#ebdbb2", + "vars": { + "--vscode-focusBorder": "#ebdbb2", + "--vscode-errorForeground": "#9d0006", + "--vscode-button-background": "#45858880", + "--vscode-button-foreground": "#3c3836", + "--vscode-button-hoverBackground": "#45858860", + "--vscode-input-background": "#f9f5d7", + "--vscode-input-border": "#ebdbb2", + "--vscode-badge-background": "#b16286", + "--vscode-badge-foreground": "#ebdbb2", + "--vscode-list-activeSelectionBackground": "#ebdbb280", + "--vscode-list-activeSelectionForeground": "#427b58", + "--vscode-sideBar-background": "#f9f5d7", + "--vscode-editorGroupHeader-tabsBackground": "#f9f5d7", + "--vscode-tab-activeBackground": "#ebdbb2", + "--vscode-tab-activeForeground": "#3c3836", + "--vscode-tab-inactiveForeground": "#7c6f64", + "--vscode-tab-inactiveBackground": "#f9f5d7", + "--vscode-editor-background": "#f9f5d7", + "--vscode-editor-foreground": "#3c3836", + "--vscode-editorWarning-foreground": "#d79921", + "--vscode-editorWidget-background": "#f9f5d7", + "--vscode-panel-border": "#ebdbb2", + "--vscode-terminal-ansiBlack": "#ebdbb2", + "--vscode-terminal-ansiBrightBlack": "#928374", + "--vscode-terminal-ansiRed": "#cc241d", + "--vscode-terminal-ansiBrightRed": "#9d0006", + "--vscode-terminal-ansiGreen": "#98971a", + "--vscode-terminal-ansiBrightGreen": "#79740e", + "--vscode-terminal-ansiYellow": "#d79921", + "--vscode-terminal-ansiBrightYellow": "#b57614", + "--vscode-terminal-ansiBlue": "#458588", + "--vscode-terminal-ansiBrightBlue": "#076678", + "--vscode-terminal-ansiMagenta": "#b16286", + "--vscode-terminal-ansiBrightMagenta": "#8f3f71", + "--vscode-terminal-ansiCyan": "#689d6a", + "--vscode-terminal-ansiBrightCyan": "#427b58", + "--vscode-terminal-ansiWhite": "#7c6f64", + "--vscode-terminal-ansiBrightWhite": "#3c3836", + "--vscode-terminal-foreground": "#3c3836", + "--vscode-terminal-background": "#f9f5d7", + "--vscode-textLink-foreground": "#076678" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "jdinhlife.gruvbox.gruvbox-light-soft", + "label": "Gruvbox Light Soft", + "type": "light", + "swatch": "#f2e5bc", + "accent": "#ebdbb2", + "vars": { + "--vscode-focusBorder": "#ebdbb2", + "--vscode-errorForeground": "#9d0006", + "--vscode-button-background": "#45858880", + "--vscode-button-foreground": "#3c3836", + "--vscode-button-hoverBackground": "#45858860", + "--vscode-input-background": "#f2e5bc", + "--vscode-input-border": "#ebdbb2", + "--vscode-badge-background": "#b16286", + "--vscode-badge-foreground": "#ebdbb2", + "--vscode-list-activeSelectionBackground": "#ebdbb280", + "--vscode-list-activeSelectionForeground": "#427b58", + "--vscode-sideBar-background": "#f2e5bc", + "--vscode-editorGroupHeader-tabsBackground": "#f2e5bc", + "--vscode-tab-activeBackground": "#ebdbb2", + "--vscode-tab-activeForeground": "#3c3836", + "--vscode-tab-inactiveForeground": "#7c6f64", + "--vscode-tab-inactiveBackground": "#f2e5bc", + "--vscode-editor-background": "#f2e5bc", + "--vscode-editor-foreground": "#3c3836", + "--vscode-editorWarning-foreground": "#d79921", + "--vscode-editorWidget-background": "#f2e5bc", + "--vscode-panel-border": "#ebdbb2", + "--vscode-terminal-ansiBlack": "#ebdbb2", + "--vscode-terminal-ansiBrightBlack": "#928374", + "--vscode-terminal-ansiRed": "#cc241d", + "--vscode-terminal-ansiBrightRed": "#9d0006", + "--vscode-terminal-ansiGreen": "#98971a", + "--vscode-terminal-ansiBrightGreen": "#79740e", + "--vscode-terminal-ansiYellow": "#d79921", + "--vscode-terminal-ansiBrightYellow": "#b57614", + "--vscode-terminal-ansiBlue": "#458588", + "--vscode-terminal-ansiBrightBlue": "#076678", + "--vscode-terminal-ansiMagenta": "#b16286", + "--vscode-terminal-ansiBrightMagenta": "#8f3f71", + "--vscode-terminal-ansiCyan": "#689d6a", + "--vscode-terminal-ansiBrightCyan": "#427b58", + "--vscode-terminal-ansiWhite": "#7c6f64", + "--vscode-terminal-ansiBrightWhite": "#3c3836", + "--vscode-terminal-foreground": "#3c3836", + "--vscode-terminal-background": "#f2e5bc", + "--vscode-textLink-foreground": "#076678" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "cocopon.iceberg-theme.iceberg", + "label": "Iceberg", + "type": "dark", + "swatch": "#161821", + "accent": "#242940", + "vars": { + "--vscode-badge-background": "#6b708920", + "--vscode-badge-foreground": "#6b7089", + "--vscode-button-background": "#c6c8d1", + "--vscode-button-foreground": "#161821", + "--vscode-button-hoverBackground": "#d2d4de", + "--vscode-descriptionForeground": "#6b7089", + "--vscode-editor-background": "#161821", + "--vscode-editor-foreground": "#c6c8d1", + "--vscode-editorGroupHeader-tabsBackground": "#0e1015", + "--vscode-editorWidget-background": "#1e2132", + "--vscode-editorWarning-foreground": "#e2a478", + "--vscode-focusBorder": "#242940", + "--vscode-input-background": "#0f1117", + "--vscode-list-activeSelectionBackground": "#1e2132", + "--vscode-list-activeSelectionForeground": "#c6c8d1", + "--vscode-panel-border": "#0e1015", + "--vscode-sideBar-background": "#161821", + "--vscode-tab-activeBackground": "#161821", + "--vscode-tab-activeForeground": "#c6c8d1", + "--vscode-tab-inactiveBackground": "#0e1015", + "--vscode-tab-inactiveForeground": "#6b7089", + "--vscode-terminal-ansiBlack": "#1e2132", + "--vscode-terminal-ansiBlue": "#84a0c6", + "--vscode-terminal-ansiBrightBlack": "#6b7089", + "--vscode-terminal-ansiBrightBlue": "#91acd1", + "--vscode-terminal-ansiBrightCyan": "#95c4ce", + "--vscode-terminal-ansiBrightGreen": "#c0ca8e", + "--vscode-terminal-ansiBrightMagenta": "#ada0d3", + "--vscode-terminal-ansiBrightRed": "#e98989", + "--vscode-terminal-ansiBrightWhite": "#d2d4de", + "--vscode-terminal-ansiBrightYellow": "#e9b189", + "--vscode-terminal-ansiCyan": "#89b8c2", + "--vscode-terminal-ansiGreen": "#b4be82", + "--vscode-terminal-ansiMagenta": "#a093c7", + "--vscode-terminal-ansiRed": "#e27878", + "--vscode-terminal-ansiWhite": "#c6c8d1", + "--vscode-terminal-ansiYellow": "#e2a478", + "--vscode-terminal-foreground": "#c6c8d1", + "--vscode-terminal-selectionBackground": "#4a548266", + "--vscode-textLink-foreground": "#84a0c6" + }, + "origin": { + "kind": "bundled" + } + }, + { + "id": "cocopon.iceberg-theme.iceberg-light", + "label": "Iceberg Light", + "type": "light", + "swatch": "#e8e9ec", + "accent": "#cbcfda", + "vars": { + "--vscode-badge-background": "#8389a320", + "--vscode-badge-foreground": "#8389a3", + "--vscode-button-background": "#33374c", + "--vscode-button-foreground": "#e8e9ec", + "--vscode-button-hoverBackground": "#262a3f", + "--vscode-descriptionForeground": "#8389a3", + "--vscode-editor-background": "#e8e9ec", + "--vscode-editor-foreground": "#33374c", + "--vscode-editorGroupHeader-tabsBackground": "#c8cfdd", + "--vscode-editorWidget-background": "#dcdfe7", + "--vscode-editorWarning-foreground": "#c57339", + "--vscode-focusBorder": "#cbcfda", + "--vscode-input-background": "#cad0de", + "--vscode-list-activeSelectionBackground": "#dcdfe7", + "--vscode-list-activeSelectionForeground": "#33374c", + "--vscode-panel-border": "#c8cfdd", + "--vscode-sideBar-background": "#e8e9ec", + "--vscode-tab-activeBackground": "#e8e9ec", + "--vscode-tab-activeForeground": "#33374c", + "--vscode-tab-inactiveBackground": "#c8cfdd", + "--vscode-tab-inactiveForeground": "#8389a3", + "--vscode-terminal-ansiBlack": "#dcdfe7", + "--vscode-terminal-ansiBlue": "#2d539e", + "--vscode-terminal-ansiBrightBlack": "#8389a3", + "--vscode-terminal-ansiBrightBlue": "#22478e", + "--vscode-terminal-ansiBrightCyan": "#327698", + "--vscode-terminal-ansiBrightGreen": "#598030", + "--vscode-terminal-ansiBrightMagenta": "#6845ad", + "--vscode-terminal-ansiBrightRed": "#cc3768", + "--vscode-terminal-ansiBrightWhite": "#262a3f", + "--vscode-terminal-ansiBrightYellow": "#b6662d", + "--vscode-terminal-ansiCyan": "#3f83a6", + "--vscode-terminal-ansiGreen": "#668e3d", + "--vscode-terminal-ansiMagenta": "#7759b4", + "--vscode-terminal-ansiRed": "#cc517a", + "--vscode-terminal-ansiWhite": "#33374c", + "--vscode-terminal-ansiYellow": "#c57339", + "--vscode-terminal-foreground": "#33374c", + "--vscode-terminal-selectionBackground": "#aeb2c666", + "--vscode-textLink-foreground": "#2d539e" + }, + "origin": { + "kind": "bundled" + } } ] diff --git a/standalone/src-tauri/Cargo.lock b/standalone/src-tauri/Cargo.lock index 74c181a6..35fcf0c2 100644 --- a/standalone/src-tauri/Cargo.lock +++ b/standalone/src-tauri/Cargo.lock @@ -1938,7 +1938,7 @@ dependencies = [ [[package]] name = "mouseterm" -version = "0.1.0" +version = "0.6.2" dependencies = [ "libc", "serde", From c2266ff60a6d118b9b79e39e80c23cd81f05e4c5 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 16 Apr 2026 12:28:33 -0700 Subject: [PATCH 20/24] Add the bundled themes into our dependencies.json --- lib/scripts/bundle-themes.mjs | 19 ++++++++++++-- lib/src/lib/themes/bundled-extensions.json | 30 ++++++++++++++++++++++ website/scripts/generate-deps.js | 7 ++++- website/src/data/dependencies.json | 28 ++++++++++++++++++++ 4 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 lib/src/lib/themes/bundled-extensions.json diff --git a/lib/scripts/bundle-themes.mjs b/lib/scripts/bundle-themes.mjs index 5902ed21..7fd80151 100644 --- a/lib/scripts/bundle-themes.mjs +++ b/lib/scripts/bundle-themes.mjs @@ -16,6 +16,7 @@ import { parse as parseJsonc } from 'jsonc-parser'; const __dirname = dirname(fileURLToPath(import.meta.url)); const OUTPUT = resolve(__dirname, '../src/lib/themes/bundled.json'); +const OUTPUT_EXTENSIONS = resolve(__dirname, '../src/lib/themes/bundled-extensions.json'); /** Extensions to download from OpenVSX. */ const EXTENSIONS = [ @@ -124,15 +125,26 @@ async function fetchExtensionThemes(namespace, name) { } console.log(` Found ${themes.length} theme(s).`); - return themes; + return { + themes, + extension: { + name: meta.displayName ?? `${namespace}/${name}`, + version: meta.version, + license: meta.license ?? null, + author: meta.publishedBy?.loginName ?? null, + homepage: meta.homepage ?? meta.repository ?? null, + }, + }; } async function main() { const allThemes = []; + const allExtensions = []; for (const ext of EXTENSIONS) { - const themes = await fetchExtensionThemes(ext.namespace, ext.name); + const { themes, extension } = await fetchExtensionThemes(ext.namespace, ext.name); allThemes.push(...themes); + allExtensions.push(extension); } allThemes.sort((a, b) => { const aIndex = PREFERRED_THEME_ORDER.indexOf(a.id); @@ -145,6 +157,9 @@ async function main() { console.log(`\nWriting ${allThemes.length} themes to ${OUTPUT}`); writeFileSync(OUTPUT, JSON.stringify(allThemes, null, 2) + '\n'); + + console.log(`Writing ${allExtensions.length} extensions to ${OUTPUT_EXTENSIONS}`); + writeFileSync(OUTPUT_EXTENSIONS, JSON.stringify(allExtensions, null, 2) + '\n'); console.log('Done.'); } diff --git a/lib/src/lib/themes/bundled-extensions.json b/lib/src/lib/themes/bundled-extensions.json new file mode 100644 index 00000000..af3ffc6a --- /dev/null +++ b/lib/src/lib/themes/bundled-extensions.json @@ -0,0 +1,30 @@ +[ + { + "name": "GitHub Theme", + "version": "6.3.5", + "license": "MIT", + "author": "open-vsx", + "homepage": "https://github.com/primer/github-vscode-theme#readme" + }, + { + "name": "Solarized & Selenized", + "version": "0.3.18", + "license": null, + "author": "santoso-wijaya", + "homepage": "https://github.com/santoso-wijaya/vscode-helios-selene" + }, + { + "name": "Gruvbox Theme", + "version": "1.29.1", + "license": "MIT", + "author": "jdinhify", + "homepage": "https://github.com/jdinhify/vscode-theme-gruvbox" + }, + { + "name": "Iceberg Theme", + "version": "2.0.5", + "license": "MIT", + "author": "cocopon", + "homepage": "https://cocopon.github.io/iceberg.vim/" + } +] diff --git a/website/scripts/generate-deps.js b/website/scripts/generate-deps.js index 8dac328a..f6d5d489 100644 --- a/website/scripts/generate-deps.js +++ b/website/scripts/generate-deps.js @@ -1,11 +1,12 @@ import { execSync } from "node:child_process"; -import { writeFileSync } from "node:fs"; +import { readFileSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const repoRoot = resolve(__dirname, "../.."); const outPath = resolve(__dirname, "../src/data/dependencies.json"); +const themeExtensionsPath = resolve(repoRoot, "lib/src/lib/themes/bundled-extensions.json"); const raw = JSON.parse( execSync("pnpm licenses list --prod --json", { cwd: repoRoot, encoding: "utf-8" }) @@ -24,6 +25,10 @@ for (const [license, packages] of Object.entries(raw)) { } } +// Merge in bundled theme extensions from OpenVSX +const themeExtensions = JSON.parse(readFileSync(themeExtensionsPath, "utf-8")); +deps.push(...themeExtensions); + deps.sort((a, b) => a.name.localeCompare(b.name)); writeFileSync(outPath, JSON.stringify(deps, null, 2) + "\n"); diff --git a/website/src/data/dependencies.json b/website/src/data/dependencies.json index c2462d60..6e31d88f 100644 --- a/website/src/data/dependencies.json +++ b/website/src/data/dependencies.json @@ -83,6 +83,27 @@ "author": "Arjun Barrett", "homepage": "https://101arrowz.github.io/fflate" }, + { + "name": "GitHub Theme", + "version": "6.3.5", + "license": "MIT", + "author": "open-vsx", + "homepage": "https://github.com/primer/github-vscode-theme#readme" + }, + { + "name": "Gruvbox Theme", + "version": "1.29.1", + "license": "MIT", + "author": "jdinhify", + "homepage": "https://github.com/jdinhify/vscode-theme-gruvbox" + }, + { + "name": "Iceberg Theme", + "version": "2.0.5", + "license": "MIT", + "author": "cocopon", + "homepage": "https://cocopon.github.io/iceberg.vim/" + }, { "name": "jsonc-parser", "version": "3.3.1", @@ -139,6 +160,13 @@ "author": null, "homepage": "https://react.dev/" }, + { + "name": "Solarized & Selenized", + "version": "0.3.18", + "license": null, + "author": "santoso-wijaya", + "homepage": "https://github.com/santoso-wijaya/vscode-helios-selene" + }, { "name": "tailwind-merge", "version": "3.5.0", From 041e77842738e46b7aaf1d492d07cf416a0e8cfb Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 16 Apr 2026 12:32:04 -0700 Subject: [PATCH 21/24] Remove unnecessary TODO.md --- docs/specs/TODO.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 docs/specs/TODO.md diff --git a/docs/specs/TODO.md b/docs/specs/TODO.md deleted file mode 100644 index 9fe4ca01..00000000 --- a/docs/specs/TODO.md +++ /dev/null @@ -1,5 +0,0 @@ -- [ ] fix headerbar color and size / font size -- [ ] fix selection size / glow -- [ ] desaturate selection on blur -- [ ] alarm right-click popup and TODO/soft-TODO popup -- [ ] dynamic shrinking on soft-TODO during typing \ No newline at end of file From ca78cf966123d9e3350565a7f08d7d10f910bed9 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 16 Apr 2026 12:33:25 -0700 Subject: [PATCH 22/24] No missing licenses. --- website/scripts/generate-deps.js | 15 +++++++++++++++ website/src/data/dependencies.json | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/website/scripts/generate-deps.js b/website/scripts/generate-deps.js index f6d5d489..8f507449 100644 --- a/website/scripts/generate-deps.js +++ b/website/scripts/generate-deps.js @@ -29,6 +29,21 @@ for (const [license, packages] of Object.entries(raw)) { const themeExtensions = JSON.parse(readFileSync(themeExtensionsPath, "utf-8")); deps.push(...themeExtensions); +// Manual overrides for extensions that don't declare a license in their metadata +const missingLicense = { + "Solarized & Selenized": "MIT", +}; +for (const dep of deps) { + if (!dep.license) { + const override = missingLicense[dep.name]; + if (!override) { + console.error(`ERROR: "${dep.name}" has no license. Add it to missingLicense in generate-deps.js`); + process.exit(1); + } + dep.license = override; + } +} + deps.sort((a, b) => a.name.localeCompare(b.name)); writeFileSync(outPath, JSON.stringify(deps, null, 2) + "\n"); diff --git a/website/src/data/dependencies.json b/website/src/data/dependencies.json index 6e31d88f..eea01380 100644 --- a/website/src/data/dependencies.json +++ b/website/src/data/dependencies.json @@ -163,7 +163,7 @@ { "name": "Solarized & Selenized", "version": "0.3.18", - "license": null, + "license": "MIT", "author": "santoso-wijaya", "homepage": "https://github.com/santoso-wijaya/vscode-helios-selene" }, From ea9584454beddceede2e2aad27cd75db0795d4b4 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 16 Apr 2026 12:57:47 -0700 Subject: [PATCH 23/24] Make sure none of the authors are null. --- website/scripts/generate-deps.js | 21 ++++++++++++++++++++- website/src/data/dependencies.json | 18 +++++++++--------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/website/scripts/generate-deps.js b/website/scripts/generate-deps.js index 8f507449..fce4ee5b 100644 --- a/website/scripts/generate-deps.js +++ b/website/scripts/generate-deps.js @@ -29,10 +29,21 @@ for (const [license, packages] of Object.entries(raw)) { const themeExtensions = JSON.parse(readFileSync(themeExtensionsPath, "utf-8")); deps.push(...themeExtensions); -// Manual overrides for extensions that don't declare a license in their metadata +// Manual overrides for dependencies missing license or author in their metadata const missingLicense = { "Solarized & Selenized": "MIT", }; +const missingAuthor = { + "@tauri-apps/api": "Tauri Apps Contributors", + "@tauri-apps/plugin-shell": "Tauri Apps Contributors", + "@tauri-apps/plugin-updater": "Tauri Apps Contributors", + "@xterm/xterm": "Christopher Jeffrey, SourceLair Private Company, xterm.js authors", + "node-addon-api": "Node.js API collaborators", + "react": "Meta Platforms, Inc. and affiliates", + "react-dom": "Meta Platforms, Inc. and affiliates", + "scheduler": "Meta Platforms, Inc. and affiliates", + "tailwindcss": "Tailwind Labs, Inc.", +}; for (const dep of deps) { if (!dep.license) { const override = missingLicense[dep.name]; @@ -42,6 +53,14 @@ for (const dep of deps) { } dep.license = override; } + if (!dep.author) { + const override = missingAuthor[dep.name]; + if (!override) { + console.error(`ERROR: "${dep.name}" has no author. Add it to missingAuthor in generate-deps.js`); + process.exit(1); + } + dep.author = override; + } } deps.sort((a, b) => a.name.localeCompare(b.name)); diff --git a/website/src/data/dependencies.json b/website/src/data/dependencies.json index eea01380..a3ac9992 100644 --- a/website/src/data/dependencies.json +++ b/website/src/data/dependencies.json @@ -17,21 +17,21 @@ "name": "@tauri-apps/api", "version": "2.10.1", "license": "Apache-2.0 OR MIT", - "author": null, + "author": "Tauri Apps Contributors", "homepage": "https://github.com/tauri-apps/tauri#readme" }, { "name": "@tauri-apps/plugin-shell", "version": "2.3.5", "license": "MIT OR Apache-2.0", - "author": null, + "author": "Tauri Apps Contributors", "homepage": "https://github.com/tauri-apps/plugins-workspace#readme" }, { "name": "@tauri-apps/plugin-updater", "version": "2.10.1", "license": "MIT OR Apache-2.0", - "author": null, + "author": "Tauri Apps Contributors", "homepage": "https://github.com/tauri-apps/plugins-workspace#readme" }, { @@ -45,7 +45,7 @@ "name": "@xterm/xterm", "version": "6.0.0", "license": "MIT", - "author": null, + "author": "Christopher Jeffrey, SourceLair Private Company, xterm.js authors", "homepage": "https://github.com/xtermjs/xterm.js#readme" }, { @@ -115,7 +115,7 @@ "name": "node-addon-api", "version": "7.1.1", "license": "MIT", - "author": null, + "author": "Node.js API collaborators", "homepage": "https://github.com/nodejs/node-addon-api" }, { @@ -129,14 +129,14 @@ "name": "react", "version": "19.2.4", "license": "MIT", - "author": null, + "author": "Meta Platforms, Inc. and affiliates", "homepage": "https://react.dev/" }, { "name": "react-dom", "version": "19.2.4", "license": "MIT", - "author": null, + "author": "Meta Platforms, Inc. and affiliates", "homepage": "https://react.dev/" }, { @@ -157,7 +157,7 @@ "name": "scheduler", "version": "0.27.0", "license": "MIT", - "author": null, + "author": "Meta Platforms, Inc. and affiliates", "homepage": "https://react.dev/" }, { @@ -185,7 +185,7 @@ "name": "tailwindcss", "version": "4.2.2", "license": "MIT", - "author": null, + "author": "Tailwind Labs, Inc.", "homepage": "https://tailwindcss.com" } ] From 156c3e27ba908d29af9fe5087f10bed490c66e4f Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 16 Apr 2026 13:26:58 -0700 Subject: [PATCH 24/24] Exclude the classic GitHub themes. --- lib/scripts/bundle-themes.mjs | 8 ++- lib/src/lib/themes/bundled.json | 108 -------------------------------- 2 files changed, 7 insertions(+), 109 deletions(-) diff --git a/lib/scripts/bundle-themes.mjs b/lib/scripts/bundle-themes.mjs index 7fd80151..d1e6f009 100644 --- a/lib/scripts/bundle-themes.mjs +++ b/lib/scripts/bundle-themes.mjs @@ -30,6 +30,12 @@ const PREFERRED_THEME_ORDER = [ 'GitHub.github-vscode-theme.github-dark-default', ]; +/** Theme IDs to exclude (e.g. legacy/classic variants). */ +const EXCLUDED_THEMES = new Set([ + 'GitHub.github-vscode-theme.github-light', + 'GitHub.github-vscode-theme.github-dark', +]); + /** * VSCode theme color keys consumed by MouseTerm. * Keep in sync with lib/src/lib/themes/convert.ts. @@ -143,7 +149,7 @@ async function main() { for (const ext of EXTENSIONS) { const { themes, extension } = await fetchExtensionThemes(ext.namespace, ext.name); - allThemes.push(...themes); + allThemes.push(...themes.filter(t => !EXCLUDED_THEMES.has(t.id))); allExtensions.push(extension); } allThemes.sort((a, b) => { diff --git a/lib/src/lib/themes/bundled.json b/lib/src/lib/themes/bundled.json index 110f5adc..d25f8698 100644 --- a/lib/src/lib/themes/bundled.json +++ b/lib/src/lib/themes/bundled.json @@ -363,114 +363,6 @@ "kind": "bundled" } }, - { - "id": "GitHub.github-vscode-theme.github-light", - "label": "GitHub Light", - "type": "light", - "swatch": "#fff", - "accent": "#2188ff", - "vars": { - "--vscode-focusBorder": "#2188ff", - "--vscode-descriptionForeground": "#6a737d", - "--vscode-errorForeground": "#cb2431", - "--vscode-textLink-foreground": "#0366d6", - "--vscode-button-background": "#159739", - "--vscode-button-foreground": "#fff", - "--vscode-button-hoverBackground": "#138934", - "--vscode-input-background": "#fafbfc", - "--vscode-input-border": "#e1e4e8", - "--vscode-badge-foreground": "#005cc5", - "--vscode-badge-background": "#dbedff", - "--vscode-sideBar-background": "#f6f8fa", - "--vscode-list-activeSelectionForeground": "#2f363d", - "--vscode-list-activeSelectionBackground": "#e2e5e9", - "--vscode-editorGroupHeader-tabsBackground": "#f6f8fa", - "--vscode-tab-activeForeground": "#2f363d", - "--vscode-tab-inactiveForeground": "#6a737d", - "--vscode-tab-inactiveBackground": "#f6f8fa", - "--vscode-tab-activeBackground": "#fff", - "--vscode-editor-foreground": "#24292e", - "--vscode-editor-background": "#fff", - "--vscode-editorWidget-background": "#f6f8fa", - "--vscode-editorWarning-foreground": "#f9c513", - "--vscode-panel-border": "#e1e4e8", - "--vscode-terminal-foreground": "#586069", - "--vscode-terminalCursor-foreground": "#005cc5", - "--vscode-terminal-ansiBrightWhite": "#d1d5da", - "--vscode-terminal-ansiWhite": "#6a737d", - "--vscode-terminal-ansiBrightBlack": "#959da5", - "--vscode-terminal-ansiBlack": "#24292e", - "--vscode-terminal-ansiBlue": "#0366d6", - "--vscode-terminal-ansiBrightBlue": "#005cc5", - "--vscode-terminal-ansiGreen": "#28a745", - "--vscode-terminal-ansiBrightGreen": "#22863a", - "--vscode-terminal-ansiCyan": "#1b7c83", - "--vscode-terminal-ansiBrightCyan": "#3192aa", - "--vscode-terminal-ansiRed": "#d73a49", - "--vscode-terminal-ansiBrightRed": "#cb2431", - "--vscode-terminal-ansiMagenta": "#5a32a3", - "--vscode-terminal-ansiBrightMagenta": "#5a32a3", - "--vscode-terminal-ansiYellow": "#dbab09", - "--vscode-terminal-ansiBrightYellow": "#b08800" - }, - "origin": { - "kind": "bundled" - } - }, - { - "id": "GitHub.github-vscode-theme.github-dark", - "label": "GitHub Dark", - "type": "dark", - "swatch": "#24292e", - "accent": "#005cc5", - "vars": { - "--vscode-focusBorder": "#005cc5", - "--vscode-descriptionForeground": "#959da5", - "--vscode-errorForeground": "#f97583", - "--vscode-textLink-foreground": "#79b8ff", - "--vscode-button-background": "#176f2c", - "--vscode-button-foreground": "#dcffe4", - "--vscode-button-hoverBackground": "#22863a", - "--vscode-input-background": "#2f363d", - "--vscode-input-border": "#1b1f23", - "--vscode-badge-foreground": "#c8e1ff", - "--vscode-badge-background": "#044289", - "--vscode-sideBar-background": "#1f2428", - "--vscode-list-activeSelectionForeground": "#e1e4e8", - "--vscode-list-activeSelectionBackground": "#39414a", - "--vscode-editorGroupHeader-tabsBackground": "#1f2428", - "--vscode-tab-activeForeground": "#e1e4e8", - "--vscode-tab-inactiveForeground": "#959da5", - "--vscode-tab-inactiveBackground": "#1f2428", - "--vscode-tab-activeBackground": "#24292e", - "--vscode-editor-foreground": "#e1e4e8", - "--vscode-editor-background": "#24292e", - "--vscode-editorWidget-background": "#1f2428", - "--vscode-editorWarning-foreground": "#ffea7f", - "--vscode-panel-border": "#1b1f23", - "--vscode-terminal-foreground": "#d1d5da", - "--vscode-terminalCursor-foreground": "#79b8ff", - "--vscode-terminal-ansiBrightWhite": "#fafbfc", - "--vscode-terminal-ansiWhite": "#d1d5da", - "--vscode-terminal-ansiBrightBlack": "#959da5", - "--vscode-terminal-ansiBlack": "#586069", - "--vscode-terminal-ansiBlue": "#2188ff", - "--vscode-terminal-ansiBrightBlue": "#79b8ff", - "--vscode-terminal-ansiGreen": "#34d058", - "--vscode-terminal-ansiBrightGreen": "#85e89d", - "--vscode-terminal-ansiCyan": "#39c5cf", - "--vscode-terminal-ansiBrightCyan": "#56d4dd", - "--vscode-terminal-ansiRed": "#ea4a5a", - "--vscode-terminal-ansiBrightRed": "#f97583", - "--vscode-terminal-ansiMagenta": "#b392f0", - "--vscode-terminal-ansiBrightMagenta": "#b392f0", - "--vscode-terminal-ansiYellow": "#ffea7f", - "--vscode-terminal-ansiBrightYellow": "#ffea7f" - }, - "origin": { - "kind": "bundled" - } - }, { "id": "santoso-wijaya.helios-selene.selenized-light", "label": "Selenized Light",