Improve attachment memory safety, downloads, and high-memory alert UX.
Queue Release Build / prepare (push) Successful in 20s
Deploy Web Apps / deploy (push) Successful in 9m2s
Queue Release Build / build-windows (push) Successful in 28m8s
Queue Release Build / build-linux (push) Successful in 47m26s
Queue Release Build / build-android (push) Successful in 19m52s
Queue Release Build / finalize (push) Successful in 4m42s

Stream large receives to disk with chunk acks to cap renderer RAM, evict
off-screen display blobs, and route exports through a disk-aware download
service. Fix the high-memory dialog (backdrop dismiss, copy, log actions),
allow diagnostics paths in the path jail, and restore persisted image
hydration after reload.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-14 00:25:22 +02:00
co-authored by Cursor
parent f0d79aa627
commit bb0ac930ad
69 changed files with 2306 additions and 430 deletions
@@ -10,6 +10,7 @@ import { RealtimeSessionFacade } from '../../../../core/realtime';
import { selectCurrentUserId } from '../../../../store/users/users.selectors';
import { DatabaseService } from '../../../../infrastructure/persistence';
import { yieldToAttachmentHydrationLoop } from '../../domain/logic/attachment-blob.rules';
import { buildAttachmentDisplayPinKey, shouldRevokeDisplayBlobForAttachment } from '../../domain/logic/attachment-blob-eviction.rules';
import {
getWatchedAttachmentRoomIdFromUrl,
isDirectMessageAttachmentRoomId,
@@ -20,6 +21,7 @@ import type {
FileAnnouncePayload,
FileCancelPayload,
FileChunkPayload,
FileChunkAckPayload,
FileNotFoundPayload,
FileRequestPayload
} from '../../domain/models/attachment-transfer.model';
@@ -44,6 +46,7 @@ export class AttachmentManagerService {
private watchedRoomId: string | null = this.extractWatchedRoomId(this.router.url);
private isDatabaseInitialised = false;
private autoDownloadRequestsByRoom = new Map<string, Promise<void>>();
private pinnedDisplayBlobKeys = new Set<string>();
constructor() {
effect(() => {
@@ -160,6 +163,48 @@ export class AttachmentManagerService {
return restored;
}
pinDisplayBlobs(attachments: readonly Pick<Attachment, 'id' | 'messageId'>[]): void {
for (const attachment of attachments) {
if (!attachment.messageId || !attachment.id) {
continue;
}
this.pinnedDisplayBlobKeys.add(buildAttachmentDisplayPinKey(attachment.messageId, attachment.id));
}
}
unpinDisplayBlobs(attachments: readonly Pick<Attachment, 'id' | 'messageId'>[]): void {
for (const attachment of attachments) {
if (!attachment.messageId || !attachment.id) {
continue;
}
this.pinnedDisplayBlobKeys.delete(buildAttachmentDisplayPinKey(attachment.messageId, attachment.id));
}
}
revokeOffscreenDisplayBlobsForMessage(messageId: string): void {
if (!messageId) {
return;
}
let hasChanges = false;
for (const attachment of this.runtimeStore.getAttachmentsForMessage(messageId)) {
if (!shouldRevokeDisplayBlobForAttachment(messageId, attachment, this.pinnedDisplayBlobKeys)) {
continue;
}
if (this.persistence.revokeAttachmentDisplayBlob(attachment)) {
hasChanges = true;
}
}
if (hasChanges) {
this.runtimeStore.touch();
}
}
requestFile(messageId: string, attachment: Attachment): Promise<void> {
return this.transfer.requestFile(messageId, attachment);
}
@@ -173,9 +218,9 @@ export class AttachmentManagerService {
}
handleFileAnnounce(payload: FileAnnouncePayload): void {
this.transfer.handleFileAnnounce(payload);
const isNew = this.transfer.handleFileAnnounce(payload);
if (payload.messageId && payload.file?.id) {
if (isNew && payload.messageId && payload.file?.id) {
this.queueAutoDownloadsForMessage(payload.messageId, payload.file.id);
}
}
@@ -184,6 +229,10 @@ export class AttachmentManagerService {
this.transfer.handleFileChunk(payload);
}
handleFileChunkAck(payload: FileChunkAckPayload): void {
this.transfer.handleFileChunkAck(payload);
}
async handleFileRequest(payload: FileRequestPayload): Promise<void> {
await this.transfer.handleFileRequest(payload);
}
@@ -218,7 +267,7 @@ export class AttachmentManagerService {
for (const messageId of messageIds) {
for (const attachment of this.runtimeStore.getAttachmentsForMessage(messageId)) {
if (await this.persistence.tryRestoreAttachmentFromLocal(attachment)) {
if (await this.persistence.tryRestoreAttachmentHostOnly(attachment)) {
hasChanges = true;
await yieldToAttachmentHydrationLoop();
}