Repository navigation
🩹 fix: Keep Chrome Surfaces and Destructive Confirms Legible in Quiet-Chrome Themes #16833
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b53aeb6
bd45a45
f5c8b6f
0eba442
b753f24
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| import { expect, test } from '@playwright/test'; | ||
| import type { Page } from '@playwright/test'; | ||
| import { clickHouseTheme } from '../../../../packages/client/src/theme/themes/clickhouse'; | ||
| import { NEW_CHAT_PATH } from '../helpers'; | ||
|
|
||
| /** | ||
| * Surfaces that stand in for a chrome outline. A theme whose `chromeBorderAlpha` is 0 draws no edge | ||
| * on chrome controls, so the chat header stops fading into the thread and takes the canvas. The | ||
| * bundled default theme keeps the gradient header. | ||
| */ | ||
|
|
||
| type Mode = 'light' | 'dark'; | ||
| type Surfaces = { headerImage: string; headerColor: string }; | ||
|
|
||
| const QUIET_ZERO_SPELLING = { | ||
| version: 1, | ||
| name: 'e2e-quiet-zero', | ||
| modes: { | ||
| light: { colors: {}, appearance: { chromeBorderAlpha: '0.0' } }, | ||
| dark: { colors: {}, appearance: { chromeBorderAlpha: '0.0' } }, | ||
| }, | ||
| } as const; | ||
|
|
||
| async function openChat(page: Page, mode: Mode, definition?: { name: string }) { | ||
| await page.addInitScript( | ||
| ([appearance, stored]) => { | ||
| localStorage.setItem('color-theme', appearance as string); | ||
| localStorage.removeItem('theme-colors'); | ||
| localStorage.removeItem('theme-name'); | ||
| if (stored) { | ||
| localStorage.setItem('theme-definition', JSON.stringify(stored)); | ||
| localStorage.setItem('theme-source', 'definition'); | ||
| } else { | ||
| localStorage.removeItem('theme-definition'); | ||
| localStorage.removeItem('theme-source'); | ||
| } | ||
| }, | ||
| [mode, definition ?? null] as [string, unknown], | ||
| ); | ||
| await page.setViewportSize({ width: 1440, height: 900 }); | ||
| await page.goto(NEW_CHAT_PATH, { timeout: 15000 }); | ||
| await expect(page.getByRole('textbox', { name: 'Message input' })).toBeVisible({ | ||
| timeout: 30000, | ||
| }); | ||
| const root = page.locator('html'); | ||
| if (definition) { | ||
| await expect(root).toHaveAttribute('data-theme', definition.name); | ||
| } else { | ||
| await expect(root).not.toHaveAttribute('data-theme'); | ||
| } | ||
| await expect(root).toHaveClass(mode === 'dark' ? /\bdark\b/ : /\blight\b/); | ||
| } | ||
|
|
||
| async function surfaces(page: Page): Promise<Surfaces> { | ||
| const header = page.locator('div[class~="theme-chrome-quiet:bg-none"]').first(); | ||
| await expect(header).toBeVisible(); | ||
| const [headerImage, headerColor] = await header.evaluate((node) => { | ||
| const style = getComputedStyle(node); | ||
| return [style.backgroundImage, style.backgroundColor]; | ||
| }); | ||
| return { headerImage, headerColor }; | ||
| } | ||
|
|
||
| test.describe('surfaces that replace a chrome outline', () => { | ||
| test('the default light theme keeps the gradient header @scenario:chrome-quiet-default-light-unchanged', async ({ | ||
| page, | ||
| }) => { | ||
| await openChat(page, 'light'); | ||
| const result = await surfaces(page); | ||
| expect(result.headerImage).toContain('linear-gradient'); | ||
| }); | ||
|
|
||
| test('the default dark theme keeps the gradient header @scenario:chrome-quiet-default-dark-unchanged', async ({ | ||
| page, | ||
| }) => { | ||
| await openChat(page, 'dark'); | ||
| const result = await surfaces(page); | ||
| expect(result.headerImage).toContain('linear-gradient'); | ||
| }); | ||
|
|
||
| test('the ClickHouse theme paints an opaque header @scenario:chrome-quiet-clickhouse-light', async ({ | ||
| page, | ||
| }) => { | ||
| await openChat(page, 'light', clickHouseTheme); | ||
| const result = await surfaces(page); | ||
| expect(result.headerImage).toBe('none'); | ||
| expect(result.headerColor).not.toBe('rgba(0, 0, 0, 0)'); | ||
| }); | ||
|
|
||
| test('the ClickHouse dark theme paints an opaque header @scenario:chrome-quiet-clickhouse-dark', async ({ | ||
| page, | ||
| }) => { | ||
| await openChat(page, 'dark', clickHouseTheme); | ||
| const result = await surfaces(page); | ||
| expect(result.headerImage).toBe('none'); | ||
| expect(result.headerColor).not.toBe('rgba(0, 0, 0, 0)'); | ||
| }); | ||
|
|
||
| test('a theme that spells the zero chrome alpha as 0.0 still paints the opaque header @scenario:chrome-quiet-zero-spelling', async ({ | ||
| page, | ||
| }) => { | ||
| await openChat(page, 'light', QUIET_ZERO_SPELLING); | ||
| const result = await surfaces(page); | ||
| expect(result.headerImage).toBe('none'); | ||
| expect(result.headerColor).not.toBe('rgba(0, 0, 0, 0)'); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,9 +15,11 @@ type ButtonVariantOptions = | |
| | 'submit' | ||
| | 'outline' | ||
| | 'outline-toggle' | ||
| | 'floating' | ||
| | 'choice' | ||
| | 'subtle' | ||
| | 'destructive' | ||
| | 'destructive-soft' | ||
| | 'secondary' | ||
| | 'ghost' | ||
| | 'quiet' | ||
|
|
@@ -71,9 +73,23 @@ const buttonVariantRecipe = cva( | |
| default: | ||
| 'bg-button-primary text-text-inverted hover:bg-button-primary-hover hover:active:bg-surface-inverted-pressed', | ||
| destructive: | ||
| 'bg-surface-destructive text-text-on-status hover:bg-surface-destructive-hover', | ||
|
Comment on lines
75
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When 2FA is enabled, AGENTS.md reference: AGENTS.md:L144-L148 Useful? React with 👍 / 👎.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in b753f24: the enabled arm of DisableTwoFactorToggle uses destructive-soft; a grep of destructive string literals outside the variant attribute finds no other caller. Precheck and scenarios pass at this head. |
||
| /** | ||
| * A destructive action offered inline, such as a row's delete or a revoke beside its | ||
| * label. A theme whose `destructiveStyle` is `soft` tints it; the confirming button of a | ||
| * destructive dialog stays `destructive`, the strongest action on screen. | ||
| */ | ||
| 'destructive-soft': | ||
| 'bg-surface-destructive text-text-on-status hover:bg-surface-destructive-hover theme-destructive-soft:bg-surface-destructive/10 theme-destructive-soft:text-text-destructive theme-destructive-soft:hover:bg-surface-destructive/14 theme-destructive-soft:hover:active:bg-surface-destructive/17', | ||
| outline: | ||
| 'text-text-primary border border-border-light bg-transparent hover:bg-surface-hover hover:active:bg-surface-pressed hover:text-text-primary', | ||
| /** | ||
| * A control floating over scrolling content, such as the scroll-to-bottom chip. A theme that | ||
| * draws no chrome outline gives it an opaque fill and a lift instead, so it never reads as a | ||
| * bare glyph over the thread. | ||
| */ | ||
| floating: | ||
| 'border border-border-chrome bg-surface-chat/90 text-text-primary hover:bg-surface-hover hover:active:bg-surface-pressed theme-chrome-quiet:bg-surface-chat theme-chrome-quiet:shadow-md theme-chrome-quiet:hover:bg-surface-hover theme-chrome-quiet:hover:active:bg-surface-pressed', | ||
| /** An outlined filter whose pressed state stays visible between activations. */ | ||
| 'outline-toggle': | ||
| 'text-text-primary border border-border-control bg-transparent transition-none hover:bg-surface-hover hover:active:bg-surface-pressed hover:text-text-primary aria-pressed:border-border-heavy aria-pressed:bg-surface-active-alt aria-pressed:hover:bg-surface-active-alt', | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
destructiveStyleissoft(as in the ClickHouse theme), removing the theme classes fromdestructivemakes every unconverted caller solid. The migration misses several clear inline dialog triggers, includingNav/SettingsTabs/ApiKeys/Item.tsx:60,Nav/SettingsTabs/Data/RevokeKeys.tsx:45,Nav/SettingsTabs/Data/DeleteCache.tsx:50, andSkills/dialogs/DeleteSkill.tsx:65; these now look like dominant confirmation buttons before the confirmation dialog is opened, contrary to the new inline-versus-confirm contract. Convert the remaining inline delete/revoke triggers todestructive-softwhile leaving their confirmation actions asdestructive.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in a744f61: the five remaining inline triggers use destructive-soft; the callers left on destructive are dialog confirms. Covered by Button.spec and the precheck at bef3482.