feat(notifications): wire the top-bar bell to the in-app inbox #97

Merged
nrupard merged 4 commits from feat/mapps-132-notification-bell into main 2026-06-09 19:39:12 +02:00
Owner

Problem

MAPPS-132: the top-bar notifications bell did not work. It was a disabled stub (disabled=true, "Notifications coming soon" tooltip) with a hard-coded always-on red dot. The frontend docs recorded the server endpoint as 501, so the bell was left decorative.

That note was stale. The server notifications module is implemented and mounted in mokosh-server (src/api/router.rs merges notifications_routes), serving GET /api/v1/notifications (the in-app inbox, channel_type='in_app', newest-first, paginated) and POST /api/v1/notifications/{id}/read. So the feature is a pure frontend wiring job, no server change needed.

Change

  • NotificationBell component in the TopBar (src/components/layout.rs): fetches the inbox via use_resource, subscribed to the active-tenant generation so it refetches on org switch, and degrades to an empty inbox on fetch error (no error surfaced in the chrome).
  • Red dot now reflects the real unread count (read_at is null) instead of being hard-coded on.
  • Clicking the bell opens a dropdown panel listing items (subject, body, timestamp) with a "No notifications yet" empty state.
  • Clicking an unread row POSTs the mark-read endpoint and refetches, so the row tint and the dot update.
  • src/hooks/fetch.rs: added post_authed_no_content / post_no_content_with_auth. The mark-read endpoint returns an empty 200, which the JSON-parsing post_authed cannot decode; these status-only POST helpers mirror delete_with_auth.
  • Corrected the stale dev-docs rows (client-server-integration.md row 17, codebase-state.md item 24). The /settings/notifications preferences page remains a separate stub and is unchanged.

Verification

  • No Rust toolchain is available in my environment, so this was NOT compiled locally. CI is expected to build it; please confirm the pipeline is green before merge.
  • Server contract verified by reading mokosh-server: route mounted in api/router.rs; list_inbox/mark_read handlers in modules/notifications/{routes,service}.rs; response is { data: [...], meta: {...} } with NotificationInboxItemResponse items (id, subject, body, read_at, created_at), matching the client DTO.
  • Client patterns mirror existing pages (pages/dashboard.rs): use_resource + active_tenant_generation() + read_unchecked().clone().unwrap_or_default(), and the { data } paginated envelope decode.

Manual test checklist (post-merge, real env)

  • Bell renders enabled; with no in-app notifications the panel shows "No notifications yet" and no red dot.
  • With unread in-app notifications, the red dot appears and the count-driving logic lights it.
  • Clicking an unread row clears its unread tint and decrements the dot once the inbox refetches.
  • Switching active org refetches the inbox.

#MAPPS-132

## Problem MAPPS-132: the top-bar notifications bell did not work. It was a disabled stub (`disabled=true`, "Notifications coming soon" tooltip) with a hard-coded always-on red dot. The frontend docs recorded the server endpoint as `501`, so the bell was left decorative. That note was stale. The server `notifications` module is implemented and mounted in `mokosh-server` (`src/api/router.rs` merges `notifications_routes`), serving `GET /api/v1/notifications` (the in-app inbox, `channel_type='in_app'`, newest-first, paginated) and `POST /api/v1/notifications/{id}/read`. So the feature is a pure frontend wiring job, no server change needed. ## Change - `NotificationBell` component in the TopBar (`src/components/layout.rs`): fetches the inbox via `use_resource`, subscribed to the active-tenant generation so it refetches on org switch, and degrades to an empty inbox on fetch error (no error surfaced in the chrome). - Red dot now reflects the real unread count (`read_at` is null) instead of being hard-coded on. - Clicking the bell opens a dropdown panel listing items (subject, body, timestamp) with a "No notifications yet" empty state. - Clicking an unread row POSTs the mark-read endpoint and refetches, so the row tint and the dot update. - `src/hooks/fetch.rs`: added `post_authed_no_content` / `post_no_content_with_auth`. The mark-read endpoint returns an empty `200`, which the JSON-parsing `post_authed` cannot decode; these status-only POST helpers mirror `delete_with_auth`. - Corrected the stale dev-docs rows (`client-server-integration.md` row 17, `codebase-state.md` item 24). The `/settings/notifications` preferences page remains a separate stub and is unchanged. ## Verification - No Rust toolchain is available in my environment, so this was NOT compiled locally. CI is expected to build it; please confirm the pipeline is green before merge. - Server contract verified by reading `mokosh-server`: route mounted in `api/router.rs`; `list_inbox`/`mark_read` handlers in `modules/notifications/{routes,service}.rs`; response is `{ data: [...], meta: {...} }` with `NotificationInboxItemResponse` items (`id, subject, body, read_at, created_at`), matching the client DTO. - Client patterns mirror existing pages (`pages/dashboard.rs`): `use_resource` + `active_tenant_generation()` + `read_unchecked().clone().unwrap_or_default()`, and the `{ data }` paginated envelope decode. ## Manual test checklist (post-merge, real env) - Bell renders enabled; with no in-app notifications the panel shows "No notifications yet" and no red dot. - With unread in-app notifications, the red dot appears and the count-driving logic lights it. - Clicking an unread row clears its unread tint and decrements the dot once the inbox refetches. - Switching active org refetches the inbox. #MAPPS-132
feat(notifications): wire the top-bar bell to the in-app inbox
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 42s
0c07ecf8a2
The notifications bell was a disabled stub (`disabled=true`, "coming soon" tooltip, a hard-coded always-on red dot) because the frontend docs recorded the server notifications endpoint as 501. That note was stale: the server `notifications` module is implemented and mounted, serving `GET /api/v1/notifications` (the in-app inbox, newest-first) and `POST /api/v1/notifications/{id}/read`. This wires the bell to it.

`NotificationBell` (in the TopBar) fetches the inbox with `use_resource`, subscribed to the active-tenant generation so it refetches on org switch, and degrades to an empty inbox on fetch error. The red dot now reflects the real unread count (`read_at IS NULL`) instead of being hard-coded. Clicking the bell opens a dropdown panel listing items (subject, body, timestamp) with a "No notifications yet" empty state. Clicking an unread row POSTs the mark-read endpoint and refetches so the row and the dot update.

`mark_read` returns an empty 200 body, which the JSON-parsing `post_authed` cannot decode, so `fetch.rs` gains `post_authed_no_content` / `post_no_content_with_auth` (status-only POST, mirroring `delete_with_auth`).

The stale dev-docs rows (client-server-integration row 17, codebase-state item 24) are corrected to reflect that the inbox endpoints exist and the bell is wired. The `/settings/notifications` preferences page remains a separate stub and is unchanged.

Note: no Rust toolchain is available in this environment, so this was not compiled locally; CI is expected to build it.

#MAPPS-132

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(notifications): close panel on outside click + show local-tz timestamps
Some checks failed
Check / clippy + fmt + tests (pull_request) Failing after 10s
ed0ca029a4
Review follow-up on the notification bell. Two UX fixes:

The dropdown only closed by toggling the bell again. Add a full-viewport click-catcher rendered behind the panel (lower z-index) so any click outside the dropdown closes it, while clicks on the panel itself are unaffected.

Notification timestamps rendered in UTC. Format them in the viewer's local timezone via the browser's `Date.toLocaleString` (the client has no `chrono-tz`, so the conversion is delegated to JS, which already knows the user's zone), with a UTC string fallback when the JS bridge is unavailable.

#MAPPS-132

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
style(notifications): satisfy rustfmt on the Date::new call
Some checks failed
Check / clippy + fmt + tests (pull_request) Failing after 6s
2dde8b2d32
#MAPPS-132

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
style(notifications): match rustfmt multiline Date::new form
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 1m14s
Create release / Create release from merged PR (pull_request) Has been skipped
0d3461855f
#MAPPS-132

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch feat/mapps-132-notification-bell 2026-06-09 19:39:13 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-apps!97
No description provided.