V1 Web PWA · Journey

Journey: Scene · anonymous viewer at oshun.app/scene/

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

automated · 3 specs verdict: partial· 2026-05-29
9sections5 minread1table

On this page
Journey at a glance
ActorsAnonymous recipientPassword recipientTenant recipientCrawler or embed hostAccessible-media user
Automation3 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. Resolve the public branch"] s2["2. Unlock a password-protected offering"] s3["3. Render the playable offering"] s4["4. Copy, embed, download, or report"] s5["5. Revisit after lifecycle changes"] s1 --> s2 --> s3 --> s4 --> s5 click s1 href "#1-resolve-the-public-branch" click s2 href "#2-unlock-a-password-protected-offering" click s3 href "#3-render-the-playable-offering" click s4 href "#4-copy-embed-download-or-report" click s5 href "#5-revisit-after-lifecycle-changes" s5 --> v(["verdict: partial"]) click v href "../results/scene-public-viewer-anonymous.html" style v stroke:#d6a531,color:#d6a531

The public Living Offering viewer is a real branch-driven server surface, not a single happy-path player. It distinguishes password, tenant, retired, transient outage, and playable outcomes; the playable branch serves attested APNG bytes, accessibility artifacts, embeds, reports, and short-lived downloads. Durable share metadata does not make every runtime artifact durable: materialized media bytes and download grants remain process-local.

Personas#

  • Anonymous recipient — follows a public or private-link URL without an Oshun session.
  • Password recipient — exchanges a shared password once, then revalidates through a short-lived HttpOnly unlock cookie.
  • Tenant recipient — reaches a tenant-internal link and must enter through a tenant-aware surface rather than the anonymous viewer.
  • Crawler or embed host — consumes robots/Open Graph/Twitter or embed/oEmbed metadata without receiving privileged content.
  • Accessible-media user — relies on reduced motion, high contrast, transcript, caption, or audio-description metadata.

Pre-conditions#

  • /scene/<shortCode> is a public middleware path. The page server-fetches GET /v1/living-scenes/public/:shortCode with cache: 'no-store'.
  • A playable share requires a retained artifact, active share state, and the correct password/tenant conditions.
  • When Postgres is configured, artifact/share/index/audit/takedown state is wired through wireDurableLivingSceneShares. Without that wiring, the same stores are process-local.
  • The share's robots policy determines whether the playable page is indexable; protected and non-playable branches are always noindex.

Steps#

1. Resolve the public branch#

The server resolves exactly one of the following outcomes:

Branch Viewer and recovery contract
Playable Attested media, transcript, report, download, and copy actions render.
Password A successful exchange sets a short-code-scoped unlock cookie.
Tenant auth Tenant-access message; no anonymous media or download surface.
Tombstone Retired offering after a definitive 4xx, missing, revoked, or deleted result.
Unavailable Temporary 5xx/network outage with a real retry, never a false retirement.

These rows map directly to the BFF resolution kinds playable, password-required, tenant-auth-required, tombstone, and unavailable.

generateMetadata uses the share's Open Graph/Twitter/robots values only for a playable branch. All non-playable branches currently share the generic title “Living Offering Retired,” including password, tenant, and transient-unavailable states; that metadata mismatch is a known gap.

2. Unlock a password-protected offering#

The raw password is posted to the unlock-token endpoint. A valid exchange mints an HMAC-signed, short-code-scoped token with a four-hour lifetime; the server action stores it in an HttpOnly path-scoped cookie. Wrong, missing, expired, or cross-scene tokens return to the password gate without leaking the media.

3. Render the playable offering#

The page displays tenant/default branding, public mark, privacy/rendition, C2PA manifest and media-hash summary, and the exact materialized APNG from /media. Reduced-motion CSS swaps the motion layer for the reduced artifact; high-contrast CSS adds a stronger viewer outline. Captions, audio-description metadata, and transcript download are exposed from the share resolution.

The keyboard-cue labels are rendered as buttons, but those buttons have no handlers. They are descriptive chrome today, not working playback shortcuts.

4. Copy, embed, download, or report#

ShareSettingsPanel copies the scene URL, iframe, and oEmbed snippets and surfaces an explicit clipboard-denied state. Download creates a 15-minute grant and redeems the same APNG bytes whose hash is carried by the C2PA manifest. The report link opens the public abuse form; it does not require the viewer to be signed in.

5. Revisit after lifecycle changes#

Revocation, originating-user deletion, or operator takedown turns the viewer into a tombstone and invalidates existing grants. A transient BFF failure stays an unavailable/retry state. The service worker treats API reads as network-only by default, so a first online visit does not establish a supported offline viewer guarantee.

Post-conditions#

  • A playable viewer sees only the public artifact, branding, attestation, and accessibility artifacts authorized by that share.
  • Password material is replaced by a scoped unlock token; the raw password is not stored in the cookie.
  • A valid download grant redeems attested media while the share is active and the grant is unexpired.
  • The viewer never claims an outage is a retirement, but its head metadata does not yet express every non-playable branch accurately.

Failure modes#

  • Restart after durable share hydration — share/artifact metadata can survive, but shareMediaById and downloadGrantsById are process-local maps and are cleared on hydration. The viewer can resolve as playable while /media returns media_not_materialized; old grants never survive restart.
  • Invalid/expired/cross-scene unlock — returns to the password gate.
  • Tenant-internal link without tenant context — renders “Tenant access required,” never public media.
  • Clipboard denied — copy controls report an explicit unavailable status.
  • Offline revisit — no supported cached media/API contract; the request may fail even after an earlier visit.
  • Accessibility cue activation — the visible cue buttons currently do nothing.
  • View analytics — there is no per-IP or per-share public view counter in this path; do not infer audience telemetry from page rendering.

E2E coverage#

Per-view files touched#

Cross-references#

Open questions#

  • Should materialized media be snapshot/object-store durable, or rebuilt and re-hashed deterministically when a durable share hydrates?
  • Should non-playable metadata distinguish password, tenant, retired, and transient-unavailable states?
  • Should keyboard-cue buttons control playback, or render as non-interactive key labels until a controller exists?
  • Is an explicit offline public-viewer product contract wanted, including revocation freshness and sensitive-cache policy?