fix(sync): board-card rollback, inbox status sync, markRead error handling
- board-card: capture prev issue before optimistic update, restore on error - useRealtimeSync: wire issue:updated WS handler to update inbox issue_status - inbox: markRead uses optimistic update, refetch on error with toast Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a2e5cbd47b
commit
6761310038
3 changed files with 18 additions and 4 deletions
|
|
@ -217,11 +217,13 @@ export default function InboxPage() {
|
|||
const handleSelect = async (item: InboxItem) => {
|
||||
setSelectedId(item.id);
|
||||
if (!item.read) {
|
||||
useInboxStore.getState().markRead(item.id);
|
||||
try {
|
||||
await api.markInboxRead(item.id);
|
||||
useInboxStore.getState().markRead(item.id);
|
||||
} catch {
|
||||
// silent — selection still works even if mark-read fails
|
||||
// Rollback: refetch to get server truth
|
||||
useInboxStore.getState().fetch();
|
||||
toast.error("Failed to mark as read");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -47,12 +47,14 @@ export const BoardCardContent = memo(function BoardCardContent({
|
|||
|
||||
const handleUpdate = useCallback(
|
||||
(updates: Partial<UpdateIssueRequest>) => {
|
||||
const prev = { ...issue };
|
||||
useIssueStore.getState().updateIssue(issue.id, updates);
|
||||
api.updateIssue(issue.id, updates).catch(() => {
|
||||
useIssueStore.getState().updateIssue(issue.id, prev);
|
||||
toast.error("Failed to update issue");
|
||||
});
|
||||
},
|
||||
[issue.id]
|
||||
[issue],
|
||||
);
|
||||
|
||||
const showPriority = storeProperties.priority;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import type {
|
|||
MemberAddedPayload,
|
||||
WorkspaceDeletedPayload,
|
||||
MemberRemovedPayload,
|
||||
IssueUpdatedPayload,
|
||||
} from "@/shared/types";
|
||||
|
||||
const logger = createLogger("realtime-sync");
|
||||
|
|
@ -78,7 +79,15 @@ export function useRealtimeSync(ws: WSClient | null) {
|
|||
if (refresh) debouncedRefresh(prefix, refresh);
|
||||
});
|
||||
|
||||
// --- Side-effect handlers (toast, navigation, self-check) ---
|
||||
// --- Side-effect handlers (toast, navigation, cross-store sync) ---
|
||||
|
||||
// Keep inbox issue_status in sync when issues change
|
||||
const unsubIssueUpdated = ws.on("issue:updated", (p) => {
|
||||
const { issue } = p as IssueUpdatedPayload;
|
||||
if (issue?.id && issue?.status) {
|
||||
useInboxStore.getState().updateIssueStatus(issue.id, issue.status);
|
||||
}
|
||||
});
|
||||
|
||||
const unsubWsDeleted = ws.on("workspace:deleted", (p) => {
|
||||
const { workspace_id } = p as WorkspaceDeletedPayload;
|
||||
|
|
@ -113,6 +122,7 @@ export function useRealtimeSync(ws: WSClient | null) {
|
|||
|
||||
return () => {
|
||||
unsubAny();
|
||||
unsubIssueUpdated();
|
||||
unsubWsDeleted();
|
||||
unsubMemberRemoved();
|
||||
unsubMemberAdded();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue