Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

06 — Screen Specifications (Notifications Module)

Full interaction spec for the Notification Inbox screen — the only fully implemented screen (plus the shell badge). Shared primitives (text fields, motion tokens, a11y baseline) live in 00-shared/02,03,08,09; referenced by name.


1. Notification Inbox (/notifications)

1.1 Layout (phone)

┌──────────────────────────────┐
│ AppBar: Notifications  [A]   │   [A] = Mark-all-read TextButton
├──────────────────────────────┤
│ NotificationListTile (unread)│   leading avatar/icon, title (bold),
│                              │   body (2-line ellipsis), timestamp,
│                              │   unread dot (right)
│ NotificationListTile (read)  │   title regular weight, dot removed
│ …                            │
│ [loading footer / retry]     │
└──────────────────────────────┘
  • Max content width 640 dp centered (tablet/desktop).
  • No separate detail page in v1 — tapping an item marks it read; if data contains a known route hint ((proposed) data.targetUrl), navigate. data is the raw event payload (notification.schema.ts:28-29), so v1 never guesses — unknown payloads only mark read.

1.2 List Item (NotificationListTile)

RegionContentRules
Leadingtype icon (see 11_Design_System_Mapping §3)40 dp, tinted surface
Titletitle (server, required)unread → bold; 1 line
Bodybody (server, required)2-line clamp, textSecondary
Trailingtimestamp + unread dotrelative time ("2m", "3h", "5d" — 00-shared/02); dot = readAt == null
Tapmark read + optional navigatefull-width hit target ≥ 48 dp

1.3 AppBar Actions

  • Mark all read — visible only when unread > 0; disabled while a request is in flight. Calls PATCH /api/v1/notifications/read-all (controller.ts:43-47).
  • Confirm dialog? No — action is reversible-cost-free; show a transient confirmation ("All notifications marked as read") instead.

1.4 Data & Pagination

  • First load: GET /api/v1/notifications?page=1&limit=20.
  • Response envelope: { data: Notification[], meta: PaginationMeta } where meta = { page, limit, totalItems, totalPages, hasNext, hasPrevious } (pagination-query.dto.ts:32-39).
  • Infinite scroll: fetch page+1 when last item enters viewport and meta.hasNext; append. Footer shows loading spinner; on failure, footer becomes a retry row (do not clear existing items).
  • Pull-to-refresh: refetch page 1 and replace the list (keep scroll position if totalItems unchanged).

1.5 Unread Filter (Filter Chip — optional v1.1)

  • unreadOnly=true (list-notifications.dto.ts:21-25, boolean-coerced from 'true'). Toggling refetches from page 1.
  • Empty-with-filter state: "You're all caught up" + "Clear filter" (proposed).

1.6 States

StateUINotes
idle (loaded)listitems sorted createdAt desc — server contract (repository.ts:66)
first-load loadingskeleton rows (4×)never spinner alone
pagination loadingfooter spinnerexisting rows untouched
emptyillustration + "No notifications"see 05 §3
offline (first load)error panel + Retrynative connectivity check; cached copy if available
offline (pagination)footer "Couldn't load more — Retry"
4xx/5xxerror panel + Retry401/403 → shell handles auth redirect
disabledn/a — no permission-gated controls on own inbox

1.7 a11y

  • Unread items: semantics announce "unread" suffix; dot is not the only indicator (title weight differs too — 00-shared/09).
  • Badge: label = "N unread notifications"; updates via live region, not focus.
  • Mark-all: full label "Mark all notifications as read".

1.8 Motion

  • First-load list items: staggered fade m-base.
  • Read transition: dot removal + weight change m-fast; optional swipe-to-mark-read (Dismissible, m-fast) — (proposed).
  • Badge count change: m-fast scale bounce; see 00-shared/08.

1.9 Analytics (proposed)

notifications.list.open | loaded(n,unread) | item.tap(id,type) | item.mark_read | mark_all | filter.set(unreadOnly) | paginate(page) | error(code)


2. Unread Badge (shell)

  • Poll vs push: no WebSocket consumer exists in this module (01 §3) — v1 refreshes on foreground + after inbox mutations; (forward-looking) realtime via WebSocket /ws (END_TO_END_USER_FLOWS.md:773-775).
  • Count ceiling: display 99+ above 99.
  • Hide when 0; no badge flash on first open of the session.

3. System Feedback (transient)

TriggerFeedback
Mark-all successSnackBar "All notifications marked as read"
Mark-read failuresilent retry ×1, then snackbar; item stays unread
Unread-count fetch failurebadge hidden silently (never show "-1")