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
@@ -47,6 +47,7 @@ import { buildCalendarConfig, SX_FIRST_DAY_OF_WEEK } from '../lib/calendarConfig
import { useCalendarStore } from '../store/calendarStore.js'
import { EventDetailPopover } from './EventDetailPopover.js'
import { EventForm } from './EventForm.js'
import { SyncStateToast } from './SyncStateToast.js'
import { AppNav } from './AppNav.js'
import { ColorLegend } from './ColorLegend.js'
import { SkeletonCalendar } from './SkeletonCalendar.js'
@@ -353,6 +354,9 @@ export function CalendarShell() {
{/* EventForm modal — conditionally rendered while eventFormOpen */}
{eventFormOpen && <EventForm />}
{/* SyncStateToast — always mounted; renders nothing when lastSyncedUid is null */}
<SyncStateToast />
</div>
)
}
@@ -424,6 +428,9 @@ export function CalendarShell() {
{/* EventForm modal — conditionally rendered while eventFormOpen */}
{eventFormOpen && <EventForm />}
{/* SyncStateToast — always mounted; renders nothing when lastSyncedUid is null */}
<SyncStateToast />
</div>
)
}