Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/ui-interaction-and-motion.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ keyboard navigation, outside dismissal, scroll locking, and focus return.
Keep the current workstation as the compatibility default and gate compact
desktop density behind both sufficient width and a fine pointer so a visual
profile never reduces touch usability.
- A style profile may declare an optional recommended day/night palette pair.
Selecting the style must never apply that pair automatically: Settings shows
it as an explicit preview and action so users can adopt the complete look
without silently losing their existing color choices.
- Delete superseded event plumbing during migration. A component is not
migrated if its old global Escape/outside-click/focus-loop implementation is
still running beside the primitive.
Expand Down
2 changes: 1 addition & 1 deletion ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
var t = storedTheme === 'day' || storedTheme === 'night' || storedTheme === 'auto'
? storedTheme
: storedTheme === 'light' ? 'day' : storedTheme === 'dark' ? 'night' : 'auto';
var palettes = ['paper', 'porcelain', 'linen', 'graphite', 'midnight', 'moss', 'iris'];
var palettes = ['paper', 'porcelain', 'linen', 'windows-classic', 'graphite', 'midnight', 'moss', 'iris'];
var storedDay = palettes.indexOf(state.dayPalette) >= 0 ? state.dayPalette : state.lightPalette;
var storedNight = palettes.indexOf(state.nightPalette) >= 0 ? state.nightPalette : state.darkPalette;
var day = palettes.indexOf(storedDay) >= 0 ? storedDay : 'paper';
Expand Down
7 changes: 6 additions & 1 deletion ui/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,10 @@ export const en = {
description: 'Color and layout preferences. Stored locally on this device.',
interfaceStyle: 'Interface style',
interfaceStyleDescription: 'Changes component shape, density, typography, and motion without changing your colors.',
recommendedPalette: '{{style}} recommended colors',
recommendedPaletteDescription: 'Optional — applies {{palette}} to both Day and Night. Choosing a style never changes colors by itself.',
applyRecommendedPalette: 'Use recommended colors',
recommendedPaletteApplied: 'Recommended colors applied',
colorMode: 'Color mode',
colorModeDescription: 'Auto follows the system between Day and Night slots; Day and Night pin one slot.',
dayPalette: 'Day palette',
Expand Down Expand Up @@ -1223,13 +1227,14 @@ export const en = {
'broker-classic': 'Dense TWS-inspired trading workstation',
},
palette: {
paper: 'Paper', porcelain: 'Porcelain', linen: 'Linen',
paper: 'Paper', porcelain: 'Porcelain', linen: 'Linen', 'windows-classic': 'Windows Classic',
graphite: 'Graphite', midnight: 'Midnight', moss: 'Moss', iris: 'Iris',
},
paletteDescription: {
paper: 'Warm, editorial neutrals',
porcelain: 'Cool, crisp workspace',
linen: 'Oat, ink, and terracotta',
'windows-classic': 'System silver with navy selection',
graphite: 'Neutral low-glare dark',
midnight: 'Deep blue night palette',
moss: 'Forest ink with soft amber',
Expand Down
7 changes: 6 additions & 1 deletion ui/src/i18n/locales/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,10 @@ export const ja: Resources = {
description: '色とレイアウトの設定。この端末にのみ保存されます。',
interfaceStyle: 'インターフェーススタイル',
interfaceStyleDescription: '選択中の色を変えずに、形状、密度、書体、モーションを変更します。',
recommendedPalette: '{{style}} のおすすめ配色',
recommendedPaletteDescription: '任意 — {{palette}}を昼と夜の両方に適用します。スタイルを選ぶだけでは色は変わりません。',
applyRecommendedPalette: 'おすすめ配色を使う',
recommendedPaletteApplied: 'おすすめ配色を適用済み',
colorMode: 'カラーモード',
colorModeDescription: '自動はシステムに追従して昼と夜のスロットを切り替え、昼と夜は対応するスロットに固定します。',
dayPalette: '昼用パレット',
Expand Down Expand Up @@ -1212,13 +1216,14 @@ export const ja: Resources = {
'broker-classic': 'TWS に着想を得た高密度な取引画面',
},
palette: {
paper: 'ペーパー', porcelain: 'ポーセリン', linen: 'リネン',
paper: 'ペーパー', porcelain: 'ポーセリン', linen: 'リネン', 'windows-classic': 'Windows クラシック',
graphite: 'グラファイト', midnight: 'ミッドナイト', moss: 'モス', iris: 'アイリス',
},
paletteDescription: {
paper: '温かみのある編集向け中間色',
porcelain: '涼しく鮮明なワークスペース',
linen: 'オート麦色、墨色、テラコッタ',
'windows-classic': 'システムシルバーと濃紺の選択色',
graphite: '低反射のニュートラルダーク',
midnight: '深いブルーの夜間パレット',
moss: '森の墨色と柔らかな琥珀色',
Expand Down
7 changes: 6 additions & 1 deletion ui/src/i18n/locales/zh-Hant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,10 @@ export const zhHant: Resources = {
description: '色彩與版面偏好,僅儲存在本機。',
interfaceStyle: '介面樣式',
interfaceStyleDescription: '調整元件外形、密度、字體與動效,不會改變你選擇的色卡。',
recommendedPalette: '{{style}}推薦配色',
recommendedPaletteDescription: '可選——將{{palette}}同時用於日間與暗夜。單獨選擇樣式不會改變目前色卡。',
applyRecommendedPalette: '套用推薦配色',
recommendedPaletteApplied: '已套用推薦配色',
colorMode: '色彩模式',
colorModeDescription: '自動模式跟隨系統在日間與暗夜槽位間切換;日間與暗夜模式會固定對應槽位。',
dayPalette: '日間色卡',
Expand Down Expand Up @@ -1219,13 +1223,14 @@ export const zhHant: Resources = {
'broker-classic': '受 TWS 啟發的高密度交易工作台',
},
palette: {
paper: '紙張', porcelain: '白瓷', linen: '亞麻',
paper: '紙張', porcelain: '白瓷', linen: '亞麻', 'windows-classic': 'Windows 經典',
graphite: '石墨', midnight: '午夜', moss: '苔色', iris: '鳶尾',
},
paletteDescription: {
paper: '溫暖的編輯風中性色',
porcelain: '清爽的冷調工作區',
linen: '燕麥紙、墨棕與陶土色',
'windows-classic': '系統銀灰與深藍選取色',
graphite: '低眩光的中性深色',
midnight: '深藍調暗夜色卡',
moss: '墨綠底色與柔和琥珀',
Expand Down
7 changes: 6 additions & 1 deletion ui/src/i18n/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ export const zh: Resources = {
description: '色彩与布局偏好,仅保存在本设备。',
interfaceStyle: '界面样式',
interfaceStyleDescription: '调整组件外形、密度、字体与动效,不会改变你选择的色卡。',
recommendedPalette: '{{style}}推荐配色',
recommendedPaletteDescription: '可选——将{{palette}}同时用于日间和暗夜。单独选择样式不会改变当前色卡。',
applyRecommendedPalette: '应用推荐配色',
recommendedPaletteApplied: '已应用推荐配色',
colorMode: '色彩模式',
colorModeDescription: '自动模式跟随系统在日间和暗夜槽位间切换;日间和暗夜模式会固定对应槽位。',
dayPalette: '日间色卡',
Expand Down Expand Up @@ -1211,13 +1215,14 @@ export const zh: Resources = {
'broker-classic': '受 TWS 启发的高密度交易工作台',
},
palette: {
paper: '纸张', porcelain: '白瓷', linen: '亚麻',
paper: '纸张', porcelain: '白瓷', linen: '亚麻', 'windows-classic': 'Windows 经典',
graphite: '石墨', midnight: '午夜', moss: '苔色', iris: '鸢尾',
},
paletteDescription: {
paper: '温暖的编辑风中性色',
porcelain: '清爽的冷调工作区',
linen: '燕麦纸、墨棕与陶土色',
'windows-classic': '系统银灰与深蓝选择色',
graphite: '低眩光的中性深色',
midnight: '深蓝调暗夜色卡',
moss: '墨绿底色与柔和琥珀',
Expand Down
16 changes: 16 additions & 0 deletions ui/src/pages/SettingsPage.appearance.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,22 @@ describe('AppearanceSection palette pair editor', () => {
expect(useThemeStore.getState().dayPalette).toBe('paper')
expect(useThemeStore.getState().nightPalette).toBe('graphite')
expect(screen.getByRole('radio', { name: 'Windows 98' }).getAttribute('aria-checked')).toBe('true')
expect(screen.getByRole('button', { name: 'Use recommended colors' })).toBeTruthy()
})

it('applies the selected style recommended palette only after explicit confirmation', () => {
render(<AppearanceSection />)

fireEvent.click(screen.getByRole('radio', { name: 'Windows 98' }))
expect(useThemeStore.getState().dayPalette).toBe('paper')
expect(useThemeStore.getState().nightPalette).toBe('graphite')

fireEvent.click(screen.getByRole('button', { name: 'Use recommended colors' }))

expect(useThemeStore.getState().theme).toBe('auto')
expect(useThemeStore.getState().dayPalette).toBe('windows-classic')
expect(useThemeStore.getState().nightPalette).toBe('windows-classic')
expect(screen.getByRole('button', { name: 'Recommended colors applied' }).hasAttribute('disabled')).toBe(true)
})

it('does not expose the retired editor tab strip preference', () => {
Expand Down
58 changes: 57 additions & 1 deletion ui/src/pages/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import {
type ThemePreferenceSlot,
} from '../theme/palettes'
import { useThemeStore, type AppTheme } from '../theme/store'
import { UI_STYLE_PROFILES, type UiStyleProfileId } from '../theme/styleProfiles'
import {
UI_STYLE_PROFILES,
type UiStyleProfileDefinition,
type UiStyleProfileId,
} from '../theme/styleProfiles'
import { useEffectivePreferenceSlot } from '../theme/useEffectiveTheme'
import { AboutOpenAliceSection } from '../components/settings/AboutOpenAliceSection'

Expand Down Expand Up @@ -50,6 +54,13 @@ export function AppearanceSection() {
const modes: readonly AppTheme[] = ['auto', 'day', 'night']
const activePalette = effectiveSlot === 'day' ? dayPalette : nightPalette
const activePaletteDefinition = paletteDefinition(activePalette)
const activeStyleDefinition: UiStyleProfileDefinition = UI_STYLE_PROFILES.find(
(profile) => profile.id === uiStyle,
)!
const recommendedPalettePair = activeStyleDefinition.recommendedPalettePair
const recommendedPaletteApplied = recommendedPalettePair != null
&& dayPalette === recommendedPalettePair.day
&& nightPalette === recommendedPalettePair.night
const editingPalette = editingSlot === 'day' ? dayPalette : nightPalette
const recommendedAppearance = editingSlot === 'day' ? 'light' : 'dark'
const visiblePalettes = paletteFilter === 'recommended'
Expand Down Expand Up @@ -82,6 +93,12 @@ export function AppearanceSection() {
setPaletteFilter('recommended')
}

const applyRecommendedPalette = () => {
if (!recommendedPalettePair) return
setDayPalette(recommendedPalettePair.day)
setNightPalette(recommendedPalettePair.night)
}

return (
<ConfigSection title={t('settings.appearance.title')} description={t('settings.appearance.description')}>
<div className="border-b border-border/60 pb-5">
Expand Down Expand Up @@ -109,6 +126,45 @@ export function AppearanceSection() {
/>
))}
</div>
{recommendedPalettePair && (
<div
data-palette-preview={recommendedPalettePair.day}
className="oa-palette-preview mt-3 flex min-w-0 flex-col gap-3 rounded-lg border border-border bg-background p-3 sm:flex-row sm:items-center"
>
<span className="oa-palette-preview-shell flex h-11 w-full shrink-0 overflow-hidden rounded border sm:w-24" aria-hidden>
<span className="oa-palette-preview-sidebar flex w-6 shrink-0 items-center justify-center border-r">
<span className="oa-palette-preview-sidebar-dot h-2 w-2 rounded-full" />
</span>
<span className="oa-palette-preview-canvas flex min-w-0 flex-1 flex-col justify-center gap-1.5 px-2">
<span className="oa-palette-preview-primary-line h-1.5 w-3/5 rounded-full" />
<span className="oa-palette-preview-muted-line h-1 w-full rounded-full" />
<span className="oa-palette-preview-muted-line h-1 w-3/4 rounded-full" />
</span>
</span>
<div className="min-w-0 flex-1">
<span className="block text-[12px] font-semibold text-foreground">
{t('settings.appearance.recommendedPalette', {
style: t(activeStyleDefinition.labelKey),
})}
</span>
<p className="mt-0.5 text-[10.5px] leading-snug text-muted-foreground">
{t('settings.appearance.recommendedPaletteDescription', {
palette: t(paletteDefinition(recommendedPalettePair.day).labelKey),
})}
</p>
</div>
<button
type="button"
onClick={applyRecommendedPalette}
disabled={recommendedPaletteApplied}
className="oa-pressable min-h-10 shrink-0 rounded-md border border-border bg-background px-3 py-1.5 text-[11px] font-medium text-foreground hover:border-primary/35 hover:text-primary disabled:cursor-default disabled:opacity-55 sm:min-h-8"
>
{t(recommendedPaletteApplied
? 'settings.appearance.recommendedPaletteApplied'
: 'settings.appearance.applyRecommendedPalette')}
</button>
</div>
)}
</div>

<div className="border-b border-border/60 py-5">
Expand Down
16 changes: 16 additions & 0 deletions ui/src/theme/noFlashTheme.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,20 @@ describe('no-flash theme bootstrap', () => {
expect(applyNoFlashTheme({ uiStyle: 'win98' }, false).uiStyle).toBe('win98')
expect(applyNoFlashTheme({ uiStyle: 'aqua' }, false).uiStyle).toBe('default')
})

it('restores the Windows Classic palette before first paint', () => {
const state = {
theme: 'auto',
dayPalette: 'windows-classic',
nightPalette: 'windows-classic',
uiStyle: 'win98',
}
expect(applyNoFlashTheme(state, false)).toEqual({
theme: 'auto',
dayPalette: 'windows-classic',
nightPalette: 'windows-classic',
uiStyle: 'win98',
palette: 'windows-classic',
})
})
})
91 changes: 91 additions & 0 deletions ui/src/theme/palette.css
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,97 @@
);
}

/* Windows 98's application chrome: system silver, navy selection, white
document fields, and a black console. The paired Win98 style profile owns
bevels and square geometry; this card remains independently selectable. */
:root[data-palette="windows-classic"],
[data-palette-preview="windows-classic"] {
color-scheme: light;

--background: #c0c0c0;
--foreground: #000000;
--card: #c0c0c0;
--card-foreground: #000000;
--popover: #c0c0c0;
--popover-foreground: #000000;
--primary: #000080;
--primary-foreground: #ffffff;
--secondary: #d4d0c8;
--secondary-foreground: #000000;
--muted: #a0a0a0;
--muted-foreground: #3f3f3f;
--accent: rgba(0, 0, 128, 0.12);
--accent-foreground: #000000;
--destructive: #800000;
--destructive-foreground: #ffffff;
--border: #808080;
--input: #808080;
--ring: #000080;

--accent-strong: rgba(0, 0, 128, 0.2);
--primary-muted: rgba(0, 0, 128, 0.18);
--success: #006400;
--success-foreground: #ffffff;
--warning: #7f4f00;
--warning-foreground: #ffffff;
--warning-background: #ffffe1;
--warning-border: #808000;
--info: #000080;
--info-foreground: #ffffff;
--ai-action: #800080;
--ai-action-foreground: #ffffff;
--backdrop: rgba(0, 0, 0, 0.48);
--code-background: rgba(255, 255, 255, 0.42);
--report-canvas: #ffffff;
--shadow-color: #000000;

--terminal-background: #000000;
--terminal-foreground: #c0c0c0;
--terminal-cursor: #ffffff;
--terminal-cursor-accent: #000000;
--terminal-selection-background: #000080;
--terminal-selection-foreground: #ffffff;
--terminal-black: #000000;
--terminal-red: #ff5f5f;
--terminal-green: #00aa00;
--terminal-yellow: #ffff00;
--terminal-blue: #6f6fff;
--terminal-magenta: #ff5fff;
--terminal-cyan: #00aaaa;
--terminal-white: #c0c0c0;
--terminal-bright-black: #808080;
--terminal-bright-red: #ff0000;
--terminal-bright-green: #00ff00;
--terminal-bright-yellow: #ffff00;
--terminal-bright-blue: #8080ff;
--terminal-bright-magenta: #ff00ff;
--terminal-bright-cyan: #00ffff;
--terminal-bright-white: #ffffff;

--chart-1: #000080;
--chart-2: #006400;
--chart-3: #7f4f00;
--chart-4: #800080;
--chart-5: #008080;
--chart-positive: #006400;
--chart-negative: #800000;
--chart-positive-muted: rgba(0, 100, 0, 0.32);
--chart-negative-muted: rgba(128, 0, 0, 0.32);
--chart-axis: #3f3f3f;
--chart-grid: #808080;

--sidebar: #c0c0c0;
--sidebar-foreground: #000000;
--sidebar-primary: #000080;
--sidebar-primary-foreground: #ffffff;
--sidebar-accent: #000080;
--sidebar-accent-foreground: #ffffff;
--sidebar-border: #808080;
--sidebar-ring: #000080;

--app-background-wash: none;
}

:root[data-palette="graphite"],
[data-palette-preview="graphite"] {
color-scheme: dark;
Expand Down
2 changes: 2 additions & 0 deletions ui/src/theme/palettes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export type ThemePaletteId =
| 'paper'
| 'porcelain'
| 'linen'
| 'windows-classic'
| 'graphite'
| 'midnight'
| 'moss'
Expand Down Expand Up @@ -29,6 +30,7 @@ export const THEME_PALETTES = [
{ id: 'paper', appearance: 'light', labelKey: 'theme.palette.paper', descriptionKey: 'theme.paletteDescription.paper' },
{ id: 'porcelain', appearance: 'light', labelKey: 'theme.palette.porcelain', descriptionKey: 'theme.paletteDescription.porcelain' },
{ id: 'linen', appearance: 'light', labelKey: 'theme.palette.linen', descriptionKey: 'theme.paletteDescription.linen' },
{ id: 'windows-classic', appearance: 'light', labelKey: 'theme.palette.windows-classic', descriptionKey: 'theme.paletteDescription.windows-classic' },
{ id: 'graphite', appearance: 'dark', labelKey: 'theme.palette.graphite', descriptionKey: 'theme.paletteDescription.graphite' },
{ id: 'midnight', appearance: 'dark', labelKey: 'theme.palette.midnight', descriptionKey: 'theme.paletteDescription.midnight' },
{ id: 'moss', appearance: 'dark', labelKey: 'theme.palette.moss', descriptionKey: 'theme.paletteDescription.moss' },
Expand Down
Loading