﻿# @xylex-group/athena-auth-ui

current version: `4.5.0`
> **Canonical integration:** `createClient` + `<AthenaAuthProvider client={athena} ui={ui}>`.  
> Ordinary type is `AthenaAuthProviderProps` (`client` + optional `ui` + `children`) — [ADR 0061](../../../docs/adr/technical/0061-athena-auth-ui-consolidation.md).  
> Auth UI is presentation only ([ADR 0022](../../../docs/adr/technical/0022-athena-auth-ui-presentation-layer.md)): session, transport, topology, and Athena Query belong to `@xylex-group/athena`.  
> Spec example: `examples/next-minimal` (four-step Discovery 1.1 canary). Migration notes: package docs / Blume site upgrading guide.  
> `AthenaProviders` / `AthenaAppProviders` alias `AthenaAuthProvider` (ledgred; expiry 2026-12-31).

current version: `4.0.3` (peer `@xylex-group/athena` `>=5.2.0`)  
Athena Auth UI components implemented with HeroUI internally. The npm package is published as `@xylex-group/athena-auth-ui`; HeroUI is an implementation detail rather than part of the package name.

### Local monorepo build / watch

This package is **Vite** (not Next Turbopack). From `packages/athena-auth-ui`:

```bash
bun run package:build    # one-shot Vite dist
bun run dev:package      # vite build --watch (Turbo: dev:watch)
```

Example apps (`next-minimal`, `next-heroui-example`) use **Next Turbopack** in dev and resolve this package’s Vite `dist/` entry facades via `scripts/next-turbopack-workspace.mjs`.

## Install

```bash
bun add @xylex-group/athena-auth-ui @xylex-group/athena
# or: pnpm add @xylex-group/athena-auth-ui @xylex-group/athena
```

Required peer dependencies:

- `react`
- `react-dom`
- `@heroui/react`
- `@heroui/styles`
- `@better-auth-ui/core`
- `@better-auth-ui/react`
- `@xylex-group/athena`
- `@tanstack/react-query`
- `@tanstack/react-pacer`
- `@gravity-ui/icons`
- `@internationalized/date`
- `bowser`

## Public API

Ordinary browser shell: Discovery 1.1 on the root client, then presentation.
Auth UI does not pick origin, `/api/auth`, `auth.routing`, or `ATHENA_AUTH_URL`.

```tsx
import { createClient } from "@xylex-group/athena/next/client"
import {
  AthenaAuthProvider,
  SignIn,
  Settings,
  useAthenaSession,
  useAthenaCapabilities,
  type AthenaAuthUiOptions,
} from "@xylex-group/athena-auth-ui"

const athena = createClient({ topology: { discover: "next" } })
const ui: AthenaAuthUiOptions = {}

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <AthenaAuthProvider client={athena} ui={ui}>
      {children}
    </AthenaAuthProvider>
  )
}
```

Root named exports: `AthenaAuthProvider`, `useAthenaSession`, `useAthenaCapabilities`, `Auth`, `SignIn`, `SignUp`, `Settings`, plus types `AthenaAuthProviderProps`, `SessionUiState`, `AthenaAuthUiOptions`. There is **no** `export *` on the root or on `/account` `/organization` `/passkeys` `/primitives` `/tables` `/next`. Page shells (`SignInPage`, …) remain on `./pages`.

Node root + Next handlers stay on Athena JS (`@xylex-group/athena/server`,
`createAthenaNextHandlers`). Hosted `url`+`key` and remote Auth are **advanced**.
Canonical setup below.

Switch `useAthenaSession()` on `status` (`loading` | `authenticated` |
`unauthenticated` | `unavailable` | `error`). Do not combine `isPending && !session`.
In-tree session screens (settings, org switcher/manager, user menu, workspace,
multi-session) use the same `status` gate. List/admin pending (API keys, billing,
chat, sidebar permissions) is not session `loading`/`error`.

Stable subpaths when you need a narrower seam:

- `@xylex-group/athena-auth-ui/account` (`AccountSettings`, `UserProfile`)
- `@xylex-group/athena-auth-ui/organization` (`OrganizationManager`, `OrganizationSwitcher`)
- `@xylex-group/athena-auth-ui/passkeys` (`Passkeys`, `PasskeyButton`)
- `@xylex-group/athena-auth-ui/primitives` (`AthenaDialog`, cards — `ResponsiveOverlay` is a ledgred alias)
- `@xylex-group/athena-auth-ui/tables` (`AthenaTable`, `WorkspaceEmptyState`)
- `@xylex-group/athena-auth-ui/next` (path resolvers + proxy **handlers**)
- `@xylex-group/athena-auth-ui/athena/client` (includes **deprecated** auth-only factories)
- `@xylex-group/athena-auth-ui/auth/routing-debug`
- `@xylex-group/athena-auth-ui/email`
- `@xylex-group/athena-auth-ui/pages`
- `@xylex-group/athena-auth-ui/plugins`
- `@xylex-group/athena-auth-ui/styles`
- `@xylex-group/athena-auth-ui/types` (`SessionUiState`, `AthenaErrorUi`)
- `@xylex-group/athena-auth-ui/client` (`toAthenaUiError`)
- `@xylex-group/athena-auth-ui/athena/server` (RSC session bootstrap: `withAthenaAuthCapabilities`)
- `@xylex-group/athena-auth-ui/athena/session-bridge` (app-origin session bridge: one-time `bridge_code`, HttpOnly cookie)

These are **not** public (use `@xylex-group/athena` / `client.auth` / `./next`):

- `./athena/proxy`, `./athena/base-url`, `./athena/request-headers`
- `./athena/query-client`, `./athena/table-query`

## Error presentation

Auth UI never renders raw transport bodies (HTML/XML CDN pages, stack traces, or oversized payloads) as toast or form copy.

1. Normalize with `toAthenaUiError()` **before** views. Public kinds: `validation` | `unauthorized` | `conflict` | `rate-limit` | `unavailable` | `internal`.
2. `AthenaAuthRequestError` keeps the original body on `raw` and uses a short summary for `message`.
3. `toAuthErrorView()` is a **deprecated** compat mapper (`AuthErrorCode`). Do not branch views on `invalid_credentials` / `network_error` as the public contract. Discovery 1.1 / transport envelopes map as:

   | SDK `code` | `AuthErrorCode` |
   | --- | --- |
   | `ATHENA_DISCOVERY_UNAVAILABLE` | `runtime_discovery_error` |
   | `ATHENA_DISCOVERY_INCOMPATIBLE` / `ATHENA_PROTOCOL_INCOMPATIBLE` / `ATHENA_DISCOVERY_CAPABILITY_MISSING` | `runtime_configuration_error` |
   | `ATHENA_AUTH_NOT_AVAILABLE` | `auth_unavailable` |
   | `ATHENA_RUNTIME_UNAVAILABLE` | `runtime_unavailable` |
   | `FAILED_TO_FETCH` / `NETWORK_UNAVAILABLE` | `request_transport_error` |

   `"Unable to reach the authentication service. Check your connection."` only when Auth HTTP transport is confirmed (`domain`/`service` = `auth`, or `operation` is `auth` / `auth.*`). Data-probe failures and bare `Failed to fetch` are not Auth connectivity. The `examples/next-minimal` canary `error.tsx` calls `toAuthErrorView` and renders `view.message` + `view.code` so `runtime_discovery_error` and `auth_unavailable` stay distinct.
4. `showDedupedDangerToast()` is the last toast boundary: unsafe strings become a generic fallback and share one dedupe key.

`AthenaErrorUi.message` / compat `AuthErrorView.message` are always safe to render. Diagnostics stay on `raw` / structured envelopes.

Stable lower-level composition seams from the root entrypoint:

- `createAuthUiTanstackQueryClient` / `getAuthUiTanstackQueryClient` — TanStack UI-cache factories from the **package root** (`import { … } from "@xylex-group/athena-auth-ui"`). Not Athena Query. `./athena/query-client` is unpublished.
- `OrganizationManager`
- `OrganizationSwitcher` (presentational field)
- `OrganizationSwitcherControl` (connected switcher + create; session SSOT + RQ mirror)
- `useOrganizationSwitcher` (headless)
- `SettingsSectionsStack`
- `AuthTable`
- `TableEmptyState`
- `CheckEmail`
- `ResetEmailSent`
- `useAthenaSession` (`SessionUiState`) / `useAthenaCapabilities`

### Organization switcher (app shell)

```tsx
import { OrganizationSwitcherControl } from "@xylex-group/athena-auth-ui"
// or: @xylex-group/athena-auth-ui/organization

// Under AthenaProviders — client session updates without router.refresh()
export function ShellOrgSwitcher() {
  return <OrganizationSwitcherControl className="w-full" showCreate />
}
```

After switch, identity is written to **`athena.auth.session` (SSOT)** only.
The Better Auth React Query key `["auth", "getSession"]` is a **derived mirror**
(one writer: `session-query-mirror`). Org switch does not patch or roll back RQ.
Use `onSuccess` only if Server Components still read active org from cookies.

## Athena Client Setup (canonical)

Ordinary browser path: Discovery 1.1 on the root Athena client. Auth UI does not
pick origin, Auth HTTP, or cookie mode.

```tsx
import { createClient } from "@xylex-group/athena/next/client"
import { AthenaAuthProvider, SignInPage } from "@xylex-group/athena-auth-ui"

export const athena = createClient({
  topology: { discover: "next" },
})

export function Providers({ children }: { children: React.ReactNode }) {
  return <AthenaAuthProvider client={athena}>{children}</AthenaAuthProvider>
}

export function AuthScreen() {
  return <SignInPage />
}
```

Session identity: `athena.auth.session` / `useAthenaSession()` → `SessionUiState`.  
Feature availability SSOT: `athena.auth.capabilities` / `useAthenaCapabilities()` plus SDK `isCapabilityEnabled` / `resolveSocialProvidersForUi` (INV-P). Passkey chrome is fail-closed (`status === "known"` and `passkeys === true`). Social lists come from `capabilities.social`; unknown/fallback does not hide login or social with `[]`. Known `false` may hide org / 2FA / password; unknown never forces those off. Explicit `AthenaAuthProvider` props (`passkey`, `organization`, `twoFactor`, `socialProviders`) are presentation overrides, not capability declarations. Do not treat plugins, Better Auth plugin IDs, method existence, or `basePath` as availability.

## Deprecated auth-only factories

`createAthenaAuthClient` / `createAthenaServerAuthClient` remain as compatibility
shims and emit a one-time dev warning. Prefer the root Athena client above.

```tsx
// @deprecated — migration only
import { createAthenaAuthClient } from "@xylex-group/athena-auth-ui/athena/client"
```

Transport (proxy hops, base URL, request headers, session-bridge cookies, package TanStack / table-query) is **not** a public Auth UI surface. Prefer:

```tsx
import { createAthenaAuthHandlers, getSession } from "@xylex-group/athena/next/server"
import { athenaAuthHandlers } from "@xylex-group/athena-auth-ui/next"
import {
  resolveAthenaAuthClientBaseUrl,
} from "@xylex-group/athena/utils"
```

Happy-path Next Auth handlers: `createAthenaAuthHandlers(athena)` from `@xylex-group/athena/next/server`. `athenaAuthHandlers` on `./next` is a convenience re-export of the package proxy implementation — not a second topology owner.

## Advanced Auth routing

Hosted `url`+`key` and remote Auth remain **advanced** (not the ordinary shell).
URL helpers live on `@xylex-group/athena/utils`. Auth UI does not publish
`./athena/base-url`. The routing-debug overlay may still switch same-origin
proxy vs direct upstream for development. `AthenaAuthProvider` `basePath` and
`credentials` are advanced UI props (page-tree / cookie presentation), not
Discovery 1.1 topology knobs.

## Pages And Components

Route-ready page shells include:

- `AuthPage`
- `CheckEmailPage`
- `SignInPage`
- `SignUpPage`
- `ForgotPasswordPage`
- `ResetEmailSentPage`
- `ResetPasswordPage`
- `SignOutPage`
- `SettingsPage`
- `AccountSettingsPage`
- `SecuritySettingsPage`

Standalone auth, settings, user, workspace, email, and plugin components intended for consumers are also exported from the root entrypoint.

## OAuth Provider Control

Use `ui.auth.methods.oauthProviders` when you want to hide or disable specific social providers without reaching into the DOM:

```tsx
import { SignInPage, type AthenaAuthUiOptions } from "@xylex-group/athena-auth-ui"

const ui: AthenaAuthUiOptions = {
  auth: {
    methods: {
      oauthProviders: {
        apple: "hidden",
        microsoft: "disabled"
      }
    }
  }
}

export function AuthScreen() {
  return <SignInPage ui={ui} />
}
```

## Email Templates

```tsx
import {
  EmailVerificationEmail,
  getAthenaAuthEmailTemplateCatalog
} from "@xylex-group/athena-auth-ui"
```

Each exported email component carries stable Athena Auth metadata such as `templateKey`, and the catalog helper can drive admin tooling or template selection UX.

## Styling

Import HeroUI styles in your app and load the package styles entrypoint so Tailwind can scan the installed auth UI bundle:

```css
@import "tailwindcss";
@import "@heroui/styles";
@import "@xylex-group/athena-auth-ui/styles";
```

## License

MIT
