multica/apps/web/features/inbox/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 { InboxItem } from "@multica/types";
import { api } from "@/shared/api";
interface InboxState {
items: InboxItem[];
loading: boolean;
fetch: () => Promise<void>;
setItems: (items: InboxItem[]) => void;
addItem: (item: InboxItem) => void;
markRead: (id: string) => void;
archive: (id: string) => void;
unreadCount: () => number;
}
export const useInboxStore = create<InboxState>((set, get) => ({
items: [],
loading: true,
fetch: async () => {
console.log("[inbox-store] fetch start");
set({ loading: true });
try {
const data = await api.listInbox();
console.log("[inbox-store] fetched", data.length, "items");
set({ items: data, loading: false });
} catch (err) {
console.error("[inbox-store] fetch failed", err);
set({ loading: false });
}
},
setItems: (items) => set({ items }),
addItem: (item) =>
set((s) => ({
items: s.items.some((i) => i.id === item.id)
? s.items
: [item, ...s.items],
})),
markRead: (id) =>
set((s) => ({
items: s.items.map((i) => (i.id === id ? { ...i, read: true } : i)),
})),
archive: (id) =>
set((s) => ({
items: s.items.map((i) => (i.id === id ? { ...i, archived: true } : i)),
})),
unreadCount: () => get().items.filter((i) => !i.read && !i.archived).length,
}));