Refresh folder pages for scoped thread updates

This commit is contained in:
kris
2026-04-07 17:33:37 +08:00
parent b06b084438
commit 4093c41949
3 changed files with 27 additions and 4 deletions

View File

@@ -1,3 +1,4 @@
import { RealtimeRefresh } from "@/components/app-runtime";
import {
AppShell,
ConversationList,
@@ -22,6 +23,12 @@ export default async function ConversationFolderPage({
return (
<AppShell bottomNav={false}>
{folder ? (
<RealtimeRefresh
projectIds={folder.threads.map((thread) => thread.projectId)}
events={["conversation.updated", "project.messages.updated"]}
/>
) : null}
<StatusBar />
<PageNav title={folder?.folderLabel ?? "项目线程"} backHref="/conversations" />
<div className="space-y-3 px-[18px] pb-6">

View File

@@ -254,16 +254,23 @@ export function NativeAppBridge() {
export function RealtimeRefresh({
events,
projectId,
projectIds,
conversationUpdatedNotes,
}: {
events: BossEventName[];
projectId?: string;
projectIds?: string[];
conversationUpdatedNotes?: string[];
}) {
const router = useRouter();
useEffect(() => {
const source = new EventSource("/api/v1/events");
const projectScopeIds = new Set(
[projectId, ...(projectIds ?? [])]
.filter((value): value is string => Boolean(value?.trim()))
.map((value) => value.trim()),
);
const listeners = Array.from(new Set([
"conversation.context_indicator.updated",
"project.context_risk.updated",
@@ -285,11 +292,11 @@ export function RealtimeRefresh({
}
}
if (projectId) {
if (projectScopeIds.size > 0) {
if (!payload || typeof payload.projectId !== "string" || !payload.projectId.trim()) {
return true;
}
if (payload.projectId !== projectId) {
if (!projectScopeIds.has(payload.projectId)) {
return false;
}
}
@@ -327,7 +334,7 @@ export function RealtimeRefresh({
}
source.close();
};
}, [conversationUpdatedNotes, events, projectId, router]);
}, [conversationUpdatedNotes, events, projectId, projectIds, router]);
return null;
}

View File

@@ -14,6 +14,7 @@ test("RealtimeRefresh supports project-scoped refresh filtering", async () => {
const source = await readWorkspaceFile("src/components/app-runtime.tsx");
assert.match(source, /projectId\?: string/, "expected RealtimeRefresh to accept an optional projectId");
assert.match(source, /projectIds\?: string\[]/, "expected RealtimeRefresh to accept optional projectIds");
assert.match(
source,
/conversationUpdatedNotes\?: string\[]/,
@@ -21,7 +22,7 @@ test("RealtimeRefresh supports project-scoped refresh filtering", async () => {
);
assert.match(
source,
/payload\.projectId !== projectId[\s\S]*return false/,
/projectScopeIds\.has\(payload\.projectId\)/,
"expected RealtimeRefresh to filter by matching projectId",
);
assert.match(
@@ -62,3 +63,11 @@ test("project conversation pages wire project-scoped realtime refresh", async ()
"expected versions page to refresh only on project goal markers",
);
});
test("folder conversation page wires folder thread ids into realtime refresh", async () => {
const folderPage = await readWorkspaceFile("src/app/conversations/folders/[folderKey]/page.tsx");
assert.match(folderPage, /<RealtimeRefresh/, "expected folder page to render RealtimeRefresh");
assert.match(folderPage, /projectIds=\{folder\.threads\.map\(\(thread\) => thread\.projectId\)\}/, "expected folder page to scope refreshes to folder thread project ids");
assert.match(folderPage, /"conversation\.updated"/, "expected folder page to listen to conversation updates");
});