# Leumas Web — @leumas/web

Leumas Web — the public hub (Google-HUD app index, social, dashboard, account, Imperium subdomains). A thin shell over @leumas/features.


**The index of everything Leumas hosts, and the home of Leumas Social.**
Find anything in Imperium, jump to it, manage your account — and use the full social network.

`leumas.tech` · dev port **5173** · a thin shell over `@leumas/features`.
Architecture: [PLATFORM.md](../../PLATFORM.md) · [APPLIANCES.md](../../APPLIANCES.md) §3

---

## Core functionality

1. **Index / search** — every app, tool, subdomain, page, capability, chatbot, automation and site
   mode in the ecosystem, from `@leumas/index-service` (`/index/*`). This is the "Google of Leumas",
   and it is a real page at **`/search`**: ranked, snippeted, faceted by kind, paged and linkable.
   ⌘K and `/search` call the SAME `/index/search` so they agree on ordering; the palette's last row
   hands the query over to the page (`AppLayout`'s `searchAllHref`). `/sitemap` redirects here.
   `/sitemap.xml` is unaffected — that is `createSeoRouter` in `@leumas/hosting`.
2. **Funnel pages** — `/apps/:slug` per-app pages with full SEO, screenshots, and "open in Studio" /
   "get the app" calls to action.
3. **Leumas Social** — the entire social product: feed, reels, watch, stories, messenger, friends,
   groups, chatrooms, discover, marketplace, random chat, sing, profiles, notifications.
   **Gated in production**: any `vite build` shows the `/social` teaser and redirects `/social/*` to
   it; dev is open. Leumas staff, tenant admins and users flagged `beta` bypass it. Override with
   `VITE_SOCIAL=full|teaser`. The default is derived from `import.meta.env.PROD` in `App.jsx` and
   NOT from a dotfile, because `.env.*` is gitignored — see the note there.
4. **Account & dashboard** — login/signup, membership & Stripe checkout, PassNode wallet, the
   `/dashboard` operator deck, API keys.
5. **Marketing & portfolio** — the public Showcase pages, company pages, gallery.
6. **Hosted-site renderer** — when a request arrives on a hosted subdomain or custom domain, the same
   build renders **that tenant's site** instead of the hub (`HostedSiteApp`).
7. **The social-only bundle** — a second entry that ships social + auth only. This is what
   `leumas-ios` loads.

## What it must never do

| Never | Because |
|---|---|
| Host sites itself | Imperium does — `@leumas/hosting` / `@leumas/imperium-server` |
| Edit site content | Studio does. Web renders, Studio authors |
| Re-implement social UI | it's `@leumas/features/surfaces/social` — Studio used the same code, iOS uses the same code |
| Ship an admin surface | Studio (`/admin`) and leumas-admin own those |
| Talk to `admin.leumas.tech` | that's staff-only infrastructure |

## Composes

```
@leumas/ui  @leumas/theme          the st- kit + tokens. No bespoke components
@leumas/features                   social · dashboard · consoles · physics · os surfaces + guards
@leumas/api-client                 every HTTP call. credentials:'include' + Bearer fallback
@leumas/config-registry            Imperium modes, DynamicPage, custom x- modes
@leumas/hosting                    subdomain resolution on the client side
@leumas/imperium-pages             the mode page components
@leumas/cinematic                  ShowcaseHero and the splash language
@leumas/hud  @leumas/media         hud mode · media globe mode
@leumas/headlock                   scroll/pointer lock helpers
```

## File tree

```
products/leumas-web/
├── package.json
├── vite.config.js [done] port 5173 · proxies /auth /api /db /social /index /mcp /leviathan /library …
├── index.html [done] hub entry
├── social.html [todo] social-only entry (iOS)
├── configs/ [done] local dev configs
├── public/cinematic/ [done]
└── src/
 ├── main.jsx [done] boots TenantBoot · registers custom x- modes
 ├── social.jsx [todo] social-only boot: socialRoutes() + auth + account
 ├── App.jsx [done] 229 lines · 37 routes + socialRoutes() + dashboardRoutes()
 ├── HostedSiteApp.jsx [done] a hosted subdomain owns the whole path space
 ├── routeComponents.js [done] route → component binding
 ├── siteHandlers.jsx [done]
    ├── bootstrap/
 │ ├── TenantBoot.jsx [done] hub vs hosted-site decision
 │ └── tenant.js [done]
 ├── cinematic/ [done]
 └── pages/ [done] 30 .jsx — Showcase, company, dashboard, DynamicPage, LibraryApp
 ├── _shared/ [done] ecosystem.jsx · brandData.js
 └── dashboard/ [done]
```

## Roadmap

### [todo] W1 · The social-only entry (roadmap B4)

- Status: todo — `social.html` + `src/social.jsx` — mount `socialRoutes()` + auth + account only
- Status: todo — Add the entry to `vite.config.js` `build.rollupOptions.input`
- Status: todo — Verify the bundle excludes cinematic, three, media, hud
- Status: todo — Point `products/leumas-ios/capacitor.config.json` `webDir` at it
- Status: todo — Keep `/social` on the hub entry — desktop users and SEO

### [todo] W2 · The link page (roadmap D1)

- Status: todo — `/link` route — reuse the existing `<Login>`, then a consent step:
      *"Link your Leumas account to Studio on DESKTOP-4F2A?"*
- Status: todo — On approve, call `GET /auth/link/authorize`, redirect to the loopback callback
- Status: todo — [warning] This page is on **leumas.tech**. A customer must never be sent to `admin.leumas.tech`

### [partial] W3 · Index depth

- Status: done — `/index/search` is the site-wide search: ranked (six fixed score bands, total ordering),
      `limit`/`offset` paged, `kind`-faceted, snippeted, and one source-load per few seconds instead
      of one per keystroke. Reports `search: 'lexical'`, the same field `/marketplace/listings` uses,
      so a semantic re-rank can arrive without every caller guessing which ranker ran.
- Status: done — `/search` page + the ⌘K "See all results" handover
- Status: done — Subdomains are searchable — `sites` is a first-class index kind, `configs` is its alias
- [~] **`plugin_listings` deliberately NOT added as an index kind.** `hydrateAppCatalogs`
      (`shared/services/marketplace/src/hydrate.js`) is the one writer keeping `plugin_listings` and
      `tools` in step from a single `defineLeumasApp`, so every listing already has a `tools` row the
      `apps`/`tools` sources serve. A `listings` kind would list every app twice and re-open the
      drift that file exists to close. The store's semantic reach instead appears on `/search` as a
      "You might also mean" strip fed by `GET /marketplace/listings`.
- Status: todo — Sitemap includes marketplace listings

### [todo] W4 · Funnel quality

- Status: todo — Every `/apps/:slug` page: screenshot, "how it's made", "how to use", pricing, install CTA
- Status: todo — Per-domain scoping so a tenant's hub shows only their apps

### [partial] W5 · README accuracy

- Status: done — This file no longer says "scaffold only" — the product is live and extensive

## Run

```bash
pnpm --filter @leumas/web dev
```

Leave `VITE_API_BASE` **unset** in dev — the Vite proxy makes the httpOnly cookie work with no CORS.
Only set it for a genuinely cross-origin API.


---
Source: products/leumas-web/README.md
Canonical: https://docs.leumas.tech/p/products/web
