Keep older chat history and image previews stable (#2490)
* fix(app): keep timeline history and previews stable Treat persisted timeline replicas as display-only so authoritative pagination always comes from the daemon. Require renewed user intent between older-history pages unless the viewport genuinely remains at the history edge. Model assistant image acquisition as loading, loaded, or failed so recreating a preview URL cannot be rendered as an error. * fix(app): keep file image previews current * fix(app): stabilize history pagination edges * fix(app): recover transient timeline loads * fix(app): retain assistant image previews * Stabilize history pagination lifecycle * Stabilize history settlement and image previews * Retry file images after reconnect * Keep active previews and pagination requests alive * Replace image hook tests with typed ports * Integrate timeline hydration with submission authority * Harden pre-hydration timeline reconciliation * Make rewind E2E use real scroll intent * Preserve live timeline state through hydration * Keep mounted image attachments retained * Protect preview persistence and lifecycle hydration * Preserve idless live assistant continuations * Preserve timeline rows across delayed hydration * test(app): cover real assistant image files * Preserve assistant tool ordering during hydration
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { AttachmentMetadata } from "@/attachments/types";
|
||||
import {
|
||||
createAssistantImageFileAcquisition,
|
||||
type AssistantImageFileAcquisitionPort,
|
||||
} from "./file-acquisition";
|
||||
|
||||
class MemoryFileAcquisitionPort implements AssistantImageFileAcquisitionPort {
|
||||
readonly reads: Array<{ cwd: string; path: string }> = [];
|
||||
|
||||
async readFile(cwd: string, path: string) {
|
||||
this.reads.push({ cwd, path });
|
||||
return {
|
||||
kind: "image" as const,
|
||||
path,
|
||||
mime: "image/png",
|
||||
size: 4,
|
||||
modifiedAt: "1",
|
||||
bytes: new Uint8Array([1, 2, 3, 4]),
|
||||
};
|
||||
}
|
||||
|
||||
async persist(input: { id: string; mimeType: string; fileName: string | null }) {
|
||||
return {
|
||||
id: input.id,
|
||||
mimeType: input.mimeType,
|
||||
storageType: "web-indexeddb" as const,
|
||||
storageKey: input.id,
|
||||
fileName: input.fileName,
|
||||
byteSize: 4,
|
||||
createdAt: 1,
|
||||
} satisfies AttachmentMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
describe("assistant image file acquisition", () => {
|
||||
it("recreates the same acquisition with a live port after reconnect", async () => {
|
||||
const common = {
|
||||
resolution: { kind: "file_rpc" as const, cwd: "/workspace", path: "reconnect.png" },
|
||||
serverId: "server",
|
||||
occurrenceKey: "agent:message:reconnect-image",
|
||||
unavailableMessage: "Image unavailable",
|
||||
};
|
||||
const disconnected = createAssistantImageFileAcquisition({ ...common, port: null });
|
||||
const connectedPort = new MemoryFileAcquisitionPort();
|
||||
const connected = createAssistantImageFileAcquisition({ ...common, port: connectedPort });
|
||||
|
||||
expect(disconnected?.key).toBe(connected?.key);
|
||||
await expect(disconnected?.locate()).rejects.toThrow("Image unavailable");
|
||||
await expect(connected?.locate()).resolves.toMatchObject({ mimeType: "image/png" });
|
||||
expect(connectedPort.reads).toEqual([{ cwd: "/workspace", path: "reconnect.png" }]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user