docs(backlog): promote session-timeout + event-reminder todos to backlog (999.3/999.4)
This commit is contained in:
@@ -1,34 +0,0 @@
|
||||
---
|
||||
title: Add notification/reminder (VALARM) options to event creation
|
||||
date: 2026-06-07
|
||||
priority: medium
|
||||
phase_hint: Phase 5 (push) or earlier in Phase 03 write-path polish
|
||||
---
|
||||
|
||||
# Add notification/reminder options to event creation
|
||||
|
||||
The event-create/edit form (`apps/pwa` EventForm → `POST /api/events`, `PATCH /:uid/edit`)
|
||||
has **no UI for setting a reminder/alarm on an event**. A user creating an event cannot
|
||||
choose "remind me 10 min / 1 hour / 1 day before". The written `.ics` therefore contains
|
||||
no `VALARM` component, so neither Fastmail's native clients nor any downstream notification
|
||||
path can fire an event reminder.
|
||||
|
||||
This is distinct from (but feeds) the Active requirement
|
||||
"Web Push notifications for event reminders and list changes" (Phase 5): even with web-push
|
||||
infrastructure, there is nothing to notify *about* unless events carry reminder data.
|
||||
|
||||
## Scope to decide when promoted
|
||||
|
||||
- Add a reminder selector to EventForm (none / at time / 10m / 30m / 1h / 1d before; possibly
|
||||
multiple).
|
||||
- Serialize chosen offsets as `VALARM` (TRIGGER) blocks in the iCalendar payload written
|
||||
back to Fastmail via the outbox.
|
||||
- On read, parse existing `VALARM`s so edits preserve/show the current reminder.
|
||||
- Decide division of labour vs Phase 5 web-push: VALARM gives native-client reminders
|
||||
(Fastmail/Apple Calendar) for free; app-delivered web-push reminders are the separate
|
||||
Phase 5 piece that would read these offsets.
|
||||
|
||||
## Open question
|
||||
|
||||
- iCalendar `VALARM` round-trips through `ical.js`; confirm tsdav PUT preserves it and that
|
||||
Fastmail honours `DISPLAY`/`AUDIO` alarms set by a third-party CalDAV client.
|
||||
@@ -1,71 +0,0 @@
|
||||
---
|
||||
title: Redirect to sign-in on session timeout instead of hanging / "couldn't load events"
|
||||
date: 2026-06-07
|
||||
priority: high
|
||||
phase_hint: Phase 03 (auth-entry UX) / Gate 2 polish
|
||||
---
|
||||
|
||||
# Redirect to sign-in on session timeout instead of hanging
|
||||
|
||||
## Problem (observed in live Gate 2 testing)
|
||||
|
||||
When the OIDC session expires **mid-use** (e.g. user leaves the tab open past
|
||||
the session/refresh lifespan, then tries to delete an event), the app does not
|
||||
recognize the signed-out state. The action (delete) appears to hang — no sync
|
||||
toast, no progress — and shortly after the calendar shows a generic
|
||||
**"couldn't load events"** error. The user is fooled into thinking the app is
|
||||
working/broken rather than understanding they've been signed out and need to
|
||||
re-authenticate.
|
||||
|
||||
## Root cause
|
||||
|
||||
Re-auth is only wired to the **initial** `/api/me` failure:
|
||||
- `CalendarShell.tsx` calls `maybeRedirectToLogin()` only on `meQuery.isError`.
|
||||
- `maybeRedirectToLogin()` (apps/pwa/src/lib/loginRedirect.ts) is **one-shot**
|
||||
(guarded by the `familysync.loginRedirectAttempted` sessionStorage flag) and
|
||||
`clearLoginRedirect()` runs on a successful `/api/me`.
|
||||
- Other calls don't drive re-auth:
|
||||
- `fetchEvents` uses `redirect: 'follow'`, so a timed-out session 302s to
|
||||
Authelia (cross-origin) → the XHR rejects → `eventsQuery` just errors
|
||||
("couldn't load events"), no redirect.
|
||||
- Write mutations (create/edit/**delete**) call the API directly; on an
|
||||
expired session the request 302s/opaque-fails and the mutation hangs/errors
|
||||
with no re-auth and no clear feedback.
|
||||
|
||||
So a session that expires after the first successful load has no path back to
|
||||
login except a manual full refresh.
|
||||
|
||||
## Desired behavior
|
||||
|
||||
Any API response that indicates "not authenticated anymore" (401, or an
|
||||
`opaqueredirect`/`type: 'opaqueredirect'` from the OIDC guard's 302 to Authelia)
|
||||
should put the app into a clear **signed-out** state and trigger a top-level
|
||||
re-auth navigation to `/api/login` — from ANY query or mutation, not just the
|
||||
initial `/api/me`. The user should never be left staring at a hung action or a
|
||||
generic data-load error when the real cause is an expired session.
|
||||
|
||||
## Scope to decide when promoted
|
||||
|
||||
- Centralize auth-expiry detection in the API client (apps/pwa/src/api/client.ts):
|
||||
a shared helper that classifies a response as "session expired" (401 or
|
||||
opaqueredirect) and throws a typed `SessionExpiredError`.
|
||||
- Apply `redirect: 'manual'` consistently (fetchEvents/mutations) so the guard's
|
||||
302 is detectable instead of hanging on the cross-origin follow.
|
||||
- A single TanStack Query handler (e.g. QueryCache/MutationCache `onError`, or
|
||||
a small auth-state listener) that, on `SessionExpiredError`, calls a
|
||||
re-armed `maybeRedirectToLogin()` — the one-shot loop guard must be reset so
|
||||
a genuine later expiry can redirect again (clear the flag on detected expiry,
|
||||
not only on successful `/api/me`).
|
||||
- Decide UX: immediate redirect to `/api/login` vs a brief "Your session
|
||||
expired — signing you back in…" toast/interstitial before redirecting (the
|
||||
non-technical Apple member should not see a raw error).
|
||||
- Make sure an in-flight write that fails on expiry is not silently lost — after
|
||||
re-auth, either resurface the unsaved action or clearly tell the user it
|
||||
wasn't saved (ties to T-03-18 no-silent-loss).
|
||||
|
||||
## Notes
|
||||
|
||||
- Relates to the earlier one-shot login redirect work (quick 260606-tv8) and the
|
||||
OIDC state-cookie-churn fix (commit bb61d21 — gate events query on auth).
|
||||
- Verify with a real expiry (or shorten `OIDC_AUTH_EXPIRES` / Authelia
|
||||
`refresh_token_lifespan` in a test) rather than only reasoning about it.
|
||||
Reference in New Issue
Block a user