Context. surface customer · domain scene · route /scene/[id]/embed · auth anon · source apps/oshun/web/src/app/scene/[id]/embed/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
Purpose#
Iframe-safe minimal embed of a public living offering. Designed to be mounted
inside third-party pages as a single full-bleed preview tile. The playable tile
carries less chrome than the full viewer (no report link, no download grant, no
accessibility panel). It is not fully chrome-free, though: the
password-required and tenant-auth-required branches each render an interactive
"Open offering" link back to /scene/<id> (see Layout regions).
/scene is in apps/oshun/web/src/proxy.ts PUBLIC_PATHS so the embed loads
anonymously.
Entry points#
<iframe src="https://oshun.app/scene/<id>/embed">mounted in a partner page / blog / CMS- Direct URL — opens the embed full-window (not typical use)
- Sharing tools or platforms that consume the OSHUN embed URL
Layout regions#
page.tsx resolves fetchPublicLivingScene(params.id) with no unlock
token. It branches in three ways (page.tsx ~15-45): a password/tenant gate
branch, the generic non-playable fallthrough, and the playable preview overlay.
kind === 'password-required' || kind === 'tenant-auth-required'#
A centred card (page.tsx ~15-37) explaining the embed cannot show this offering:
- Heading:
Password requiredorTenant access required(chosen bykind) - Body: "This offering is not embeddable. Open it on OSHUN to view."
- "Open offering" link
(
<a href="/scene/<id>" target="_top" rel="noopener">, teal button) — the one interactive element on the embed surface; breaks out of the iframe (target="_top") to the full viewer where the gate can be satisfied
kind !== 'playable' (generic fallthrough, e.g. tombstone)#
- Main: full-screen grid centred message — "This offering has been retired." This bare tile is reached only after the password/tenant branch above; it has no interactive elements.
kind === 'playable'#
- Main (relative, overflow-hidden):
- Background layer: absolute-positioned
linear-gradient(120deg, rgba(3,7,18,0.18), rgba(3,7,18,0.66)), url(<openGraph.imageUrl>)cover-centred - Bottom gradient panel: absolute bottom strip from black/80 to
transparent, padding 4
- Title (
openGraph.title, 2xl serif) - Description (
openGraph.description, smaller slate text) - Visible-mark short code (mono, smaller, near-bottom)
- Title (
- Background layer: absolute-positioned
States#
- Password required —
kind === 'password-required'→ "Password required" card with the "Open offering" break-out link - Tenant-auth required —
kind === 'tenant-auth-required'→ "Tenant access required" card with the same "Open offering" link - Retired / other non-playable — generic fallthrough → minimal "This offering has been retired." tile (no interactive elements)
- Playable (default) — image background + title + description + short code
- No tenant branding — the playable embed does not surface tenant logo or attribution (unlike the main viewer)
- Inside an iframe — verify the page sets headers (e.g.,
X-Frame-Options, CSPframe-ancestors) consistent with embed use; not configured inpage.tsxitself - Standalone visit — embed loads but lacks navigation; intended to be embedded, not navigated to directly
- Reduced motion — no animations on this surface; no special handling needed
- Offline — SW-cache may serve a previously fetched embed
Interactions#
The playable and the bare-retired tiles are read-only (no links, buttons, or forms). The two gate branches are the exception:
- "Open offering" link (password-required / tenant-auth-required
branches only) —
<a href="/scene/<id>" target="_top" rel="noopener">; breaks out of the iframe to the full viewer. This is the embed's only interactive element. - Verify the playable and generic-retired branches contain no anchor / button / form descendants (only the gate branches render a link)
Data & contracts#
- Reads:
fetchPublicLivingScene(params.id)from@/lib/living-scenes-public(no unlock token) - Writes: none
- Realtime: none
- Caching: request-time server fetch; verify whether the embed is separately cached (it should be more aggressively cacheable than the full viewer)
- Auth/role check: anon (
/sceneis insrc/proxy.tsPUBLIC_PATHS) - Metadata:
title: 'Embedded Living Offering'(bare; the| OSHUNsuffix comes from the root layout'stitle.template = '%s | OSHUN'),robots: { index: false, follow: false }
Cross-references#
- Sibling scene routes:
scene-id.md— full viewerscene-id-report.md— abuse report
- Component sources:
apps/oshun/web/src/lib/living-scenes-public.ts
Open questions / known gaps#
- Password-protected scenes through the embed —
fetchPublicLivingSceneis called without an unlock token, so password-required (and tenant-auth-required) scenes render the "not embeddable" card with an "Open offering" break-out link rather than the preview. Confirm this break-out-to-full-viewer behaviour is the intended design for private scenes (the embed itself never accepts a password). Verified inscene-password-and-grants.spec.ts: password-protected embed renderstarget="_top""Open offering" and no full viewer chrome / Report / Download-grant controls. - No CSP
frame-ancestorsheader orX-Frame-Optionsconfiguration visible at the page level; check the Next config / middleware / response headers for iframe-allow rules - No analytics / view telemetry from this embed surface — verify whether view tracking happens server-side
- No accessibility panel; embed does not advertise caption / transcript availability — confirm whether the embedding partner is responsible for that