V1 Web PWA · Surface walkthrough

Explore

A per-surface walkthrough of the V1 Web PWA customer surface: layout, states, interactions, data, and cross-references.

walked
8sections6 minread

On this page

Context. surface customer · domain discovery · route /explore · auth signed-in · source apps/oshun/web/src/app/explore/page.tsx

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.

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 tabWEB_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); the explore route is the canonical parent for search
  • Home "See all" rail link — see 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 stageShellRouteAnchor + 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 pillsMetricPill per domain when results or curated routes exist
  • Outage noticeCustomerShellOutageBanner shown when the BFF /v1/search returns partialFailure
  • Search feed — switches between five feedState values: curated | loading | results | empty | idle
  • Curated discovery railCuratedCards rendered when no query
  • Collections, concept entry points, spotlightsfilteredCollections, 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#

  • 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.
  • Loading (debounced)feedState === 'loading'; LoadingState shown after the 260 ms debounce while fetch(/v1/search) is in flight. Covered by ExploreDashboard.test.tsx.
  • Results populatedfeedState === '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.
  • Empty resultsfeedState === 'empty' after a successful fetch that returns no results. Covered by ExploreDashboard.test.tsx.
  • Error / abortfetch failure (non-AbortError) clears results and outage notice; surfaces no toast in this branch. Covered by ExploreDashboard.test.tsx.
  • 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.
  • 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.
  • 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.
  • Shell state preview?shellState= swaps the route content for the preview component. Covered by shell-surface-states.spec.ts.
  • Reduced motiondisableAnimation 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#

  • 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.
  • Search suggestion chips — clicking sets query to suggestion text. Covered by ExploreDashboard.test.tsx.
  • 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#

  • 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.
  • Domain cue button (DomainCueButton) — domain affordance below tabs. Covered by ExploreDashboard.test.tsx and explore-blended-discovery.spec.ts.

Intent chip rail#

  • 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#

  • Curated card click (Link) — navigates to constructed href via buildExploreDomainHref. Covered by ExploreDashboard.test.tsx and explore-blended-discovery.spec.ts.
  • 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.
  • Search result opentrackSearchResultOpened from @/analytics/searchResultTelemetry (per result open). Covered by ExploreDashboardSearchTelemetry.test.tsx.

Secondary panel#

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

Domain briefing panel#

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

Outage banner#

  • 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#

Open questions / known gaps#

  • ?unavailableDomain= is handled by ShellLayout, not passed from /explore/page.tsx into ExploreDashboard; documented above.
  • The section outage banner has an explicit "Retry search" action wired to searchAttempt; covered above.
  • The client no longer hard-codes the search bearer at the call site; it calls resolveBffAuthToken() and the BFF still enforces auth/scope.
  • 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.