---
name: create-enginex-app
description: Use when creating a new engineX product app, or scaffolding/wiring/registering an app in this monorepo (apps/products/*). Covers the full install flow — scaffold, bilingual manifest, pages/API, client-shell re-exports, DB registration, force-dynamic, and verification — so a new app is correct and visible in the sidebar the first time.
---

# Create an engineX app

engineX is a **shell + plugins** platform: `apps/client` is the shell, and each product app in `apps/products/<slug>` is an installable plugin. An app becomes visible when it is (1) scaffolded, (2) re-exported into the client shell, and (3) registered in the database. Miss any of the three and it 404s or never appears. Follow every step.

## Golden rules (do not skip)
- **Bilingual always.** Every user-facing string has an Arabic sibling (`label`/`labelAr`, `name`/`nameAr`). Never add a string without its Arabic counterpart.
- **`export const dynamic = "force-dynamic"`** must be the first line of every client-shell page re-export, or the build crashes prerendering client components.
- **The sidebar reads the DB, not code.** Editing `lib/manifest.ts` does NOT update the sidebar — you must also write `app_registry.manifest` (register step).
- **The full manifest JSON is the source of truth.** `getInstalledAppsForUser` spreads `app.manifest` — a manifest missing `routes.main` crashes the whole client for all users. Keep the manifest complete.
- Compose UI from `@platform/ui` (PageHeader, Card, Button, Badge, Input, Drawer, etc.) + `lucide-react`. Tailwind only. Use logical classes (`ms-`/`me-`/`ps-`/`start-`), never physical (`ml-`/`pl-`), for RTL.

## Step-by-step

### 1. Scaffold the app
```bash
bash scripts/create-app.sh
```
It prompts for: **slug** (lowercase, no spaces), **name** + **Arabic name**, **description** + **Arabic description**, **category** (`operations|sales|finance|marketing|general`), **icon** (a [lucide](https://lucide.dev) name, e.g. `Package`), and a **dev port**. It creates `apps/products/<slug>/` with `lib/manifest.ts`, `pages/page.tsx`, `api/health/`, `next.config.ts`, `package.json`, and a dev shell.

### 2. Fill in the manifest — `apps/products/<slug>/lib/manifest.ts`
Add your sidebar menu items (bilingual). Each item: `id`, `label`, `labelAr`, `path` (starts with `/<slug>`), `icon` (lucide name), `category`, `categoryAr`. Keep `routes.main` = `/<slug>`. Example item:
```ts
{ id: "<slug>-dashboard", label: "Dashboard", labelAr: "لوحة التحكم", path: "/<slug>", icon: "LayoutDashboard", category: "Overview", categoryAr: "نظرة عامة" },
```

### 3. Build your pages — `apps/products/<slug>/pages/**/page.tsx`
Client components. Use `useLocale()` from `@platform/ui` for `t(en, ar)`. Compose from `@platform/ui` + `lucide-react`. A sub-route lives at `pages/<feature>/page.tsx` → served at `/<slug>/<feature>`. API handlers live at `api/<feature>/route.ts` and MUST be org-scoped:
```ts
import { requireOrgContext, isOrgContext } from "@platform/auth";
const ctx = await requireOrgContext();
if (!isOrgContext(ctx)) return ctx;
// ctx.organizationId, ctx.userId — always filter DB queries by organizationId
```
For DB persistence, add org-scoped models to `packages/db/prisma/schema.prisma` (with `@@index([organizationId])` + a real FK), run `pnpm --filter @platform/db exec prisma db push` locally, and add an **additive** SQL file in `packages/db/prisma/migrations-manual/` for stage/prod (never `db push` on prod).

### 4. Wire it into the client shell
```bash
node scripts/install-app.js <slug>
```
This generates page + API re-exports under `apps/client/app/(platform)/<slug>/` and `apps/client/app/api/<slug>/`. **Verify each generated page re-export starts with** `export const dynamic = "force-dynamic";`. `install-app.js` handles top-level routes; for any **sub-route** you add later, create BOTH re-exports by hand:
- Page: `apps/client/app/(platform)/<slug>/<feature>/page.tsx` → `export const dynamic = "force-dynamic";` then `export { default } from "../../../../products/<slug>/pages/<feature>/page";`
- API: `apps/client/app/api/<slug>/<feature>/route.ts` → `export { POST } from "../../../../products/<slug>/api/<feature>/route";`
- Long AI routes need `export const maxDuration = 300;` **declared** (not re-exported) in the re-export file.

### 5. Register the app in the database (makes it appear in the sidebar)
Easiest: re-sync every on-disk app to the DB:
```bash
pnpm --filter @platform/db exec tsx prisma/install-all-apps.ts
```
This upserts `app_registry` from each app's manifest and installs it ACTIVE for every org. (Alternatively, register via the Admin Console → Apps → Register App, or the manual `INSERT INTO app_registry ... ` in CLAUDE.md §"App Installation".) Verify no app is missing required manifest fields:
```sql
SELECT slug FROM app_registry WHERE manifest->>'name' IS NULL OR manifest->'routes'->>'main' IS NULL; -- must be empty
```

### 6. Run and verify
```bash
pnpm --filter @platform/client dev   # or: pnpm dev (all apps)
```
Open `http://localhost:3000/<slug>` — the app should render and appear in the sidebar (log in first). If it 404s, a re-export is missing; if it's not in the sidebar, the DB registration (step 5) didn't run; if a page 500s about prerender, a re-export is missing `force-dynamic`.

## Adding a page/feature to an EXISTING app
1. Add `pages/<feature>/page.tsx` (+ `api/<feature>/route.ts` if it needs data).
2. Add a menu item to `lib/manifest.ts` (bilingual).
3. Create the two client-shell re-exports by hand (step 4 above) — `install-app.js` won't add sub-routes.
4. Re-run `install-all-apps.ts` (step 5) so the sidebar picks up the new menu item.
5. Restart the client and verify.

## Common failures (check these first)
- **404 on the app route** → missing client-shell page re-export.
- **Not in the sidebar** → `app_registry.manifest` not updated (run `install-all-apps.ts`).
- **500 "prerender"/null state on load** → a re-export page is missing `export const dynamic = "force-dynamic"`.
- **`Module not found` for `@platform/auth`** in a product API route → add it to that app's `package.json`: `pnpm --filter @platform/app-<slug> add '@platform/auth@workspace:*'`.
- **Blank app name / crash** → the manifest JSON is incomplete (needs `name`, `version`, `routes.main`, `menu`).

## Reference
The authoritative, longer version lives in `CLAUDE.md` §"App Installation (full flow)". When in doubt, mirror an existing app such as `apps/products/pm` (the manifest reference) or `apps/products/inspection-engine` (a full-featured example).
