Context. surface customer · domain discovery · route /library/collection/[collectionId] · auth signed-in · source apps/oshun/web/src/app/library/collection/[collectionId]/page.tsx
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
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 —
/librarycollection cards push/library/collection/<id>viabuildLibraryCollectionHrefandrouter.push - "Create collection from filters" —
handleCreateCollectionFromFiltersproduces a collection and navigates here - Direct URL / bookmark — yes;
collectionIdis decoded viadecodeURIComponent - Query overrides —
?domain=<all|tara|veritas|nyx|arete|nisaba|metis>and?kind=<libraryKindFilter>seedinitialDomainFilterandinitialKindFilter - Shell middleware — auth required; this view does not declare its own
surfacefor shell-state preview (it does not callresolveShellRouteSurfaceContent)
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 — Layout regions — for the full surface map.
Key collection-mode differences (read from LibraryDashboard.tsx):
- Frame title and summary are sourced from
selectedCollection.labelandselectedCollection.descriptioninstead 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 theShellRouteAnchorplaceholder ("Loading saved continuity, collections, and the next best return point.") renders - Collection found —
resolveInitialCollectionId(collections, initialCollectionId)matches; collection editor + items populate - Collection missing —
resolveInitialCollectionIdreturnsnull(unknown id);selectedCollectionIdfalls back tonulland the view degrades to the default/librarydashboard (same fallback logic as navigating with no filter); URL still reads/library/collection/<id> - Custom (user-created) collection — hydrated from
localStorage['oshun.library.collections.v1'] - Query-driven filter pre-population —
?domain=/?kind=are converted viaisDomainFilterandnormalizeLibraryKindFilter; invalid values default to'all' - 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 — 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)callsrouter.push(buildLibraryCollectionHref(...)); URL updates to/library/collection/<newId> - Collection editor inputs (label / description / followUp) — drive
collectionEditorand propagate viasyncCollection - Remove item from collection —
setCollectionItemIdsmutates the collection's item-id list; metrics recompute viawithCollectionMetrics
E2E coverage#
- Backed by
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— 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 itemslocalStorage['oshun.library.collections.v1']— stored collection payload
- Route params:
params.collectionId: string— URL segment,decodeURIComponentd 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 - Shell:
shell/01-app-shell.md - Component sources:
apps/oshun/web/src/components/library/LibraryDashboard.tsxapps/oshun/web/src/components/library/libraryModels.ts
- Feature spec:
V1/features.md
Open questions / known gaps#
- Document the missing-collection behavior —
page.tsxdoes 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 thispage.tsxdoes 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