feat(core): migrate workspace + runtimes to TanStack Query (Phase 3+4)

- Create core/workspace/ with queries (members, agents, skills, list) and mutations
- Create core/runtimes/ with queries
- Migrate 11 consumer files from useWorkspaceStore.members/agents/skills to useQuery
- Replace all WS refreshMap entries with qc.invalidateQueries
- Simplify workspace store: delete members/agents/skills fields + refresh methods,
  hydrateWorkspace becomes synchronous (TQ auto-fetches on component mount)
- Delete useRuntimeStore (no consumers left), runtimes-page uses local useState + TQ
- Remove workspace→runtime cross-store dependency
- Clean up dead test helper mocks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Naiyuan Qing 2026-04-07 17:19:52 +08:00
parent 1d812bd446
commit e40341ab73
24 changed files with 251 additions and 289 deletions

View file

@ -68,6 +68,7 @@ import { useAuthStore } from "@/features/auth";
import { useWorkspaceStore, useActorName } from "@/features/workspace";
import { useWorkspaceId } from "@core/hooks";
import { issueListOptions, issueDetailOptions } from "@core/issues/queries";
import { memberListOptions, agentListOptions } from "@core/workspace/queries";
import { useUpdateIssue, useDeleteIssue } from "@core/issues/mutations";
import { useIssueTimeline } from "@/features/issues/hooks/use-issue-timeline";
import { useIssueReactions } from "@/features/issues/hooks/use-issue-reactions";
@ -177,12 +178,12 @@ export function IssueDetail({ issueId, onDelete, defaultSidebarOpen = true, layo
const router = useRouter();
const user = useAuthStore((s) => s.user);
const workspace = useWorkspaceStore((s) => s.workspace);
const members = useWorkspaceStore((s) => s.members);
const agents = useWorkspaceStore((s) => s.agents);
const currentMemberRole = members.find((m) => m.user_id === user?.id)?.role;
// Issue navigation — read from TQ list cache
const wsId = useWorkspaceId();
const { data: members = [] } = useQuery(memberListOptions(wsId));
const { data: agents = [] } = useQuery(agentListOptions(wsId));
const currentMemberRole = members.find((m) => m.user_id === user?.id)?.role;
const { data: allIssues = [] } = useQuery(issueListOptions(wsId));
const currentIndex = allIssues.findIndex((i) => i.id === id);
const prevIssue = currentIndex > 0 ? allIssues[currentIndex - 1] : null;

View file

@ -43,7 +43,9 @@ import {
PRIORITY_CONFIG,
} from "@/features/issues/config";
import { StatusIcon, PriorityIcon } from "@/features/issues/components";
import { useWorkspaceStore } from "@/features/workspace";
import { useQuery } from "@tanstack/react-query";
import { useWorkspaceId } from "@core/hooks";
import { memberListOptions, agentListOptions } from "@core/workspace/queries";
import { ActorAvatar } from "@/components/common/actor-avatar";
import {
useIssueViewStore,
@ -155,8 +157,9 @@ function ActorSubContent({
noAssigneeCount?: number;
}) {
const [search, setSearch] = useState("");
const members = useWorkspaceStore((s) => s.members);
const agents = useWorkspaceStore((s) => s.agents);
const wsId = useWorkspaceId();
const { data: members = [] } = useQuery(memberListOptions(wsId));
const { data: agents = [] } = useQuery(agentListOptions(wsId));
const query = search.toLowerCase();
const filteredMembers = members.filter((m) =>
m.name.toLowerCase().includes(query),

View file

@ -3,8 +3,11 @@
import { useState } from "react";
import { Lock, UserMinus } from "lucide-react";
import type { Agent, IssueAssigneeType, UpdateIssueRequest } from "@/shared/types";
import { useQuery } from "@tanstack/react-query";
import { useAuthStore } from "@/features/auth";
import { useWorkspaceStore, useActorName } from "@/features/workspace";
import { useActorName } from "@/features/workspace";
import { useWorkspaceId } from "@core/hooks";
import { memberListOptions, agentListOptions } from "@core/workspace/queries";
import { ActorAvatar } from "@/components/common/actor-avatar";
import {
PropertyPicker,
@ -44,8 +47,9 @@ export function AssigneePicker({
const setOpen = controlledOnOpenChange ?? setInternalOpen;
const [filter, setFilter] = useState("");
const user = useAuthStore((s) => s.user);
const members = useWorkspaceStore((s) => s.members);
const agents = useWorkspaceStore((s) => s.agents);
const wsId = useWorkspaceId();
const { data: members = [] } = useQuery(memberListOptions(wsId));
const { data: agents = [] } = useQuery(agentListOptions(wsId));
const { getActorName } = useActorName();
const currentMember = members.find((m) => m.user_id === user?.id);