Merge activity theme contrast fix into main

The detached worktree carries the verified Token usage dark-theme contrast
repair. Merge it into local main while preserving the existing local main
history for desktop opener work.

Constraint: Local main already contains unpushed desktop opener commits
Confidence: high
Scope-risk: narrow
Directive: Keep future activity heatmap palette changes isolated to activity theme tokens
Tested: cd desktop && bun run test ActivitySettings.test.tsx globals.test.ts
Tested: bun run check:desktop
Not-tested: Post-merge full verify gate
This commit is contained in:
程序员阿江(Relakkes) 2026-05-13 18:48:35 +08:00
commit b27ff74962
4 changed files with 121 additions and 11 deletions

View File

@ -112,6 +112,9 @@ describe('ActivitySettings', () => {
expect(tooltip).toHaveTextContent('May 9, 2026')
expect(tooltip).toHaveTextContent('4 sessions · 128K tokens')
expect(tooltip).not.toHaveTextContent(/messages|tools/i)
expect(tooltip.className).toContain('--color-activity-tooltip-surface')
expect(tooltip.className).toContain('--color-activity-tooltip-border')
expect(todayCell.className).toContain('--color-activity-cell-border')
expect(screen.queryByText('Selected day')).not.toBeInTheDocument()
})
})

View File

@ -30,11 +30,11 @@ const HEAT_CELL_MIN = 6
const HEAT_CELL_MAX = 22
const TOOLTIP_WIDTH = 172
const HEAT_COLORS = [
'var(--color-surface-container)',
'var(--color-primary-fixed)',
'var(--color-primary-fixed-dim)',
'var(--color-primary-container)',
'var(--color-primary)',
'var(--color-activity-heat-0)',
'var(--color-activity-heat-1)',
'var(--color-activity-heat-2)',
'var(--color-activity-heat-3)',
'var(--color-activity-heat-4)',
]
const DATE_LOCALES: Record<Locale, string> = {
en: 'en-US',
@ -380,8 +380,8 @@ export function ActivitySettings() {
aria-describedby={activeTooltipDate === day.date ? tooltipId : undefined}
className={`rounded-[3px] border transition-[border-color,transform] hover:scale-110 focus:outline-none focus:ring-2 focus:ring-[var(--color-brand)] focus:ring-offset-2 focus:ring-offset-[var(--color-surface)] ${
isSelected
? 'border-[var(--color-text-primary)]'
: 'border-[var(--color-border)]/30 hover:border-[var(--color-text-secondary)]'
? 'border-[var(--color-activity-cell-border-active)]'
: 'border-[var(--color-activity-cell-border)] hover:border-[var(--color-activity-cell-border-hover)]'
}`}
style={{
width: heatCellSize,
@ -401,11 +401,11 @@ export function ActivitySettings() {
<div
id={`activity-day-tooltip-${tooltipDay.date}`}
role="tooltip"
className="pointer-events-none absolute z-20 min-w-[172px] rounded-md border border-[var(--color-inverse-surface)] bg-[var(--color-inverse-surface)] px-3 py-2 text-xs shadow-xl"
className="pointer-events-none absolute z-20 min-w-[172px] rounded-md border border-[var(--color-activity-tooltip-border)] bg-[var(--color-activity-tooltip-surface)] px-3 py-2 text-xs shadow-xl"
style={tooltipStyle}
>
<div className="font-medium text-[var(--color-inverse-on-surface)]">{formatDateLabel(tooltipDay.date, locale)}</div>
<div className="mt-1 text-[var(--color-primary-fixed)]">
<div className="font-medium text-[var(--color-activity-tooltip-text)]">{formatDateLabel(tooltipDay.date, locale)}</div>
<div className="mt-1 text-[var(--color-activity-tooltip-muted)]">
{formatSessionCount(tooltipDay.sessionCount, t)} · {formatTokens(tooltipDay.tokens)} tokens
</div>
</div>
@ -419,7 +419,7 @@ export function ActivitySettings() {
<span
key={color}
aria-hidden="true"
className="rounded-[3px] border border-[var(--color-border)]/30"
className="rounded-[3px] border border-[var(--color-activity-cell-border)]"
style={{ width: heatCellSize, height: heatCellSize, backgroundColor: color }}
/>
))}

View File

@ -148,7 +148,11 @@
--color-inverse-primary: #FFB59D;
--color-success: #16A34A;
--color-success-container: #E8F5E2;
--color-info: #2D628F;
--color-info-container: #E4F1FF;
--color-warning: #CA8A04;
--color-warning-container: #FFF3D6;
--font-headline: 'Manrope', sans-serif;
--font-body: 'Inter', sans-serif;
@ -175,6 +179,22 @@
--color-surface-selected: var(--color-surface-container);
--color-model-option-selected-bg: var(--color-primary-fixed);
--color-model-option-selected-border: rgba(143, 72, 47, 0.2);
--color-activity-heat-0: var(--color-surface-container);
--color-activity-heat-1: #FFE6DE;
--color-activity-heat-2: #FFBCA8;
--color-activity-heat-3: #E57A58;
--color-activity-heat-4: #8F482F;
--color-activity-cell-border: rgba(135, 115, 109, 0.3);
--color-activity-cell-border-hover: rgba(84, 67, 62, 0.72);
--color-activity-cell-border-active: var(--color-text-primary);
--color-activity-tooltip-surface: #2F312E;
--color-activity-tooltip-border: rgba(255, 255, 255, 0.12);
--color-activity-tooltip-text: #F2F1ED;
--color-activity-tooltip-muted: #FFB59D;
--color-success-container: #E8F5E2;
--color-info: #2D628F;
--color-info-container: #E4F1FF;
--color-warning-container: #FFF3D6;
--color-inspector-surface: #FBFAF6;
--color-inspector-panel: #F4F2ED;
--color-inspector-chip: #EBE7DF;
@ -335,13 +355,29 @@
--color-inverse-primary: #FFB59D;
--color-success: #087E4F;
--color-success-container: #DCFAE6;
--color-info: #0F5C8F;
--color-info-container: #D9ECFF;
--color-warning: #B54708;
--color-warning-container: #FEF0C7;
--color-surface-sidebar: #F1F4F7;
--color-surface-hover: #F2F5F8;
--color-surface-selected: #E9EEF3;
--color-model-option-selected-bg: #FFF0EA;
--color-model-option-selected-border: rgba(143, 72, 47, 0.2);
--color-activity-heat-0: #EEF2F6;
--color-activity-heat-1: #FFE9E1;
--color-activity-heat-2: #FFC4B1;
--color-activity-heat-3: #E57A58;
--color-activity-heat-4: #8F482F;
--color-activity-cell-border: rgba(139, 152, 167, 0.32);
--color-activity-cell-border-hover: rgba(75, 85, 99, 0.72);
--color-activity-cell-border-active: var(--color-text-primary);
--color-activity-tooltip-surface: #111827;
--color-activity-tooltip-border: rgba(255, 255, 255, 0.14);
--color-activity-tooltip-text: #F9FAFB;
--color-activity-tooltip-muted: #FFC4B1;
--color-inspector-surface: #FFFFFF;
--color-inspector-panel: #F7F9FB;
--color-inspector-chip: #EEF3F6;
@ -483,13 +519,29 @@
--color-inverse-primary: #8F482F;
--color-success: #7EDB8B;
--color-success-container: rgba(126, 219, 139, 0.16);
--color-info: #74E0F7;
--color-info-container: rgba(116, 224, 247, 0.16);
--color-warning: #F7C46C;
--color-warning-container: rgba(247, 196, 108, 0.16);
--color-surface-sidebar: #171615;
--color-surface-hover: var(--color-surface-container-highest);
--color-surface-selected: var(--color-surface-container);
--color-model-option-selected-bg: rgba(255, 181, 159, 0.13);
--color-model-option-selected-border: rgba(255, 181, 159, 0.34);
--color-activity-heat-0: #242322;
--color-activity-heat-1: #4A2B24;
--color-activity-heat-2: #85432F;
--color-activity-heat-3: #C85F3C;
--color-activity-heat-4: #FF8A5C;
--color-activity-cell-border: rgba(229, 226, 225, 0.08);
--color-activity-cell-border-hover: rgba(255, 181, 159, 0.48);
--color-activity-cell-border-active: #FBE7E1;
--color-activity-tooltip-surface: #F2F1ED;
--color-activity-tooltip-border: rgba(255, 181, 159, 0.32);
--color-activity-tooltip-text: #1B1C1A;
--color-activity-tooltip-muted: #8F3217;
--color-inspector-surface: #131211;
--color-inspector-panel: #1C1B1B;
--color-inspector-chip: #252120;

View File

@ -0,0 +1,55 @@
import { describe, expect, it } from 'vitest'
import css from './globals.css?raw'
function getThemeBlock(selector: ':root,\n[data-theme="light"]' | '[data-theme="white"]' | '[data-theme="dark"]') {
const start = css.indexOf(`${selector} {`)
expect(start).toBeGreaterThanOrEqual(0)
const bodyStart = css.indexOf('{', start)
let depth = 0
for (let index = bodyStart; index < css.length; index += 1) {
const char = css[index]
if (char === '{') depth += 1
if (char === '}') {
depth -= 1
if (depth === 0) {
return css.slice(bodyStart + 1, index)
}
}
}
throw new Error(`Theme block not closed: ${selector}`)
}
describe('desktop theme tokens', () => {
const themes = [':root,\n[data-theme="light"]', '[data-theme="white"]', '[data-theme="dark"]'] as const
const requiredTokens = [
'--color-activity-heat-0',
'--color-activity-heat-1',
'--color-activity-heat-2',
'--color-activity-heat-3',
'--color-activity-heat-4',
'--color-activity-cell-border',
'--color-activity-cell-border-hover',
'--color-activity-cell-border-active',
'--color-activity-tooltip-surface',
'--color-activity-tooltip-border',
'--color-activity-tooltip-text',
'--color-activity-tooltip-muted',
'--color-success-container',
'--color-info',
'--color-info-container',
'--color-warning-container',
]
it('defines activity and status tokens for every supported theme', () => {
for (const theme of themes) {
const block = getThemeBlock(theme)
for (const token of requiredTokens) {
expect(block, `${theme} should define ${token}`).toContain(`${token}:`)
}
}
})
})