mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-24 02:39:42 +02:00
fix(mention): prefetch squads so @mention list shows all squads
Closes MUL-2176
This commit is contained in:
@@ -1,20 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { agentListOptions } from "../workspace/queries";
|
||||
import { agentListOptions, squadListOptions } from "../workspace/queries";
|
||||
import { runtimeListOptions } from "../runtimes/queries";
|
||||
import { agentTaskSnapshotOptions } from "./queries";
|
||||
|
||||
// Subscribe to the three queries that power agent presence so they're warm
|
||||
// by the time any hover card / inline indicator first renders. Without this
|
||||
// warm-up, surfaces that don't otherwise touch the snapshot (inbox, issues,
|
||||
// chat) flash a skeleton on first hover while the fetch is in flight.
|
||||
// Subscribe to the queries that power agent presence and the @mention
|
||||
// suggestion list so they're warm by the time any hover card / inline
|
||||
// indicator / mention popup first renders. Without this warm-up, surfaces
|
||||
// that don't otherwise touch the snapshot (inbox, issues, chat) flash a
|
||||
// skeleton on first hover while the fetch is in flight, and the @mention
|
||||
// list may show incomplete results (e.g. missing squads).
|
||||
//
|
||||
// useRealtimeSync (WS task / agent / daemon invalidations) and the 30s
|
||||
// presence tick keep these caches fresh after the initial fetch — this hook
|
||||
// only collapses the cold-start window.
|
||||
// useRealtimeSync (WS task / agent / daemon / squad invalidations) and the
|
||||
// 30s presence tick keep these caches fresh after the initial fetch — this
|
||||
// hook only collapses the cold-start window.
|
||||
//
|
||||
// All three are workspace-scoped; the queryKeys include wsId so workspace
|
||||
// All queries are workspace-scoped; the queryKeys include wsId so workspace
|
||||
// switch automatically refetches the new workspace's data with no extra
|
||||
// wiring here. The workspace-scoped layouts on both apps gate rendering on
|
||||
// "workspace resolved", so callers can safely pass useWorkspaceId() — by the
|
||||
@@ -23,4 +25,5 @@ export function useWorkspacePresencePrefetch(wsId: string | undefined): void {
|
||||
useQuery({ ...agentListOptions(wsId ?? ""), enabled: !!wsId });
|
||||
useQuery({ ...runtimeListOptions(wsId ?? ""), enabled: !!wsId });
|
||||
useQuery({ ...agentTaskSnapshotOptions(wsId ?? ""), enabled: !!wsId });
|
||||
useQuery({ ...squadListOptions(wsId ?? ""), enabled: !!wsId });
|
||||
}
|
||||
|
||||
@@ -61,11 +61,17 @@ function fakeQc(data: {
|
||||
visibility?: "workspace" | "private";
|
||||
owner_id?: string | null;
|
||||
}>;
|
||||
squads?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
archived_at: string | null;
|
||||
}>;
|
||||
issues?: Array<{ id: string; identifier: string; title: string; status: string }>;
|
||||
}): QueryClient {
|
||||
const map = new Map<string, unknown>();
|
||||
map.set(JSON.stringify(workspaceKeys.members("ws-1")), data.members ?? []);
|
||||
map.set(JSON.stringify(workspaceKeys.agents("ws-1")), data.agents ?? []);
|
||||
map.set(JSON.stringify(workspaceKeys.squads("ws-1")), data.squads ?? []);
|
||||
const byStatus: ListIssuesCache["byStatus"] = {};
|
||||
for (const status of PAGINATED_STATUSES) {
|
||||
const bucket = (data.issues ?? []).filter((i) => i.status === status);
|
||||
@@ -244,4 +250,39 @@ describe("createMentionSuggestion", () => {
|
||||
const items = result as MentionItem[];
|
||||
expect(items.some((i) => i.type === "issue" && i.id === "i1")).toBe(true);
|
||||
});
|
||||
|
||||
it("includes all non-archived squads in the mention list", () => {
|
||||
const qc = fakeQc({
|
||||
members: [{ user_id: "u1", name: "Alice", role: "member" }],
|
||||
squads: [
|
||||
{ id: "s1", name: "Jiayuan's Coding Team", archived_at: null },
|
||||
{ id: "s2", name: "独立团", archived_at: null },
|
||||
{ id: "s3", name: "Archived Squad", archived_at: "2026-01-01T00:00:00Z" },
|
||||
],
|
||||
});
|
||||
searchIssuesMock.mockReturnValue(new Promise(() => {}));
|
||||
|
||||
const config = createMentionSuggestion(qc);
|
||||
const result = config.items!({ query: "", editor: {} as never });
|
||||
|
||||
const items = result as MentionItem[];
|
||||
expect(items.filter((i) => i.type === "squad")).toHaveLength(2);
|
||||
expect(items.some((i) => i.type === "squad" && i.label === "Jiayuan's Coding Team")).toBe(true);
|
||||
expect(items.some((i) => i.type === "squad" && i.label === "独立团")).toBe(true);
|
||||
expect(items.some((i) => i.type === "squad" && i.label === "Archived Squad")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns no squads when the squads cache is empty (not yet fetched)", () => {
|
||||
const qc = fakeQc({
|
||||
members: [{ user_id: "u1", name: "Alice", role: "member" }],
|
||||
// squads not provided — simulates cache miss
|
||||
});
|
||||
searchIssuesMock.mockReturnValue(new Promise(() => {}));
|
||||
|
||||
const config = createMentionSuggestion(qc);
|
||||
const result = config.items!({ query: "", editor: {} as never });
|
||||
|
||||
const items = result as MentionItem[];
|
||||
expect(items.filter((i) => i.type === "squad")).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user