Files
multica/packages/core/chat/index.ts
Naiyuan Qing a744cd4f45 feat(chat): redesign state, header, and unread tracking
State management
- Pending task / live timeline are now Query-cache single source;
  Zustand mirror removed (fixes duplicate assistant render caused by
  the invalidate→refetch race window)
- WS subscriptions moved from ChatWindow to global useRealtimeSync so
  pending state survives minimize and refresh
- New GET /chat/sessions/:id/pending-task to recover live state on mount
- Drafts persisted per-session (was per-workspace)

Unread tracking
- Migration 040: chat_session.unread_since (event-driven; old chats
  stay clean — no mass backfill)
- POST /chat/sessions/:id/read clears unread; broadcasts
  chat:session_read so other devices sync
- New GET /chat/pending-tasks aggregate for the FAB
- ChatFab: brand-color impulse animation while running, brand-dot
  badge of unread session count
- ChatWindow auto-marks read when user is viewing the session

Header redesign
- Two independent dropdowns: agent (avatar + name + My/Others
  grouping) at the input bottom-left; session (title + agent avatar)
  in the header
- ⊕ new-chat button replaces the old + and history buttons
- Session dropdown lists all sessions across agents with avatars
- Empty state: 3 clickable starter prompts that send immediately
- Mention link renderer falls through to default span on null —
  fixes @member/@agent/@all silently disappearing app-wide
- User messages render through Markdown
- Enter submits in chat input only (with IME guard + codeBlock skip);
  bubble menu hidden in chat

Misc
- Partial index on agent_task_queue for fast pending-task lookup
- 2 new storage keys added to clearWorkspaceStorage
- useMarkChatSessionRead has onError rollback
- chat.* namespace logs across store, mutations, components, realtime

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 18:21:11 +08:00

39 lines
1.3 KiB
TypeScript

export { createChatStore, CHAT_MIN_W, CHAT_MIN_H, CHAT_DEFAULT_W, CHAT_DEFAULT_H, DRAFT_NEW_SESSION } from "./store";
export type { ChatStoreOptions, ChatState, ChatTimelineItem } from "./store";
import type { createChatStore as CreateChatStoreFn } from "./store";
type ChatStoreInstance = ReturnType<typeof CreateChatStoreFn>;
/** Module-level singleton — set once at app boot via `registerChatStore()`. */
let _store: ChatStoreInstance | null = null;
/**
* Register the chat store instance created by the app.
* Must be called at boot before any component renders.
*/
export function registerChatStore(store: ChatStoreInstance) {
_store = store;
}
/**
* Singleton accessor — a Zustand hook backed by the registered instance.
* Supports `useChatStore(selector)` and `useChatStore.getState()`.
*/
export const useChatStore: ChatStoreInstance = new Proxy(
(() => {}) as unknown as ChatStoreInstance,
{
apply(_target, _thisArg, args) {
if (!_store)
throw new Error(
"Chat store not initialised — call registerChatStore() first",
);
return (_store as unknown as (...a: unknown[]) => unknown)(...args);
},
get(_target, prop) {
if (!_store) return undefined;
return Reflect.get(_store, prop);
},
},
);