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:
@@ -5,9 +5,11 @@ import type { WorkspaceDescriptorPayload } from "@getpaseo/protocol/messages";
|
||||
|
||||
import {
|
||||
normalizeWorkspaceDescriptor,
|
||||
selectAgentTimelineState,
|
||||
useSessionStore,
|
||||
type WorkspaceDescriptor,
|
||||
} from "./session-store";
|
||||
import type { StreamItem } from "../types/stream";
|
||||
import { patchWorkspaceScripts } from "../contexts/session-workspace-scripts";
|
||||
|
||||
function createWorkspace(
|
||||
@@ -53,6 +55,54 @@ function getTestSessionReferences() {
|
||||
};
|
||||
}
|
||||
|
||||
describe("agent timeline state", () => {
|
||||
it("commits canonical items, range, and older availability as one synced state", () => {
|
||||
initializeTestSession();
|
||||
const items: StreamItem[] = [
|
||||
{
|
||||
kind: "assistant_message",
|
||||
id: "canonical-row",
|
||||
text: "canonical",
|
||||
timestamp: new Date("2026-07-27T10:00:00.000Z"),
|
||||
},
|
||||
];
|
||||
|
||||
useSessionStore.getState().applyAgentTimelineResponseState("test-server", "agent-1", {
|
||||
items,
|
||||
head: [],
|
||||
range: { epoch: "epoch-1", startSeq: 51, endSeq: 100 },
|
||||
older: "available",
|
||||
synchronized: true,
|
||||
acknowledgedClientMessageIds: [],
|
||||
});
|
||||
|
||||
expect(
|
||||
selectAgentTimelineState(useSessionStore.getState().sessions["test-server"], "agent-1"),
|
||||
).toEqual({
|
||||
status: "synced",
|
||||
items,
|
||||
range: { epoch: "epoch-1", startSeq: 51, endSeq: 100 },
|
||||
older: "available",
|
||||
});
|
||||
});
|
||||
|
||||
it("represents an empty authoritative timeline without inventing a range", () => {
|
||||
initializeTestSession();
|
||||
useSessionStore.getState().applyAgentTimelineResponseState("test-server", "agent-1", {
|
||||
items: [],
|
||||
head: [],
|
||||
range: null,
|
||||
older: "none",
|
||||
synchronized: true,
|
||||
acknowledgedClientMessageIds: [],
|
||||
});
|
||||
|
||||
expect(
|
||||
selectAgentTimelineState(useSessionStore.getState().sessions["test-server"], "agent-1"),
|
||||
).toEqual({ status: "synced", items: [], range: null, older: "none" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeWorkspaceDescriptor", () => {
|
||||
it("normalizes workspace scripts and invalid activity timestamps", () => {
|
||||
const scripts = [
|
||||
|
||||
Reference in New Issue
Block a user