Q143: earlier messages come from the database, page by page, back to the first one
Review limit (5) reached; the last finding (exhaustion latched) is fixed by taking it from each latest hasMore. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b18eb9ad14
commit
075dea725a
12 files changed
+213
-15
No files matched your search
@@ -166,6 +166,29 @@ test("the pinned CEO conversation is the Messages front door", async () => {
|
||||
await expect(ceoRow.locator(".nav-badge")).toHaveText("1");
|
||||
await page.getByRole("button", { name: "Jump to latest" }).click();
|
||||
await expect(ceoRow.locator(".nav-badge")).toHaveCount(0);
|
||||
|
||||
// History older than the loaded window comes from the database, back to the very first message.
|
||||
await page.evaluate(async () => {
|
||||
const api = (window as unknown as { agentCompany: {
|
||||
loadSnapshot: () => Promise<{ ok: true; data: { activeOrgId: string; employees: Array<{ id: string; orgId: string }>; chatSpaces: Array<{ id: string; orgId: string; name: string }> } }>;
|
||||
sendMessage: (input: unknown) => Promise<unknown>;
|
||||
} }).agentCompany;
|
||||
const snapshot = (await api.loadSnapshot()).data;
|
||||
const employee = snapshot.employees.find((item) => item.orgId === snapshot.activeOrgId)!;
|
||||
const general = snapshot.chatSpaces.find((item) => item.orgId === snapshot.activeOrgId && item.name === "general")!;
|
||||
for (let index = 0; index < 520; index += 1) {
|
||||
await api.sendMessage({ orgId: snapshot.activeOrgId, spaceId: general.id, threadId: null, senderType: "employee", senderId: employee.id, bodyMd: `General line ${index}`, attachmentFileIds: [] });
|
||||
}
|
||||
});
|
||||
await page.locator(".convo-item", { hasText: "general" }).click();
|
||||
await expect(chatLog.getByText("General line 519")).toBeVisible();
|
||||
const start = chatLog.getByText("This is the start of the conversation.");
|
||||
for (let click = 0; click < 10 && !(await start.isVisible()); click += 1) {
|
||||
await chatLog.getByRole("button", { name: "Show earlier messages" }).click();
|
||||
await page.waitForTimeout(300);
|
||||
}
|
||||
await expect(start).toBeVisible();
|
||||
await expect(chatLog.getByText("General line 0", { exact: true })).toBeVisible();
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { DatabaseConnection } from "../../src/main/database/connection";
|
||||
import { OrganizationService } from "../../src/main/services/organization-service";
|
||||
import { ChatService } from "../../src/main/services/chat-service";
|
||||
import { SnapshotService } from "../../src/main/services/snapshot-service";
|
||||
import { threadHistory } from "../../src/main/services/chat-history";
|
||||
import { spaceHistory, threadHistory } from "../../src/main/services/chat-history";
|
||||
|
||||
describe("a thread's history", () => {
|
||||
test("holds the whole thread even after newer traffic pushed it out of the recent window", () => {
|
||||
@@ -37,3 +37,42 @@ describe("a thread's history", () => {
|
||||
connection.close();
|
||||
}, 60_000);
|
||||
});
|
||||
|
||||
describe("a conversation's history", () => {
|
||||
test("pages back from the earliest loaded message to the very first, beyond the recent window", () => {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-space-history-${randomUUID()}.db`));
|
||||
const chat = new ChatService(connection.db);
|
||||
let orgId = "";
|
||||
connection.transaction(() => {
|
||||
orgId = new OrganizationService(connection.db).createOrganization({ organizationName: "Acme", employeeName: "Yang", mission: "m", goal: "g" });
|
||||
});
|
||||
const employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
|
||||
const spaceId = (connection.db.prepare("select id from chat_spaces where org_id = ? and name = 'general'").get(orgId) as { id: string }).id;
|
||||
const ids: string[] = [];
|
||||
for (let index = 0; index < 250; index += 1) {
|
||||
ids.push(connection.transaction(() => chat.sendMessage({
|
||||
orgId, spaceId, threadId: null, senderType: "employee", senderId: employeeId, bodyMd: `Line ${index}`, attachmentFileIds: [],
|
||||
})));
|
||||
}
|
||||
// Every message shares one instant, so only insertion order can page them correctly.
|
||||
connection.db.prepare("update chat_messages set created_at = '2026-03-01T00:00:00.000Z' where space_id = ?").run(spaceId);
|
||||
const first = spaceHistory(connection.db, orgId, spaceId, ids[200], { limit: 120 });
|
||||
expect(first.messages.map((message) => message.bodyMd)).toEqual(Array.from({ length: 120 }, (_, index) => `Line ${index + 80}`));
|
||||
expect(first.hasMore).toBe(true);
|
||||
const last = spaceHistory(connection.db, orgId, spaceId, first.messages[0].id, { limit: 120 });
|
||||
expect(last.messages.map((message) => message.bodyMd)).toEqual(Array.from({ length: 80 }, (_, index) => `Line ${index}`));
|
||||
expect(last.hasMore).toBe(false);
|
||||
// The loaded range refreshes in one query from its oldest message up to the cursor, edits included.
|
||||
connection.db.prepare("update chat_messages set body_md = 'Line 0 (edited)' where id = ?").run(ids[0]);
|
||||
const refreshed = spaceHistory(connection.db, orgId, spaceId, ids[200], { fromMessageId: ids[0] });
|
||||
expect(refreshed.messages.map((message) => message.bodyMd)).toEqual(["Line 0 (edited)", ...Array.from({ length: 199 }, (_, index) => `Line ${index + 1}`)]);
|
||||
expect(refreshed.hasMore).toBe(false);
|
||||
expect(spaceHistory(connection.db, orgId, spaceId, ids[200], { fromMessageId: ids[1] }).hasMore).toBe(true);
|
||||
// Each page carries the threads of its messages, which the snapshot may have dropped.
|
||||
expect(refreshed.threads.map((thread) => thread.rootMessageId).sort()).toEqual(ids.slice(0, 200).sort());
|
||||
// With nothing loaded, the newest messages come first.
|
||||
expect(spaceHistory(connection.db, orgId, spaceId, null, { limit: 2 }).messages.map((message) => message.bodyMd)).toEqual(["Line 248", "Line 249"]);
|
||||
expect(() => spaceHistory(connection.db, orgId, spaceId, "msg_elsewhere", { limit: 10 })).toThrow("The earliest loaded message is not in this conversation.");
|
||||
connection.close();
|
||||
}, 60_000);
|
||||
});
|
||||
Reference in new issue
Block a user