multica/apps/web/features/issues/store.ts
Naiyuan Qing 66b1defab7 refactor: migrate stores to features/, remove dead packages, add modals + workspace sync
## Store migration (packages → features)
- Delete `packages/store/` — stores moved into web app's feature modules
- Delete `packages/hooks/` — replaced by feature-level hooks
- `features/issues/store.ts` — useIssueStore (was packages/store/issue-store)
- `features/inbox/store.ts` — useInboxStore (was packages/store/inbox-store)
- `features/workspace/store.ts` — absorbs agent state (was packages/store/agent-store)
- All imports updated from `@multica/store` → `@/features/*/store`

## Global modal system
- `features/modals/store.ts` — useModalStore (zustand)
- `features/modals/registry.tsx` — ModalRegistry renders active modal
- Mounted in app/layout.tsx alongside Toaster
- Create Workspace dialog now works (was broken: DropdownMenu ate click)

## Workspace real-time sync
- useRealtimeSync subscribes to workspace:updated, member:removed
- Member removal → auto-switch to another workspace
- Workspace settings update → sidebar reflects name change
- Workspace switch → parallel fetch issues + inbox + agents

## Bug fixes
- theme-provider: guard event.key for IME composition (isComposing check)
- task.go: publish comment:created + inbox:new events on task complete/fail
- listeners.go: broadcast comment:created, workspace:updated, member events
- events.go: add EventCommentUpdated, EventCommentDeleted constants

## Cleanup
- Remove _features/ tracking files (dev-only, not for main)
- Remove server/server binary from worktree
- Update CLAUDE.md to reflect new architecture

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 16:37:22 +08:00

51 lines
1.4 KiB
TypeScript

"use client";
import { create } from "zustand";
import type { Issue } from "@multica/types";
import { api } from "@/shared/api";
interface IssueState {
issues: Issue[];
loading: boolean;
activeIssueId: string | null;
fetch: () => Promise<void>;
setIssues: (issues: Issue[]) => void;
addIssue: (issue: Issue) => void;
updateIssue: (id: string, updates: Partial<Issue>) => void;
removeIssue: (id: string) => void;
setActiveIssue: (id: string | null) => void;
}
export const useIssueStore = create<IssueState>((set) => ({
issues: [],
loading: true,
activeIssueId: null,
fetch: async () => {
console.log("[issue-store] fetch start");
set({ loading: true });
try {
const res = await api.listIssues({ limit: 200 });
console.log("[issue-store] fetched", res.issues.length, "issues");
set({ issues: res.issues, loading: false });
} catch (err) {
console.error("[issue-store] fetch failed", err);
set({ loading: false });
}
},
setIssues: (issues) => set({ issues }),
addIssue: (issue) =>
set((s) => ({
issues: s.issues.some((i) => i.id === issue.id)
? s.issues
: [...s.issues, issue],
})),
updateIssue: (id, updates) =>
set((s) => ({
issues: s.issues.map((i) => (i.id === id ? { ...i, ...updates } : i)),
})),
removeIssue: (id) =>
set((s) => ({ issues: s.issues.filter((i) => i.id !== id) })),
setActiveIssue: (id) => set({ activeIssueId: id }),
}));