From 88a773e4cd297756fba81aa5652ecbdcf8daee4d Mon Sep 17 00:00:00 2001 From: tarik02 Date: Sat, 1 Aug 2026 18:32:42 +0000 Subject: [PATCH 1/2] perf(web): skip base64 for oversized image candidates --- apps/web/src/lib/imageCompression.ts | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/apps/web/src/lib/imageCompression.ts b/apps/web/src/lib/imageCompression.ts index 1c57fdad1a5..be45024f38c 100644 --- a/apps/web/src/lib/imageCompression.ts +++ b/apps/web/src/lib/imageCompression.ts @@ -140,27 +140,28 @@ function createCanvas(width: number, height: number): Canvas2D | null { * and it keeps alpha, so screenshots with transparency survive intact. * Browsers that can't encode it silently fall back to JPEG. */ -async function encodeToDataUrl( +async function encodeCanvas( canvas: OffscreenCanvas | HTMLCanvasElement, quality: number, mimeType: string, -): Promise<{ dataUrl: string; mimeType: string } | null> { + budgetChars: number, +): Promise<{ dataUrl: string | null; mimeType: string } | null> { if (typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement) { const dataUrl = canvas.toDataURL(mimeType, quality); // toDataURL silently returns a PNG when the requested type is unsupported. if (!dataUrl.startsWith(`data:${mimeType}`)) return null; - return { dataUrl, mimeType }; + return { dataUrl: dataUrl.length <= budgetChars ? dataUrl : null, mimeType }; } const blob = await (canvas as OffscreenCanvas).convertToBlob({ type: mimeType, quality }); if (blob.type && blob.type !== mimeType) return null; + const dataUrlLength = `data:${mimeType};base64,`.length + 4 * Math.ceil(blob.size / 3); + if (dataUrlLength > budgetChars) return { dataUrl: null, mimeType }; return { dataUrl: await blobToDataUrl(blob, mimeType), mimeType }; } /** * Draws `bitmap` scaled to fit `maxDimension` and encodes it, stepping - * quality down until the data URL fits `budgetChars`. Returns the smallest - * encoding produced, even if it still exceeds the budget, so the caller can - * decide whether to keep or drop it. + * quality down until the data URL fits `budgetChars`. */ async function encodeWithinBudget( bitmap: ImageBitmap, @@ -175,7 +176,7 @@ async function encodeWithinBudget( // Probe WebP once; JPEG (no alpha) needs a white matte, so the fill has to // happen before drawing and depends on which codec we end up using. - const probe = await encodeToDataUrl(target.canvas, QUALITY_STEPS[0], "image/webp"); + const probe = await encodeCanvas(target.canvas, QUALITY_STEPS[0], "image/webp", 0); const mimeType = probe ? "image/webp" : "image/jpeg"; if (mimeType === "image/jpeg") { @@ -184,18 +185,14 @@ async function encodeWithinBudget( } target.context.drawImage(bitmap, 0, 0, width, height); - let smallest: { dataUrl: string; mimeType: string } | null = null; for (const quality of QUALITY_STEPS) { - const encoded = await encodeToDataUrl(target.canvas, quality, mimeType); + const encoded = await encodeCanvas(target.canvas, quality, mimeType, budgetChars); if (!encoded) break; - if (smallest === null || encoded.dataUrl.length < smallest.dataUrl.length) { - smallest = encoded; - } - if (encoded.dataUrl.length <= budgetChars) { - return encoded; + if (encoded.dataUrl !== null) { + return { dataUrl: encoded.dataUrl, mimeType: encoded.mimeType }; } } - return smallest; + return null; } type ReencodeResult = From 4f3ff6027135a75d662a8a83f56686793a6a540c Mon Sep 17 00:00:00 2001 From: Taras Date: Mon, 3 Aug 2026 09:17:19 +0300 Subject: [PATCH 2/2] fix(server): restore bounded thread replay --- apps/server/src/ws.ts | 76 ++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 45 deletions(-) diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts index 1b2a7ca7c82..a07795e4907 100644 --- a/apps/server/src/ws.ts +++ b/apps/server/src/ws.ts @@ -298,7 +298,7 @@ const PROVIDER_STATUS_DEBOUNCE_MS = 200; // past this gap a single O(active-threads) snapshot is cheaper and bounded. // Matches the event store's default page size (DEFAULT_READ_FROM_SEQUENCE_LIMIT). const SHELL_RESUME_MAX_GAP = 1_000; -const THREAD_DELTA_SUBSCRIPTION_MAX_GAP = 1_000; +const THREAD_RESUME_MAX_GAP = 1_000; function toAuthAccessStreamEvent( change: PairingGrantStore.BootstrapCredentialChange | SessionStore.SessionCredentialChange, @@ -987,7 +987,7 @@ const makeWsRpcLayer = ( const makeThreadSubscription = Effect.fn("Ws.makeThreadSubscription")(function* (input: { readonly request: OrchestrationSubscribeThreadInput; - readonly maxReplayGap: number | null; + readonly missingThread: "error" | "not-found"; }) { const isThisThreadDetailEvent = (event: OrchestrationEvent) => event.aggregateKind === "thread" && @@ -1021,56 +1021,42 @@ const makeWsRpcLayer = ( if (input.request.afterSequence !== undefined) { const afterSequence = input.request.afterSequence; - if (input.maxReplayGap !== null) { - const headSequence = yield* orchestrationEngine.latestSequence; - const replayGap = headSequence - afterSequence; - if (replayGap < 0 || replayGap > input.maxReplayGap) { - const snapshot = yield* loadThreadSnapshot(input.request.threadId); - if (Option.isNone(snapshot)) { + const headSequence = yield* orchestrationEngine.latestSequence; + const replayGap = headSequence - afterSequence; + if (replayGap < 0 || replayGap > THREAD_RESUME_MAX_GAP) { + const snapshot = yield* loadThreadSnapshot(input.request.threadId); + if (Option.isNone(snapshot)) { + if (input.missingThread === "not-found") { return Stream.concat( Stream.make({ kind: "not-found" as const }), synchronizedThenLive, ); } - return Stream.concat( - Stream.make({ kind: "snapshot" as const, snapshot: snapshot.value }), - synchronizedThenLive, - ); + return yield* new OrchestrationGetSnapshotError({ + message: `Thread ${input.request.threadId} was not found`, + cause: input.request.threadId, + }); } - - const catchUpStream = orchestrationEngine.readEvents(afterSequence, replayGap).pipe( - Stream.filter(isThisThreadDetailEvent), - Stream.map((event) => ({ - kind: "event" as const, - event: projectActivityEvent(event), - })), - Stream.mapError( - (cause) => - new OrchestrationGetSnapshotError({ - message: `Failed to replay thread ${input.request.threadId} events`, - cause, - }), - ), + return Stream.concat( + Stream.make({ kind: "snapshot" as const, snapshot: snapshot.value }), + synchronizedThenLive, ); - return Stream.concat(catchUpStream, synchronizedThenLive); } - const catchUpStream = orchestrationEngine - .readEvents(afterSequence, Number.MAX_SAFE_INTEGER) - .pipe( - Stream.filter(isThisThreadDetailEvent), - Stream.map((event) => ({ - kind: "event" as const, - event: projectActivityEvent(event), - })), - Stream.mapError( - (cause) => - new OrchestrationGetSnapshotError({ - message: `Failed to replay thread ${input.request.threadId} events`, - cause, - }), - ), - ); + const catchUpStream = orchestrationEngine.readEvents(afterSequence, replayGap).pipe( + Stream.filter(isThisThreadDetailEvent), + Stream.map((event) => ({ + kind: "event" as const, + event: projectActivityEvent(event), + })), + Stream.mapError( + (cause) => + new OrchestrationGetSnapshotError({ + message: `Failed to replay thread ${input.request.threadId} events`, + cause, + }), + ), + ); return Stream.concat(catchUpStream, synchronizedThenLive); } @@ -1364,7 +1350,7 @@ const makeWsRpcLayer = ( [ORCHESTRATION_WS_METHODS.subscribeThread]: (input) => observeRpcStreamEffect( ORCHESTRATION_WS_METHODS.subscribeThread, - makeThreadSubscription({ request: input, maxReplayGap: null }).pipe( + makeThreadSubscription({ request: input, missingThread: "error" }).pipe( Effect.map((stream) => Stream.filter( stream, @@ -1379,7 +1365,7 @@ const makeWsRpcLayer = ( ORCHESTRATION_WS_METHODS.subscribeThreadWithDelta, makeThreadSubscription({ request: input, - maxReplayGap: THREAD_DELTA_SUBSCRIPTION_MAX_GAP, + missingThread: "not-found", }), { "rpc.aggregate": "orchestration" }, ),