feat(03-06): implement SyncStateToast with polled sync-status feedback (D-05/D-06/D-08/D-09)

- SyncStateToast: pending/done/failed/dead states per UI-SPEC
- refetchInterval 3000ms while pending; disabled on terminal status
- done + conflict (412) invalidate ['events'] cache (D-06/D-08)
- done auto-dismisses after 2s; failed/dead persist with dismiss button
- role=status (pending/done) and role=alert (failed/dead) for a11y
- Mounted in CalendarShell (both phone + tablet/desktop layouts)
- EventForm.onSuccess: setLastSyncedUid(uid) instead of invalidateQueries
This commit is contained in:
Lucas Berger
2026-06-05 18:43:36 -04:00
parent 6874e1a074
commit aa7c4c37d4
5 changed files with 286 additions and 50 deletions
+5 -2
View File
@@ -104,6 +104,7 @@ function parseDateTime(iso: string): { date: string; time: string } {
export function EventForm() {
const { eventFormOpen, eventFormMode, eventFormUid, setEventForm } = useCalendarStore()
const setLastSyncedUid = useCalendarStore((s) => s.setLastSyncedUid)
const queryClient = useQueryClient()
const titleRef = useRef<HTMLInputElement>(null)
@@ -190,8 +191,10 @@ export function EventForm() {
eventFormMode === 'edit' && eventFormUid
? updateEvent(eventFormUid, payload)
: createEvent(payload),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['events'] })
onSuccess: (data) => {
// Wire sync-toast: set the returned UID so SyncStateToast starts polling (D-05/D-09)
setLastSyncedUid(data.uid)
// Do NOT invalidate here — SyncStateToast does it on done/conflict (D-06/D-08)
if (calendarUrl) writeLastCalendarUrl(calendarUrl)
setEventForm(false)
},