mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
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:
commit
b27ff74962
@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@ -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 }}
|
||||
/>
|
||||
))}
|
||||
|
||||
@ -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;
|
||||
|
||||
55
desktop/src/theme/globals.test.ts
Normal file
55
desktop/src/theme/globals.test.ts
Normal 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}:`)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user