Oshun Platform · Architecture

Product Surfaces

A focused page within the Oshun Platform Architecture documentation. The full map and every sibling page live in the Architecture hub.

10sections13 minread2tables

On this page

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 (which shows the request path) and beside Customer-Facing Domains (which the surfaces render); it is part of the V1 architecture set hubbed at ../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).

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 Tenant Consoleapps/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.

Messaging Channels#

Two surfaces extend Oshun into Telegram, tracked under §26 and detailed in Messaging Channels:

  • Telegram Botapps/oshun/telegram-bot/ (@oshun/telegram-bot).
  • Telegram Mini Appapps/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.