V1 Web PWA · Walk result

Journey result: Library save, collection, and share

A dated evidence record for V1 Web PWA: observed behavior, current source reconciliation, automation evidence, and explicit proof limits.

verdict: partial· 2026-05-29
8sections7 minread1table

On this page
  • Walked: 2026-05-29 at bf12b0f7d8c291499e1df1ec75d4bd21acf3f6b1. The retained walker was an ephemeral /tmp script. It rendered /library, /library/collections, and /library/collections/new, then observed a collection-create POST.
  • Reconciled: 2026-07-18 against the current journey, all four Library routes, the saved-item and collection sources, the sharing engine/BFF, the recipient route, and six focused browser specs. This was source review, not a fresh application run.
  • Verdict: partial — the dated route/create slice passed and later browser suites substantially deepened save, collection editing, share-policy, and recipient-denial evidence. The complete journey still spans two disconnected collection models, keeps dashboard collection composition on one device, and resolves shared permission without rendering the sender's collection contents.
  • Current authority: WALKTHROUGH/journeys/library-save-collection-share.md and its partial coverage-registry row.

Result at a glance#

Evidence plane Current implementation Strongest surviving proof Authority limit
Saved items Domain saves converge on webLibraryStore; signed-in snapshots use /v1/library/saved-items, with optimistic local cache and guest migration Real-BFF save/read-back, unsave, same-user clean-device hydrate, LWW sync, tombstones, isolation, cap paywall, and several direct producers Not every producer or entitlement/resume permutation is exercised; Nyx event save still owns a separate local action store
Server collection catalog /library/collections/new posts label + opaque item ids to the member-scoped /v1/library/collections; /library/collections lists those rows Live-BFF accepted, pending, validation, offline, focus, owner-isolation, and mobile form coverage Create/list is a separate plane from the dashboard editor; the success/list links point to /library?collectionId=…, which the root page does not read
Dashboard collection composition /library/collection/:id edits label, description, follow-up, membership, and archive state in oshun.library.collections.v1 Create-from-filter, edit, membership toggle, archive, corrupt fallback, unknown deep link, and v0→v1 migration Browser-local only; labels, membership, and archive state do not follow the member to another device
Share policy CollectionShareControls writes private, named-user, link, and public-profile policy through /v1/library/collections/:id/* Token issue/revoke, invite/referral attribution, permission tiers, owner checks, and policy read-back over the BFF The policy store is process memory, and policy creation does not prove that a corresponding server collection payload exists
Recipient route /share/:token asks the BFF to resolve a collection id and requested permission Valid grant, wrong/revoked token, insufficient tier, and same-id local-shadow rejection Current tests use signed-in recipients; the page shows id/tier/via only, not sender labels, items, provenance, or embed cards

Evidence map#

The current UI crosses three persistence planes. The dashed branch is the key disconnect a green create response cannot reveal.

flowchart LR A[Domain Save] --> B[Saved-items BFF] B --> C[Library hydrate] C --> D[Dashboard collection editor] D --> E[Local collection payload] D --> F[Share-policy BFF] F --> G[Share token] G --> H[Recipient permission view] I[Server collection form] --> J[Server collection catalog] J -. collectionId query ignored .-> C H -. contents not delivered .-> E

Proven observations#

The dated slice is reproducible as a historical claim#

  • Both abbreviated commits in the old record resolve in repository history: the walk at bf12b0f7d8c291499e1df1ec75d4bd21acf3f6b1 and the earlier route repair at 8ad5d6a4dfac400ef925ef95df2d6bf155c57158.
  • All three dated routes remain in the current 701-route inventory. The new collection form still POSTs { label, itemIds } to /v1/library/collections, exposes pending/error/accepted states, preserves fields after server/offline failure, and moves focus to its accepted status.
  • The member-scoped BFF collection route rejects anonymous callers, validates a two-character label, records the owner, returns 202, and lists only the caller's rows plus legacy unowned migration rows. Its backing tier can be PostgreSQL, a protected development snapshot, or test memory.
  • The original “0 page errors, 0 persistent failures” remains a dated walker observation. The missing /tmp walker means its exact selectors, environment, and raw report cannot be independently inspected now.

Saved-item continuity is substantially deeper today#

  • webLibraryStore keeps an offline cache but hydrates signed-in members from the saved-items BFF. Focused browser evidence covers direct Nisaba passage save, guest→sign-in migration, clean-device hydration, resume navigation, saved-queue transitions, and unsave row removal.
  • Companion real-HTTP suites cover same-user cross-device propagation, LWW conflict accounting, tombstone deletion, per-user isolation, and the 25-save free-tier cap. Search, Explore, Veritas, Sophia, Atelier, Tara, Nyx search results, and Metis producers have targeted evidence elsewhere in the current journey registry.
  • This evidence is appropriately stronger than “the page rendered.” It proves server read-back for named producers and states; it does not prove every saveable surface or every canonical resume destination.

Collection composition and sharing are not one persisted object#

  • The Dashboard collection manager builds /library/collection/:id links and persists its own versioned label, description, follow-up, membership, and archive model under oshun.library.collections.v1.
  • The separate server catalog links a created row to /library?collectionId=<id>, while app/library/page.tsx only parses domain and kind. That link opens the generic Library rather than selecting the server-created collection. The form spec currently asserts the href, not a successful open-and-edit continuation.
  • CollectionShareControls can attach a valid BFF policy to a Dashboard collection id, but the sharing route governs authorization only. It does not fetch or deliver the Dashboard's local collection record.
  • The recipient route deliberately avoids the recipient's same-id local shadow. Its granted state displays the server-resolved collection id, tier, and resolution channel; this is a correct isolation repair, not content sharing.

Share authorization fails closed within its tested scope#

  • Tokens are generated from 32 random bytes, returned once, and stored as a SHA-256 hash. The engine validates view/comment/copy ordering, expiry, revocation, owner-only mutation, named-user grants, and public-profile policy.
  • Browser/BFF specs cover issue→resolve→revoke, wrong-token denial, insufficient permission, invite/referral attribution, private state, named-user copy, and public-profile comment policy read-back.
  • The current LibrarySharePolicyStore is an in-process Map. A BFF restart or a different instance loses its policies unless a durable adapter replaces this binding; “real BFF” must not be read as production durability.

Boundaries and gaps#

  • The dated pass belongs to a route/create slice, not the current journey. Rendering three pages and observing one POST never proved domain save, collection editing, link authorization, recipient isolation, or read-back.
  • A server collection row is not the Dashboard collection. The create/list route, local editor, and share policy currently agree only on an opaque id; they do not share one authoritative collection payload.
  • The “Open it” continuation is broken by contract. Both the catalog and accepted form emit ?collectionId=, but /library ignores that parameter.
  • A permission card is not shared content. The recipient does not receive the sender's label, member items, ordering, provenance, grounding, synthetic indicators, or embeddable card.
  • A process-local policy is not durable authorization. Restart and multi-instance behavior remain outside the present sharing proof.
  • Anonymous link access is not proven. The resolution BFF route requires an auth context, and current browser evidence signs in the recipient.
  • Collection composition is not cross-device. Saved items sync; Dashboard collection metadata and membership remain localStorage-only.
  • Several journey branches remain open. Nyx event-save convergence, filter-URL write-back, offline resume, gated-item resume, collection delete, public-profile collection rendering, and provenance-preserving embed cards are not closed by this result.
  • Source reconciliation is not execution. None of the cited web/BFF suites was rerun for this documentation audit.

Re-run evidence#

Run the live BFF and production-like web app at the same immutable commit, then execute the focused specs one at a time with one worker:

bash
pnpm --dir apps/oshun/web exec playwright test -c playwright.config.ts e2e/library-collections-new.spec.ts --project=chromium --workers=1
pnpm --dir apps/oshun/web exec playwright test -c playwright.config.ts e2e/library-save-collection-share.spec.ts --project=chromium --workers=1
pnpm --dir apps/oshun/web exec playwright test -c playwright.config.ts e2e/library-collection-management.spec.ts --project=chromium --workers=1
pnpm --dir apps/oshun/web exec playwright test -c playwright.config.ts e2e/collection-share.spec.ts --project=chromium --workers=1
pnpm --dir apps/oshun/web exec playwright test -c playwright.config.ts e2e/library-shared-resolution.spec.ts --project=chromium --workers=1
pnpm --dir apps/oshun/web exec playwright test -c playwright.config.ts e2e/library-shared-web-view.spec.ts --project=chromium --workers=1

Retain the web/BFF commit, backing-store tier, environment manifest, Playwright artifacts, and member identities. Add an explicit create→open→edit→second-device read-back assertion before upgrading this result beyond partial.

Source trail#

Cross-references#

Open questions#

  • Which collection model becomes canonical: the BFF catalog, the Dashboard local payload, or a merged server contract with migration from both?
  • Should server-created collection links use /library/collection/:id, and what server read must hydrate that route before editing is enabled?
  • When will share policies move from the in-process map to the same durable, owner-scoped collection boundary?
  • What payload should an authorized recipient receive, and how will it preserve ordering, provenance, grounding, synthetic indicators, and entitlement gates?
  • Is anonymous link-with-key access intended? If so, what limited auth/rate/ revocation boundary replaces the current signed-in precondition?
  • Which test will prove one continuous domain-save→collection-create→share→ recipient-content flow with clean sender and recipient browser contexts?