---
path: /aaa-upgrade
surface: customer
domain: system
auth: signed-in
source: apps/oshun/web/src/app/aaa-upgrade/page.tsx
status: walked + e2e-covered
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; route-state, CTA target, source
  sanitisation, anonymous redirect, and touch-target evidence refreshed
  2026-06-27 in apps/oshun/web/e2e/aaa-upgrade-entitlement-bounce.spec.ts;
  reduced-motion static state, route-local CTA focus-visible styling, link
  count, and telemetry absence added 2026-06-27; view and CTA telemetry added
  2026-06-30; mobile standalone no-overflow/touch-target proof and real
  service-worker offline document replay added 2026-07-02'
---

# AAA Upgrade · Continue in Yemaya AAA Studio

## Purpose

Yemaya AAA-tier upgrade disclosure (§24.11). Users with the `aaa-creator` or
`operator-admin` entitlement who hit an AAA-only route on the contemplative
product are routed here. Users without the entitlement are hard-blocked by
middleware before they reach this page.

The page is **disclosure copy + two CTAs**: continue into Yemaya AAA Studio (the
destination product) or back to the gallery. There is no upgrade purchase flow
here — the entitlement is presumed to already exist.

## Entry points

- Middleware bounce: AAA-only contemplative routes redirect users carrying the
  right entitlement to `/aaa-upgrade?from=<orig>` where `from` records the
  source path
- Direct URL — bookmarkable; gating is enforced upstream by middleware
- Studio navigation links pointing back to this disclosure

## Layout regions

`page.tsx` is a server async component reading `searchParams` to extract a
sanitised `from` string. Renders a single inline-styled `<main>` (no shell
chrome) with `data-testid="aaa-upgrade-page"`:

- **Title (h1)**: "This surface lives in Yemaya AAA Studio"
- **Body paragraph**: "Oshun keeps the contemplative product curated — raw graph
  editors, LoRA training, model merging, and the rest of the AAA-tier generation
  surfaces live in Yemaya AAA Studio. Your entitlement includes access."
- **"You arrived from" line** (conditional): rendered when `from` is a string;
  shows `<code data-testid="aaa-upgrade-from">{from}</code>`
- **Action row**:
  - Primary `<Link href="/studio/yemaya/studio-web" data-action="open-yemaya">`:
    "Continue in Yemaya Studio" — uses `--lilith-terracotta` background, white
    text. When a safe `from` is present, the link appends
    `?from=<encoded /studio/isis/... path>`. Click dispatches
    `aaa_upgrade_open_yemaya_clicked`.
  - Secondary
    `<Link href="/studio/generation-gallery" data-action="back-to-gallery">`:
    "Back to gallery" — paper border, ink colour. Click dispatches
    `aaa_upgrade_back_to_gallery_clicked`.

Layout uses inline styles (no design-system primitives): grid gap 16, padding
40, max-width 720, centred.

## States

- [x] **Default (no `from`)** — title + body + action row; no "arrived from"
      line
- [x] **With `?from=<path>`** — extra line surfaces the origin route as `<code>`
- [x] **`from` is array** — coerced to `null` by the type guard
      (`typeof fromRaw === 'string' ? fromRaw : null`)
- [x] **Anonymous user** — middleware redirects to `/welcome` before reaching
      the page
- [x] **Signed-in user without AAA entitlement on an AAA-only source route** —
      middleware hard-blocks before this page renders
- [x] **Reduced motion** — no CSS keyframe animation is present on this surface
      under `prefers-reduced-motion: reduce`
- [x] **Offline** — real Chromium service-worker warm/reload caches
      `/aaa-upgrade`; `context.setOffline(true)` replays the disclosure document
      from the service worker with no generic offline fallback card
- [x] **Standalone PWA** — mocked home-screen relaunch at 390×844 preserves
      `display-mode: standalone`, renders both CTAs, preserves the safe
      `from=/studio/isis/gpu-worker` echo, keeps both CTAs ≥44px, and has no
      horizontal overflow

## Interactions

### Title / body

- [x] Non-interactive copy

### Action row

- [x] **"Continue in Yemaya Studio"** (Link, `data-action="open-yemaya"`)
  - Function: navigates to `/studio/yemaya/studio-web` and preserves only a safe
    internal `/studio/isis/...` source path as `from`
  - Keyboard: Tab → Enter activates
  - Focus: route-local `:focus-visible` outline is visible when focused
  - Touch target: padded 10×16; verified ≥ 44px in browser layout
  - Visual: `--lilith-terracotta` background, white text
- [x] **"Back to gallery"** (Link, `data-action="back-to-gallery"`)
  - Function: navigates to `/studio/generation-gallery`
  - Keyboard: Tab → Enter activates
  - Focus: route-local `:focus-visible` outline is visible when focused
  - Touch target: padded 10×16; verified ≥ 44px in browser layout

## Data & contracts

- **Reads**:
  - `searchParams.from?: string | string[] | undefined`
- **Writes**: none
- **Telemetry**: the client action row dispatches `aaa_upgrade_viewed` on mount,
  then `aaa_upgrade_open_yemaya_clicked` or
  `aaa_upgrade_back_to_gallery_clicked` for the two CTAs. Payloads include
  `surface: 'aaa_upgrade'`, the allowlisted `sourcePath` or `null`, and the
  fixed destination for click events.
- **Realtime**: none
- **Caching**: not explicitly cached
- **Auth/role check**: middleware enforces `aaa-creator` / `operator-admin`
  entitlement upstream; the page itself does not re-check
- **Metadata**: `title: 'Continue in Yemaya AAA Studio'`,
  `description: 'Yemaya AAA Studio hosts the professional generation surfaces.'`,
  `alternates.canonical: '/aaa-upgrade'`

## Cross-references

- Shell auth + entitlements:
  [`shell/04-auth-session.md`](../../shell/04-auth-session.md)
- Destination route: `/studio/yemaya/studio-web` (the primary "open-yemaya" CTA
  links directly to Yemaya Studio Web, not `/studio` or `/welcome/yemaya`)
- Studio gallery: `/studio/generation-gallery` (see `studio/`)
- Component sources:
  - `apps/oshun/web/src/app/aaa-upgrade/page.tsx`
  - `apps/oshun/web/src/app/aaa-upgrade/AaaUpgradeActions.tsx`
  - `apps/oshun/web/src/analytics/aaaUpgradeTelemetry.ts`

## Automated coverage

- `apps/oshun/web/e2e/aaa-upgrade-entitlement-bounce.spec.ts`
  - AAA `aaa-creator` boundary bounce to `/aaa-upgrade?from=<source>`, rendered
    source echo, and CTA href with the encoded safe source
  - Contemplative and anonymous/malformed sessions hard-blocked at AAA-only
    source routes without leaking `/aaa-upgrade`
  - `operator-admin` boundary bounce
  - Hostile external `from` dropped, with no external links rendered
  - Direct signed-in `/aaa-upgrade` default state, repeated `from` values
    dropped, anonymous direct page redirect, both CTA touch targets, visible
    focus outlines, exactly two page links, no telemetry hook attributes,
    `aaa_upgrade_viewed`, both CTA click telemetry events, and zero keyframed
    descendants under reduced motion
  - Mobile standalone relaunch with safe `from` propagation, no horizontal
    overflow, and ≥44px CTAs; real service-worker offline document replay of the
    warmed `/aaa-upgrade` page
- `apps/oshun/web/e2e/entitlements-aaa-bff.spec.ts` verifies the real BFF
  entitlement resolver contract returns `/aaa-upgrade` as the upgrade URL.
- `apps/oshun/web/src/app/__tests__/aaa-upgrade.test.tsx` covers component-level
  valid-source propagation and hostile-source suppression.

## Open questions / known gaps

- [x] Confirm the exact middleware rule that lands users here (the legacy
      `/studio/isis/*` tree bounces AAA and operator sessions to this disclosure
      and hard-blocks contemplative/anonymous access)
- [x] The page is described as an "upgrade flow" but doesn't include a purchase
      / entitlement-grant step — confirm the user-journey assumption
      (entitlement is pre-existing; this page is disclosure + continuation only)
- [x] CTAs use raw `<Link>` not design-system buttons; automated evidence now
      covers touch targets and route-local visible focus outlines
- [x] View and CTA telemetry are emitted without raw telemetry/analytics
      attributes in the DOM. `aaa-upgrade-entitlement-bounce.spec.ts` captures
      `aaa_upgrade_viewed`, `aaa_upgrade_open_yemaya_clicked`, and
      `aaa_upgrade_back_to_gallery_clicked`.
- [x] `from` is rendered as `<code>` text after route allowlisting; hostile
      external URLs and repeated values are dropped rather than echoed or linked
- [ ] Visual chrome doesn't match Lilith manuscript register used by other
      system pages; inline styles only
