feat(inbox-ui): /pages/inbox, header badge, Notifications tab (G-P3-2)
Plan item: G-P3-2 @gauzy/plugin-inbox-ui (Gauzy ↔ Ever Works integration plan, 04-inbox-and-email.md §1–3, 06-gauzy-implementation-plan.md Phase 3).
Owned by 7dae55c0/gw-gauzy-inbox. Other lanes, please don't push to this branch.
What
The Angular side of the Inbox (@gauzy/plugin-inbox, G-P3-1, already on develop).
/pages/inbox(permissionINBOX_VIEW):- Tabs: All, Decisions, Email, Notifications, Archived.
- Filters: kind, source, unread only, debounced search.
- Layout: a list ("load more" on the API cursor) next to the detail pane of the selected item. Opening an unread item marks it read.
- Detail panes:
- Question: the item's options, with the recommended one highlighted, plus a free-text reply. When the item asks for a reason, text is required.
- Approval: Approve / Reject (or the item's own options), with a note.
- Escalation: what was tried, the decision needed and the confidence, with a resolve note.
- Notice: read-only.
- Email: a sandboxed
<iframe srcdoc>viewer.sandbox=""grants nothing (no scripts, no same-origin, no forms, no navigation). The document's own CSP blocks every fetch, remote images included. The message is never in the app's DOM, andbypassSecurityTrustHtmlis not used. P4 plugs the stored email HTML into the same viewer. - Per item: read / unread, archive / unarchive, snooze (1 h / tomorrow 09:00 / next Monday 09:00 / wake now), and links to open the task, project or agent.
- A refusal from the source (e.g. 409 "Already decided in Ever Works") appears in the reply pane, and the item is read again.
- Notifications tab: Gauzy's
EmployeeNotifications, read live from/api/employee-notificationfor the signed-in user's own employee (mark read, mark all read, load more). The receiver is stated explicitly; otherwise aCHANGE_SELECTED_EMPLOYEEholder would list the whole tenant's notifications. Nothing is copied into the Inbox. - Header badge: a bell with my unread count. It reads every 60 s (not while the tab is hidden), and again at once after the page changes an item. It stops on 404 / 403.
Production stays unchanged (flag off)
The UI follows the API. The nav entry and the header badge are added only after GET /api/inbox/items/counts answers for the signed-in user, and that route exists only where GAUZY_INBOX_ENABLED=true loads the backend plugin. With the flag unset (the default, production today), there is no new nav entry and no badge. The /pages/inbox route is registered, but opened directly it only says the Inbox is off.
Core (additive, @gauzy/ui-core)
NavigationBuilderService.registerHeaderAction({ id, component, order?, class?, permissions? })/removeHeaderAction(id)/headerActions$is a generic extension point for plugins' header actions.- The header renders these actions after its own, through
NgComponentOutletinside annb-action. The list is empty unless a plugin registers an action, so nothing changes for other installations. - Spec:
navigation-builder.service.spec.ts.
Wiring
tsconfig.jsonpathapps/gauzy/package.jsonandplugin-ui.config.ts- root build lists:
build:package:plugins:pre*,gauzy*,all*, plusbuild:package:plugin:inbox-ui* - webapp Dockerfile
- API webpack copy-exclusion (
plugin-inbox-ui) - egress
ui-routes.json(/pages/inbox, manual entry)
Verification (owner rule tonight: no local test runs; the full suites run once on develop after all merges)
tsc --noEmiton the plugin's sources and specs plus the navigation builder: no errors in these files. The remaining errors are in unrelated ui-core files and come from modules missing in the local install.prettierandcspellclean on the new and touched files. Only the lines I changed are in the diff; untouched files were not reformatted.- Specs added:
- helpers (tabs, reply checks, Approve/Reject defaults, escalation details, snooze times, links, sources);
- service requests;
- email viewer (sandbox, CSP, text escaping);
- badge polling (at once / 60 s / after a change / stops on 404 / continues after 502);
- plugin registration (entry points only when the API serves the Inbox, and only once);
- the header-action registry.
🤖 Generated with Claude Code
Summary by cubic
Adds the Angular Inbox UI as a new @gauzy/plugin-inbox-ui plugin: a /pages/inbox page (tabs, filters, item detail panes with replies) plus a header badge showing the unread count. Both appear only where the API serves the Inbox, so installations without GAUZY_INBOX_ENABLED=true (the default) keep the current navigation and header unchanged.
Inbox page and badge
- Tabs: All, Decisions, Email, Notifications, Snoozed, Archived; filters for kind, source, unread, and debounced search. The page asks the API (counts) before rendering any tab, so where the Inbox is off, not even Notifications shows.
- Detail panes: Question (options, recommended highlighted, free-text reply, required reason), Approval (approve/reject with note), Escalation (resolve note), Notice (read-only), Email.
- Email opens in a sandboxed
<iframe srcdoc>whose document CSP blocks scripts, remote images, forms, and all fetches; the sandbox blocks popups and top-level navigation, and the message never enters the app's DOM. - Per-item read/unread, archive, snooze presets (wake now lives on the Snoozed tab), links to the task/project/agent, and source refusals (e.g., a 409 "Already decided" shows in the reply pane). Opening an unread item marks it read; under "unread only" the row leaves the list, but the opened item stays open until archived, snoozed, or woken — even when a fresh first page no longer lists it.
- When an item leaves the list (archived, snoozed, woken, read under "unread only"), the list restarts while more pages exist, because the API pages by offset and "load more" would otherwise skip one.
- Failed read/archive/snooze/mark-all-read actions are shown; the Notifications tab re-reads after mark-all-read.
- Notifications tab reads the signed-in user's own employee's notifications from
/api/employee-notification; nothing is copied into the Inbox. - Header badge polls the unread count every 60 s, refreshes after page changes, and stops on 404/403; temporary availability failures (5xx, network) are retried with a growing delay. Availability follows the user: signing out turns the Inbox off at once and cancels a request in flight, and on a user change the badge is removed and re-added while the nav entry is hidden (the builder cannot remove a section).
Core extension point
NavigationBuilderService.registerHeaderAction()lets plugins add header actions rendered after the built-in ones; the header renders nothing extra unless a plugin registers one.