10 — Interaction Specification (Notifications Module)
- 1. Item Tap (mark read + optional navigate)
- 2. Unread Badge Mutations
- 3. Mark All Read
- 4. Pull-to-Refresh
- 5. Infinite Scroll
- 6. Unread Filter (proposed v1.1)
- 7. Swipe-to-Mark-Read (proposed)
- 8. Keyboard / Shortcut (desktop)
- 9. Motion Summary
Precise interaction rules for the inbox + badge. Motion tokens from 00-shared/08; accessibility baseline 00-shared/09.
1. Item Tap (mark read + optional navigate)
- Tap tile → if
readAt == null: optimistic flip to read (dot off, weight down,m-fast) → firePATCH /api/v1/notifications/:id/read(controller.ts:37-41). - Response
200with document → reconcile (no visual change needed). - Response
200 null(not owner) → revert? No — treat as already-read; remove from unread scope. - Error → retry once silently; then snackbar "Couldn't update" and revert tile.
- If
datacarries a recognized route hint (proposeddata.targetUrl) → navigate after successful mark; v1 has no hint → no navigation.
2. Unread Badge Mutations
| Trigger | Effect |
|---|---|
| tap item (unread) | decrement on optimistic flip; server fail → re-increment |
| mark-all success | → 0 immediately |
| inbox opened | refetch count; reconcile with list-derived count if drift |
| app foreground / login | refetch count |
| new notification arrives | count+1 (v1: on next fetch — no WS push, 01 §3) |
3. Mark All Read
- Enabled iff
unread > 0and no in-flight request. - Fire
PATCH /api/v1/notifications/read-all(controller.ts:43-47) → on success: badge 0, all tiles read (m-fastbatch, no per-tile animations), snackbar confirmation. - Failure: revert, snackbar with Retry.
4. Pull-to-Refresh
- Refetch page 1 (and unread-count in parallel). Replace list on success; keep
scroll offset when
meta.totalItemsunchanged.
5. Infinite Scroll
- Trigger: last visible index ≥
data.length - threshold(4)andmeta.hasNext. - Fetch
page+1; append; dedupe by_id(defensive — server pagination is offset-based, stable under insert-on-top drift can duplicate a row across pages; dedupe client-side, see 14 §B).
6. Unread Filter (proposed v1.1)
- Chip toggle → reset to page 1 with
unreadOnly(dto:21-25); chip shows active state; empty-with-filter shows "all caught up" (05 §3).
7. Swipe-to-Mark-Read (proposed)
Dismissibleend-dismiss (m-fast) → same as §1 (optimistic + API), no navigate. Do not allow swipe on already-read tiles.
8. Keyboard / Shortcut (desktop)
m= mark all read when inbox focused;j/kmove focus;Enter= tap. (proposed) — adaptivity in 00-shared/11.
9. Motion Summary
| Interaction | Token | Note |
|---|---|---|
| item read flip | m-fast | dot fade + weight change, no slide |
| first-load stagger | m-base | rows fade, 40 ms stagger |
| badge count change | m-fast | scale 1→1.15→1 |
| page swap (filter/refresh replace) | m-base | crossfade list |
| snackbar confirmations | m-base | standard toast |