Revert the timeline optimistic and pagination rework (#2596)

* Add npm release track invariant and beta dist-tag procedure

* Revert "Keep older chat history and image previews stable (#2490)"

The timeline optimistic/pagination rework needs more work than main releases can wait for. Reverted together with #2484; both are reapplied on integration/timeline-optimistic-rework.

* Revert "Stop completed turns from appearing stuck (#2484)"

See the #2490 revert. #2490 built on this submission-authority change, so the two revert and reapply as a pair.
This commit is contained in:
Mohamed Boudra
2026-07-29 13:35:43 +02:00
committed by GitHub
parent ab24070075
commit acb8506d1f
81 changed files with 1721 additions and 7893 deletions
@@ -1,15 +0,0 @@
import { describe, expect, it } from "vitest";
import { collectRetainedAttachmentIds, retainAttachmentForGarbageCollection } from "./gc-retention";
describe("attachment garbage-collection retention", () => {
it("keeps an attachment referenced until its final owner releases it", () => {
const releaseFirst = retainAttachmentForGarbageCollection("preview-1");
const releaseSecond = retainAttachmentForGarbageCollection("preview-1");
expect(collectRetainedAttachmentIds()).toContain("preview-1");
releaseFirst();
expect(collectRetainedAttachmentIds()).toContain("preview-1");
releaseSecond();
expect(collectRetainedAttachmentIds()).not.toContain("preview-1");
});
});
@@ -1,22 +0,0 @@
const retentionCounts = new Map<string, number>();
export function retainAttachmentForGarbageCollection(attachmentId: string): () => void {
retentionCounts.set(attachmentId, (retentionCounts.get(attachmentId) ?? 0) + 1);
let released = false;
return () => {
if (released) {
return;
}
released = true;
const nextCount = (retentionCounts.get(attachmentId) ?? 1) - 1;
if (nextCount <= 0) {
retentionCounts.delete(attachmentId);
return;
}
retentionCounts.set(attachmentId, nextCount);
};
}
export function collectRetainedAttachmentIds(): ReadonlySet<string> {
return new Set(retentionCounts.keys());
}
+1 -47
View File
@@ -1,11 +1,7 @@
import { afterEach, describe, expect, it } from "vitest";
import type { AttachmentMetadata, AttachmentStore, SaveAttachmentInput } from "@/attachments/types";
import { __setAttachmentStoreForTests } from "./store";
import {
encodeAttachmentsForSend,
garbageCollectAttachments,
persistAttachmentFromBytes,
} from "./service";
import { encodeAttachmentsForSend, persistAttachmentFromBytes } from "./service";
function createAttachment(input: Partial<AttachmentMetadata> = {}): AttachmentMetadata {
return {
@@ -98,46 +94,4 @@ describe("attachment service", () => {
{ data: "att_send:base64", mimeType: "image/jpeg" },
]);
});
it("does not collect an attachment persisted while garbage collection is starting", async () => {
let releaseSave: () => void = () => undefined;
let reportSaveStarted: () => void = () => undefined;
const saveStarted = new Promise<void>((resolve) => {
reportSaveStarted = resolve;
});
const saveGate = new Promise<void>((resolve) => {
releaseSave = resolve;
});
const garbageCollections: string[][] = [];
const store: AttachmentStore = {
...createRecordingStore(),
async save(input) {
reportSaveStarted();
await saveGate;
return createAttachment({ id: input.id });
},
async garbageCollect({ referencedIds }) {
garbageCollections.push([...referencedIds]);
},
};
__setAttachmentStoreForTests(store);
const persist = persistAttachmentFromBytes({
id: "assistant-preview",
bytes: new Uint8Array([1, 2, 3]),
mimeType: "image/png",
});
await saveStarted;
const collect = garbageCollectAttachments({ referencedIds: new Set() });
try {
await Promise.resolve();
expect(garbageCollections).toEqual([]);
} finally {
releaseSave();
await Promise.all([persist, collect]);
}
expect(garbageCollections).toEqual([["assistant-preview"]]);
});
});
+11 -77
View File
@@ -1,40 +1,5 @@
import { collectRetainedAttachmentIds } from "@/attachments/gc-retention";
import type { AttachmentMetadata } from "@/attachments/types";
import { getAttachmentStore } from "@/attachments/store";
import type { AttachmentMetadata, SaveAttachmentInput } from "@/attachments/types";
const activePersistence = new Set<Promise<AttachmentMetadata>>();
const persistedDuringGarbageCollection = new Set<string>();
let pendingGarbageCollections = 0;
let garbageCollectionTail: Promise<void> = Promise.resolve();
let persistenceBarrier: Promise<void> | null = null;
let releasePersistenceBarrier: (() => void) | null = null;
async function waitForPersistenceBarrier(): Promise<void> {
const barrier = persistenceBarrier;
if (!barrier) {
return;
}
await barrier;
await waitForPersistenceBarrier();
}
async function persistAttachment(input: SaveAttachmentInput): Promise<AttachmentMetadata> {
await waitForPersistenceBarrier();
const pending = (async () => {
const store = await getAttachmentStore();
const attachment = await store.save(input);
if (pendingGarbageCollections > 0) {
persistedDuringGarbageCollection.add(attachment.id);
}
return attachment;
})();
activePersistence.add(pending);
try {
return await pending;
} finally {
activePersistence.delete(pending);
}
}
export async function persistAttachmentFromBlob(input: {
blob: Blob;
@@ -42,7 +7,8 @@ export async function persistAttachmentFromBlob(input: {
fileName?: string | null;
id?: string;
}): Promise<AttachmentMetadata> {
return await persistAttachment({
const store = await getAttachmentStore();
return await store.save({
id: input.id,
mimeType: input.mimeType,
fileName: input.fileName,
@@ -56,7 +22,8 @@ export async function persistAttachmentFromDataUrl(input: {
fileName?: string | null;
id?: string;
}): Promise<AttachmentMetadata> {
return await persistAttachment({
const store = await getAttachmentStore();
return await store.save({
id: input.id,
mimeType: input.mimeType,
fileName: input.fileName,
@@ -70,7 +37,8 @@ export async function persistAttachmentFromBytes(input: {
fileName?: string | null;
id?: string;
}): Promise<AttachmentMetadata> {
return await persistAttachment({
const store = await getAttachmentStore();
return await store.save({
id: input.id,
mimeType: input.mimeType,
fileName: input.fileName,
@@ -84,7 +52,8 @@ export async function persistAttachmentFromFileUri(input: {
fileName?: string | null;
id?: string;
}): Promise<AttachmentMetadata> {
return await persistAttachment({
const store = await getAttachmentStore();
return await store.save({
id: input.id,
mimeType: input.mimeType,
fileName: input.fileName,
@@ -164,41 +133,6 @@ export async function deleteAttachments(
export async function garbageCollectAttachments(input: {
referencedIds: ReadonlySet<string>;
}): Promise<void> {
pendingGarbageCollections += 1;
if (!persistenceBarrier) {
persistenceBarrier = new Promise<void>((resolve) => {
releasePersistenceBarrier = resolve;
});
}
const previousGarbageCollection = garbageCollectionTail;
const currentGarbageCollection = (async () => {
await previousGarbageCollection;
while (activePersistence.size > 0) {
await Promise.allSettled(activePersistence);
}
const referencedIds = new Set(input.referencedIds);
for (const id of collectRetainedAttachmentIds()) {
referencedIds.add(id);
}
for (const id of persistedDuringGarbageCollection) {
referencedIds.add(id);
}
const store = await getAttachmentStore();
await store.garbageCollect({ referencedIds });
})();
garbageCollectionTail = currentGarbageCollection.catch(() => undefined);
try {
await currentGarbageCollection;
} finally {
pendingGarbageCollections -= 1;
if (pendingGarbageCollections === 0) {
persistedDuringGarbageCollection.clear();
const release = releasePersistenceBarrier;
releasePersistenceBarrier = null;
persistenceBarrier = null;
release?.();
}
}
const store = await getAttachmentStore();
await store.garbageCollect({ referencedIds: input.referencedIds });
}
@@ -1,7 +1,6 @@
import { describe, expect, it } from "vitest";
import {
createImageSourceCacheKey,
createPreviewAttachmentId,
fileUriToPath,
localFileSourceToPath,
parseDataUrl,
@@ -87,29 +86,4 @@ describe("parseImageDataUrl", () => {
it("ignores SVG data URLs", () => {
expect(parseImageDataUrl("data:image/svg+xml;base64,PHN2ZyAvPg==")).toBeNull();
});
it("distinguishes image data that differs only in the middle", () => {
const prefix = "a".repeat(64);
const suffix = "z".repeat(64);
const first = `data:image/png;base64,${prefix}${"b".repeat(256)}${suffix}`;
const second = `data:image/png;base64,${prefix}${"c".repeat(256)}${suffix}`;
expect(createImageSourceCacheKey(first)).not.toBe(createImageSourceCacheKey(second));
});
it("gives equal-length preview content distinct attachment identities", () => {
expect(
createPreviewAttachmentId({
mimeType: "image/png",
contentLength: 512,
contentKey: "first-content",
}),
).not.toBe(
createPreviewAttachmentId({
mimeType: "image/png",
contentLength: 512,
contentKey: "second-content",
}),
);
});
});
+2 -5
View File
@@ -56,7 +56,7 @@ export function parseImageDataUrl(
if (!isRasterImageMimeType(parsed.mimeType)) {
return null;
}
const fingerprint = `${parsed.mimeType}\0${parsed.base64}`;
const fingerprint = `${parsed.mimeType}\0${parsed.base64.length}\0${parsed.base64.slice(0, 64)}\0${parsed.base64.slice(-64)}`;
return {
...parsed,
cacheKey: `data-image:${parsed.mimeType}:${parsed.base64.length}:${hashString(fingerprint)}`,
@@ -87,15 +87,12 @@ export function createPreviewAttachmentId(input: {
size?: number | null;
modifiedAt?: string | null;
contentLength?: number | null;
contentKey?: string | null;
}): string {
const path = input.path?.trim() ?? "";
const size = Number.isFinite(input.size) ? String(input.size) : "";
const modifiedAt = input.modifiedAt?.trim() ?? "";
const contentLength = Number.isFinite(input.contentLength) ? String(input.contentLength) : "";
const contentKey = input.contentKey?.trim() ?? "";
const identity = `${input.mimeType}\0${path}\0${size}\0${modifiedAt}\0${contentLength}`;
const hash = hashString(contentKey ? `${identity}\0${contentKey}` : identity);
const hash = hashString(`${input.mimeType}\0${path}\0${size}\0${modifiedAt}\0${contentLength}`);
return `preview_${size || contentLength || "unknown"}_${hash}`;
}