# Product Surfaces

This page is the catalog of every place a human actually touches Oshun V1 — the
web app, the mobile apps, the operator consoles, the messaging channels, the
desktop companion, and the supporting service and content surfaces — and how
they are assembled from a small set of shared shell libraries rather than
rebuilt per surface. It serves engineers deciding _which app or lib a feature
belongs in_, and product/design leads who need the real inventory of what ships
and what is still gated. It sits one layer below the
[High-Level Architecture](./high-level-architecture.md) (which shows the request
path) and beside [Customer-Facing Domains](./customer-domains.md) (which the
surfaces render); it is part of the V1 architecture set hubbed at
[../ARCHITECTURE.md](../ARCHITECTURE.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 via the shared shell and the single BFF. The exceptions are explicit
and small (Metis runs its own app/microservice stack), and the operator world is
deliberately split into its own apps with strict isolation. Surfaces are
overwhelmingly real code: every named app below 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.

## Surface Inventory

The customer-facing and operator apps live under `apps/oshun/` (plus the Metis
stack under `apps/metis/`). Every entry below was confirmed against the real
`package.json` `name` field.

| Surface                          | Path                                         | Package                      | Notes                                                    |
| -------------------------------- | -------------------------------------------- | ---------------------------- | -------------------------------------------------------- |
| Customer Web                     | `apps/oshun/web/`                            | `@oshun/web`                 | Next.js app; PWA, all six domains, Studio subroute       |
| Customer Mobile                  | `apps/oshun/mobile/`                         | `@oshun/mobile`              | Expo + Expo Router; `scheme: oshun`                      |
| Oshun BFF                        | `apps/oshun/bff/`                            | `@oshun/bff`                 | Fastify; single API front door (§ in BFF page)           |
| Admin Web                        | `apps/oshun/admin/`                          | `@oshun/admin`               | Operator review/safety/governance console                |
| Admin Mobile                     | `apps/oshun/admin-mobile/`                   | `@oshun/admin-mobile`        | Expo Router; on-call operator app                        |
| Tenant Console                   | `apps/oshun/tenant-admin/`                   | `@oshun/tenant-admin`        | Standalone Next app (§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 surface (§26)                               |
| Content Service                  | `apps/oshun/content-service/`                | `@oshun/content-service-app` | Deployable HTTP service shell for agentic content (§3.2) |
| Legal (docs)                     | `apps/oshun/legal/`                          | _none — markdown only_       | `privacy-policy.md`, `terms-of-service.md`; no app       |
| Metis Web / Admin / API / Worker | `apps/metis/{web,admin,api-gateway,worker}/` | (Metis stack)                | Only domain with its own dedicated app stack             |
| Metis Mobile                     | `apps/metis/mobile/`                         | (Metis stack)                | A separate Metis mobile app distinct from Oshun mobile   |

Two of these are **not** mentioned in the prose elsewhere in V1 and deserve
calling out explicitly:

- **`apps/oshun/content-service` (`@oshun/content-service-app`)** is a real,
  deployable surface, not a UI page. Its `src/main.ts` boots
  `createContentHttpServer` / `createDurableContentService` from
  `@oshun/content-service` over a real Iris-routed writer and a calibrated
  `JudgePanel` (`@oshun/content-quality-judge`). It is **fail-loud**: with no
  model provider configured (`ANTHROPIC_API_KEY` absent) it throws a
  `NotConfiguredError` at startup rather than fabricating generations, and the
  cloud deploy of it is honestly marked `[~]` in the backlog.
- **`apps/oshun/legal`** is a markdown-only surface (`privacy-policy.md`,
  `terms-of-service.md`) with **no** `package.json`. It is correctly referenced
  as the home of legal copy, but it is a docs folder, not a buildable app — the
  marketing/landing experience itself lives in the web app.

> **Metis is the only domain with its own apps.** Every other customer-facing
> domain renders through `apps/oshun/web` and `apps/oshun/mobile` via the shared
> shell and the BFF. The one nuance to the older "renders solely through
> `apps/oshun/mobile`" framing is that a separate `apps/metis/mobile/` exists —
> so on mobile, Metis is not _solely_ the Oshun mobile app either.

## Customer Web — `apps/oshun/web`

The customer web app (`@oshun/web`) is the flagship surface and the reference
implementation of the dual-namespace pattern described below.

- The V1 **dashboard** at `apps/oshun/web/src/app/page.tsx` is Tara-centered
  with mixed-domain recommendations and reason labels.
- A desktop-grade **multi-panel workspace** at
  `apps/oshun/web/src/app/(workspace)/` for continuity, search, reading,
  evidence inspection, study, notes, and assistant work.
- A collapsible **assistant dock** (`src/components/assistant-dock/`), a
  **command palette** (`src/components/command-palette/`), and **mixed-domain
  recommendations** (`src/components/recommendations/`).
- **Universal search** at `src/app/search/page.tsx` spanning rituals, practices,
  concepts, passages, claims, sources, notebooks, collections, programs, sky
  events, courses, lessons, and generated artifacts.
- Other top-level customer routes live under
  `src/app/{lilith,operator,studio,events,explore,library,activity,landing,…}/`.
- Performance budgets are enforced at `apps/oshun/web/lighthouse-budget.json`
  (LCP ≤ 2.5s, INP ≤ 200ms, CLS ≤ 0.1), with Playwright coverage for
  keyboard-only flows, accessibility, visual regression, and resilience on every
  critical journey.

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

Customer domains ship **two coordinated namespaces**, both V1, for distinct
audiences:

1. **Consumer hub plus depth** at
   `src/app/{tara,arete,veritas,nyx,nisaba,metis}/*`. These are the
   Lilith-design-system "rooms" (`TaraRoom`, `AreteRoom`, `VeritasRoom`,
   `NyxRoom`, `NisabaRoom`, and Metis surfaces) — presentational, contemplative,
   optimized for first-time and returning consumers. The Nisaba hub at
   `src/app/nisaba/` already carries deep subroutes (`compare`, `daily`,
   `graph`, `lexicon`, `manuscript`, `notebook`, `notebooks`, `plan`,
   `scholar`), and the Metis hub at `src/app/metis/` carries `assessment`,
   `byom`, `courses`, `ingest`, `lesson`, `lessons`, `session`, `tutor`, and
   `upload`.
2. **Power-user deep tools** at `src/app/domains/{…}/*` — heavier client
   workspaces for scholars, power users, customer-mode operators, and
   editorial/curation roles. They are reachable through deep links and
   in-context entry points from the consumer hub, not the canonical first read
   of a domain.

The important correction to earlier documentation: **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 the `DomainPreviewBannerForPath`). The catch-all extends coverage to
`nisaba` and `metis`:

- `apps/oshun/web/src/navigation/routes.ts` declares
  `WEB_DOMAIN_IDS = ['tara','veritas','nyx','arete','nisaba']` and
  `WEB_NAVIGABLE_DOMAIN_IDS = [...WEB_DOMAIN_IDS, 'metis']`.
- `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` ("Primary Texts & Scholarship") and
  `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 older claim that
"Nisaba and Metis do not yet ship parallel `/domains/*` namespaces" is stale and
contradicted both by this code and by the general dual-namespace pattern itself.

What _is_ true — and is a 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." Reachable route ≠ launched domain.

### 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"`, and `theme_color: "#f1ebdd"` (the parchment tone of
  the Lilith design system). Its app shortcuts map directly to the six domains
  ("Meditate" → Tara, "Read" → Veritas, "Sky Tonight" → Nyx, "Check In" → Arete,
  "Study" → Nisaba, "Learn" → Metis).
- `apps/oshun/web/public/sw.js` is the real service worker.
- `apps/oshun/web/src/lib/pwa-service-worker.ts` implements the registration,
  update, and offline plumbing. Its real exports include
  `registerServiceWorker(options)`, `applyWaitingServiceWorker(...)`, and
  `resolvePwaUpdateRouteContext(currentPath)` (which returns a
  `PwaUpdateRouteContext` with a `PwaUpdateRouteMode` of
  `'reading' | 'standard'` so an update prompt never interrupts a deep-read
  flow). It also exports `queueBackgroundAction` for background sync and
  `requestServiceWorkerMemberDataClear()` for privacy teardown.
- It is covered by `apps/oshun/web/src/lib/pwa-service-worker.test.ts` and a
  policy-level test at
  `apps/oshun/web/src/__tests__/pwa-service-worker-policy.test.ts`.

## Customer Mobile — `apps/oshun/mobile`

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

- `app.json` sets `expo.name: "OSHUN"`, `slug: "oshun-mobile"`,
  `scheme: "oshun"` (the deep-link scheme shared with the navigation lib),
  `ios.bundleIdentifier: "com.oshun.mobile"`, and Universal-Link
  `associatedDomains` including `applinks:oshun.app`, `applinks:www.oshun.app`,
  and `applinks:app.oshun.com`.
- The tab IA is real Expo Router under `app/(tabs)/`: `index`, `explore`,
  `activity`, `library`, and `profile` (with `_layout.tsx` and co-located tests
  such as `index.test.ts` and `profile.test.tsx`).
- **Deep-link routing** for all top-level domains and high-value object types
  runs through `@oshun/navigation` (see the shared route map below).
- Per-domain UX includes continuation cards, recovery-after-drift logic, two-way
  calendar sync (Google / Apple / Outlook), and camera/microphone capture with
  per-domain redaction.
- **Wears, widgets, and Live-Activity-style controls** come from
  `@oshun/shell-wearable`.
- Hand-off between mobile and desktop runs through **Iris-backed continuation
  state** (see [Iris — Assistant Memory Substrate](./substrate-iris.md)).

The mobile app's test and release tooling is concrete and worth naming
specifically — the older "mobile E2E" framing understates it. Under
`apps/oshun/mobile/scripts/` you'll find 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`). EAS build/submit configuration backs the
actual store pipeline.

## Shared Consumer Shell — `libs/oshun/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 shell is built from:

- **`@oshun/shell-core`** — home / explore / activity / library / notifications
  / profile / settings / assistant entry points.
- **`@oshun/shell-assistant`** — the collapsible assistant dock and context
  hand-off.
- **`@oshun/shell-routines`** — cross-domain routine and continuation cards.
- **`@oshun/shell-wearable`** — the watch / widget surface.
- **`@oshun/navigation`** — current-domain persistence, deep links, route-level
  analytics, and command-surface registration.
- **`@oshun/design-tokens`** and **`@oshun/ui`** — the design system and
  component library.

### Two real shell libs the older docs omit

The composition list above looked exhaustive but was not. Two additional
**real** shell libraries exist and ship code; neither was named in the earlier
architecture or feature docs:

- **`@oshun/shell-desktop`** (`libs/oshun/shell-desktop/`) is an entire
  Electron-class **desktop companion surface** — a whole surface category
  neither legacy doc mentions. Its `src/` contains `desktop-engine.ts`,
  `window-manager.ts`, `tray-companion.ts`, `protocol-handler.ts`,
  `update-manager.ts`, `widget-engine.ts`, `notification-bridge.ts`, and
  `shortcut-manager.ts`. 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`, `achievement-definitions.ts`,
  `challenge-templates.ts`, and `social-accountability.ts`.

### 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 (`enqueue`, `getAll`, and a retry-aware
`flush(handler, policy)` in `queue.ts`) and the `OfflineCache` class, over typed
contracts `SyncQueueItem`, `RetryPolicy`, and `ConnectivityState`. This is the
substrate the service worker's `queueBackgroundAction` and the mobile
recovery-after-drift logic both lean on.

## Shared Route Map — `@oshun/navigation`

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 maps **all six**
domains uniformly to a `webPath`, a `mobilePath`, and a `deepLinkBase`:

| 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`   |

The fact that `nisaba` and `metis` carry the same `webPath`/`deepLinkBase` shape
as the other four is the navigation-layer corroboration that the `/domains/*`
namespace is genuinely six-wide. (The deep-link _prefix_ is mirrored on the BFF
side too: `libs/oshun/domain-registry/src/registry.ts` records the matching
`deep-link-prefix` such as `oshun://tara` and the BFF base path
`/api/oshun/domains/tara` per domain.) Mobile tab paths derive from the same map
via `OSHUN_TAB_PATHS`, so a single edit to `routes.ts` keeps web links, mobile
tabs, and deep links coherent.

## Operator Surfaces

Operator tooling is deliberately split from customer surfaces and from each
other, with strict isolation between platform operators and per-tenant admins.

- **Oshun Admin (Web)** — `apps/oshun/admin/` (`@oshun/admin`). Review queues,
  safety/support, privacy/DSAR, persona/model/rights/provenance, incidents, and
  launch governance. It depends on `@oshun/contracts`, `@oshun/design-tokens`,
  `@oshun/navigation`, and `@oshun/shell-assistant` — reusing the same shell
  primitives as the customer apps rather than a separate component stack.
- **Oshun Admin (Mobile)** — `apps/oshun/admin-mobile/` (`@oshun/admin-mobile`).
  This is a full Expo Router app in its own right, not an afterthought. Its
  `app/` carries `login.tsx`, `step-up.tsx`, `unauthorized.tsx`, a `(operator)/`
  tab group (`inbox`, `incidents`, `index`, `more`), and a `workspace` route;
  its `src/` spans `auth`, `incidents`, `offline`, `review`, `urgent-queue`,
  `notifications`, `support`, `telemetry`, `accessibility`, `feedback`, and
  more. It is built for on-call review, approvals, incident triage, and
  step-up-authenticated smaller-screen workflows.
- **Oshun Studio** — lives **under** `apps/oshun/web/src/app/studio/`, not as a
  separate `apps/oshun/studio` app. Authoring, editorial, asset, taxonomy,
  localization, and versioning UIs share the Oshun shell and design system. See
  [Oshun Studio — Authoring, Editorial, Curation](./oshun-studio.md).
- **Oshun Tenant Console** — `apps/oshun/tenant-admin/` (`@oshun/tenant-admin`),
  a standalone Next app (§20). Tenant-scoped admin shell with strict isolation
  from operator-only Admin: members, roles, content scopes, SSO/SCIM, audit,
  integrations, and lifecycle comms. The tenancy graph and policy inheritance
  are described in
  [Data Architecture and Tenancy](./data-architecture-tenancy.md).

## Messaging Channels

Two surfaces extend Oshun into Telegram, tracked under §26 and detailed in
[Messaging Channels](./messaging-channels.md):

- **Telegram Bot** — `apps/oshun/telegram-bot/` (`@oshun/telegram-bot`).
- **Telegram Mini App** — `apps/oshun/telegram-miniapp/`
  (`@oshun/telegram-miniapp`), an embedded web surface.

## Public Web and Distribution

- Marketing / landing lives at `apps/oshun/web/src/app/landing/`; legal copy
  lives at `apps/oshun/legal/` (the markdown-only docs surface noted above).
- Distribution covers app-store assets, Universal Links / Android App Links,
  TestFlight, Play internal/beta tracks, a minimum-version strategy, and
  forced-update rules — the mobile side of which is wired through the
  `validate-store-submission.mjs` / `render-store-assets.mjs` tooling and the
  `associatedDomains` declared in `app.json`.

## Why It Works This Way

- **One web app, one mobile app, one shell.** Centralizing navigation, the
  assistant, routines, and the offline substrate in `libs/oshun/shell-*` and
  `@oshun/offline` means a new domain becomes routes and a
  `DomainRouteExperience` branch, not a new application. The six-wide
  `routes.ts` map is what lets a single deep-link change stay 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.
- **Operators are isolated by app, not by route guard alone.** Splitting Admin
  Web, Admin Mobile, and the Tenant Console into distinct apps — with platform
  operators and tenant admins never sharing a console — is the surface-level
  expression of the tenancy isolation model.
- **Fail loud over fake.** The content-service surface throws
  `NotConfiguredError` rather than fabricating generations when no provider is
  configured, and the docs mark its cloud deploy `[~]`. That candor is the
  intended posture across surfaces: a reachable-but-gated surface says so.

## Related

- [High-Level Architecture](./high-level-architecture.md) — where these surfaces
  sit in the request path.
- [Customer-Facing Domains](./customer-domains.md) — what the domain routes
  render, and the domain registry that gates launch status.
- [Substrate — Iris](./substrate-iris.md) — the continuation state behind
  desktop↔mobile hand-off.
- [Oshun Studio — Authoring, Editorial, Curation](./oshun-studio.md) — the
  Studio subroute surface.
- [Messaging Channels](./messaging-channels.md) — the Telegram bot and mini app.
- [Data Architecture and Tenancy](./data-architecture-tenancy.md) — the
  isolation model behind the operator and tenant surfaces.
