---
path: /activity
surface: customer
domain: discovery
auth: signed-in
source: apps/oshun/web/src/app/activity/page.tsx
status: walked
last_walked:
  '2026-06-27 automated coverage reconciliation by Codex — real-dev Activity
  Playwright expanded for scoped empty live timeline, live loading, real Nisaba
  notebook write/read through BFF activity aggregation, support-lane absence
  notes, focal shortcuts, desktop/laptop support layouts; component, telemetry,
  shell-state, outage, PWA/offline, accessibility, and BFF contract coverage
  cross-checked.'
---

# Activity

## Purpose

A cross-domain continuity surface. Activity surfaces the real threads worth
returning to — saved continuations, notebooks, tutoring/session artifacts,
assistant excerpts, and backed achievement progress — grouped into "continuity
threads" so the next move is easy to pick up. Milestones, streak calendar, and
weekly digest have UI components but no live V1 backing projection yet; the
route renders explicit unavailable notes instead of fixture data.

## Entry points

- **Shell nav: Activity tab** — `WEB_SHELL_ROUTE_PATHS.activity`; primary entry
  from any shell route
- **Home rail** ("See all" on the activity feed on `/`) — see
  [`home.md`](./home.md)
- **Home focal-stage shortcut** — `Back to Home` and `Open Library` rail
  shortcuts inside the Activity focal stage cross-link from sibling shell
  surfaces (so users return here from there)
- **Re-entry from messages, library, or domain surfaces** when an action card
  deep-links to `/activity`
- **Direct URL / bookmark** — yes, auth required (no anonymous redirect logic in
  `page.tsx`; relies on shell middleware)
- **Shell state preview** — `?shellState=<mode>` swaps the content for
  `ShellSurfaceStatePreview` (loading / empty / error / offline / etc.) via
  `resolveShellRouteSurfaceContent({ surface: 'activity' })`

## Layout regions

`page.tsx` mounts `ShellLayout` with `active="activity"` and breadcrumbs
`Home → Activity`, then renders `<ActivityDashboard />` (or the shell-state
preview).

Inside `ActivityDashboard`:

- **Focal stage** (`ShellRouteAnchor` + `ShellLandingFocalStage`):
  - Focal canvas headline ("N threads are ready to reopen right now."), summary,
    and up to two "lead thread" cards (`data-activity-focal-thread`)
  - Focal rail with `DataSourceIndicator` (Live data / Offline data, refresh)
    plus metric pills (Threads, Cross-domain, Domains, Ready now)
  - Useful next surfaces card (Back to Home, Open Library)
- **Primary lane** — `EnhancedTimeline`:
  - Section header "Continuity timeline" with summary line
    `N threads · N cross-domain · N ready to resume` and "Mark all read"
    affordance
  - Continuity summary tile grid (`data-activity-continuity-stat`)
  - `WorkspaceToolbar` ("Activity controls") with domain `WorkspaceSegmentRail`
    and thread-type chip pills, plus a `Reset filters` chip when filters are on
  - Timeline list with a vertical guideline; time-grouped continuity thread
    cards
- **Support lane** (`WorkspaceSupportLane`):
  - `AchievementsSection` — achievement card grid with rarity borders + detail
    overlay when `/v1/achievements/user` + `/definitions` return records
  - `MilestonesSection` — milestone cards with confetti celebration when data is
    supplied; live route currently shows an unavailable note
  - `StreakCalendarSection` — per-day streak calendar with domain dots when data
    is supplied; live route currently shows an unavailable note
  - `WeeklyDigestSection` — glass-style weekly review panel with expandable
    highlights when data is supplied; live route currently shows an unavailable
    note
  - Docked aside on wide desktop (`supportDocked`); collapses into a single lane
    below the timeline on smaller breakpoints

## States

- [x] **Initial live fetch / loading** — `useActivityData()` starts
      `loading=true`; `DataSourceIndicator` intentionally returns null while the
      shell, focal stage, and timeline stay composed. Covered by
      `activity-cross-domain-continuity.spec.ts` with a delayed real
      `/v1/activity` request.
- [x] **Live data, populated** — `isLive=true`; live BFF activity timeline
      renders a real seeded Nisaba notebook in the focal lead card and the
      timeline. Covered by `activity-cross-domain-continuity.spec.ts` and BFF
      route tests for real Nisaba/Metis records.
- [x] **Honest empty live timeline** — scoped real BFF read with no Nisaba/Metis
      records shows "Nothing is waiting to reopen right now.", no invented
      continuity threads, no retired `thread-grounding-evidence`, and
      `data-activity-empty`. Covered by
      `activity-cross-domain-continuity.spec.ts`.
- [x] **Offline / fallback / endpoint failure** — live endpoint failure renders
      `CustomerShellOutageBanner` ("Live activity is unavailable") and keeps
      activity sections honest-empty instead of showing fixture fallback data.
      Covered by `shell-partial-outage-banners.spec.ts` and
      `ActivityDashboard.test.tsx`.
- [x] **Error (recoverable)** — `error` text is surfaced in the data-source
      indicator and outage banner reason; Refresh retries
      `useActivityData().refresh()`. Covered by component tests and the
      route-level outage Playwright spec.
- [x] **Empty filter result** — timeline shows "No continuity threads match the
      current filters." when active domain/kind filters remove all threads.
      Covered by `activity-cross-domain-continuity.spec.ts` and component filter
      tests.
- [x] **No unread items** — "Mark all read" is hidden when `unreadCount=0`; read
      cards have no unread dot. Covered by the empty Activity E2E and
      `ActivityDashboard.test.tsx`.
- [x] **Active filters** — domain `!== 'all'` or kind `!== 'all'` reveals
      `Reset filters` chip. Covered by `ActivityDashboard.test.tsx`.
- [x] **Achievement detail overlay** — clicking an achievement opens the
      modal-style `AchievementDetailOverlay`; close button hides it. Covered by
      `ActivityDashboard.test.tsx`.
- [x] **Unavailable live support projections** — milestone, streak-calendar, and
      weekly-digest backing projections are absent in V1; the live route shows
      unavailable notes for each. Covered by
      `activity-cross-domain-continuity.spec.ts`.
- [x] **Milestone celebration** — `MilestonesSection` triggers a confetti burst
      via `setCelebrating(id)` for ~2.5 s when milestone data is supplied.
      Covered by `ActivityDashboard.test.tsx` fixture coverage.
- [x] **Reduced motion** — `disableAnimation` suppresses entrance animations,
      confetti, pulses, and spin in component coverage; shell PWA reduced-motion
      spinner behavior is covered in `pwa-lifecycle-deepening.spec.ts`.
- [x] **Shell state preview** — `?shellState=loading|empty|error|offline|...`
      replaces the workspace with `ShellSurfaceStatePreview` (handled in
      `page.tsx`). Covered by `shell-surface-states.spec.ts`.
- [x] **Standalone PWA** — Activity relies on shell PWA behavior; standalone
      cold start and offline cached shell restore `/activity` without a
      route-specific branch. Covered by `pwa-lifecycle-deepening.spec.ts` and
      `offline-shell-routes.spec.ts`.

## Interactions

### Focal stage

- [x] **Lead thread card** (link, `data-activity-focal-thread`)
  - Function: navigates to `thread.actionHref` or
    `WEB_SHELL_ROUTE_PATHS.activity`
  - Mobile: wraps; domains chip-bar reflows
  - Telemetry: _none in this branch._
  - Coverage: real Nisaba notebook focal lead href asserted in
    `activity-cross-domain-continuity.spec.ts`; component layout covered in
    `ActivityDashboard.test.tsx`.
- [x] **`Refresh`** (button inside `DataSourceIndicator`)
  - Function: calls `useActivityData().refresh()`; re-fetches the activity
    payload
  - Disabled when: _none — always clickable; loading branch returns null
    entirely_
  - Coverage: `ActivityDashboard.test.tsx`.
- [x] **`Back to Home` / `Open Library`** shortcut cards
  - Function: navigate to `WEB_SHELL_ROUTE_PATHS.home` /
    `WEB_SHELL_ROUTE_PATHS.library`
  - Coverage: route hrefs asserted in
    `activity-cross-domain-continuity.spec.ts`.

### Continuity timeline header

- [x] **`Mark all read`** (button)
  - Function: optimistically marks every timeline item read after a 400 ms
    delay; spinner shows during the delay
  - Disabled when: `markingAllRead === true` (cursor `wait`, 0.6 opacity)
  - Hidden when: `unreadCount === 0`
  - Coverage: spinner + all-read transition covered in
    `ActivityDashboard.test.tsx`; hidden state covered in
    `activity-cross-domain-continuity.spec.ts`.

### Activity controls toolbar

- [x] **Domain segments** (`DomainFilterTabs`, `WorkspaceSegmentRail`)
  - Function: filters threads by `WebNavigableDomainId | 'all'`
  - Aria-label: "Activity domains"
  - Options: All, Tara, Veritas, Nyx, Arete, Nisaba, Metis
  - Coverage: route empty filters in `activity-cross-domain-continuity.spec.ts`;
    component Tara/Metis filtering in `ActivityDashboard.test.tsx`.
- [x] **Thread type pills** (`KindFilterPills`, `WorkspaceChoiceChip`)
  - Function: filters by `ActivityKind | 'all'`
  - Options: All types, update, reminder, event, insight
  - Coverage: route kind-empty filter in
    `activity-cross-domain-continuity.spec.ts`; component kind filtering in
    `ActivityDashboard.test.tsx`.
- [x] **`Reset filters`** (button) — appears only when filters are active;
      resets to All / All
- [x] **`<N> threads` status badge** (`WorkspaceStatusBadge`) — non-interactive

### Continuity thread card (one per thread; nested step rows)

- [x] **Card hover/focus** — translates 2 px upward, soft drop-shadow
- [x] **Unread dot** — pulses; suppressed when `disableAnimation` or
      `unreadCount === 0`
- [x] **Domain path label, step count, status label, priority badge, timestamp**
      — non-interactive eyebrows
- [x] **Thread reason and metrics row** — non-interactive
- [x] **Domain chips** — non-interactive (one per `thread.domains`)
- [x] **Step row · action link** (Link, `data-activity-thread-step-action`)
  - Function: navigates to `step.actionHref`
  - Telemetry: `trackActivityReentryOpened` with `trigger: 'step'`
- [x] **Step row · Mark read** (button) — visible only on unread steps; calls
      `handleMarkRead(step.id)`
- [x] **Step assistant highlight link** — Telemetry:
      `trackActivityReentryOpened` with `trigger: 'assistant-highlight'`
- [x] **Step study-continuation link** (notebook / course / assessment /
      tutoring) — Telemetry: `trackActivityReentryOpened` with
      `trigger: 'study-continuation'`
- [x] **Thread action link** (e.g., "Resume") — Telemetry:
      `trackActivityReentryOpened` with `trigger: 'thread'`
  - Coverage: thread/card rendering and mark-read interactions in
    `ActivityDashboard.test.tsx`; thread/step/assistant/study telemetry in
    `ActivityDashboardTelemetry.test.tsx` and
    `activity-reentry-analytics.spec.ts`; real notebook action href and click in
    `activity-cross-domain-continuity.spec.ts`.

### Achievements section

- [x] **Achievement card** (button) — opens `AchievementDetailOverlay`
- [x] **Achievement detail overlay**
  - **Close (×)** — closes overlay
  - **Backdrop click** — closes overlay
  - Lists Requirements and Tips when present
  - Shows progress bar when locked; "Unlocked on …" when unlocked
  - Coverage: `ActivityDashboard.test.tsx`.

### Milestones section

- [x] **Milestone celebrate trigger** — fires `ConfettiBurst` for ~2.5 s when
      milestone data is supplied. Covered by `ActivityDashboard.test.tsx`.

### Streak calendar

- [x] **Per-day cell** — entrance staggered; reduced motion suppresses
      animations
- [x] **Domain dots inside cells** — non-interactive indicators
  - Coverage: `ActivityDashboard.test.tsx`.

### Weekly digest

- [x] **Highlights expand/collapse** — expanding panel inside `glass`-styled
      section
  - Coverage: `ActivityDashboard.test.tsx`.

## Data & contracts

- **Reads**: `useActivityData()` (see
  `apps/oshun/web/src/components/activity/data/useActivityData.ts`) fans out to
  live backends:
  - Timeline: `GET /v1/activity` (BFF alias for `/activity`), returning only
    real member activity from authorized domains.
  - Achievements: `GET /v1/achievements/user` plus
    `GET /v1/achievements/definitions`.
  - Milestones, streak calendar, and weekly digest: no live V1 backing
    projection yet; the hook leaves these empty/null.
- **Writes**: none from `page.tsx`; `Mark read` / `Mark all read` are local
  state mutations on the `items` list
- **Realtime**: _None observed in this surface — refresh is manual._
- **Caching**: `useActivityData` stores client state; BFF activity responses use
  authenticated cache keys partitioned by user, scopes, tenant/residency, route,
  pagination, tier, and suspended-domain headers.
- **Auth/role check**: `page.tsx` has no auth guard; depends on shell middleware
  and the BFF bearer scopes (`domain:*` or `domain:<id>`) for Activity
  aggregation
- **Telemetry**: `trackActivityReentryOpened` from
  `@/analytics/activityReentryTelemetry` (surface `timeline`, triggers
  `thread | step | study-continuation | assistant-highlight`)

## Automated coverage

- `apps/oshun/web/e2e/activity-cross-domain-continuity.spec.ts` — real-dev
  Activity route coverage for scoped empty live timeline, live loading, real
  Nisaba notebook write/read through BFF aggregation, focal lead card, thread
  action navigation, shortcut hrefs, filter-empty states, Metis tab, desktop
  docked support lane, unavailable live support projections, and laptop
  collapsed support lane.
- `apps/oshun/web/e2e/activity-reentry-analytics.spec.ts` — route-level re-entry
  analytics for thread, step, assistant-highlight, and study-continuation
  triggers with mocked contract-shaped timeline data.
- `apps/oshun/bff/src/__tests__/activity-route.test.ts` — BFF activity contract:
  real authorized-domain timelines, no fabricated rows for empty domains,
  degraded adapter handling, domain-scope filtering, real Nisaba notebook, real
  Metis ingest/tutoring activity, legacy `/v1/activity` alias, pagination, cache
  hits, auth required, and missing-domain-scope rejection.
- `apps/oshun/web/src/components/activity/__tests__/ActivityDashboard.test.tsx`
  — component coverage for dashboard composition, fallback banner, refresh,
  timeline cards, filtering, mark-read, mark-all-read, achievements overlay,
  milestone celebration, streak calendar, weekly digest, reduced animation, and
  support-lane responsive behavior.
- `apps/oshun/web/src/components/activity/__tests__/ActivityDashboardTelemetry.test.tsx`
  — telemetry payload coverage for thread, step, assistant-highlight, and
  study-continuation re-entry with active filters.
- Shared shell/PWA/viewport/a11y coverage:
  `shell-partial-outage-banners.spec.ts`, `shell-surface-states.spec.ts`,
  `pwa-lifecycle-deepening.spec.ts`, `offline-shell-routes.spec.ts`,
  `viewport-fit.spec.ts`, `redesigned-surface-breakpoints.spec.ts`, and
  `shell-route-accessibility.spec.ts`.

## Cross-references

- Shell: [`shell/01-app-shell.md`](../../shell/01-app-shell.md)
- Sibling routes:
  - [`home.md`](./home.md) — activity feed entry rail
  - [`library.md`](./library.md) — focal-rail shortcut destination
  - [`messages.md`](./messages.md)
  - [`explore.md`](./explore.md)
  - [`search.md`](./search.md)
  - [`switcher.md`](./switcher.md)
- Component sources:
  - `apps/oshun/web/src/components/activity/ActivityDashboard.tsx`
  - `apps/oshun/web/src/components/activity/activityContinuityModels.ts`
  - `apps/oshun/web/src/components/activity/data/useActivityData.ts`
  - `apps/oshun/web/src/analytics/activityReentryTelemetry.ts`
- Feature spec: [`V1/features.md`](../../../V1/features.md)
- Architecture: [`V1/ARCHITECTURE.md`](../../../V1/ARCHITECTURE.md)

## Open questions / known gaps

- [x] **Runtime walk (2026-05-29) — defect FIXED & verified:** React 418
      hydration mismatch resolved (time-derived values now deferred to a
      post-mount effect via `useClientNow`/`useIsMounted` from
      `@/hooks/useClientTime`; for `/coordinates` the missed `RiseSetCalculator`
      site was gated). Re-walked against a fresh build: 0 page errors, 0 console
      errors, HTTP 200. React error #418; visit https://react.dev/errors/418?a.
      React error 418 is a hydration mismatch — server-rendered HTML differs
      from the client (this view renders time/position-dependent content, e.g.
      `new Date()`/`toLocale*`, without `suppressHydrationWarning`). Fix:
      compute time-dependent values in an effect or gate with
      `suppressHydrationWarning`. Status kept `drafted` until fixed.
- [x] Confirm the real endpoint(s) feeding `useActivityData()` and whether the
      "Live data" badge corresponds to a specific BFF route: timeline is
      `/v1/activity`; achievements fan out to `/v1/achievements/user` and
      `/v1/achievements/definitions`; `isLive` is true when either live section
      resolves.
- [x] Document write-side semantics: `Mark read` / `Mark all read` are currently
      client-only state transitions, not BFF writes.
- [x] Confirm whether achievement, milestone, streak, and weekly-digest sources
      are part of the same `useActivityData()` payload or fan-out separately:
      achievements fan out separately; milestone, streak, and weekly digest have
      no live V1 backing projection and remain empty/null.
- [x] Verify that the focal-stage `Back to Home` / `Open Library` cards meet
      touch-target rules on mobile. Covered by
      `activity-cross-domain-continuity.spec.ts` at 390 px width.
