fix: Bug - Sending files and attachment issues

Hydrate playable media after disk receive, relay file-announce to sibling
devices via account_sync, bind DM attachments to pre-allocated message ids,
and improve gallery retry/cancel UX with bounded parallel auto-downloads.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-14 13:05:23 +02:00
co-authored by Cursor
parent bb0ac930ad
commit fa45052432
30 changed files with 785 additions and 71 deletions
@@ -85,6 +85,61 @@ test.describe('Multi-device attachment sharing', () => {
await expect(getButton.first()).toBeVisible({ timeout: 20_000 });
});
});
test('relays file-announce metadata to a sibling device that is already online during upload', async ({
createClient
}) => {
const suffix = uniqueMultiDeviceName('attach-online');
const credentials = {
username: `online_${suffix}`,
displayName: 'Multi Device User',
password: MULTI_DEVICE_PASSWORD
};
const serverName = `Attachment Online Relay ${suffix}`;
const fileName = `${suffix}-relay.bin`;
const caption = `Uploaded while device B was online ${suffix}`;
const fileAttachment = createBinaryFilePayload(fileName, 'application/octet-stream', `relay-body-${suffix}`);
const clientA = await createClient();
const clientB = await createClient();
const messagesA = new ChatMessagesPage(clientA.page);
const messagesB = new ChatMessagesPage(clientB.page);
await test.step('device A registers and creates a server', async () => {
const registerPage = new RegisterPage(clientA.page);
await registerPage.goto();
await registerPage.register(credentials.username, credentials.displayName, credentials.password);
await expect(clientA.page).toHaveURL(/\/dashboard/, { timeout: 15_000 });
const search = new ServerSearchPage(clientA.page);
await search.createServer(serverName, { description: 'Sibling online file-announce relay coverage' });
await expect(clientA.page).toHaveURL(/\/room\//, { timeout: 15_000 });
await messagesA.waitForReady();
});
await test.step('device B logs into the same server before the upload starts', async () => {
await loginSecondDeviceIntoServer(clientB.page, credentials, serverName);
await clientA.page.bringToFront();
await messagesA.waitForReady();
await clientB.page.bringToFront();
await messagesB.waitForReady();
});
await test.step('device A uploads while device B is already in the room', async () => {
await clientA.page.bringToFront();
await messagesA.attachFiles([fileAttachment]);
await messagesA.sendMessage(caption);
await expect(messagesA.getMessageItemByText(caption)).toBeVisible({ timeout: 30_000 });
});
await test.step('device B learns attachment metadata without a server-rail click dance', async () => {
await clientB.page.bringToFront();
await expect(messagesB.getMessageItemByText(caption)).toBeVisible({ timeout: 90_000 });
await expect(messagesB.getMessageItemByText(caption).getByText(fileName, { exact: false }))
.toBeVisible({ timeout: 90_000 });
});
});
});
function createBinaryFilePayload(name: string, mimeType: string, content: string): ChatDropFilePayload {
@@ -0,0 +1,61 @@
import { test, expect } from '../../fixtures/multi-client';
import { RegisterPage } from '../../pages/register.page';
import { ServerSearchPage } from '../../pages/server-search.page';
import { ChatMessagesPage, type ChatDropFilePayload } from '../../pages/chat-messages.page';
test.describe('Multi-image gallery grouping', () => {
test.describe.configure({ timeout: 180_000 });
test('groups three images in one message bubble with a visible grid', async ({ createClient }) => {
const suffix = uniqueName('gallery');
const client = await createClient();
const registerPage = new RegisterPage(client.page);
const search = new ServerSearchPage(client.page);
const messages = new ChatMessagesPage(client.page);
const serverName = `Gallery Group ${suffix}`;
const imageNames = [
`${suffix}-one.svg`,
`${suffix}-two.svg`,
`${suffix}-three.svg`
];
const images = imageNames.map((name) => createSvgFilePayload(name));
await registerPage.goto();
await registerPage.register(`gallery_${suffix}`, 'Gallery User', 'TestPass123!');
await expect(client.page).toHaveURL(/\/dashboard/, { timeout: 15_000 });
await search.createServer(serverName, { description: 'Multi-image gallery regression server' });
await expect(client.page).toHaveURL(/\/room\//, { timeout: 15_000 });
await messages.waitForReady();
await messages.attachFiles(images);
await messages.sendPendingAttachments();
for (const imageName of imageNames) {
await messages.expectMessageImageLoaded(imageName);
}
const messageId = await messages.getMessageIdContainingImage(imageNames[0]);
expect(messageId).toBeTruthy();
const bubble = client.page.locator(`[data-message-id="${messageId}"]`);
await expect(bubble.locator('img[alt$=".svg"]')).toHaveCount(3, { timeout: 20_000 });
await expect(bubble.locator('.chat-image-grid')).toBeVisible({ timeout: 20_000 });
});
});
function uniqueName(prefix: string): string {
return `${prefix}-${Date.now()}-${Math.floor(Math.random() * 10_000)}`;
}
function createSvgFilePayload(name: string): ChatDropFilePayload {
const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><rect width="32" height="32" fill="#4A217A"/></svg>';
return {
name,
mimeType: 'image/svg+xml',
base64: Buffer.from(svg, 'utf8').toString('base64')
};
}