V1 Web PWA · Journey

Journey: Search → Explore → deep-read → library → save

A source-reconciled journey record for V1 Web PWA — shipped behavior, state boundaries, failure modes, and the automation evidence available today.

automated · 5 specs verdict: partial· 2026-05-29
9sections6 minread1table

On this page
Journey at a glance
ActorsResearcherExplorerReturning readerSaved-search user
Automation5 E2E spec(s) · verdict: partial· 2026-05-29

Journey flow#

Generated from the authored steps below — click a node to jump to that section.

flowchart TD s1["1. Search and filter the live result set"] s2["2. Enter through Explore when useful"] s3["3. Preview evidence and open the canonical de…"] s4["4. Save one stable library identity"] s5["5. Hydrate Library and resume in a second con…"] s6["6. Optionally save the search workspace"] s1 --> s2 --> s3 --> s4 --> s5 --> s6 click s1 href "#1-search-and-filter-the-live-result-set" click s2 href "#2-enter-through-explore-when-useful" click s3 href "#3-preview-evidence-and-open-the-canonical-destination" click s4 href "#4-save-one-stable-library-identity" click s5 href "#5-hydrate-library-and-resume-in-a-second-context" click s6 href "#6-optionally-save-the-search-workspace" s6 --> v(["verdict: partial"]) click v href "../results/search-explore-deep-read-library-save.html" style v stroke:#d6a531,color:#d6a531

This is one of the repository's strongest cross-view customer joins. Search and Explore share the live /v1/search contract; save actions from both surfaces write the same authenticated saved-items API; Library can hydrate without its browser cache in a second context and resume several canonical destination types. Saved queries are a different contract and remain browser-local.

Personas#

  • Researcher — filters /search, previews evidence, opens a domain object, and saves it before or after deep reading.
  • Explorer — begins with curated /explore, issues a live query, and joins the same result/save contract.
  • Returning reader — opens /library in a clean browser context and resumes the saved canonical object.
  • Saved-search user — preserves a query/filter workspace for reuse in the same browser profile.

Pre-conditions#

  • The customer is signed in and the BFF grants the requested domain scope.
  • /v1/search returns SearchResultItem records with stable domain, kind, id, score, and launch metadata.
  • The authenticated Library saved-items routes are reachable. LocalStorage is a cache for saved items, not their production source of truth.
  • The target domain route understands the origin and path produced by buildSearchResultLaunchPath / buildLibraryResumeHref.

Steps#

1. Search and filter the live result set#

/search initializes from ?q= and the advanced URL state, then calls GET /v1/search?q=&domain=. Domain, kind, sort, score, saved-only, and match scope round-trip through the URL. A submitted query is also added to the local recent-search list. The error branch clears stale results, retains the submitted query, and offers a same-query Retry action.

2. Enter through Explore when useful#

/explore calls the same BFF for a typed query and rebuilds the result set when the domain tab changes. Curated cards use buildExploreDomainHref; live result rows use buildSearchResultLaunchPath. Both paths preserve an origin=explore handoff rather than pretending the Explore card itself is a deep reader.

3. Preview evidence and open the canonical destination#

Search preview exposes the result template and grounded-evidence context before launch. The live-BFF browser matrix proves these destination families. Each path is wrapped by /domains/<domain>?origin=explore&path=….

Result Proved destination path
Nisaba passage /library/passage/<id>
Veritas claim/story /claims/<id> · /story/<id>
Tara practice /meditate/session/<id>
Nyx sky event /events/<canonical-slug>
Metis course /courses/<id>

The Nisaba and Veritas branches assert actual reading/evidence content, not only the URL or domain hub. Search stores its workspace signature, result key, and scroll offset before launch and restores the long-list position on Back.

4. Save one stable library identity#

Eligible Search and Explore rows map the result to one (domain, itemId) Library identity and call /v1/library/saved-items/:domain/:itemId. The Nisaba in-domain save control uses the same identity, so save → in-domain unsave → resave moves the server row 1 → 0 → 1 instead of creating duplicates. The Veritas producer also writes the common saved-items contract.

5. Hydrate Library and resume in a second context#

/library reads authenticated saved items from the BFF, applies domain/kind filters, and builds the resume href from stored metadata. Browser coverage clears the local Library cache, opens a second context for the same member, and proves saved-item visibility and destination resume for Nisaba passage, Veritas claim/story, Tara practice, Nyx sky event, and Metis course. This uses the saved-items BFF, not Iris preference sync.

6. Optionally save the search workspace#

“Save this search” persists the query and advanced filters under oshun-search-saved-v1 (maximum eight). Recents use oshun-search-recent-v1 (maximum six, newest first, case-insensitive promotion). Those browser-local records reapply URL workspace state; they are not the cross-device Library-item store. The BFF separately records recent query text for suggestions and exposes its newest eight.

Post-conditions#

  • The selected result opens a domain-specific deep-read surface with the same stable object identity.
  • A saved item is readable from the authenticated BFF, de-duplicated across covered producers, and visible after clearing the local cache.
  • The covered destination matrix resumes in a second browser context for the same account.
  • Recent/saved query workspace state remains local, while recent-query suggestions and saved Library items have separate BFF contracts.
  • trackSearchResultOpened, trackLibraryItemSaved, and trackLibraryItemUnsaved are covered at their actual click/save boundaries.

Failure modes#

  • Domain-scope denial — a live 403 domain_scope_missing renders Search unavailable, removes stale rows, records the recent query, and retries the exact request on demand.
  • Destination drift — a URL may be structurally valid yet land on a hub or blank template. The seeded matrix asserts domain workspace anchors and canonical Nyx slug conversion.
  • Producer identity drift — covered for the Search/Explore/Nisaba paths; other non-search producers still need the same item-level de-dup proof.
  • Cold-start deep link — the server-provided search params initialize the workspace without flashing the no-query state.
  • Browser Back — the Nisaba long-list case restores the same row and scroll offset; other destination types are not separately asserted for scroll.
  • Offline chain — Search has no offline result contract and API reads are network-only by default. A Library shell may open while its resume target still requires network.
  • Saved-search cross-device assumption — false; those workspaces are localStorage records.

E2E coverage#

Per-view files touched#

Cross-references#

Open questions#

  • Which remaining live-BFF object templates and non-search save producers still lack destination plus second-context resume proof?
  • Should saved query workspaces sync across devices, and if so under which account/privacy contract?
  • What is the intended offline behavior when Library metadata is available but the canonical deep-read route is not warmed?
  • /explore still needs an explicit contract for unavailableDomainId wiring.