feat(FN-4583): complete Step 4 — fetch newest chat room message window

Fusion-Task-Id: FN-4583
Fusion-Task-Lineage: 4d9b088d-a705-4376-b547-15b37e5370aa
This commit is contained in:
Fusion
2026-05-15 02:09:26 -07:00
committed by gsxdsm
parent 7c0e661e56
commit e240993d5a
5 changed files with 30 additions and 6 deletions

View File

@@ -73,7 +73,7 @@ describe("chat room legacy API client", () => {
it("builds message endpoints", async () => {
const fetchMock = vi.spyOn(globalThis, "fetch").mockImplementation(async () => jsonResponse({ success: true }));
await fetchChatRoomMessages("room-1", { limit: 2, offset: 1, before: "2026-01-01" }, "proj-3");
await fetchChatRoomMessages("room-1", { limit: 2, offset: 1, before: "2026-01-01", order: "desc" }, "proj-3");
await postChatRoomMessage("room-1", { content: "hello", mentions: ["agent-x"] }, "proj-3");
await deleteChatRoomMessage("room-1", "msg-1", "proj-3");
@@ -83,6 +83,7 @@ describe("chat room legacy API client", () => {
expect(listUrl).toContain("limit=2");
expect(listUrl).toContain("offset=1");
expect(listUrl).toContain("before=2026-01-01");
expect(listUrl).toContain("order=desc");
const [, postInit] = fetchMock.mock.calls[1] as [string, RequestInit];
expect(postInit.method).toBe("POST");
@@ -91,4 +92,14 @@ describe("chat room legacy API client", () => {
const [, delInit] = fetchMock.mock.calls[2] as [string, RequestInit];
expect(delInit.method).toBe("DELETE");
});
it("omits order param when undefined", async () => {
const fetchMock = vi.spyOn(globalThis, "fetch").mockImplementation(async () => jsonResponse({ messages: [] }));
await fetchChatRoomMessages("room-1", { limit: 2 }, "proj-3");
const [listUrl] = fetchMock.mock.calls[0] as [string, RequestInit];
expect(listUrl).toContain("limit=2");
expect(listUrl).not.toContain("order=");
});
});

View File

@@ -8466,13 +8466,14 @@ export function removeChatRoomMember(id: string, agentId: string, projectId?: st
export function fetchChatRoomMessages(
id: string,
opts?: { limit?: number; offset?: number; before?: string },
opts?: { limit?: number; offset?: number; before?: string; order?: "asc" | "desc" },
projectId?: string,
): Promise<ChatRoomMessageListResponse> {
const search = new URLSearchParams();
if (opts?.limit !== undefined) search.set("limit", String(opts.limit));
if (opts?.offset !== undefined) search.set("offset", String(opts.offset));
if (opts?.before) search.set("before", opts.before);
if (opts?.order) search.set("order", opts.order);
const qs = search.toString();
return api<ChatRoomMessageListResponse>(
withProjectId(`/chat/rooms/${encodeURIComponent(id)}/messages${qs ? `?${qs}` : ""}`, projectId),

View File

@@ -102,7 +102,7 @@ export function useChatRooms(
try {
const [membersData, messagesData] = await Promise.all([
fetchChatRoomMembers(room.id, projectId),
fetchChatRoomMessages(room.id, { limit: 100 }, projectId),
fetchChatRoomMessages(room.id, { limit: 100, order: "desc" }, projectId),
]);
setActiveRoomMembers(membersData.members);
setMessages(messagesData.messages);
@@ -208,14 +208,14 @@ export function useChatRooms(
message.id === optimisticMessage.id ? postResult.message : message));
}
const latestMessages = await fetchChatRoomMessages(roomId, { limit: 100 }, projectId);
const latestMessages = await fetchChatRoomMessages(roomId, { limit: 100, order: "desc" }, projectId);
if (activeRoomRef.current?.id !== roomId) {
return;
}
setMessages(latestMessages.messages);
} catch (error) {
try {
const latestMessages = await fetchChatRoomMessages(roomId, { limit: 100 }, projectId);
const latestMessages = await fetchChatRoomMessages(roomId, { limit: 100, order: "desc" }, projectId);
if (activeRoomRef.current?.id === roomId) {
setMessages(latestMessages.messages);
}

View File

@@ -258,6 +258,10 @@ describe("Chat Room API Routes", () => {
expect(page.status).toBe(200);
expect((page.body as any).messages.map((m: any) => m.id)).toEqual([m2.id, m3.id]);
const newestPage = await request(app, "GET", `/api/chat/rooms/${room.id}/messages?limit=2&order=desc`);
expect(newestPage.status).toBe(200);
expect((newestPage.body as any).messages.map((m: any) => m.id)).toEqual([m2.id, m3.id]);
const del1 = await request(app, "DELETE", `/api/chat/rooms/${room.id}/messages/${m1.id}`);
expect(del1.status).toBe(200);

View File

@@ -251,15 +251,23 @@ export function registerChatRoomRoutes(ctx: ApiRoutesContext): void {
const room = chatStore.getRoom(roomId);
if (!room) throw notFound(`Chat room ${roomId} not found`);
const { limit: limitStr, offset: offsetStr, before } = req.query as { limit?: string; offset?: string; before?: string };
const { limit: limitStr, offset: offsetStr, before, order: orderStr } = req.query as {
limit?: string;
offset?: string;
before?: string;
order?: string;
};
const limit = limitStr !== undefined ? parseInt(String(limitStr), 10) : 50;
const offset = offsetStr !== undefined ? parseInt(String(offsetStr), 10) : 0;
const order = orderStr === undefined ? "asc" : String(orderStr);
if (!Number.isFinite(limit) || limit < 1) throw badRequest("limit must be a positive integer");
if (!Number.isFinite(offset) || offset < 0) throw badRequest("offset must be a non-negative integer");
if (order !== "asc" && order !== "desc") throw badRequest("order must be either asc or desc");
const messages = chatStore.getRoomMessages(roomId, {
limit,
offset,
order,
...(before ? { before } : {}),
});