Skip to content

Move Recent Activity Into A Global Activity Panel #617

Description

@b-at-neu

Goal

Move "Recent activity" out of the dashboards and into one panel reachable from anywhere in the app.

Today ActivityFeed is a SectionCard rendered on all three dashboards (admin-dashboard.tsx, manager-dashboard.tsx, user-dashboard.tsx), fed by getMyRecentActivity and getRecentApplications. It's only visible while standing on the dashboard.

Scope decision: no unread state. The feed stays derived from existing data — no schema change, no lastSeenNotificationsAt, no badge, no mark-as-read. This ticket relocates and improves access to information the app already computes. Per-item read state can come later if it's ever wanted.

Where the trigger goes

There is no shared persistent header — app-shell.tsx is sidebar (desktop, md:flex) + MobileNav (mobile, md:hidden) + main. So the trigger needs a home in both. They're structurally parallel, which makes this clean:

  • Desktop — sidebar.tsx, the h-14 header bar. It currently holds only the logo link and has free space to its right. Add the trigger there with ml-auto.
  • Mobile — mobile-nav.tsx, the existing ml-auto cluster. That cluster already holds the hamburger SheetTrigger; the activity trigger goes immediately to its left, so the cluster reads [activity] [menu].

Both headers are h-14 with the same bg-sidebar border-sidebar-border treatment and the same logo block, so the control lands top-right on both breakpoints — one position to learn.

Rejected alternatives, and why:

  • Sidebar footer beside UserMenu — puts it bottom-left on desktop and top-right on mobile. Two different places for one control.
  • A NavList entry — nav items navigate to routes. A control that opens a drawer shouldn't sit among them pretending to be a destination.

The panel itself

  • Sheet with side="right". components/ui/sheet.tsx already exists, docs/DESIGN.md establishes Sheet as this app's drawer pattern, and one component works at every breakpoint. side="right" mattersMobileNav's menu sheet is side="left", so opposite sides keep the two drawers from reading as the same surface.
  • Use CONCEPT_ICONS.activity, not a bell. A bell promises unread counts this ticket deliberately doesn't have. The activity icon is already the one on the current SectionCard, so the vocabulary stays consistent — see Standardize Lucide Iconography Across The App #572.
  • Contents: the existing ActivityFeedList markup, largely as-is — status dot, text, relative time via LocalTime — with the same empty state. Grouping by day is nice if it falls out cheaply; it isn't the point.
  • Only render the trigger when signed in. identity is null for anonymous visitors and there's nothing to show.

Scope

1. Add the trigger in both sidebar.tsx and mobile-nav.tsx as described.

2. Build the sheet around the existing feed markup, extracted from activity-feed.tsx so the list renders identically in its new home.

3. Preserve the per-persona split. The feed already differs by role — getMyRecentActivity for applicants, getRecentApplications for reviewers — and reviewer content must stay inside buildApplicationScopeWhere, so a manager sees only their managed positions' activity. Do not collapse the two into one unscoped query.

4. Remove "Recent activity" from all three dashboards. Move it, don't duplicate it — two copies of one list is worse than today, and removing the card reclaims real dashboard space.

5. Server-rendered, opened client-side. Fetch on the server as the dashboards already do and pass items down through the shell; the sheet is a client leaf owning only open/close. No useEffect fetching.

Relationship to other tickets

  • Manager Daily Digest #549 (manager daily digest) — when scoping the email epic, an in-app "new since you were last here" surface was the alternative to a cron-driven digest email. This panel is the beginning of that surface, but without unread state it does not replace a digest: a manager still has no way to learn that something changed without opening the panel and reading it. Revisit Manager Daily Digest #549 once this lands — the gap it fills is "tell me", which this ticket deliberately does not.
  • Standardize Lucide Iconography Across The App #572 (iconography) — the trigger icon should come from the shared CONCEPT_ICONS vocabulary.

Non-goals

  • No unread state, badge, or mark-as-read. Explicitly deferred.
  • No Notification model and no schema change of any kind.
  • No /notifications page.
  • No push notifications and no email — that's the Email Notifications For Application Events #400 epic.
  • No new event types; this surfaces what the app already computes.

Acceptance criteria

  • The trigger appears top-right on both desktop and mobile, on every authenticated page.
  • It does not render for anonymous visitors.
  • The panel opens from the right and does not conflict with the left-side mobile nav drawer.
  • An applicant sees their own activity; a manager sees only their managed positions'; an admin sees across published positions.
  • "Recent activity" no longer appears on any dashboard.
  • Empty state handled, matching the current SectionCardEmpty treatment.
  • Keyboard operable with focus trapped in the sheet; verified in both themes at 375px, 768px and 1280px.
  • No new database columns or migrations.
  • docs/WORKFLOWS.md updated — the dashboard entries (AP-1, PM-1 and the admin equivalent) each describe the Recent activity card.
  • npm run prettier:check, eslint:check, tsc:check, test all pass.

Tests

  • tests/db/ — the reviewer feed respects position scope; the applicant feed returns only the caller's own activity.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions