---
path: /explore
surface: customer
domain: discovery
auth: signed-in
source: apps/oshun/web/src/app/explore/page.tsx
status: walked
last_walked:
  '2026-06-27 automated coverage reconciliation by Codex — ExploreDashboard
  component states, mocked taxonomy Playwright, real-BFF `/v1/search`
  Playwright, domain-switcher, shell partial-outage banners, shell-state
  previews, viewport fit, PWA/offline fallback, and docs center generation
  cross-checked.'
---

# Explore

## Purpose

A way into anything. Explore is a top-level shell surface that combines a
domain/intent-filtered curated discovery feed with debounced full-text search
across the six domains. It is the breadth counterpart to the home shell's
recency view.

## Entry points

- **Shell nav: Explore tab** — `WEB_SHELL_ROUTE_PATHS.explore`; one of the five
  primary shell routes (Home, Explore, Activity, Library, Profile)
- **Search breadcrumb** — `/search` breadcrumbs to "Explore" (see
  [`search.md`](./search.md)); the explore route is the canonical parent for
  search
- **Home "See all" rail link** — see [`home.md`](./home.md)
- **Library focal-stage links** that route to `/explore`
- **Domain unavailable redirect** — middleware can land users here with
  `?unavailableDomain=<id>&unavailableReason=<reason>`; the shared `ShellLayout`
  renders the shell-scoped banner and preserves the query across shell tabs.
  `ExploreDashboard` still exposes `unavailableDomainId` props for
  embedded/direct component use, but `/explore/page.tsx` does not pass route
  search params into those props.
- **Hydrated query** — `?q=…` and `?domain=<searchDomain>` populate the search
  input and active domain on first paint via `useBrowserSearchParams()`
- **Direct URL / bookmark** — yes; URL state mirrors `q` and `domain`
- **Shell state preview** — `?shellState=<mode>` swaps content for
  `ShellSurfaceStatePreview` via
  `resolveShellRouteSurfaceContent({ surface: 'explore' })`

## Layout regions

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

Inside `ExploreDashboard`:

- **Hero / focal stage** — `ShellRouteAnchor` + `ShellLandingFocalStage` with
  search input, suggestions, hero copy that adapts to `activeIntent` and
  `deferredQuery`
- **Domain tab rail** (`DomainTabRail`) — All / Tara / Veritas / Nyx / Arete /
  Nisaba / Metis
- **Intent filter chips** — derived from `EXPLORE_INTENT_FILTERS`
- **Domain breakdown metric pills** — `MetricPill` per domain when results or
  curated routes exist
- **Outage notice** — `CustomerShellOutageBanner` shown when the BFF
  `/v1/search` returns `partialFailure`
- **Search feed** — switches between five `feedState` values:
  `curated | loading | results | empty | idle`
- **Curated discovery rail** — `CuratedCard`s rendered when no query
- **Collections, concept entry points, spotlights** — `filteredCollections`,
  `EXPLORE_CONCEPT_ENTRY_POINTS`, `EXPLORE_SPOTLIGHTS`, all filtered by domain +
  intent
- **Domain briefing panel** (`DomainBriefingPanel`) — per active domain
  (`getExploreDomainSection`)
- **Secondary collapsible panel** (`secondaryOpen`) — additional collections,
  concept entries, spotlights
- **Saved state badges** — derived from `useOshunWebLibraryStore`; library save
  toggles available per result

## States

- [x] **Idle (no query, curated visible)** — `feedState === 'curated'`; curated
      cards, collections, concept entries, and spotlights render. Covered by
      `ExploreDashboard.test.tsx`, `explore-blended-discovery.spec.ts`, and
      `viewport-fit.spec.ts`.
- [x] **Loading (debounced)** — `feedState === 'loading'`; `LoadingState` shown
      after the 260 ms debounce while `fetch(/v1/search)` is in flight. Covered
      by `ExploreDashboard.test.tsx`.
- [x] **Results populated** — `feedState === 'results'`; results filtered by
      intent. Covered by `ExploreDashboard.test.tsx`,
      `explore-blended-discovery.spec.ts`, `nisaba-search-filters.spec.ts`, and
      `explore-real-search-continuity.spec.ts` against the real local BFF.
- [x] **Empty results** — `feedState === 'empty'` after a successful fetch that
      returns no results. Covered by `ExploreDashboard.test.tsx`.
- [x] **Error / abort** — `fetch` failure (non-`AbortError`) clears results and
      outage notice; surfaces no toast in this branch. Covered by
      `ExploreDashboard.test.tsx`.
- [x] **Partial failure (search outage)** — `payload.partialFailure === true`
      triggers `CustomerShellOutageBanner` with degraded-domain list and retry
      control. Covered by `ExploreDashboard.test.tsx` and
      `shell-partial-outage-banners.spec.ts`.
- [x] **Unavailable domain redirect** — `/explore?unavailableDomain=...` shows
      the shell-scoped partial outage banner through `ShellLayout`; the
      dashboard prop remains defaulted for the route. Covered by
      `shell-partial-outage-banners.spec.ts` and
      `shell-domain-unavailable-fallback.spec.ts`.
- [x] **Saved-item state** — items in the user's Library store show "Saved"
      affordance (via `savedItemKeys`). Covered by
      `ExploreDashboardSearchTelemetry.test.tsx` save/unsave assertions and
      `explore-real-search-continuity.spec.ts`, which saves a live Nisaba result
      from `/explore`, waits for the real saved-items BFF write, verifies
      persisted metadata, and hydrates the saved row from a clean second-device
      `/library`.
- [x] **Shell state preview** — `?shellState=` swaps the route content for the
      preview component. Covered by `shell-surface-states.spec.ts`.
- [x] **Reduced motion** — `disableAnimation` prop disables card scroll-in /
      filter chip transitions; Playwright fixtures also emulate reduced motion
      suite-wide. Covered by `ExploreDashboard.test.tsx` and the Explore E2E
      specs that import `./fixtures`.

## Interactions

### Hero search input

- [x] **Search input** (text input inside `ShellLandingFocalStage`)
  - Function: updates `query`; `useDeferredValue` debounces; mutates URL via
    `window.history.replaceState` (no Next router push)
  - Keyboard: standard text input
  - Sends `/v1/search?q=&domain=` with 260 ms debounce
  - Coverage: `ExploreDashboard.test.tsx`, `explore-blended-discovery.spec.ts`,
    `nisaba-search-filters.spec.ts`, and
    `explore-real-search-continuity.spec.ts`.
- [x] **Search suggestion chips** — clicking sets `query` to suggestion text.
      Covered by `ExploreDashboard.test.tsx`.
- [x] **Hero focal links** (`Current handoffs`) — typed into the focal rail from
      `ShellLandingFocalCard`; hrefs are built through `buildActionHref`.
      Covered by `ExploreDashboard.test.tsx`.

### Domain tab rail

- [x] **Each domain tab** (button)
  - Function: sets `activeDomain`; updates URL `domain` param; triggers re-fetch
  - Aria/role: tab list inside `WorkspaceSegmentRail`-style component
  - Coverage: `ExploreDashboard.test.tsx`, `domain-switcher.spec.ts`, and
    `explore-real-search-continuity.spec.ts`.
- [x] **Domain cue button** (`DomainCueButton`) — domain affordance below tabs.
      Covered by `ExploreDashboard.test.tsx` and
      `explore-blended-discovery.spec.ts`.

### Intent chip rail

- [x] **Each intent chip** (`FilterChip`)
  - Function: sets `activeIntent` (returns to/away from `'all'`)
  - Active state visible by `data-active` attribute
  - Coverage: `ExploreDashboard.test.tsx` and
    `explore-blended-discovery.spec.ts`.

### Curated card / collection / spotlight / concept entry

- [x] **Curated card click** (Link) — navigates to constructed href via
      `buildExploreDomainHref`. Covered by `ExploreDashboard.test.tsx` and
      `explore-blended-discovery.spec.ts`.
- [x] **Library save toggle** (when result is library-saveable) — calls
      `toggleOshunWebLibraryItem(buildLibraryItemFromSearchResult(...))`;
      `trackLibraryItemSaved` / `trackLibraryItemUnsaved` fires. Covered by
      `ExploreDashboardSearchTelemetry.test.tsx`; the signed-in real-BFF write
      and clean-device Library hydrate are covered by
      `explore-real-search-continuity.spec.ts`.
- [x] **Search result open** — `trackSearchResultOpened` from
      `@/analytics/searchResultTelemetry` (per result open). Covered by
      `ExploreDashboardSearchTelemetry.test.tsx`.

### Secondary panel

- [x] **Expand/collapse button** — toggles `secondaryOpen`; reveals
      `secondaryCollections`, `secondaryConceptEntries`, `secondarySpotlights`.
      Covered by `ExploreDashboard.test.tsx` and
      `progressive-disclosure.spec.ts`.

### Domain briefing panel

- [x] **Domain action links** (`DomainActionLink`) — domain-specific deeper
      routes; targets vary per `getExploreDomainSection(activeDomain)`. Covered
      by `ExploreDashboard.test.tsx` and `explore-blended-discovery.spec.ts`.
- [x] **`DomainQuickLink`** — quick-navigation tile to the active domain root.
      Covered by `domain-switcher.spec.ts`.

### Outage banner

- [x] **Retry search** — section-scoped outage banner primary action increments
      `searchAttempt` and reissues the same `/v1/search` request. Covered by
      `ExploreDashboard.test.tsx`; shell-level outage banner copy is covered by
      `shell-partial-outage-banners.spec.ts`.

## Data & contracts

- **Reads**:
  - BFF `GET /v1/search?q=<query>&domain=<domain>` (via `buildOshunBffUrl`) —
    returns `ExploreSearchPayload` with `results`, `partialFailure`, `errors`,
    `domainStatus`; direct client calls authenticate with
    `resolveBffAuthToken()`
  - Local discovery models: `EXPLORE_CURATED_ITEMS`, `EXPLORE_COLLECTIONS`,
    `EXPLORE_CONCEPT_ENTRY_POINTS`, `EXPLORE_SPOTLIGHTS`,
    `EXPLORE_QUERY_SUGGESTIONS`, `EXPLORE_INTENT_FILTERS`
  - `useOshunWebLibraryStore()` — saved-item snapshot
- **Writes**:
  - `toggleOshunWebLibraryItem` toggles the library store. For signed-in members
    the store pushes non-demo saves to
    `/v1/library/saved-items/<domain>/<itemId>` and hydrates them back on a
    clean device.
- **Realtime**: _None._
- **Caching**: client-side state for query/intent/domain; URL mirrors query and
  domain; no SWR cache observed
- **Auth/role check**: relies on shell middleware plus the BFF auth pre-handler
  on `/v1/search`
- **Telemetry**:
  - `trackSearchResultOpened` (search-result clicks)
  - `trackLibraryItemSaved` / `trackLibraryItemUnsaved`

## Cross-references

- Shell: [`shell/01-app-shell.md`](../../shell/01-app-shell.md)
- Sibling routes:
  - [`search.md`](./search.md) — dedicated search results view (also issues
    `/v1/search`)
  - [`home.md`](./home.md) — recommendations rail leads here
  - [`library.md`](./library.md) — focal-rail shortcut target
  - [`activity.md`](./activity.md), [`messages.md`](./messages.md),
    [`switcher.md`](./switcher.md)
- Component sources:
  - `apps/oshun/web/src/components/explore/ExploreDashboard.tsx`
  - `apps/oshun/web/src/components/explore/exploreDiscoveryModels.ts`
  - `apps/oshun/web/src/components/search/search-config.ts`
- Feature spec: [`V1/features.md`](../../../V1/features.md)
- Architecture: [`V1/ARCHITECTURE.md`](../../../V1/ARCHITECTURE.md)

## Open questions / known gaps

- [x] `?unavailableDomain=` is handled by `ShellLayout`, not passed from
      `/explore/page.tsx` into `ExploreDashboard`; documented above.
- [x] The section outage banner has an explicit "Retry search" action wired to
      `searchAttempt`; covered above.
- [x] The client no longer hard-codes the search bearer at the call site; it
      calls `resolveBffAuthToken()` and the BFF still enforces auth/scope.
- [x] Explore has no internal dashboard offline branch; route-level offline and
      cached fallback behavior is covered by
      `pwa-install-update-offline.spec.ts` and `shell-surface-states.spec.ts`.
