diff --git a/frontend/scripts/ui_builder_artifact.json b/frontend/scripts/ui_builder_artifact.json index 4360794dbe..01cfbb2888 100644 --- a/frontend/scripts/ui_builder_artifact.json +++ b/frontend/scripts/ui_builder_artifact.json @@ -1,5 +1,5 @@ { "baseUrl": "https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev", - "version": "1f1fe4f", - "sha256": "4c20b2b51f324e93dda3b46d914ebf7bc7d0cb4dc7eb2d37068408191220d206" + "version": "76ee616", + "sha256": "1d696d38a152179ef6e1cada5df302d078cc677b6dbc244b5c42335e11feabac" } diff --git a/frontend/src/lib/assets/tokens/githubDark.json b/frontend/src/lib/assets/tokens/githubDark.json new file mode 100644 index 0000000000..ea5b36145e --- /dev/null +++ b/frontend/src/lib/assets/tokens/githubDark.json @@ -0,0 +1,35 @@ +{ + "surface-accent-primary": "#506ab8", + "surface-accent-hover": "#5873c8", + "surface-accent-clicked": "#425797", + "text-primary": "#d5dbe2", + "text-secondary": "#8b949e", + "text-primary-inverse": "#24292f", + "text-secondary-inverse": "#57606a", + "text-tertiary-inverse": "#6e7781", + "surface-selected": "#212830", + "surface-disabled": "#151b23", + "surface-secondary": "#010409", + "surface-hover": "#6e768119", + "surface-primary": "#0d1117", + "border-light": "#242a32", + "border-normal": "#6e7681", + "border-accent": "#4a6ed6", + "surface-accent-selected": "#141a2e", + "surface-accent-secondary": "#e3e8ee", + "surface-tertiary": "#151b23", + "text-emphasis": "#dfe5eb", + "text-hint": "#6e7681", + "text-disabled": "#484f58", + "surface-accent-secondary-hover": "#d0d7de", + "surface-accent-secondary-clicked": "#afb8c1", + "component-button-accent-secondary": "#f0f6fc", + "text-emphasis-inverse": "#1f2328", + "reserved-ai": "#d2a8ff", + "component-virtual-node": "#151b23", + "text-accent": "#bdc8ee", + "border-selected": "#5a72c4", + "surface-sunken": "#010409", + "text-tertiary": "#6e7681", + "surface-input": "#0c0f14" +} diff --git a/frontend/src/lib/assets/tokens/tokens.json b/frontend/src/lib/assets/tokens/tokens.json index f4f35c81d9..349b4376b8 100644 --- a/frontend/src/lib/assets/tokens/tokens.json +++ b/frontend/src/lib/assets/tokens/tokens.json @@ -239,7 +239,12 @@ "magenta-950": "#6e2ba1" } }, - "guidelines": { "mode-1": { "blue": "#5e81ac", "demo-background": "#ffffff00" } }, + "guidelines": { + "mode-1": { + "blue": "#5e81ac", + "demo-background": "#ffffff00" + } + }, "tailwind-c-s-s-v-3-3-2": { "mode-1": { "black": "#000000", diff --git a/frontend/src/lib/components/EditorTheme.svelte b/frontend/src/lib/components/EditorTheme.svelte index 9b24acab45..ba123e3dd5 100644 --- a/frontend/src/lib/components/EditorTheme.svelte +++ b/frontend/src/lib/components/EditorTheme.svelte @@ -1,5 +1,5 @@ - + (darkVariant = getDarkModeVariant())} + on:close={removeHash} + {disableChatOffset} +>
{#if scopes == undefined}
-
- Theme +
+
+ Theme +
+
+ Dark variant + { + darkVariant = v + setDarkModeVariant(v) + }} + > + {#snippet children({ item })} + + + {/snippet} + +
Windmill diff --git a/frontend/src/lib/components/apps/editor/PublicApp.svelte b/frontend/src/lib/components/apps/editor/PublicApp.svelte index 8995684e28..c0cf693e0f 100644 --- a/frontend/src/lib/components/apps/editor/PublicApp.svelte +++ b/frontend/src/lib/components/apps/editor/PublicApp.svelte @@ -1,5 +1,6 @@ - + { + darkVariant = getAppliedDarkModeVariant() + }} +/> diff --git a/frontend/src/lib/components/sidebar/sidebarChrome.ts b/frontend/src/lib/components/sidebar/sidebarChrome.ts index 110435c058..71ff703d27 100644 --- a/frontend/src/lib/components/sidebar/sidebarChrome.ts +++ b/frontend/src/lib/components/sidebar/sidebarChrome.ts @@ -1,5 +1,6 @@ -// Sidebar rail background. Deliberately raw hex — no theme token exists for -// the rail chrome — kept here as the single source for every host that renders -// or mimics the sidebar (app layout, kitchen-sink harness). +// Sidebar rail background. Kept here as the single source for every host that +// renders or mimics the sidebar (app layout, kitchen-sink harness). +// The dark value is a theme-driven CSS variable so it adapts across dark +// variants (default vs GitHub dark) — see tailwind.config.cjs `--sidebar-bg-dark`. export const SIDEBAR_BG = '#F3F3F7' -export const SIDEBAR_BG_DARK = '#1e232e' +export const SIDEBAR_BG_DARK = 'var(--sidebar-bg-dark)' diff --git a/frontend/src/lib/components/vscode.ts b/frontend/src/lib/components/vscode.ts index 1277ac1b4a..f30ffcd780 100644 --- a/frontend/src/lib/components/vscode.ts +++ b/frontend/src/lib/components/vscode.ts @@ -1,6 +1,7 @@ import '@codingame/monaco-vscode-standalone-typescript-language-features' import { editor as meditor, Uri as mUri } from 'monaco-editor' +import { getAppliedDarkModeVariant } from '$lib/darkModeVariant' export let isInitialized = false export let isInitializing = false @@ -12,6 +13,15 @@ import { import getLanguagesServiceOverride from '@codingame/monaco-vscode-languages-service-override' import { getCssColor } from '$lib/utils' +// Monaco theme name matching the current document theme (light / dark / GitHub dark variant). +export function getEditorTheme(): string { + const classes = document.documentElement.classList + if (!classes.contains('dark')) { + return 'myTheme' + } + return getAppliedDarkModeVariant() === 'github' ? 'github-dark' : 'nord' +} + export function buildWorkerDefinition() { const envEnhanced = getEnhancedMonacoEnvironment() const getWorker = (moduleId: string, label: string) => { @@ -248,6 +258,47 @@ export async function initializeVscode(caller?: string, htmlContainer?: HTMLElem } }) + meditor.defineTheme('github-dark', { + base: 'vs-dark', + inherit: true, + rules: [ + { background: '0D1117', token: '' }, + { foreground: '8b949e', token: 'comment' }, + { foreground: 'a5d6ff', token: 'string' }, + { foreground: '79c0ff', token: 'constant.numeric' }, + { foreground: '79c0ff', token: 'constant.language' }, + { foreground: 'ff7b72', token: 'keyword' }, + { foreground: 'ff7b72', token: 'storage' }, + { foreground: 'ff7b72', token: 'storage.type' }, + { foreground: 'ffa657', token: 'entity.name.class' }, + { foreground: 'ffa657', fontStyle: 'bold', token: 'entity.other.inherited-class' }, + { foreground: 'd2a8ff', token: 'entity.name.function' }, + { foreground: '7ee787', token: 'entity.name.tag' }, + { foreground: '79c0ff', token: 'entity.other.attribute-name' }, + { foreground: 'd2a8ff', token: 'support.function' }, + { foreground: 'f0f6fc', background: 'f85149', token: 'invalid' }, + { foreground: 'f0f6fc', background: 'bd561d', token: 'invalid.deprecated' }, + { foreground: '79c0ff', token: 'constant.color.other.rgb-value' }, + { foreground: '79c0ff', token: 'constant.character.escape' }, + { foreground: 'ffa657', token: 'variable.other.constant' }, + { token: 'string.value.json', foreground: 'a5d6ff' }, // string values in JSON + { token: 'keyword.json', foreground: '79c0ff' } // true, false, null in JSON + ], + colors: { + 'editor.foreground': '#e6edf3', + 'editor.background': getCssColor('surface-input', { format: 'hex-github-dark' }), + 'editor.selectionBackground': '#3392FF44', + 'editor.inactiveSelectionBackground': '#3392FF22', + 'editor.lineHighlightBackground': '#161b22', + 'editorCursor.foreground': '#e6edf3', + 'editorWhitespace.foreground': '#484f58', + 'editorIndentGuide.background1': '#21262d', + 'editorIndentGuide.activeBackground1': '#30363d', + 'editorLineNumber.foreground': '#6e7681', + 'editorLineNumber.activeForeground': '#e6edf3' + } + }) + meditor.defineTheme('myTheme', { base: 'vs', inherit: true, @@ -262,11 +313,7 @@ export async function initializeVscode(caller?: string, htmlContainer?: HTMLElem } }) - if (document.documentElement.classList.contains('dark')) { - meditor.setTheme('nord') - } else { - meditor.setTheme('myTheme') - } + meditor.setTheme(getEditorTheme()) } catch (e) { console.error('Failed to initialize monaco services', e) } finally { diff --git a/frontend/src/lib/darkModeVariant.ts b/frontend/src/lib/darkModeVariant.ts new file mode 100644 index 0000000000..1e1bbff28b --- /dev/null +++ b/frontend/src/lib/darkModeVariant.ts @@ -0,0 +1,27 @@ +export type DarkModeVariant = 'default' | 'github' + +const DARK_MODE_VARIANT_KEY = 'dark-mode-variant' + +export function getDarkModeVariant(): DarkModeVariant { + return window.localStorage.getItem(DARK_MODE_VARIANT_KEY) === 'github' ? 'github' : 'default' +} + +// The variant currently applied to the DOM (the `github-dark` class) — the +// runtime source of truth, as opposed to the persisted preference above. +export function getAppliedDarkModeVariant(): DarkModeVariant { + return typeof document !== 'undefined' && + document.documentElement.classList.contains('github-dark') + ? 'github' + : 'default' +} + +// The `github-dark` class only takes effect when `dark` is also present (see tailwind.config.cjs). +// It lives on permanently so it survives the many independent `dark` toggle sites. +export function applyDarkModeVariant(variant: DarkModeVariant = getDarkModeVariant()): void { + document.documentElement.classList.toggle('github-dark', variant === 'github') +} + +export function setDarkModeVariant(variant: DarkModeVariant): void { + window.localStorage.setItem(DARK_MODE_VARIANT_KEY, variant) + applyDarkModeVariant(variant) +} diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 28d33335ae..e9acfdacab 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -2029,6 +2029,8 @@ export function validateRetryConfig(retry: Retry | undefined): string | null { } export type CssColor = keyof (typeof tokensFile)['tokens']['light'] import tokensFile from './assets/tokens/tokens.json' +// Hand-authored variant kept out of the Figma-generated tokens.json. +import githubDarkTokens from './assets/tokens/githubDark.json' import { darkModeName, lightModeName } from './assets/tokens/colorTokensConfig' import BarsStaggered from './components/icons/BarsStaggered.svelte' import { GitIcon } from './components/icons' @@ -2041,7 +2043,7 @@ export function getCssColor( format = 'css-var' }: { alpha?: number - format?: 'css-var' | 'hex-dark' | 'hex-light' + format?: 'css-var' | 'hex-dark' | 'hex-light' | 'hex-github-dark' } ): string { if (format === 'hex-light') { @@ -2050,6 +2052,9 @@ export function getCssColor( if (format === 'hex-dark') { return tokensFile.tokens[darkModeName][color] } + if (format === 'hex-github-dark') { + return githubDarkTokens[color] + } return `rgb(var(--color-${color}) / ${alpha})` } diff --git a/frontend/src/routes/(root)/(logged)/+layout.svelte b/frontend/src/routes/(root)/(logged)/+layout.svelte index 7dbe5ca376..9de1123ff9 100644 --- a/frontend/src/routes/(root)/(logged)/+layout.svelte +++ b/frontend/src/routes/(root)/(logged)/+layout.svelte @@ -1052,7 +1052,7 @@ style:width="{railWidth}rem" >
{#if !isCollapsed} diff --git a/frontend/src/routes/(root)/+layout.svelte b/frontend/src/routes/(root)/+layout.svelte index 8b7736965d..3db5efcf60 100644 --- a/frontend/src/routes/(root)/+layout.svelte +++ b/frontend/src/routes/(root)/+layout.svelte @@ -19,6 +19,7 @@ // import EditorTheme from '$lib/components/EditorTheme.svelte' import { computeDrift } from '$lib/forLater' import { setLicense } from '$lib/enterpriseUtils' + import { applyDarkModeVariant } from '$lib/darkModeVariant' import { deepEqual } from 'fast-equals' interface Props { children?: import('svelte').Snippet @@ -255,6 +256,8 @@ } else { document.documentElement.classList.remove('dark') } + + applyDarkModeVariant() {@render children?.()} diff --git a/frontend/src/routes/user/login_callback/[client_name]/+page.svelte b/frontend/src/routes/user/login_callback/[client_name]/+page.svelte index 737da14321..1aeb57f223 100644 --- a/frontend/src/routes/user/login_callback/[client_name]/+page.svelte +++ b/frontend/src/routes/user/login_callback/[client_name]/+page.svelte @@ -1,5 +1,6 @@