---
path: /library/collection/[collectionId]
surface: customer
domain: discovery
auth: signed-in
source: apps/oshun/web/src/app/library/collection/[collectionId]/page.tsx
status: walked
last_walked:
  '2026-05-29 automated runtime walk (Playwright headless) — render, /v1 data
  (2xx), console/page-errors, expected content, screenshot verified; live
  screen-reader, touch, offline, and telemetry-delivery checks pending a manual
  AT pass. Evidence: WALKTHROUGH/results/runtime-sweep-2026-05-29.md; content
  re-verified 2026-06-03 against current source; focused direct-route state
  addendum 2026-06-25 covers found/missing/custom/corrupt-storage collection
  states, query seeding, edit persistence, and add/remove membership with real
  BFF-seeded Playwright coverage'
---

# Library — Collection deep link

## Purpose

A bookmarkable deep link into one Library collection. Renders the same
`LibraryDashboard` as `/library`, but pre-selects the collection identified by
the URL segment and pre-filters by domain / kind if those query parameters are
present.

## Entry points

- **Library collection card** — `/library` collection cards push
  `/library/collection/<id>` via `buildLibraryCollectionHref` and `router.push`
- **"Create collection from filters"** — `handleCreateCollectionFromFilters`
  produces a collection and navigates here
- **Direct URL / bookmark** — yes; `collectionId` is decoded via
  `decodeURIComponent`
- **Query overrides** — `?domain=<all|tara|veritas|nyx|arete|nisaba|metis>` and
  `?kind=<libraryKindFilter>` seed `initialDomainFilter` and `initialKindFilter`
- **Shell middleware** — auth required; this view does not declare its own
  `surface` for shell-state preview (it does not call
  `resolveShellRouteSurfaceContent`)

## Layout regions

`page.tsx` mounts `ShellLayout` with `active="library"` (no breadcrumbs prop)
and renders only `<LibraryDashboard ... />` configured with
`initialDomainFilter`, `initialKindFilter`, and
`initialCollectionId = decodeURIComponent(params.collectionId)`. There is no
sr-only `<h1>` on this page — headings are provided by `LibraryDashboard`.

All visible layout regions are owned by `LibraryDashboard`. See
[`library.md`](./library.md) — Layout regions — for the full surface map.

Key collection-mode differences (read from `LibraryDashboard.tsx`):

- **Frame title and summary** are sourced from `selectedCollection.label` and
  `selectedCollection.description` instead of the filter-derived strings
- **Followup** copy on the collection card uses `selectedCollection.followUp`
- **Editor** state (`collectionEditor`) is initialized from the resolved
  collection on mount

## States

- [ ] **Loading (pre-hydration)** — `hasMounted === false`; only the
      `ShellRouteAnchor` placeholder ("Loading saved continuity, collections,
      and the next best return point.") renders
- [x] **Collection found** —
      `resolveInitialCollectionId(collections, initialCollectionId)` matches;
      collection editor + items populate
- [x] **Collection missing** — `resolveInitialCollectionId` returns `null`
      (unknown id); `selectedCollectionId` falls back to `null` and the view
      degrades to the default `/library` dashboard (same fallback logic as
      navigating with no filter); URL still reads `/library/collection/<id>`
- [x] **Custom (user-created) collection** — hydrated from
      `localStorage['oshun.library.collections.v1']`
- [x] **Query-driven filter pre-population** — `?domain=` / `?kind=` are
      converted via `isDomainFilter` and `normalizeLibraryKindFilter`; invalid
      values default to `'all'`
- [x] **Edits persist** — label / description / followUp edits write back to
      localStorage (via the same effect chain as `/library`)
- [ ] **Empty collection** — the collection has no items; "Create collection
      from filters" remains the primary repair path

## Interactions

All interactions match [`library.md`](./library.md) — Interactions. Diffs
specific to the collection-mode entry:

- [ ] **Back-to-all-saved navigation** — clearing the selected collection in the
      dashboard calls `navigateToCollection(null)` →
      `router.push(WEB_SHELL_ROUTE_PATHS.library)` (route returns to `/library`)
- [ ] **Selecting a different collection from the lane** —
      `navigateToCollection     (collectionId)` calls
      `router.push(buildLibraryCollectionHref(...))`; URL updates to
      `/library/collection/<newId>`
- [x] **Collection editor inputs** (label / description / followUp) — drive
      `collectionEditor` and propagate via `syncCollection`
- [x] **Remove item from collection** — `setCollectionItemIds` mutates the
      collection's item-id list; metrics recompute via `withCollectionMetrics`

## E2E coverage

- Backed by
  [`apps/oshun/web/e2e/library-collection-management.spec.ts`](../../../apps/oshun/web/e2e/library-collection-management.spec.ts)
  — real authenticated shell + BFF-seeded saved items drive the collection-found
  route, unknown collection fallback with query-seeded filters, user-created
  collection hydration from localStorage with invalid item-id sanitization,
  corrupt collection-storage fallback to defaults, collection editor persistence
  across reload, create-from-filter navigation, add/remove membership count
  recomputation, archive, and desktop return-lane layout.
- Backed by
  [`apps/oshun/web/src/components/library/__tests__/LibraryDashboard.test.tsx`](../../../apps/oshun/web/src/components/library/__tests__/LibraryDashboard.test.tsx)
  — component coverage asserts the same collection-id resolution, custom storage
  hydration/sanitization, corrupted storage fallback, edit persistence, and
  membership toggling without network.

## Data & contracts

- **Reads** (same as `/library`):
  - `useOshunWebLibraryStore()` — saved items
  - `localStorage['oshun.library.collections.v1']` — stored collection payload
- **Route params**:
  - `params.collectionId: string` — URL segment, `decodeURIComponent`d before
    use
- **Query params**:
  - `searchParams.domain` → `LibraryDomainFilter` (`'all'` if invalid)
  - `searchParams.kind` → `LibraryKindFilter` (`'all'` if invalid)
- **Writes**: same as `/library` — local store + localStorage; no BFF writes
  observed
- **Realtime**: _None._
- **Caching**: client store + localStorage
- **Auth/role check**: shell middleware (no in-page guard)

## Cross-references

- Parent route: [`library.md`](./library.md)
- Shell: [`shell/01-app-shell.md`](../../shell/01-app-shell.md)
- Component sources:
  - `apps/oshun/web/src/components/library/LibraryDashboard.tsx`
  - `apps/oshun/web/src/components/library/libraryModels.ts`
- Feature spec: [`V1/features.md`](../../../V1/features.md)

## Open questions / known gaps

- [x] Document the missing-collection behavior — `page.tsx` does not 404; the
      dashboard keeps the unknown collection URL, clears selected collection
      state, preserves valid query-seeded filters, and renders the default
      library dashboard without collection management.
- [ ] Confirm whether `?domain=` and `?kind=` are stable in the URL after the
      user changes filters from inside the dashboard (filters mutate state but
      this `page.tsx` does not write them back to the URL)
- [ ] Document collection sharing semantics — the deep link is bookmarkable but
      uses localStorage-derived state, so a shared link will show the target
      user's own collections, not the sender's
