Skip to content

Commit 72f093c

Browse files
authored
feat: complete Mesh UX and Matrix hardening
Merge the verified Mesh UX, Matrix hardening, and timezone-independent grouping test into main.
2 parents a374a70 + 97c2428 commit 72f093c

131 files changed

Lines changed: 7879 additions & 1471 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: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ async function installAuthenticatedMatrixMock(
4343
service: parsed.searchParams.get('community_service')
4444
?? parsed.searchParams.get('service'),
4545
admissionService: parsed.searchParams.get('admission'),
46+
communityName: 'Invited Mesh Community',
47+
inviterDisplayName: 'Bob',
48+
inviterUserId: '@bob:mesh.test',
49+
joinRule: 'public',
50+
communityServiceDisplayName: 'Matrix Test Service',
4651
storedAt: 1_752_000_000_000,
4752
expiresAt: 1_754_592_000_000,
4853
}
@@ -415,14 +420,18 @@ async function openAuthenticatedShell(
415420
): Promise<void> {
416421
await installAuthenticatedMatrixMock(page, currentDeepLinks)
417422
await page.goto('/')
423+
if (currentDeepLinks?.length) {
424+
await expect(
425+
page.getByRole('dialog', { name: 'Review community invitation' }),
426+
).toBeVisible({ timeout: 10_000 })
427+
return
428+
}
418429
await expect(
419430
page.getByRole('navigation', { name: 'Communities and direct messages' }),
420431
).toBeVisible({ timeout: 10_000 })
421-
if (!currentDeepLinks?.length) {
422-
await expect(page.getByRole('log', { name: 'Messages in #general' })).toBeVisible({
423-
timeout: 10_000,
424-
})
425-
}
432+
await expect(page.getByRole('log', { name: 'Messages in #general' })).toBeVisible({
433+
timeout: 10_000,
434+
})
426435
}
427436

428437
function ipcCalls(page: Page): Promise<IpcCall[]> {
@@ -460,6 +469,16 @@ test.describe('authenticated desktop shell', () => {
460469
'mesh://join?v=3&kind=matrix&room=!invited:mesh.test&via=mesh.test&service=https%3A%2F%2Fmatrix.mesh.test'
461470
await openAuthenticatedShell(page, [invite])
462471

472+
const review = page.getByRole('dialog', { name: 'Review community invitation' })
473+
await expect(review).toBeVisible()
474+
await expect(review.getByText('Invited Mesh Community', { exact: true })).toBeVisible()
475+
await expect(review.getByText('Invited by Bob', { exact: true })).toBeVisible()
476+
await expect(review.getByText('Matrix Test Service', { exact: true })).toBeVisible()
477+
await expect.poll(async () => (
478+
(await ipcCalls(page)).filter((call) => call.command === 'matrix_join_community')
479+
)).toEqual([])
480+
481+
await review.getByRole('button', { name: 'Confirm and continue' }).click()
463482
await expect.poll(async () => (
464483
(await ipcCalls(page)).filter((call) => call.command === 'matrix_join_community')
465484
)).toEqual([{
@@ -469,6 +488,9 @@ test.describe('authenticated desktop shell', () => {
469488
via: ['mesh.test'],
470489
},
471490
}])
491+
await expect(
492+
page.getByRole('navigation', { name: 'Communities and direct messages' }),
493+
).toBeVisible()
472494
await expect.poll(async () => (
473495
(await ipcCalls(page))
474496
.filter((call) => [
@@ -489,7 +511,7 @@ test.describe('authenticated desktop shell', () => {
489511
'aria-current',
490512
'page',
491513
)
492-
await expect(page.getByRole('dialog', { name: 'Join a community' })).toHaveCount(0)
514+
await expect(review).toHaveCount(0)
493515

494516
})
495517

@@ -672,11 +694,21 @@ test.describe('authenticated desktop shell', () => {
672694
await openAuthenticatedShell(page)
673695

674696
const randomChannel = page.getByRole('button', {
675-
name: 'Text room: random, 1 unread',
697+
name: /^Text room: random/,
676698
})
677699
await randomChannel.click({ button: 'right' })
678700
const menu = page.getByRole('menu', { name: 'Actions for random' })
679-
await expect(menu.getByRole('menuitem', { name: 'Mute for 15 minutes' })).toBeVisible()
701+
await menu.getByRole('menuitem', { name: 'Mute notifications' }).click()
702+
703+
await expect.poll(async () => ipcCalls(page)).toContainEqual({
704+
command: 'matrix_set_room_notification_mode',
705+
args: {
706+
roomId: '!random:mesh.test',
707+
mode: 'nothing',
708+
},
709+
})
710+
711+
await randomChannel.click({ button: 'right' })
680712
await menu
681713
.getByRole('menuitem', { name: 'Notifications: Only @mentions' })
682714
.click()

mesh/e2e/dm-message-keyboard-actions.spec.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,10 @@ test.describe('DM message action bar keyboard access (V-25 follow-up)', () => {
313313

314314
const bobRow = page.getByRole('group', { name: /^Message from Bob,/ })
315315
const ownRow = page.getByRole('group', { name: /^Message from alice,/ })
316-
const reactButtonBob = bobRow.getByRole('button', { name: 'Add reaction' })
317-
const replyButtonBob = bobRow.getByRole('button', { name: 'Reply to message' })
316+
// DMs now use the shared channel row, whose accessible action names
317+
// include the author so repeated controls remain distinguishable.
318+
const reactButtonBob = bobRow.getByRole('button', { name: 'React to message from Bob' })
319+
const replyButtonBob = bobRow.getByRole('button', { name: 'Reply to Bob' })
318320
const editButtonOwn = ownRow.getByRole('button', { name: 'Edit message' })
319321
// Opacity/pointer-events live on the action bar's wrapper div, not the
320322
// buttons themselves — assert visibility there, matching Message.tsx's
@@ -328,15 +330,17 @@ test.describe('DM message action bar keyboard access (V-25 follow-up)', () => {
328330

329331
// --- React (on Bob's message) ---
330332
await page.keyboard.press('Enter')
331-
const thumbsUp = page.getByRole('button', { name: '👍', exact: true })
333+
const thumbsUp = page.getByRole('button', { name: 'React with thumbs up', exact: true })
332334
await tabUntilFocused(page, thumbsUp, 'forward')
333335
await page.keyboard.press('Enter')
334336

335337
await expect.poll(async () => ipcCalls(page)).toContainEqual({
336338
command: 'matrix_toggle_reaction',
337339
args: { roomId: '!alice-bob-dm:mesh.test', eventId: '$dm-history', key: '👍' },
338340
})
339-
await expect(bobRow.getByRole('button', { name: '👍 1' })).toBeVisible()
341+
await expect(
342+
bobRow.getByRole('button', { name: /1 reaction, you reacted/ }),
343+
).toBeVisible()
340344

341345
// --- Reply (to Bob's message) ---
342346
await tabUntilFocused(page, replyButtonBob, 'forward')
@@ -375,12 +379,12 @@ test.describe('DM message action bar keyboard access (V-25 follow-up)', () => {
375379
await expect(page.getByText("A DM Bob didn't send to himself.")).toBeVisible()
376380

377381
const bobRow = page.getByRole('group', { name: /^Message from Bob,/ })
378-
const reactButtonBob = bobRow.getByRole('button', { name: 'Add reaction' })
382+
const reactButtonBob = bobRow.getByRole('button', { name: 'React to message from Bob' })
379383
await tabUntilFocused(page, reactButtonBob, 'backward')
380384

381385
await expect(reactButtonBob).toHaveAttribute('aria-expanded', 'false')
382386
await page.keyboard.press('Enter')
383-
const thumbsUp = page.getByRole('button', { name: '👍', exact: true })
387+
const thumbsUp = page.getByRole('button', { name: 'React with thumbs up', exact: true })
384388
await expect(thumbsUp).toBeVisible()
385389
await expect(reactButtonBob).toHaveAttribute('aria-expanded', 'true')
386390

@@ -389,7 +393,7 @@ test.describe('DM message action bar keyboard access (V-25 follow-up)', () => {
389393
await expect(reactButtonBob).toBeFocused()
390394
await expect(reactButtonBob).toHaveAttribute('aria-expanded', 'false')
391395
// No emoji was picked — the picker was dismissed, not activated.
392-
await expect(bobRow.getByRole('button', { name: '👍 1' })).toHaveCount(0)
396+
await expect(bobRow.getByRole('button', { name: /1 reaction, you reacted/ })).toHaveCount(0)
393397
expect(await ipcCalls(page)).not.toContainEqual(
394398
expect.objectContaining({ command: 'matrix_toggle_reaction' }),
395399
)
@@ -400,7 +404,7 @@ test.describe('DM message action bar keyboard access (V-25 follow-up)', () => {
400404
await expect(page.getByText("A DM Bob didn't send to himself.")).toBeVisible()
401405

402406
const bobRow = page.getByRole('group', { name: /^Message from Bob,/ })
403-
const reactButtonBob = bobRow.getByRole('button', { name: 'Add reaction' })
407+
const reactButtonBob = bobRow.getByRole('button', { name: 'React to message from Bob' })
404408
const actionBarBob = reactButtonBob.locator('xpath=..')
405409
await expect(actionBarBob).toHaveCSS('opacity', '0')
406410

mesh/e2e/matrix-messaging.spec.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,17 @@ async function installAuthenticatedMatrixMessagingMock(page: Page): Promise<void
253253
dmTimeline.push(message)
254254
return message
255255
}
256+
case 'matrix_send_message': {
257+
// DM text now uses the same durable room queue as channels. Keep
258+
// attachment sends on the dedicated protected-DM command below.
259+
const message = directMessage(String(args.body))
260+
dmTimeline.push(message)
261+
return {
262+
...message,
263+
channelId: conversation.id,
264+
transactionId: String(args.transactionId),
265+
}
266+
}
256267
case 'matrix_send_dm_attachment': {
257268
const filename = args.attachmentGrant === 'grant-mesh-beta'
258269
? 'mesh-beta.pdf'
@@ -469,7 +480,7 @@ test.describe('Matrix direct messaging and encrypted attachments', () => {
469480
expect(await page.evaluate(() => document.activeElement === document.body)).toBe(false)
470481
})
471482

472-
test('sends DM text through the dedicated Matrix direct-message command', async ({ page }) => {
483+
test('sends DM text through the durable Matrix room queue', async ({ page }) => {
473484
await openDirectMessage(page)
474485

475486
const composer = page.getByRole('textbox', { name: 'Message Bob' })
@@ -483,13 +494,14 @@ test.describe('Matrix direct messaging and encrypted attachments', () => {
483494

484495
const calls = await ipcCalls(page)
485496
expect(calls).toContainEqual({
486-
command: 'matrix_send_dm',
497+
command: 'matrix_send_message',
487498
args: expect.objectContaining({
488-
recipientUserId: '@bob:mesh.test',
499+
roomId: '!alice-bob-dm:mesh.test',
489500
body: 'A production-path encrypted DM',
501+
transactionId: expect.any(String),
490502
}),
491503
})
492-
expect(calls.some((call) => call.command === 'matrix_send_message')).toBe(false)
504+
expect(calls.some((call) => call.command === 'matrix_send_dm')).toBe(false)
493505
})
494506

495507
test('selects and sends a DM attachment through the native dialog and encrypted Matrix IPC', async ({ page }) => {
@@ -532,7 +544,12 @@ test.describe('Matrix direct messaging and encrypted attachments', () => {
532544
await openDirectMessage(page)
533545

534546
await page.getByRole('button', { name: 'Download encrypted-plan.pdf' }).click()
535-
const openButton = page.getByRole('button', { name: 'Open encrypted-plan.pdf' })
547+
// The shared channel/DM row also exposes the protected preview button;
548+
// exact matching selects the downloaded-file action.
549+
const openButton = page.getByRole('button', {
550+
name: 'Open encrypted-plan.pdf',
551+
exact: true,
552+
})
536553
await expect(openButton).toBeVisible()
537554
await openButton.click()
538555

mesh/e2e/message-keyboard-actions.spec.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -300,15 +300,17 @@ test.describe('message action bar keyboard access (V-25)', () => {
300300

301301
// --- React ---
302302
await page.keyboard.press('Enter')
303-
const thumbsUp = page.getByRole('button', { name: '👍', exact: true })
303+
const thumbsUp = page.getByRole('button', { name: 'React with thumbs up', exact: true })
304304
await tabUntilFocused(page, thumbsUp, 'forward')
305305
await page.keyboard.press('Enter')
306306

307307
await expect.poll(async () => ipcCalls(page)).toContainEqual({
308308
command: 'matrix_toggle_reaction',
309309
args: { roomId: '!general:mesh.test', eventId: '$bob-message', key: '👍' },
310310
})
311-
await expect(page.getByRole('button', { name: '👍 1' })).toBeVisible()
311+
// Reaction buttons now expose count and ownership as a complete accessible
312+
// name instead of relying on their abbreviated visible text.
313+
await expect(page.getByRole('button', { name: /👍, 1 reaction, you reacted/ })).toBeVisible()
312314

313315
// --- Reply ---
314316
await tabUntilFocused(page, replyButton, 'forward')
@@ -344,7 +346,7 @@ test.describe('message action bar keyboard access (V-25)', () => {
344346
// --- Reaction picker: Escape closes it and returns focus to the trigger ---
345347
await expect(reactButton).toHaveAttribute('aria-expanded', 'false')
346348
await page.keyboard.press('Enter')
347-
const thumbsUp = page.getByRole('button', { name: '👍', exact: true })
349+
const thumbsUp = page.getByRole('button', { name: 'React with thumbs up', exact: true })
348350
await expect(thumbsUp).toBeVisible()
349351
await expect(reactButton).toHaveAttribute('aria-expanded', 'true')
350352

@@ -353,7 +355,7 @@ test.describe('message action bar keyboard access (V-25)', () => {
353355
await expect(reactButton).toBeFocused()
354356
await expect(reactButton).toHaveAttribute('aria-expanded', 'false')
355357
// No emoji was picked — the picker was dismissed, not activated.
356-
await expect(page.getByRole('button', { name: '👍 1' })).toHaveCount(0)
358+
await expect(page.getByRole('button', { name: /👍, 1 reaction, you reacted/ })).toHaveCount(0)
357359

358360
// --- Context menu: Shift+F10 moves focus into the first menu item ---
359361
await page.keyboard.press('Shift+F10')

mesh/e2e/onboarding-accessibility.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ test('@a11y has no automated WCAG A/AA violations on account-service selection',
240240
})
241241

242242
test('@a11y has no automated WCAG A/AA violations on sign in', async ({ page }) => {
243-
await page.getByRole('button', { name: 'Choose Matrix.org' }).click()
243+
await page.getByRole('button', { name: 'Sign in with Matrix.org' }).click()
244244
const signInHeading = page.getByRole('heading', { name: 'Sign in to Matrix.org' })
245245
await expect(signInHeading).toBeVisible()
246246
await waitForAccountScreenMotion(page)
@@ -267,10 +267,10 @@ for (const viewport of [
267267
await expect(control).toBeInViewport()
268268
}
269269

270-
await assertReachable('Choose Matrix.org')
270+
await assertReachable('Sign in with Matrix.org')
271271
await assertReachable('More public services')
272272
await assertReachable('Use another service')
273-
await page.getByRole('button', { name: 'Choose Matrix.org' }).click()
273+
await page.getByRole('button', { name: 'Sign in with Matrix.org' }).click()
274274
await page.getByRole('textbox', { name: 'Username' }).fill('compact-user')
275275
await page.locator('input[name="password"]').fill('a long compact passphrase')
276276
await expect(page.getByRole('button', { name: 'Sign in', exact: true })).toBeEnabled()
@@ -390,8 +390,8 @@ test('prefills and resolves a cold-start invitation before account creation', as
390390
command: 'resolve_pending_invitation',
391391
args: {},
392392
}])
393-
await expect(page.getByRole('button', { name: 'Choose Matrix.org' })).toBeVisible()
394-
await page.getByRole('button', { name: 'Choose community-hosted service' }).click()
393+
await expect(page.getByRole('button', { name: 'Sign in with Matrix.org' })).toBeVisible()
394+
await page.getByRole('button', { name: 'Create account with community.example' }).click()
395395
await expect(page.getByText('Invitation saved securely on this device')).toBeVisible()
396396
await expect(page.getByText('Community target: !invited:friends.example.')).toBeVisible()
397397
await expect(page.getByRole('textbox', { name: 'Invitation code' })).toHaveCount(0)
@@ -405,7 +405,7 @@ test('keeps trust context and account setup usable in a narrow window', async ({
405405
await expect(shell).toBeVisible()
406406
await expect(page.getByText('Conversations that stay yours.')).toBeVisible()
407407
await expect(page.getByRole('list', { name: 'Setup progress' })).toBeVisible()
408-
await expect(page.getByRole('button', { name: 'Choose Matrix.org' })).toBeVisible()
408+
await expect(page.getByRole('button', { name: 'Sign in with Matrix.org' })).toBeVisible()
409409

410410
const bounds = await shell.boundingBox()
411411
expect(bounds?.x).toBeGreaterThanOrEqual(0)

mesh/e2e/runtime-budgets.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ test('keeps the default onboarding path within wider-beta runtime budgets', asyn
2828

2929
const startedAt = Date.now()
3030
await page.goto('/')
31-
await expect(page.getByRole('button', { name: 'Choose Matrix.org' })).toBeVisible({
31+
await expect(page.getByRole('button', { name: 'Sign in with Matrix.org' })).toBeVisible({
3232
timeout: STARTUP_READY_BUDGET_MS,
3333
})
3434
const readyMs = Date.now() - startedAt

mesh/src-tauri/src/app_runtime/helpers.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub fn signed_file_announcement_to_message(
3939
edited_at: None,
4040
deleted_at: None,
4141
reply_to_id: None,
42+
thread_root_id: None,
4243
transaction_id: None,
4344
client_request_id: None,
4445
delivery_status: None,

mesh/src-tauri/src/app_runtime/history.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ pub(super) fn reconstruct_messages_from_events(
245245
edited_at: None,
246246
deleted_at: None,
247247
reply_to_id,
248+
thread_root_id: None,
248249
transaction_id: None,
249250
client_request_id: None,
250251
delivery_status: None,
@@ -523,6 +524,7 @@ mod tests {
523524
edited_at: None,
524525
deleted_at: None,
525526
reply_to_id,
527+
thread_root_id: None,
526528
transaction_id: None,
527529
client_request_id: None,
528530
delivery_status: None,

mesh/src-tauri/src/app_runtime/message_handler.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ pub(super) async fn route_signed_message(app_handle: &AppHandle, envelope: &Sign
7373
edited_at: None,
7474
deleted_at: None,
7575
reply_to_id: payload.reply_to_id,
76+
thread_root_id: None,
7677
transaction_id: None,
7778
client_request_id: None,
7879
delivery_status: None,

mesh/src-tauri/src/app_runtime/network_router.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,7 @@ async fn route_incoming_dm(app_handle: &AppHandle, envelope: &SignedEnvelope) {
776776
edited_at: None,
777777
deleted_at: None,
778778
reply_to_id: None,
779+
thread_root_id: None,
779780
delivery_status: Some("sent".into()),
780781
};
781782

0 commit comments

Comments
 (0)