# Product Surfaces

This page is the customer-facing inventory of _where_ Oshun V1 is touched: the
web app and its two coordinated namespaces, the phone-first mobile app, the
shared shell every customer app composes from, the desktop companion, the
offline substrate, and the public distribution funnel. It serves product and
design leads who need the real list of what ships versus what is reachable but
launch-gated, and engineers deciding which surface a customer capability belongs
in. It is the feature-side companion to the architecture catalog at
[Product Surfaces](../architecture/product-surfaces.md), which documents how the
same surfaces are _assembled_ from shared libraries and the single BFF; the hub
for this set is [../features.md](../features.md).

The defining property of the V1 surface layer is **composition over
duplication**: there is one customer web app, one customer mobile app, and one
shared shell, and almost every customer-facing domain renders _through_ those
two apps. These are real, not aspirational — every named app has its own
`package.json` and a substantial `src/`; the PWA stack is implemented and
test-covered; and the mobile apps are real Expo / Expo Router projects with
end-to-end Maestro suites and a store pipeline. Where a surface is reachable but
not yet launch-enabled (Metis) or fail-loud without credentials
(`content-service`), this page says so plainly rather than implying it ships.

## The customer surface map

Eight named customer-and-operator apps live under `apps/oshun/`, plus a separate
Metis stack under `apps/metis/`. Package names below were confirmed against each
app's `package.json` `name` field.

| Surface           | Path                           | Package                      | Audience / role                            |
| ----------------- | ------------------------------ | ---------------------------- | ------------------------------------------ |
| Customer Web      | `apps/oshun/web/`              | `@oshun/web`                 | Everyday and power-user customers          |
| Customer Mobile   | `apps/oshun/mobile/`           | `@oshun/mobile`              | Phone-first customers                      |
| Oshun BFF         | `apps/oshun/bff/`              | `@oshun/bff`                 | The single API front door behind both      |
| Admin Web         | `apps/oshun/admin/`            | `@oshun/admin`               | Platform operators (review/safety/gov)     |
| Admin Mobile      | `apps/oshun/admin-mobile/`     | `@oshun/admin-mobile`        | On-call operators                          |
| Tenant Console    | `apps/oshun/tenant-admin/`     | `@oshun/tenant-admin`        | Tenant admins (§20)                        |
| Telegram Bot      | `apps/oshun/telegram-bot/`     | `@oshun/telegram-bot`        | Messaging channel (§26)                    |
| Telegram Mini App | `apps/oshun/telegram-miniapp/` | `@oshun/telegram-miniapp`    | Embedded web channel surface (§26)         |
| Content Service   | `apps/oshun/content-service/`  | `@oshun/content-service-app` | Deployable agentic-content service (§3.2)  |
| Legal (docs)      | `apps/oshun/legal/`            | _none — markdown only_       | `privacy-policy.md`, `terms-of-service.md` |

Two of these entries are not described in the prose elsewhere in V1 and are
worth naming explicitly, because earlier drafts of this document omitted them
entirely:

- **`apps/oshun/content-service` (`@oshun/content-service-app`)** is a real,
  deployable HTTP service surface — not a UI page. Its `src/main.ts` boots
  `createContentHttpServer` / `createDurableContentService` from
  `@oshun/content-service` over a real Iris-routed creative writer and a
  calibrated three-member `JudgePanel` (`@oshun/content-quality-judge`). It is
  deliberately **fail-loud**: with no model provider configured
  (`ANTHROPIC_API_KEY` absent) it throws a `NotConfiguredError` at startup
  rather than fabricate generations, and its cloud deploy is honestly marked
  `[~]` in the backlog. See
  [Isis Generation Control](./isis-generation-control.md) and
  [Sophia Grounding](./sophia-grounding.md) for the content pipeline behind it.
- **`apps/oshun/legal`** is a markdown-only docs surface (`privacy-policy.md`,
  `terms-of-service.md`) with **no** `package.json`. It is the home of legal
  copy but not a buildable app; the marketing and landing experience itself
  lives in the web app under `apps/oshun/web/src/app/landing/`. The
  customer-facing privacy controls those documents describe are detailed in
  [Privacy, Consent, Data Portability, and User Controls](./privacy-consent-portability.md).

> **Metis is the only domain with its own app stack.** Every other
> customer-facing domain renders through `@oshun/web` and `@oshun/mobile` via
> the shared shell. The nuance for mobile: a separate `apps/metis/mobile/`
> (`@metis/mobile`) exists alongside
> `apps/metis/{web,admin,api-gateway,worker}`, so on mobile Metis is _not
> solely_ the Oshun mobile app either. This is the only exception to the
> one-web-one-mobile rule.

The operator apps (Admin Web, Admin Mobile, Tenant Console) and the Telegram
channel surfaces are covered in depth on their own pages —
[Admin Products — Web and Mobile](./admin-products.md),
[Tenant, Institution, and Operator Toolkit](./tenant-toolkit.md), and
[Telegram Surfaces](./telegram-surfaces.md). The rest of _this_ page focuses on
the customer experience: web, mobile, the shared shell, the desktop companion,
and distribution.

## Customer Web

The customer web app (`@oshun/web`, a Next.js App-Router project) is the
flagship surface. V1 web includes:

- A redesigned **dashboard** (`src/app/page.tsx`) that reflects the V1 home
  model and the Tara-centered product narrative — calm by default, with
  mixed-domain recommendations and reason labels.
- A desktop-grade **multi-panel workspace** (`src/app/(workspace)/`) for
  continuity, search, reading, evidence inspection, study, note taking, and
  assistant use.
- A collapsible **assistant dock** with persistent context handoff from the
  current screen — see [Assistant Experience](./assistant-experience.md).
- **Mixed-domain recommendations** with reason labels and suppression rules —
  see
  [Search, Discovery, Recommendations, and Knowledge Graph](./search-discovery-recommendations.md).
- Domain modules for Tara ritual continuation, Arete weekly review, Veritas
  grounded briefing, Nyx events, Nisaba passages, and Metis study continuation.
- **Universal search** (`src/app/search/page.tsx`) across rituals, practices,
  concepts, passages, claims, sources, notebooks, collections, programs, sky
  events, courses, lessons, and learning artifacts — with advanced filters,
  result grouping, saved searches, recent searches, and assistant-assisted query
  refinement.
- Split-view reading, compare, source inspection, evidence panels, citation
  trails, and notebook entry points.
- A global **command palette** and quick-action surface for navigation,
  assistant invocation, search, recent items, creation actions, agent
  invocation, and jump-to-anything across customer surfaces.
- Multi-window and multi-tab support with per-tab continuity, draggable side
  panels, persistent reading and study tabs, and stable deep-link state.
- Hand-off and continuity between desktop and mobile — resumable reading,
  resumable study, resumable assistant transcripts, and notebook sync (the
  continuation state lives in Iris; see
  [Iris Memory and Identity](./iris-memory-identity.md)).
- Multi-format export, print, share-card, and embed flows for stories, claims,
  passages, lessons, transcripts, ritual scripts, and notebook entries _where
  rights permit_.
- A genuine **PWA**: install, update, relaunch, offline shell, recent-content
  fallback, service-worker caching, and web performance budgets (detailed
  below).
- Keyboard-only, accessibility, visual-regression, Lighthouse, Playwright, and
  resilience coverage for critical web journeys — see
  [Analytics, Observability, Testing, and Security](./analytics-and-testing.md).

### The dual-namespace pattern — and the truth about all six domains

V1 web ships **two coordinated customer-surface namespaces per domain** that
share BFF data and design tokens but serve distinct audiences. Both are V1, and
neither is a deprecation candidate for the other — they are an intentional
audience split:

1. **`/<domain>` and `/<domain>/*`** — the consumer hub plus presentational
   depth, the Tara-centered home for everyday customers. These are the calm
   Lilith-design-system "rooms" (`TaraRoom`, `AreteRoom`, `VeritasRoom`,
   `NyxRoom`, `NisabaRoom`, and the Metis surfaces), optimized for first-time
   and returning customers, reading, ritual continuation, and contemplative
   tone.
2. **`/domains/<domain>/*`** — heavier client workspaces for power users,
   scholars, operators in customer mode, and editorial / curation roles.
   Examples: `/domains/arete/journal`, `/domains/veritas/fact-check`,
   `/domains/nyx/star-chart`, `/domains/tara/teachers`. These are reachable from
   the consumer surface through deep links and context-specific entry points,
   but are not the canonical first read of the domain.

**Correction to earlier documentation.** Prior drafts claimed that "Metis and
Nisaba ship only at `/<domain>/*` today; their power-user surfaces are mounted
inside the consumer hub rather than at a parallel namespace." That carve-out is
stale and contradicts both the general dual-namespace pattern _and_ the code. In
reality the `/domains/*` namespace covers **all six domains**, not four:

- The _static_ directories under `apps/oshun/web/src/app/domains/` are `arete`,
  `nyx`, `tara`, and `veritas`, alongside a dynamic catch-all `[domainId]/` and
  a shared `layout.tsx` (which renders `DomainPreviewBannerForPath`). The
  catch-all is what extends coverage to `nisaba` and `metis`.
- `apps/oshun/web/src/navigation/routes.ts` declares
  `WEB_DOMAIN_IDS = ['tara','veritas','nyx','arete','nisaba']` (line 52) and
  `WEB_NAVIGABLE_DOMAIN_IDS = [...WEB_DOMAIN_IDS, 'metis']` (line 53).
- `apps/oshun/web/src/app/domains/[domainId]/page.tsx` gates on
  `isWebNavigableDomainId` and ships per-domain SEO metadata in `DOMAIN_META`
  for all six ids, including `nisaba` ("Nisaba — Primary Texts & Scholarship")
  and `metis` ("Metis — Learning & Tutoring").
- `apps/oshun/web/src/components/DomainRouteExperience.tsx` explicitly lists
  `'nisaba'` and `'metis'` (lines 66–67) and carries Metis-specific feature
  routing (`resolveMetisFeatureRouteTarget`,
  `resolveMetisShellEntryFlowFromPath`).

So `/domains/nisaba` and `/domains/metis` **are** reachable web routes — served
by the dynamic catch-all rather than static directories. The consumer hubs are
real too: `apps/oshun/web/src/app/nisaba/` already carries deep subroutes
(`compare`, `daily`, `graph`, `lexicon`, `manuscript`, `notebook`, `notebooks`,
`plan`, `scholar`), and `apps/oshun/web/src/app/metis/` carries `assessment`,
`byom`, `courses`, `ingest`, `lesson`, `lessons`, `session`, `tutor`, and
`upload`. See [Nisaba — Scholarly Study](./domain-nisaba.md) and
[Metis — Education and Tutoring](./domain-metis.md).

**Reachable route ≠ launched domain.** What _is_ true — and is a launch-status
fact, not a routing gap — is that Metis is `availability: 'planned'` in
`libs/oshun/domain-registry/src/registry.ts`, so `getAvailableDomains()` filters
it out of the launch-enabled set. The 2026-06-23 triage (recorded in
`V1/AUDIT_2026-06-24.md`) confirms the live shell's `shellDomainCount` drops
from 5 to 4 post-hydration when `nisaba` is disconnected and `metis` is
`planned`. The Metis domain route copy reflects this honestly — its description
says learner surfaces "will surface here once Metis shell launch readiness is
complete."

### PWA — real, implemented, test-covered

The web app is a genuine Progressive Web App, not a manifest stub:

- `apps/oshun/web/public/manifest.json` declares `name: "OSHUN"`,
  `short_name: "OSHUN"`, `start_url: "/?surface=pwa"`, `scope: "/"`,
  `display: "standalone"` (with `display_override` of `window-controls-overlay`
  then `standalone`), and `theme_color: "#f1ebdd"` — the parchment tone of the
  Lilith design system. Its app shortcuts map directly to the six domains
  (`Meditate` → `/domains/tara`, `Read` → `/domains/veritas`, `Sky Tonight` →
  `/domains/nyx`, `Check In` → `/domains/arete`, `Study` → `/domains/nisaba`,
  `Learn` → `/domains/metis`, each with `?origin=home`).
- `apps/oshun/web/public/sw.js` is the real service worker (≈20 KB).
- `apps/oshun/web/src/lib/pwa-service-worker.ts` implements registration,
  update, and offline plumbing. Real exports include
  `registerServiceWorker(options)`, `applyWaitingServiceWorker(...)`,
  `resolvePwaUpdateRouteContext(currentPath)` (returns a `PwaUpdateRouteContext`
  with a `PwaUpdateRouteMode` of `'reading' | 'standard'`, so an update prompt
  offers "Keep reading" and stages the update behind the current reading flow
  instead of interrupting a deep-read), `queueBackgroundAction(...)` for
  background sync, and `requestServiceWorkerMemberDataClear()` for privacy
  teardown.
- Coverage lives in `apps/oshun/web/src/lib/pwa-service-worker.test.ts` and the
  policy-level `pwa-service-worker-policy.test.ts`. Performance budgets are
  enforced in `lighthouse-budget.json`.

## Customer Mobile

`@oshun/mobile` is a real **Expo + Expo Router** project, not a thin wrapper.
Oshun mobile V1 includes:

- A phone-first IA with home, domain switching, library, assistant, and profile
  reachable with one-handed use. The tab layout is real Expo Router under
  `app/(tabs)/`: `index`, `explore`, `activity`, `library`, and `profile` (with
  `_layout.tsx` and co-located tests like `index.test.ts` and
  `profile.test.tsx`).
- **Deep-link routing** into all top-level domains and high-value object types.
  `app.json` sets `expo.scheme: "oshun"`,
  `ios.bundleIdentifier: "com.oshun.mobile"` (Android
  `package: "com.oshun.mobile"`), and Universal-Link `associatedDomains`
  including `applinks:oshun.app`, `applinks:www.oshun.app`, and
  `applinks:app.oshun.com`. Android App Links auto-verify (`autoVerify: true`)
  over `pathPrefix` values `/d`, `/domains`, `/app`, `/home`, `/explore`,
  `/search`, `/activity`, and more.
- Start-of-day and return-session home behavior with quick actions for
  meditation, check-in, briefing, tonight, study, and assistant follow-up.
- Contextual assistant invocation from primary screens — see
  [Assistant Experience](./assistant-experience.md).
- Haptic, motion, and transition behavior for major mobile flows.
- Push routing and notification controls by domain, persona, quiet hours, and
  sensitivity.
- Offline and retry support for recent meditations, audio assets, passages,
  notes, allowed Veritas briefings, and shell continuity (on the
  `@oshun/offline` substrate described below).
- Mobile **continuation cards** for Tara, Arete, Veritas, Nyx, Nisaba, and
  Metis, plus **recovery-after-drift** logic for habits, study, routines, and
  interrupted sessions.
- A mobile **command surface** (search / sheet / quick-action) for navigation,
  assistant, agent invocation, capture, and jump-to-anything.
- Hand-off to and from desktop for reading, study, journaling, voice sessions,
  and assistant transcripts.
- **Calendar integrations** (Google, Apple, Outlook) for Tara rituals, Arete
  reminders, Nyx events, and Metis study sessions, with two-way sync where
  authorized.
- Camera, microphone, and photo-library **capture** for journaling, evidence
  uploads, source ingestion, voice notes, and assistant attachments — with
  explicit permission strings in `infoPlist` (`NSCameraUsageDescription`,
  `NSMicrophoneUsageDescription`, `NSPhotoLibraryUsageDescription`) and
  per-domain redaction policies.
- **Share extension** targets for ingesting URLs, articles, and media into
  notebooks, sources, and the assistant (declared via the iOS
  `CFBundleDocumentTypes` for `public.url` / `public.image` / `com.adobe.pdf`
  and `NSUserActivityTypes` like `com.oshun.mobile.share-url`).
- Lock screen, widgets, watch surface, and Live-Activity-style controls for
  active rituals, study sessions, and voice tutoring where the platform supports
  it (`NSSupportsLiveActivities: true`). The wearable/widget surface is composed
  from `@oshun/shell-wearable` (see the shell section).
- Mobile accessibility, crash reporting, performance, battery, memory, caching,
  device-matrix, and mobile E2E coverage.

The phrase "mobile E2E" understates what actually ships. Under
`apps/oshun/mobile/scripts/` the real test and release tooling includes
Maestro-based end-to-end suites (`run-maestro-suite.sh`,
`run-maestro-device-matrix.sh`), Android performance gates
(`validate-android-fps.sh`, `parse-gfxinfo.mjs`), and store-submission tooling
(`render-store-assets.mjs`, `validate-store-submission.mjs`,
`capture-store-submission-assets.sh`), backed by EAS build/submit configuration
for the actual store pipeline.

## Shared Consumer Shell

The two customer apps above do not each reimplement navigation, the assistant,
routines, or wearable surfaces — they compose them from shell libraries under
`libs/oshun/`. The shared shell provides:

- Home, explore, activity, library, notifications, profile, settings, and
  assistant entry points.
- Domain-aware navigation for Tara, Arete, Veritas, Nyx, Nisaba, and Metis.
- Current-domain persistence, deep links, route-level analytics, feature flags,
  experiments, and onboarding education across all domains.
- Tara-led home ordering, blended exploration, cross-domain quick actions, and
  mixed-domain recommendations.
- Cross-domain activity timelines with assistant highlights, study continuation,
  notebook continuation, provenance state, and grounding state.
- Library support for meditations, claims, sources, passages, notebooks,
  collections, programs, rituals, sky events, courses, and generated artifacts.
- Notification-center behavior for mixed-domain, admin, support, study,
  grounding, privacy, and safety events, plus a unified message center with
  filters, history, delivery receipts, deep links, snooze, and unsubscribe.
- Profile and settings for account, billing entry, memory controls, disclosure,
  privacy, notifications, data export, data deletion, sessions, devices, locale,
  timezone, and personalization.
- A unified **command surface** (palette on web, sheet on mobile) for
  navigation, search, assistant, agent invocation, creation, recent items, and
  shortcuts.
- Universal sharing, invite, and referral flows with deep-linkable invitations,
  per-link permissions, attribution, expiry, and revocation.
- Public profiles for opted-in customers, creators, scholars, and teachers —
  with curated artifacts, audience-appropriate disclosure, follow controls, and
  preserved synthetic-content indicators.
- Cross-device hand-off and session continuity for reading, study, voice,
  assistant transcripts, ritual sessions, journaling, and authoring drafts.
- A **multi-account switcher** for individuals holding multiple identities (e.g.
  personal account and tenant-scoped account) with explicit identity indicator
  and isolation guarantees.
- In-app changelog / "what's new" feed, contextual help center, walkthrough
  overlays, keyboard-shortcut reference, in-app feedback / bug reporting /
  feature requests with screenshot and trace-id attachment.
- Bookmarks, reading list, saved-for-later, tasks, and queue surface with
  cross-domain aggregation, smart sorting, due/snooze, and resume.
- Calendar entry points with two-way sync across major providers.
- Loading, empty, offline, partial-outage, degraded, retry, and traceable BFF
  partial-response states.
- A **banner system** for tenant-, region-, or feature-scoped status messages
  with severity, audience scoping, locale, dismissal rules, and accessibility.
- Auth, onboarding, session refresh, re-authentication, account recovery,
  profile sync, cold-start personalization, and public-web-to-app conversion.

### Which libraries the shell is built from

The shell is assembled from a small set of shared libraries — see the
architecture companion page for the full lib-by-lib breakdown. The core members
are `@oshun/shell-core` (entry points and the command surface, e.g.
`command-surface.ts`, `activity-timeline.ts`, `account-switcher.ts`,
`current-domain-persistence.ts`, `deep-links.ts`), `@oshun/shell-assistant` (the
assistant dock and context hand-off — `assistant-engine.ts`, `action-router.ts`,
`context-handoff.ts`, `cross-domain-continuity.ts`), `@oshun/shell-routines`
(routine and continuation cards — `routine-engine.ts`, `routine-recommender.ts`,
`step-executors.ts`), `@oshun/shell-wearable` (the watch/widget/complication
surface — `wearable-engine.ts`, `complication-engine.ts`,
`active-session-surfaces.ts`, `haptic-patterns.ts`), `@oshun/navigation`, and
the design system (`@oshun/design-tokens`, `@oshun/ui`).

**Two real shell libraries earlier docs omitted.** That composition list looked
exhaustive but was not — two additional real, code-bearing shell libraries exist
and represent surface capabilities neither legacy doc named:

- **`@oshun/shell-desktop`** (`libs/oshun/shell-desktop/`) is an entire
  **Electron-class desktop companion surface** — a whole surface category that
  goes unmentioned in the source feature list. Its `src/` contains
  `desktop-engine.ts` (`DesktopEngine`), `window-manager.ts` (`WindowManager`),
  `tray-companion.ts` (`buildTrayMenu`, `computeTrayIconState`),
  `protocol-handler.ts` (`ProtocolHandler`, `buildDeepLink`, `buildShellLink`),
  `update-manager.ts` (`UpdateManager`), `widget-engine.ts` (`WidgetEngine`,
  `getWidgetTemplate`), `notification-bridge.ts` (`NotificationBridge`,
  `buildStreakAtRiskNotification`, `buildDailySummaryNotification`), and
  `shortcut-manager.ts` (`ShortcutManager`, `normalizeAccelerator`). In plain
  terms: window/tray lifecycle, OS-level protocol (deep-link) handling, an
  auto-update path, desktop widgets, native notification bridging, and global
  shortcuts.
- **`@oshun/shell-achievements`** (`libs/oshun/shell-achievements/`) supplies
  the cross-domain accomplishment layer. Its `src/` contains
  `achievement-engine.ts` (`AchievementEngine`, `InMemoryAchievementStore`),
  `achievement-definitions.ts` (`getAchievementsByDomain`,
  `getCrossDomainAchievements`, `getSecretAchievements`),
  `challenge-templates.ts` (`getChallengesByDomain`,
  `getCrossDomainChallenges`), and `social-accountability.ts`
  (`analyzePartnershipHealth`, `computeCheckInSchedule`,
  `computeEngagementScore`, `generateAccountabilityInsights`).

### Offline substrate — `@oshun/offline`

The PWA and mobile offline behavior is not bespoke per app — it rides on the
real **`@oshun/offline`** library (`libs/oshun/offline/`), which the legacy
shell-composition list also omitted. Its `src/index.ts` re-exports `types`,
`storage`, `cache`, `retry`, `queue`, and `connectivity`. The concrete building
blocks are the `OfflineSyncQueue` class (`queue.ts`) and the `OfflineCache`
class (`cache.ts`), over typed contracts `SyncQueueItem<TPayload>`,
`RetryPolicy`, and `ConnectivityState` (`types.ts`). This is the substrate the
service worker's `queueBackgroundAction` and the mobile recovery-after-drift
logic both lean on, so offline retry and sync behave consistently across web and
mobile.

### One route map, six domains

Deep links and per-platform paths are defined once in
`libs/oshun/navigation/src/routes.ts` and consumed by every surface. The scheme
is fixed (`OSHUN_SCHEME = 'oshun'`), and the `domain` block of `OSHUN_ROUTE_MAP`
maps all six domains uniformly to a `webPath`, a `mobilePath`, and a
`deepLinkBase` (lines 96–129):

| Domain  | `webPath`          | `mobilePath`       | `deepLinkBase`    |
| ------- | ------------------ | ------------------ | ----------------- |
| tara    | `/domains/tara`    | `/domains/tara`    | `oshun://tara`    |
| veritas | `/domains/veritas` | `/domains/veritas` | `oshun://veritas` |
| nyx     | `/domains/nyx`     | `/domains/nyx`     | `oshun://nyx`     |
| arete   | `/domains/arete`   | `/domains/arete`   | `oshun://arete`   |
| nisaba  | `/domains/nisaba`  | `/domains/nisaba`  | `oshun://nisaba`  |
| metis   | `/domains/metis`   | `/domains/metis`   | `oshun://metis`   |

That `nisaba` and `metis` carry the same shape as the other four is the
navigation-layer corroboration that the `/domains/*` namespace is genuinely
six-wide. A single edit to `routes.ts` keeps web links, mobile tab paths
(`OSHUN_TAB_PATHS`), and deep links coherent — which is exactly why a new domain
becomes routes plus a `DomainRouteExperience` branch, not a new application. The
full assembly story is in the architecture companion at
[Product Surfaces](../architecture/product-surfaces.md).

## Public Web and Distribution

V1 includes the public distribution surface:

- A **public marketing website** with product positioning, conversion paths, and
  semantic SEO. Marketing / landing lives at `apps/oshun/web/src/app/landing/`;
  legal copy lives at the markdown-only `apps/oshun/legal/`.
- **Open Graph**, social-sharing metadata, preview assets, and public-to-auth
  app funnel tracking (per-domain OG images such as `/og/domain-<id>.png` are
  emitted from `DOMAIN_META` in the `[domainId]` route).
- **Apple App Store and Google Play** submission assets, listing content,
  screenshots, privacy labels, and review preparation — wired through the
  `render-store-assets.mjs` / `validate-store-submission.mjs` mobile tooling.
- **Universal Links, Android App Links, TestFlight**, Play Store internal/beta
  testing, a minimum-version strategy, forced-update rules, and external
  deep-link E2E coverage — the App-Links side declared in `app.json`
  `associatedDomains` (iOS) and the auto-verified `intentFilters` (Android).

## Why it works this way

- **One web app, one mobile app, one shell.** Centralizing navigation, the
  assistant, routines, achievements, and the offline substrate in
  `libs/oshun/shell-*` and `@oshun/offline` means adding a domain is a routing
  and experience change, not a new app. The six-wide `routes.ts` map is what
  keeps a single deep-link edit coherent across web, mobile, and the BFF.
- **Reachability and launch status are separate concerns.** A `/domains/<id>`
  route can exist and render while the domain registry still reports the domain
  as `beta` or `planned`. Encoding launch state in
  `domain-registry/src/registry.ts` (and filtering it via
  `getAvailableDomains()`) keeps the routing layer honest: the shell can show
  fewer domains post-hydration without any route being deleted.
- **Fail loud over fake.** The `content-service` surface throws
  `NotConfiguredError` rather than fabricate generations when no provider is
  configured, and its cloud deploy is honestly marked `[~]`. That candor is the
  intended posture across surfaces — a reachable-but-gated surface says so
  rather than pretending to ship.

## Related

- [Product Surfaces](../architecture/product-surfaces.md) — the
  architecture-side catalog of how these surfaces are assembled from shared libs
  and the BFF.
- [V1 Product Promise](./product-promise.md) — the launch-configuration framing
  (five enabled domains, Metis visible-but-planned).
- [Assistant Experience](./assistant-experience.md) — the dock and
  command-surface capabilities the shell composes.
- [Admin Products — Web and Mobile](./admin-products.md) and
  [Tenant, Institution, and Operator Toolkit](./tenant-toolkit.md) — the
  operator surfaces split out of the customer apps.
- [Telegram Surfaces](./telegram-surfaces.md) and
  [WhatsApp, Email, Push, SMS, Discord, and Slack](./other-channels.md) — the
  channel surfaces beyond web and mobile.
- [Architecture, Platform Foundations, and Security](./platform-foundations-and-security.md)
  — the BFF and platform layer behind every surface.
