Skip to content

Commit 890b4e4

Browse files
Dhawal RankaDhawal Ranka
authored andcommitted
merge hyperminimal redesign
2 parents 7ed451a + 8e50f06 commit 890b4e4

74 files changed

Lines changed: 2995 additions & 1571 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

mesh/e2e/authenticated-shell.spec.ts

Lines changed: 119 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,14 @@ async function installAuthenticatedMatrixMock(page: Page): Promise<void> {
273273
}
274274
case 'matrix_typing_users':
275275
return []
276+
case 'matrix_room_pins':
277+
return {
278+
roomId: String(args.roomId),
279+
eventIds: [],
280+
messages: [],
281+
unavailableEventIds: [],
282+
canManage: true,
283+
}
276284
case 'matrix_mark_read':
277285
case 'matrix_set_typing':
278286
case 'matrix_save_composer_draft':
@@ -423,14 +431,38 @@ test.describe('authenticated desktop shell', () => {
423431

424432
const context = page.getByRole('complementary', { name: 'Room context for general' })
425433
await expect(context).toBeVisible()
426-
await expect(context.getByRole('tab', { name: 'Ledger' })).toHaveAttribute('aria-selected', 'true')
434+
const peopleTab = context.getByRole('tab', { name: 'People' })
435+
const ledgerTab = context.getByRole('tab', { name: 'Ledger' })
436+
const filesTab = context.getByRole('tab', { name: 'Files' })
437+
const pinsTab = context.getByRole('tab', { name: 'Pins' })
438+
await expect(ledgerTab).toHaveAttribute('aria-selected', 'true')
439+
await expect(ledgerTab).toHaveAttribute('tabindex', '0')
440+
await expect(peopleTab).toHaveAttribute('tabindex', '-1')
441+
await expect(filesTab).toHaveAttribute('tabindex', '-1')
442+
await expect(pinsTab).toHaveAttribute('tabindex', '-1')
427443
await expect(context.getByText('Protected end to end')).toBeVisible()
428444
await expect(context.getByText('1 need review')).toBeVisible()
429445
await expect(context.getByText('Ready', { exact: true })).toBeVisible()
430446

431-
await context.getByRole('tab', { name: 'Pins' }).click()
447+
await ledgerTab.focus()
448+
await page.keyboard.press('ArrowRight')
449+
await expect(filesTab).toBeFocused()
450+
await expect(filesTab).toHaveAttribute('aria-selected', 'true')
451+
await expect(context.getByRole('tabpanel')).toHaveAttribute(
452+
'aria-labelledby',
453+
await filesTab.getAttribute('id') ?? '',
454+
)
455+
await page.keyboard.press('End')
456+
await expect(pinsTab).toBeFocused()
457+
await expect(pinsTab).toHaveAttribute('aria-selected', 'true')
432458
await expect(context.getByText('Nothing pinned yet')).toBeVisible()
433-
await context.getByRole('tab', { name: 'Files' }).click()
459+
await page.keyboard.press('Home')
460+
await expect(peopleTab).toBeFocused()
461+
await expect(peopleTab).toHaveAttribute('aria-selected', 'true')
462+
await page.keyboard.press('ArrowLeft')
463+
await expect(pinsTab).toBeFocused()
464+
await page.keyboard.press('ArrowLeft')
465+
await expect(filesTab).toBeFocused()
434466
await expect(context.getByText('No files shared yet')).toBeVisible()
435467

436468
await context.getByRole('button', { name: 'Close room context' }).click()
@@ -473,6 +505,27 @@ test.describe('authenticated desktop shell', () => {
473505
})
474506
})
475507

508+
test('hands Security focus back to the persistent User Settings trigger', async ({ page }) => {
509+
await openAuthenticatedShell(page)
510+
511+
const settingsButton = page.getByRole('button', { name: 'User settings' })
512+
await settingsButton.click()
513+
const settingsDialog = page.getByRole('dialog', { name: 'User Settings' })
514+
await expect(settingsDialog).toBeVisible()
515+
516+
await settingsDialog.getByRole('button', { name: 'Open your devices' }).click()
517+
const securityDialog = page.getByRole('dialog', { name: 'Your devices' })
518+
await expect(settingsDialog).toHaveCount(0)
519+
await expect(securityDialog).toBeVisible()
520+
await expect(page.getByRole('dialog')).toHaveCount(1)
521+
522+
await page.keyboard.press('Escape')
523+
524+
await expect(securityDialog).toHaveCount(0)
525+
await expect(settingsButton).toBeFocused()
526+
expect(await page.evaluate(() => document.activeElement === document.body)).toBe(false)
527+
})
528+
476529
test('opens community management as an accessible sheet and restores focus', async ({ page }) => {
477530
await openAuthenticatedShell(page)
478531

@@ -567,16 +620,42 @@ test.describe('authenticated desktop shell', () => {
567620
})
568621

569622
test.describe('authenticated narrow shell', () => {
570-
test.use({ viewport: { width: 390, height: 844 } })
623+
test.use({ viewport: { width: 390, height: 844 }, hasTouch: true })
571624

572625
test('@a11y has no automated WCAG A/AA violations with navigation open', async ({ page }) => {
573626
await openAuthenticatedShell(page)
574627
await page.getByRole('button', { name: 'Open room navigation' }).click()
575628
await expect(page.getByRole('button', { name: 'Close room navigation' })).toBeVisible()
629+
const navigationDrawer = page.locator('#mesh-context-sidebar')
630+
await expect(navigationDrawer).toHaveAttribute('aria-modal', 'true')
631+
expect(await navigationDrawer.evaluate((drawer) => drawer.contains(document.activeElement))).toBe(true)
632+
for (let index = 0; index < 10; index += 1) await page.keyboard.press('Tab')
633+
expect(await navigationDrawer.evaluate((drawer) => drawer.contains(document.activeElement))).toBe(true)
576634

577635
await expectNoWcagViolations(page, 'Authenticated narrow shell with room navigation')
578636
})
579637

638+
test('closes room navigation with Escape and restores its trigger', async ({ page }) => {
639+
await openAuthenticatedShell(page)
640+
const drawerButton = page.getByRole('button', { name: 'Open room navigation' })
641+
await drawerButton.click()
642+
await expect(page.locator('#mesh-context-sidebar')).toHaveAttribute('data-open', 'true')
643+
644+
await page.keyboard.press('Escape')
645+
646+
await expect(page.locator('#mesh-context-sidebar')).toHaveAttribute('data-open', 'false')
647+
await expect(drawerButton).toBeFocused()
648+
})
649+
650+
test('keeps message actions reachable on touch-only layouts', async ({ page }) => {
651+
await openAuthenticatedShell(page)
652+
const actions = page.locator('.mesh-message-actions').first()
653+
654+
await expect(actions).toHaveCSS('opacity', '1')
655+
await expect(actions).toHaveCSS('pointer-events', 'auto')
656+
await expect(actions.getByRole('button', { name: /React to message/ })).toBeVisible()
657+
})
658+
580659
test('opens the room drawer, changes rooms, and sends a message without overflow', async ({ page }) => {
581660
await openAuthenticatedShell(page)
582661

@@ -617,7 +696,20 @@ test.describe('authenticated narrow shell', () => {
617696
const context = page.getByRole('complementary', { name: 'Room context for general' })
618697
await expect(context).toBeVisible()
619698
await expect(page.locator('.mesh-room-context-backdrop')).toBeVisible()
620-
await context.getByRole('tab', { name: 'Ledger' }).click()
699+
expect(await context.evaluate((panel) => panel.contains(document.activeElement))).toBe(true)
700+
for (let index = 0; index < 8; index += 1) await page.keyboard.press('Tab')
701+
expect(await context.evaluate((panel) => panel.contains(document.activeElement))).toBe(true)
702+
const ledgerTab = context.getByRole('tab', { name: 'Ledger' })
703+
const peopleTab = context.getByRole('tab', { name: 'People' })
704+
const pinsTab = context.getByRole('tab', { name: 'Pins' })
705+
await ledgerTab.focus()
706+
await page.keyboard.press('ArrowLeft')
707+
await expect(peopleTab).toBeFocused()
708+
await page.keyboard.press('ArrowLeft')
709+
await expect(pinsTab).toBeFocused()
710+
await page.keyboard.press('Home')
711+
await expect(peopleTab).toBeFocused()
712+
await ledgerTab.click()
621713
await expect(context.getByText('Protected end to end')).toBeVisible()
622714
await expectNoWcagViolations(page, 'Authenticated narrow room context drawer')
623715

@@ -643,6 +735,28 @@ test.describe('authenticated narrow shell', () => {
643735
await expect(dialog).toHaveCount(0)
644736
})
645737

738+
test('hands narrow Security focus back to the persistent User Settings trigger', async ({ page }) => {
739+
await openAuthenticatedShell(page)
740+
741+
await page.getByRole('button', { name: 'Open room navigation' }).click()
742+
const settingsButton = page.getByRole('button', { name: 'User settings' })
743+
await settingsButton.click()
744+
const settingsDialog = page.getByRole('dialog', { name: 'User Settings' })
745+
await expect(settingsDialog).toBeVisible()
746+
747+
await settingsDialog.getByRole('button', { name: 'Open your devices' }).click()
748+
const securityDialog = page.getByRole('dialog', { name: 'Your devices' })
749+
await expect(settingsDialog).toHaveCount(0)
750+
await expect(securityDialog).toBeVisible()
751+
await expect(page.getByRole('dialog')).toHaveCount(1)
752+
753+
await page.keyboard.press('Escape')
754+
755+
await expect(securityDialog).toHaveCount(0)
756+
await expect(settingsButton).toBeFocused()
757+
expect(await page.evaluate(() => document.activeElement === document.body)).toBe(false)
758+
})
759+
646760
test('fits community management to the viewport and restores its drawer trigger', async ({ page }) => {
647761
await openAuthenticatedShell(page)
648762

mesh/e2e/design-system.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,23 @@ import { expectNoWcagViolations } from './helpers/accessibility'
33

44
const runtimeErrors = new WeakMap<Page, string[]>()
55

6+
function relativeLuminance(hex: string): number {
7+
const channels = [1, 3, 5].map((index) => Number.parseInt(hex.slice(index, index + 2), 16) / 255)
8+
const [red, green, blue] = channels.map((channel) =>
9+
channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4,
10+
)
11+
return (0.2126 * red) + (0.7152 * green) + (0.0722 * blue)
12+
}
13+
14+
function contrastRatio(foreground: string, background: string): number {
15+
const foregroundLuminance = relativeLuminance(foreground)
16+
const backgroundLuminance = relativeLuminance(background)
17+
return (
18+
(Math.max(foregroundLuminance, backgroundLuminance) + 0.05)
19+
/ (Math.min(foregroundLuminance, backgroundLuminance) + 0.05)
20+
)
21+
}
22+
623
async function sampleAmbientMotion(page: Page): Promise<string[]> {
724
const probe = page.locator('[data-ambient-motion-probe]')
825
await expect(probe).toBeAttached()
@@ -56,6 +73,46 @@ test('renders every supported theme and exposes keyboard-operable primitives', a
5673
await expect(page.getByRole('tabpanel').getByText('Detail content')).toBeVisible()
5774
})
5875

76+
test('keeps semantic foregrounds and focus indicators contrast-safe in light theme', async ({ page }) => {
77+
const lightTheme = page.locator('section[data-theme="light"]')
78+
const tokens = await lightTheme.evaluate((element) => {
79+
const styles = getComputedStyle(element)
80+
const value = (name: string) => styles.getPropertyValue(name).trim()
81+
return {
82+
canvas: value('--surface-canvas'),
83+
accent: value('--content-accent'),
84+
link: value('--content-link'),
85+
focus: value('--border-focus'),
86+
success: value('--status-success'),
87+
warning: value('--status-warning'),
88+
avatar: value('--avatar-sand'),
89+
onAvatar: value('--content-on-avatar'),
90+
}
91+
})
92+
93+
expect(contrastRatio(tokens.accent, tokens.canvas)).toBeGreaterThanOrEqual(4.5)
94+
expect(contrastRatio(tokens.link, tokens.canvas)).toBeGreaterThanOrEqual(4.5)
95+
expect(contrastRatio(tokens.success, tokens.canvas)).toBeGreaterThanOrEqual(4.5)
96+
expect(contrastRatio(tokens.warning, tokens.canvas)).toBeGreaterThanOrEqual(4.5)
97+
expect(contrastRatio(tokens.focus, tokens.canvas)).toBeGreaterThanOrEqual(3)
98+
expect(contrastRatio(tokens.onAvatar, tokens.avatar)).toBeGreaterThanOrEqual(4.5)
99+
})
100+
101+
test('renders the command palette as one compact surface', async ({ page }) => {
102+
await page.getByRole('button', { name: /Commands/ }).click()
103+
104+
const dialog = page.getByRole('dialog', { name: 'Command palette' })
105+
const input = dialog.getByRole('combobox', { name: 'Command palette' })
106+
const listbox = dialog.getByRole('listbox')
107+
await expect(dialog).toBeVisible()
108+
await expect(input).toBeFocused()
109+
await expect(listbox).toBeVisible()
110+
await expect(dialog).toHaveCSS('padding', '0px')
111+
await expect(input).toHaveCSS('border-top-width', '0px')
112+
await expect(listbox).toHaveCSS('position', 'static')
113+
await expect(listbox).toHaveCSS('box-shadow', 'none')
114+
})
115+
59116
test('runs ambient motion when the operating system allows it', async ({ page }) => {
60117
await page.emulateMedia({ reducedMotion: 'no-preference' })
61118
await page.reload()

mesh/e2e/matrix-messaging.spec.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,8 +456,15 @@ test.describe('Matrix direct messaging and encrypted attachments', () => {
456456
await expect(page.getByText('Message backup')).toBeVisible()
457457

458458
await page.getByRole('button', { name: 'Review devices and backup' }).click()
459-
await expect(page.getByRole('dialog', { name: 'Your devices' })).toBeVisible()
459+
const securityDialog = page.getByRole('dialog', { name: 'Your devices' })
460+
await expect(securityDialog).toBeVisible()
461+
await expect(page.getByRole('dialog')).toHaveCount(1)
460462
await expect(page.getByText('Message backup is ready')).toBeVisible()
463+
464+
await page.keyboard.press('Escape')
465+
await expect(securityDialog).toHaveCount(0)
466+
await expect(trustSummary).toBeFocused()
467+
expect(await page.evaluate(() => document.activeElement === document.body)).toBe(false)
461468
})
462469

463470
test('sends DM text through the dedicated Matrix direct-message command', async ({ page }) => {

mesh/e2e/onboarding-accessibility.spec.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,29 @@ test('@a11y has no automated WCAG A/AA violations on account creation', async ({
3636

3737
test('@a11y has no automated WCAG A/AA violations on sign in', async ({ page }) => {
3838
await page.getByRole('button', { name: 'Sign in', exact: true }).click()
39-
await expect(page.getByRole('heading', { name: 'Sign in somewhere else' })).toBeVisible()
39+
const signInHeading = page.getByRole('heading', { name: 'Sign in somewhere else' })
40+
await expect(signInHeading).toBeVisible()
4041
await waitForAccountScreenMotion(page)
42+
await expect(signInHeading).toBeFocused()
4143

4244
await expectNoWcagViolations(page, 'Sign In screen')
4345
})
4446

47+
test('keeps the primary account actions visible at a compact desktop height', async ({ page }) => {
48+
await page.setViewportSize({ width: 1280, height: 720 })
49+
50+
const createAccount = page.getByRole('button', { name: 'Create account' })
51+
const signIn = page.getByRole('button', { name: 'Sign in', exact: true })
52+
await expect(createAccount).toBeVisible()
53+
await expect(signIn).toBeVisible()
54+
55+
const createBounds = await createAccount.boundingBox()
56+
const signInBounds = await signIn.boundingBox()
57+
expect((createBounds?.y ?? 720) + (createBounds?.height ?? 0)).toBeLessThanOrEqual(720)
58+
expect((signInBounds?.y ?? 720) + (signInBounds?.height ?? 0)).toBeLessThanOrEqual(720)
59+
expect(await page.locator('main').evaluate((element) => element.scrollHeight <= element.clientHeight)).toBe(true)
60+
})
61+
4562
test('keeps trust context and account setup usable in a narrow window', async ({ page }) => {
4663
await page.setViewportSize({ width: 390, height: 844 })
4764

@@ -57,3 +74,20 @@ test('keeps trust context and account setup usable in a narrow window', async ({
5774
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390)
5875
await expectNoWcagViolations(page, 'Narrow account setup')
5976
})
77+
78+
test('keeps the onboarding masthead compact at tablet widths', async ({ page }) => {
79+
await page.setViewportSize({ width: 768, height: 1024 })
80+
await page.reload()
81+
82+
const shell = page.locator('[data-onboarding-shell]')
83+
const masthead = shell.locator('aside')
84+
await expect(shell).toBeVisible()
85+
await expect(masthead).toBeVisible()
86+
87+
const dimensions = await masthead.evaluate((element) => ({
88+
width: element.getBoundingClientRect().width,
89+
height: element.getBoundingClientRect().height,
90+
}))
91+
expect(dimensions.width).toBeLessThanOrEqual(768)
92+
expect(dimensions.height).toBeLessThan(240)
93+
})

mesh/scripts/check-design-tokens.mjs

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ const visualPatterns = [
6969
kind: 'box-shadow focus ring',
7070
expression: /\bfocus(?:-visible)?:ring(?:-[^\s'"]+)?/gi,
7171
},
72+
{
73+
kind: 'stock elevation class',
74+
expression: /\bshadow-(?:sm|md|lg|xl|2xl)\b/gi,
75+
},
7276
]
7377

7478
function visualViolations(source) {
@@ -95,11 +99,11 @@ function undersizedControlTokens(source) {
9599

96100
// Keep self-tests here so weakening either detector cannot silently pass.
97101
const detectorFixture = "colors: { bad: '#fff', worse: 'rgb(1 2 3)', alsoBad: 'oklch(50% 0.1 240)' }"
98-
const componentFixture = 'bg-[#fff] text-[11px] border-white/10 bg-yellow-500/10 font-bold text-xl var(--ref-neutral-1) oklch(50% 0.1 240) focus-visible:ring-2'
102+
const componentFixture = 'bg-[#fff] text-[11px] border-white/10 bg-yellow-500/10 font-bold text-xl var(--ref-neutral-1) oklch(50% 0.1 240) focus-visible:ring-2 shadow-lg'
99103
const densityFixture = '--density-control-sm: 28px; --density-control-md: 32px;'
100104
if (
101105
literalColorViolations(detectorFixture).length !== 3
102-
|| visualViolations(componentFixture).length !== 10
106+
|| visualViolations(componentFixture).length !== 11
103107
|| undersizedControlTokens(densityFixture).length !== 1
104108
) {
105109
throw new Error('Design-token checker self-test failed')
@@ -120,25 +124,25 @@ for (const match of rootBlock.matchAll(/^\s*(--[\w-]+)\s*:\s*([^;]+);/gm)) {
120124
}
121125

122126
const expectedReferenceColors = new Map([
123-
['--ref-neutral-1', '#101013'],
124-
['--ref-neutral-2', '#17181c'],
125-
['--ref-neutral-3', '#202127'],
126-
['--ref-neutral-4', '#27292f'],
127-
['--ref-neutral-5', '#2e3037'],
128-
['--ref-neutral-6', '#373941'],
129-
['--ref-neutral-7', '#454751'],
130-
['--ref-neutral-8', '#5d606b'],
131-
['--ref-neutral-9', '#6b6e79'],
132-
['--ref-neutral-10', '#797b86'],
133-
['--ref-neutral-11', '#b2b4bb'],
134-
['--ref-neutral-12', '#edeef2'],
135-
['--ref-accent-9', '#c19f66'],
136-
['--ref-accent-10', '#cfaf79'],
137-
['--ref-accent-11', '#e3c697'],
138-
['--ref-green-11', '#57bd72'],
139-
['--ref-red-11', '#ed756e'],
140-
['--ref-amber-11', '#edb345'],
141-
['--ref-blue-11', '#52b5f4'],
127+
['--ref-neutral-1', '#101113'],
128+
['--ref-neutral-2', '#1b1c1f'],
129+
['--ref-neutral-3', '#202125'],
130+
['--ref-neutral-4', '#27292e'],
131+
['--ref-neutral-5', '#2e3036'],
132+
['--ref-neutral-6', '#383a40'],
133+
['--ref-neutral-7', '#4a4d54'],
134+
['--ref-neutral-8', '#5a5e65'],
135+
['--ref-neutral-9', '#71757c'],
136+
['--ref-neutral-10', '#9fa3aa'],
137+
['--ref-neutral-11', '#c6c8cc'],
138+
['--ref-neutral-12', '#f0f1f2'],
139+
['--ref-accent-9', '#d4c0a1'],
140+
['--ref-accent-10', '#e3d4bb'],
141+
['--ref-accent-11', '#f1e8db'],
142+
['--ref-green-11', '#3dbe72'],
143+
['--ref-red-11', '#f06a73'],
144+
['--ref-amber-11', '#f0b232'],
145+
['--ref-blue-11', '#6fafff'],
142146
])
143147

144148
for (const [name, expected] of expectedReferenceColors) {

mesh/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ export default function App() {
314314

315315
if (isLoading && !showOnboarding) {
316316
return (
317-
<div className="flex min-h-screen items-center justify-center bg-bg-primary">
317+
<div className="flex min-h-screen items-center justify-center bg-surface-canvas">
318318
<div className="flex flex-col items-center gap-3">
319319
<Spinner size={24} />
320320
<p className="text-sm text-muted">Loading...</p>

0 commit comments

Comments
 (0)