# Get started (/docs/get-started) An **Enjab tool** is a web app that two Enjab services take care of for you: * **[Sign in with Enjab Auth](/docs/sign-in-with-enjab)** handles *who the user is* and *whether they may use your tool*. You never build a login screen or touch a password. * **[Enjab UI](/docs/enjab-ui)** gives you the *look* - a themed component registry and a dashboard shell, so every Enjab tool feels like one product. You build the part in the middle: your tool's actual features. Everything else is provided. ## The shape of an Enjab tool [#the-shape-of-an-enjab-tool] 1. It lives on an **`enjab.ae`** domain (e.g. `yourtool.enjab.ae`). Auth refuses any other domain. 2. The instant a signed-out visitor touches any page, they are redirected to Enjab to sign in. No login button, no login page of your own. 3. Once signed in, you key all your own data by the user's stable Enjab id (`sub`). 4. Your dashboard uses the Enjab UI shell, so the sidebar, account block, and the "an Enjab product" byline are consistent with every other tool. ## Fastest path [#fastest-path] ### Register the tool (one time) [#register-the-tool-one-time] An Enjab admin adds your tool in [auth.enjab.ae/admin](https://auth.enjab.ae) and hands you a `ENJAB_CLIENT_ID` + `ENJAB_CLIENT_SECRET`. See [Sign in with Enjab Auth → register](/docs/sign-in-with-enjab) for the exact steps. ### Wire up login [#wire-up-login] Drop in the four reference files (`lib/enjab-auth.ts`, the callback route, the logout route, and `proxy.ts`). Set your four environment variables. That is the whole integration - copy them from [Sign in with Enjab Auth](/docs/sign-in-with-enjab). ### Add the UI [#add-the-ui] Install the Enjab UI chrome and theme, then build your screens with the registry components so your tool matches the rest of Enjab. See [Enjab UI](/docs/enjab-ui). ## Handing this to an agent [#handing-this-to-an-agent] Give your coding agent this site's [`/llms.txt`](/llms.txt) (or the specific page's raw markdown) and the task. The pages are written so the agent can do each step end to end. # Overview (/docs) Welcome to **Enjab Developers** - the single source of truth for building on Enjab. Everything an Enjab tool needs is here: how to add **Sign in with Enjab Auth**, how to ship on-brand UI with the **Enjab UI** design system, and how to bring an existing tool up to the latest standards. One central place, kept current, for every Enjab tool that exists today and every one we build next. ## Built for agents first [#built-for-agents-first] These docs are written so a **coding agent** can read them and do the work, with humans reading the same pages. Every page is a normal markdown file you can fetch raw: | Endpoint | What it returns | | ---------------------------------- | ----------------------------------------- | | [`/llms.txt`](/llms.txt) | The index of every page, with links | | [`/llms-full.txt`](/llms-full.txt) | Every page concatenated into one document | | `/llms.mdx/docs//content.md` | A single page, raw markdown | Point an agent at `/llms.txt` and let it pull the pages it needs. No separate "agent version" of the docs - the page you read is the page the agent reads. ## The tools [#the-tools] ## Where to start [#where-to-start] If you are building a new tool, read [Get started](/docs/get-started) first - it covers how the pieces fit together and the fastest path to a working integration. If you are an agent, start at [`/llms.txt`](/llms.txt). # Enjab Auth (/docs/changelog/enjab-auth) Changes to [Sign in with Enjab Auth](/docs/sign-in-with-enjab), newest first. The current contract version is `2026.06.07a`. To bring a tool up to date, see [Update an existing tool](/docs/update-an-existing-tool/auth) or use the combined update prompt on the [Changelog overview](/docs/changelog). ## Super admin can change a user's email [#super-admin-can-change-a-users-email] `Enjab Auth` · 7 Jun 2026 **Changed.** A super admin can change any user's email (including their own) from the Enjab Auth admin. The stable user id (`sub`) never changes, so nothing keyed by `sub` breaks. Regular users cannot change their own email. **Apply.** Key your own records by `sub`, never by email. Mirror the email from `/userinfo` on every request (the same place you read `name`), so an admin email change reflects in your tool immediately. ## Stronger accounts: password policy, forced first change, one super admin [#stronger-accounts-password-policy-forced-first-change-one-super-admin] `Enjab Auth` · 7 Jun 2026 **Changed.** Enjab Auth now enforces account security centrally, none of which your tool implements: a strong password policy (12 to 24 characters, with an uppercase letter, a lowercase letter, a number, a symbol, and never the person's name), a forced password change on first login and after any admin reset (the user cannot continue until they set a new password, before two-factor), mandatory TOTP two-factor, and exactly one super admin per organization. Users can change their own password later from Settings on the hub. **Apply.** Nothing in your tool. All of this happens at auth.enjab.ae before a token is ever issued; your tool still just receives the user object. ## Disabled accounts are blocked at the door, is\_active removed [#disabled-accounts-are-blocked-at-the-door-is_active-removed] `Enjab Auth` · 7 Jun 2026 **Changed.** A disabled account is now stopped wherever it appears, with a plain "your account is disabled" message: at login (signed out, not let in), on the hub, and on the consent screen. Because a tool can therefore only ever receive an active user, the `is_active` field is removed from the user object (token response and `/userinfo`), the `EnjabUser` type, and the OpenID `claims_supported` list. **Apply.** Delete `is_active` from your `EnjabUser` type and any code that read it (it was always `true`). No other change is needed. ## Redirect host locked to the domain, path is editable [#redirect-host-locked-to-the-domain-path-is-editable] `Enjab Auth` · 7 Jun 2026 **Changed.** A tool's `redirect_uri` host is locked to its registered domain; you choose only the path (default `/api/auth/callback`). Changing a tool's domain in admin severs the old domain: it drops the old redirect from the allowlist and revokes that tool's sessions. **Apply.** Keep your callback on your registered `enjab.ae` domain. If an admin changes your tool's domain, update `ENJAB_REDIRECT_URI` to match and re-authenticate. ## Central sign-out revokes every issued token [#central-sign-out-revokes-every-issued-token] `Enjab Auth` · 6 Jun 2026 **Changed.** Signing out of Enjab Auth itself (the hub) now revokes every access token issued to every tool. Each tool loses access on its next `/userinfo` check. **Apply.** Re-validate every request with `getUser()` (it calls `/userinfo` fresh, `cache: "no-store"`). Never cache the user object beyond the request, or a signed-out person keeps working. ## /userinfo re-checks tool access on every call [#userinfo-re-checks-tool-access-on-every-call] `Enjab Auth` · 6 Jun 2026 **Changed.** `/userinfo` re-checks that the user still has access to your tool, not just that the account exists. A revoked role or grant returns `401` immediately, the token alone is never enough. **Apply.** Read the user fresh on each request and treat a `401` as signed-out (redirect to `/authorize`). Do not trust a cached user or cookie claims. ## Confirmed, per-tool sign-out [#confirmed-per-tool-sign-out] `Enjab Auth` · 6 Jun 2026 **Changed.** Sign-out is per-tool and confirmed on Enjab Auth. Your "Sign out" button navigates to `/api/auth/logout`, which sends the user to Enjab Auth's confirm page; confirming clears only your tool's session, never the central session. **Apply.** Use the logout route from the [Next.js reference](/docs/sign-in-with-enjab/nextjs). Never call a sign-out or clear the session yourself. ## Roles are sent only to role-aware tools [#roles-are-sent-only-to-role-aware-tools] `Enjab Auth` · 6 Jun 2026 **Changed.** `roles` and `is_super_admin` are included in the user object only if an admin marks your tool role-aware; otherwise the keys are absent. **Apply.** Treat access as binary. Do not gate features by role unless your tool is role-aware. See [Roles](/docs/sign-in-with-enjab/roles). ## User name plus greeting and consent screen [#user-name-plus-greeting-and-consent-screen] `Enjab Auth` · 6 Jun 2026 **Changed.** The user object includes the person's `name` (set in Enjab Auth admin). Enjab Auth shows a greeting and consent screen on the way in. **Apply.** Greet the user with `user.name`. Do not build a consent screen; Enjab Auth owns it. ## Drop-in dashboard shell wired to Enjab Auth [#drop-in-dashboard-shell-wired-to-enjab-auth] `Enjab Auth` · 6 Jun 2026 **Changed.** The reference now includes a complete dashboard shell: the Enjab UI sidebar wired to the signed-in user (real name, email, sign out, byline). **Apply.** Build your dashboard from the shell. See [Dashboard shell](/docs/sign-in-with-enjab/dashboard). ## Redirects restricted to enjab.ae [#redirects-restricted-to-enjabae] `Enjab Auth` · 6 Jun 2026 **Changed.** Enjab Auth refuses to send a code to any `redirect_uri` that is not https on a registered `enjab.ae` domain. This is what stops a random site from harvesting a login. **Apply.** Host your tool on an `enjab.ae` domain and register the exact redirect URI in admin. ## Sign in with Enjab Auth published (OAuth2 + RBAC) [#sign-in-with-enjab-auth-published-oauth2--rbac] `Enjab Auth` · 6 Jun 2026 **Changed.** Initial release: OAuth 2.0 authorization-code flow, the token response returns the user profile, `/userinfo` for fresh reads, and org-level role-based access control. **Apply.** Integrate per [Sign in with Enjab Auth](/docs/sign-in-with-enjab). # Enjab UI (/docs/changelog/enjab-ui) Changes to the [Enjab UI](/docs/enjab-ui) design system, newest first. To bring a tool up to date, see [Update an existing tool](/docs/update-an-existing-tool/ui) or use the combined update prompt on the [Changelog overview](/docs/changelog). ## 2026.06.07e: Buttons must not change size on hover or click [#20260607e-buttons-must-not-change-size-on-hover-or-click] `Enjab UI` · 7 Jun 2026 **Changed.** New rule: a button must never change size on hover, focus, or active/selected state (no layout shift). Change only color or background. The usual cause is a border toggling on/off on an auto-width button; keep a border in every state (`border border-transparent` where you don't want a visible one). See [Foundations](/docs/enjab-ui/foundations). **Apply.** Audit selected/active button states for border, padding, or scale changes that resize the box. ## 2026.06.07d: Small screens must have full functionality [#20260607d-small-screens-must-have-full-functionality] `Enjab UI` · 7 Jun 2026 **Changed.** Strengthened the responsive rule: a phone must do everything desktop can, exactly. Collapse or restyle to fit (stack, hamburger, icon buttons, scroll), but never hide, drop, or break a control on small screens. If a labelled button doesn't fit, make it an icon button. See [Foundations](/docs/enjab-ui/foundations). **Apply.** Audit each dashboard at 360px: every action reachable and working, tap targets at least `size-9`. ## 2026.06.07c: No em-dashes [#20260607c-no-em-dashes] `Enjab UI` · 7 Jun 2026 **Changed.** New rule: never use an em-dash (`—`) or en-dash (`–`) anywhere (UI copy, headings, code, comments, content). Use a comma, period, colon, parentheses, or a spaced hyphen instead. See [Foundations](/docs/enjab-ui/foundations). **Apply.** Sweep your copy and replace any `—` / `–`. ## 2026.06.07b: Custom 404 page required [#20260607b-custom-404-page-required] `Enjab UI` · 7 Jun 2026 **Changed.** New `@enjab-ui/not-found` and a new rule: every Enjab tool ships a custom `app/not-found.tsx` (the tool's gradient mark, `404`, **Page not found**, a back-home button, and the byline), never the framework default. See [Foundations](/docs/enjab-ui/foundations). **Apply.** Install it and edit the glyph + home link: `npx shadcn add @enjab-ui/not-found`. ## 2026.06.07a: Browser tab titles read "Page - Service" [#20260607a-browser-tab-titles-read-page---service] `Enjab UI` · 7 Jun 2026 **Changed.** New rule: every browser tab title reads `Page - Service` (for example `Sign in - Enjab Auth`), never the bare service name. Set a title template in the root layout (`title: { default, template: "%s - Service" }`) and give each page its own short title; client pages use a tiny server `layout.tsx`. See [Foundations](/docs/enjab-ui/foundations). **Apply.** Add the title template to your root layout and a `title` to each page. The home page can keep the bare service name. ## 2026.06.06l: Dashboards are responsive (phone-usable) [#20260606l-dashboards-are-responsive-phone-usable] `Enjab UI` · 6 Jun 2026 **Changed.** The shell is responsive now: `@enjab-ui/sidebar` collapses to a hamburger drawer below `lg`, `dashboard-shell` stacks, `page-header` tightens. `data-table` already scrolls. New rule: landing pages fully responsive; dashboards desktop-optimized but phone-usable (stack grids, `p-4 sm:p-6`), never desktop-only. **Apply.** Re-install: `npx shadcn add @enjab-ui/sidebar @enjab-ui/dashboard-shell @enjab-ui/page-header`. Make your content stack on small screens (`grid-cols-1 sm:grid-cols-2 ...`). ## 2026.06.06k: Tools show their OWN brand (Enjab logo moves to the byline) [#20260606k-tools-show-their-own-brand-enjab-logo-moves-to-the-byline] `Enjab UI` · 6 Jun 2026 **Changed.** New `@enjab-ui/app-mark`: a tool's own mark (the gradient favicon square + the tool name). The sidebar brand header now shows this (pass `appName` + `appIcon`), not the Enjab logo. The Enjab parent logo now appears only in the "an Enjab product" byline. Demo dashboard + landing updated. **Apply.** Install `@enjab-ui/app-mark` and pass `appName` + `appIcon` (the same glyph as your favicon) to `@enjab-ui/sidebar`. Re-install sidebar. Use the Enjab logo only in the byline. ## 2026.06.06j: Favicon and in-app icon must match [#20260606j-favicon-and-in-app-icon-must-match] `Enjab UI` · 6 Jun 2026 **Changed.** New rule: a tool's favicon and any in-app icon or brand mark must be the SAME image. The favicon glyph is canonical; mirror it into one small component so the tab icon and the on-screen mark cannot drift. Also: customize the favicon glyph with inline SVG (Lucide React components do not render in the icon generator). **Apply.** When you set a custom favicon glyph, render the identical mark (same gradient square + glyph) anywhere the app shows its own icon. ## 2026.06.06h: Favicon is now customizable (swap the letter or use an icon) [#20260606h-favicon-is-now-customizable-swap-the-letter-or-use-an-icon] `Enjab UI` · 6 Jun 2026 **Changed.** `@enjab-ui/favicon` is code-generated: `app/icon.tsx` + `app/apple-icon.tsx` render the Enjab gradient square with a glyph. Edit the `GLYPH` constant to use a different letter or paste an icon's inline SVG (Lucide React components do not render in the generator). PNG output, so it works in Safari with no separate `.ico`. Default stays "E". **Apply.** Re-install: `npx shadcn add @enjab-ui/favicon`. Delete any old `app/icon.svg` so it does not clash. To brand a tool, set `GLYPH` in `app/icon.tsx` + `app/apple-icon.tsx`. ## 2026.06.06g: Buttons show a loading state [#20260606g-buttons-show-a-loading-state] `Enjab UI` · 6 Jun 2026 **Changed.** Button gained a `loading` prop: it disables itself and shows a spinner while an async action runs. New rule: any button whose action takes time must use it (or `useFormStatus` for server-action forms), never stay clickable and silent. **Apply.** Re-install: `npx shadcn add @enjab-ui/button`. Use `` for slow actions. ## 2026.06.06f: Alert component (success / info / warning / danger) [#20260606f-alert-component-success--info--warning--danger] `Enjab UI` · 6 Jun 2026 **Changed.** New `@enjab-ui/alert`: one consistent in-page message box in four tones. Icon + title carry the meaning, so status is never color alone. Replaces hand-rolled colored notice boxes. **Apply.** Install: `npx shadcn add @enjab-ui/alert`. Use it for persistent, view-tied messages (form errors, warnings, success/info notices). Keep using a toast for transient feedback and `status-pill` for per-row status. ## 2026.06.06e: Tables are strict now (no more ugly tables) [#20260606e-tables-are-strict-now-no-more-ugly-tables] `Enjab UI` · 6 Jun 2026 **Changed.** `data-table` is fully self-styled: comfortable rows, mono uppercase headers, soft line separators, and a canvas hover, all enforced inside the component. It no longer inherits the cramped shadcn table defaults, so every Enjab tool's tables look identical to the demo dashboard. The base table primitive is Enjab-styled too (same padding, header, borders, hover), so even a raw table cannot drift. **Apply.** Re-install: `npx shadcn add @enjab-ui/data-table`. Build all tables from it (never hand-roll a table). ## 2026.06.06d: Account block is now identical everywhere [#20260606d-account-block-is-now-identical-everywhere] `Enjab UI` · 6 Jun 2026 **Changed.** `sidebar-footer` always renders the same two lines (display name + email + Sign out); derives the name from the email when none is given, so it cannot drift between tools. **Apply.** Re-install: `npx shadcn add @enjab-ui/sidebar-footer @enjab-ui/sidebar`. ## 2026.06.06c: Ready dashboard chrome (sidebar, shell, header, table) [#20260606c-ready-dashboard-chrome-sidebar-shell-header-table] `Enjab UI` · 6 Jun 2026 **Changed.** New `@enjab-ui/sidebar`, `dashboard-shell`, `page-header`, `data-table` so every tool's chrome is identical. `logo` now loads from the hosted URL; `sidebar-footer` handles email-only accounts. **Apply.** Build dashboards from these (do not hand-roll the sidebar, topbar, or table): `npx shadcn add @enjab-ui/sidebar @enjab-ui/dashboard-shell @enjab-ui/page-header @enjab-ui/data-table`. Re-install `logo` + `sidebar-footer` for the fixes. ## 2026.06.06b: Instant navigation with skeletons [#20260606b-instant-navigation-with-skeletons] `Enjab UI` · 6 Jun 2026 **Changed.** Screens open instantly with a Skeleton while data loads, never a blank or frozen page. **Apply.** Add a `loading.tsx` to every data-fetching route, with a skeleton that mirrors the layout (`animate-pulse` on `bg-muted`, no layout shift). ## 2026.06.06: Favicon needs a raster for Safari [#20260606-favicon-needs-a-raster-for-safari] `Enjab UI` · 6 Jun 2026 **Changed.** The favicon rule then required a raster, not just SVG, because Safari ignores SVG-only favicons (blank tab). Superseded by the code-generated PNG favicon (see 2026.06.06h). **Apply.** Move to the code-generated favicon: `npx shadcn add @enjab-ui/favicon`. ## 2026.06.05d: Favicon required [#20260605d-favicon-required] `Enjab UI` · 5 Jun 2026 **Changed.** New `@enjab-ui/favicon`. Every Enjab project must ship a favicon, no exceptions. **Apply.** Add it: `npx shadcn add @enjab-ui/favicon`. ## 2026.06.05c: Bigger Enjab byline logo [#20260605c-bigger-enjab-byline-logo] `Enjab UI` · 5 Jun 2026 **Changed.** The "an Enjab product" byline logo is larger for legibility. **Apply.** Re-install: `npx shadcn add @enjab-ui/enjab-byline` (and `@enjab-ui/sidebar-footer`). ## 2026.06.05b: Required sidebar footer + Enjab byline [#20260605b-required-sidebar-footer--enjab-byline] `Enjab UI` · 5 Jun 2026 **Changed.** New `@enjab-ui/sidebar-footer`: account block (avatar, email, sign out) plus the byline. New `@enjab-ui/enjab-byline`: standalone byline for dashboards with no sidebar. **Apply.** Every dashboard must show the byline. Sidebar: put `sidebar-footer` at the very bottom. No sidebar: place `enjab-byline` anywhere. ## 2026.06.05a: Typography: Inter Display headings [#20260605a-typography-inter-display-headings] `Enjab UI` · 5 Jun 2026 **Changed.** Headings use Inter Display, body Satoshi, data Fragment Mono (matches enjab.ae). Earlier builds wrongly used Satoshi for headings. **Apply.** Headings must use the heading font (`font-heading` or any `h1`-`h6`), never Satoshi. Re-apply the theme: `npx shadcn add https://ui.enjab.ae/r/theme.json`. ## 2026.06.04: Enjab UI registry published [#20260604-enjab-ui-registry-published] `Enjab UI` · 4 Jun 2026 **Changed.** Initial registry: `theme`, `button`, `status-pill`, `stat-card`, `logo`, `reveal`. **Apply.** Set up per the [Enjab UI](/docs/enjab-ui) docs. # Overview (/docs/changelog) The single, central changelog for the Enjab platform. Each entry says what changed and the exact action to adopt it. Pick the area you depend on, or use the one prompt below to bring a tool up to date on everything at once. ## Update everything, one prompt [#update-everything-one-prompt] Hand this to a coding agent to bring a tool up to date on both Enjab UI and Enjab Auth. It reads the central docs, works out what the project has not adopted, and applies only that. ```text Update this project to the latest Enjab standards (UI and Auth). Read https://developers.enjab.ae/llms.txt and the changelog at https://developers.enjab.ae/docs/changelog (both the Enjab UI and the Enjab Auth pages), then apply ONLY the changelog entries this project has not adopted yet. For Enjab UI: re-install changed components with `npx shadcn add @enjab-ui/` (overwrite) and re-apply the theme with `npx shadcn add https://ui.enjab.ae/r/theme.json` if tokens changed. For Enjab Auth: re-sync the drop-in reference files (lib/enjab-auth.ts, the callback and logout routes, proxy.ts) and re-check the integration rules (re-validate every request via /userinfo with cache: "no-store", no login UI of your own, confirmed per-tool sign-out, access is binary unless the tool is role-aware). Do not refactor or restyle anything else. Keep the change surface minimal. ``` Updates are not refactors. Apply only the entries the project has not adopted, and leave the rest of the codebase exactly as it is. For a focused update of one area, see [Update an existing tool](/docs/update-an-existing-tool). # Branding and favicon (/docs/enjab-ui/branding) ## Favicon setup [#favicon-setup] Every Enjab tool ships the favicon via `@enjab-ui/favicon`. Install it once: ```sh npx shadcn@latest add @enjab-ui/favicon ``` This adds `app/icon.tsx` and `app/apple-icon.tsx` (code-generated PNG icons: the Enjab gradient square plus a glyph). Safari renders these natively, so no separate `.ico` is needed. ### Edit the glyph [#edit-the-glyph] Open `app/icon.tsx` and find the `GLYPH` constant at the top. Replace it with a letter or paste an icon's inline SVG: ```tsx title="app/icon.tsx" const GLYPH = "E"; // Change to your letter, or paste an icon's raw markup ``` Paste the raw `` markup, not a Lucide React component. The icon generator runs at build time and cannot execute components. Do the same in `app/apple-icon.tsx`. Use the same glyph in both files. ### Delete old static icons [#delete-old-static-icons] If the project had a previous version with `app/icon.svg`, delete it so it doesn't clash with the code-generated icons. ## In-app brand mark [#in-app-brand-mark] Render the same glyph in your app wherever it shows its own square icon or logo (sign-in panels, header marks, dashboards). Mirror the glyph SVG into one small component so the tab icon and the on-screen mark stay identical. The glyph in `app/icon.tsx` is the source of truth. The same image (same gradient square, same glyph) MUST appear on screen wherever the app brands itself. Never let them drift. ## Sidebar brand and app-mark [#sidebar-brand-and-app-mark] Use the `@enjab-ui/app-mark` component in the sidebar and landing navbars. It combines the favicon square and the tool name: ```tsx import { AppMark } from '@enjab-ui/app-mark'; ``` Pass the same `glyph` you set in `app/icon.tsx`. The sidebar component (`@enjab-ui/sidebar`) accepts `appName` and `appIcon` props. Pass the glyph there too, and it renders the tool's own brand at the top, not the Enjab logo. ## Enjab parent logo [#enjab-parent-logo] The Enjab logo (`@enjab-ui/logo`) appears in EXACTLY ONE place: the "an Enjab product" byline. * Sidebar dashboards: use `@enjab-ui/sidebar-footer` (account block plus byline) pinned to the bottom. * Dashboards without a sidebar: place `@enjab-ui/enjab-byline` anywhere sensible. Never put the Enjab logo in the navbar or sidebar top. Never restyle these components (fixed sizes keep all dashboards identical). ## Branding rules summary [#branding-rules-summary] | What | Where | Rule | | ------------- | ------------------------------------------------------ | ---------------------------------------------------------------- | | Favicon | `app/icon.tsx` + `app/apple-icon.tsx` | Edit `GLYPH`, same value in both files, code-generated only | | In-app mark | Anywhere the app shows its own icon | Mirror the favicon glyph SVG into a component, keep it identical | | Sidebar brand | `@enjab-ui/app-mark` in sidebar/navbar | Pass tool name + glyph, never the Enjab logo | | Parent logo | `@enjab-ui/enjab-byline` or `@enjab-ui/sidebar-footer` | Required on every dashboard, exactly one place, never restyled | All Enjab tools use light mode only. Reference theme tokens (`bg-teal`, `text-navy`, `bg-success`) instead of hardcoding colors. # Components (/docs/enjab-ui/components) ## Component Catalog [#component-catalog] Install components via the namespace: ```sh npx shadcn@latest add @enjab-ui/ ``` Or directly without registry configuration: ```sh npx shadcn@latest add https://ui.enjab.ae/r/.json ``` | Component | Purpose | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **theme** | Enjab design tokens (colors, fonts, radius, sidebar, charts). Install first, it sets tokens every component depends on. | | **button** | Pill-shaped button with `navy` variant and `loading` prop that disables and shows a spinner during async actions. | | **status-pill** | Colored dot plus label. Status is never color alone. | | **alert** | In-page message box in four tones (success, info, warning, danger). Icon and title carry the meaning. Pass `tone`, `title`, and optional body or `action`. | | **stat-card** | KPI card with a mono value and trend delta. | | **logo** | The Enjab parent logo. Light backgrounds only. Use only in the "an Enjab product" byline, never as a tool's own brand. | | **app-mark** | A tool's own brand mark (the gradient favicon square and tool name). Use at the top of the sidebar and on landing navbars. Pass `name` and `glyph` (the same glyph as your favicon). | | **reveal** | Framer Motion scroll reveal. Landing pages only. | | **enjab-byline** | "an Enjab product" byline. Required on every dashboard. | | **sidebar-footer** | Required sidebar bottom. Account block (avatar, email, sign out) plus the Enjab byline. Keeps all dashboards identical. | | **favicon** | The Enjab favicon, code-generated (app/icon.tsx and app/apple-icon.tsx). Required in every project. Edit the GLYPH constant to swap the letter or paste an icon's inline SVG, same gradient and shape. | | **sidebar** | The dashboard sidebar. The brand header shows the tool's own mark and name (pass `appName` and `appIcon`, the same glyph as your favicon), not the Enjab logo. Grouped nav with active state, account footer, and "an Enjab product" byline. Plugs into Enjab Auth: pass the getUser() result as `user` (name and email) and a sign-out handler. | | **dashboard-shell** | The dashboard layout (fixed sidebar and scrollable main column). Compose with sidebar and page-header. | | **page-header** | The dashboard topbar (title, subtitle, action slot). Aligns with the sidebar brand header. | | **data-table** | The Enjab data table, fully self-styled (bordered card, mono uppercase headers, comfortable rows with soft separators and canvas hover, optional title, empty state). Enforced look, identical everywhere. Pass columns and rows; cells stay flexible. Never hand-roll the table. | ## Feedback: Three Components [#feedback-three-components] Enjab has a specific feedback strategy. Never hand-roll colored message boxes. * **alert** (`@enjab-ui/alert`): Use for a persistent message tied to the current view (form validation errors, a warning before a destructive action, a success or informational notice that should stay). Pass `tone` (success, info, warning, or danger), a `title`, and optional body or `action`. Icon and title carry meaning. Tone mapping: success = it worked / safe, info = neutral context, warning = caution / needs attention, danger = error / blocked. * **toast** (via Sonner): Use for transient "it worked / it failed" feedback that fires right after an action and auto-dismisses. Does not block the view. * **status-pill** (`@enjab-ui/status-pill`): Use for the status of a single row, item, or field. Colored dot plus label. Status is never color alone, always pair with text. ## Loading Actions [#loading-actions] Any button that triggers a slow action (network request, server action, async submit) must give feedback: disable the button and show a spinner while it runs. Use the button's `loading` prop: ```tsx ``` The button disables and shows a spinner automatically. For a form posting to a server action, drive it with `useFormStatus`: ```tsx const { pending } = useFormStatus() return ``` This prevents double-clicks and signals to the user that something is happening. Never leave a button clickable and silent during a slow action. ## Installation [#installation] Install `theme` first. It sets the Enjab design tokens that every component depends on: ```sh npx shadcn@latest add @enjab-ui/theme ``` Then add other components as needed: ```sh npx shadcn@latest add @enjab-ui/button npx shadcn@latest add @enjab-ui/data-table ``` ## Dashboard Shell [#dashboard-shell] Build the dashboard chrome from ready components: ```tsx {/* content */} ``` Never hand-roll the sidebar, topbar, or table. Only the cell content and page content are custom. # Dashboard chrome (/docs/enjab-ui/dashboard-chrome) The dashboard chrome (sidebar, topbar, data table) is built from ready-made Enjab UI components. Never hand-roll these core pieces, reimplementing them causes visual drift across projects. Only the page content is custom. ## Components [#components] Build the entire chrome from these four: * **@enjab-ui/dashboard-shell**: The dashboard layout. Fixed sidebar on the left, scrollable main column. Compose with sidebar and page-header. * **@enjab-ui/sidebar**: The left navigation panel. Shows the tool's own brand (via `appName` + `appIcon`, the same glyph as your favicon), not the Enjab logo. Grouped nav items with active state tracking. Account footer (avatar, email, sign-out) plus the "an Enjab product" byline pinned to the bottom. Plugs into Enjab Auth: pass `user` (name + email from getUser()) and a sign-out handler. * **@enjab-ui/page-header**: The topbar. Title, subtitle, and an action slot. Aligns with the sidebar brand header (same height, same bottom border). * **@enjab-ui/data-table**: The dashboard table. Fully self-styled (bordered card, mono uppercase headers, comfortable rows, soft separators, canvas hover). Pass columns and rows; cell content stays flexible. Optional title and empty state included. ## Assembly pattern [#assembly-pattern] ```tsx title="app/dashboard/layout.tsx" import { DashboardShell } from "@enjab-ui/dashboard-shell" import { Sidebar } from "@enjab-ui/sidebar" import { PageHeader } from "@enjab-ui/page-header" export default function DashboardLayout({ children }) { const user = await getUser() // Enjab Auth return ( } user={user} onSignOut={handleSignOut} >
Action} /> {children}
) } ``` ## App mark and favicon [#app-mark-and-favicon] The `appIcon` passed to sidebar must match your favicon. Store the glyph in one component to keep them in sync: ```tsx title="app/components/app-glyph.tsx" export function AppGlyph() { return ( {/* Your icon SVG markup */} ) } ``` Use it in both the sidebar and favicon: ```tsx title="app/icon.tsx" import { AppGlyph } from "./components/app-glyph" const GLYPH = // ... icon generation code ``` The Enjab logo (@enjab-ui/logo) appears in exactly one place: the "an Enjab product" byline at the bottom of the sidebar. Never put it in the navbar or sidebar top. ## Instant navigation [#instant-navigation] Every route that fetches data must ship a loading.tsx skeleton that mirrors the real page. Navigation must feel instant even while data loads. Never show a blank or frozen screen. ```tsx title="app/dashboard/loading.tsx" export default function DashboardLoading() { return (
) } ``` Skeletons use `animate-pulse` on `bg-muted` with the same layout as the real content. No layout shift when the real page loads. ## Data table [#data-table] Never build your own table. Use @enjab-ui/data-table with columns and rows: ```tsx title="app/dashboard/patients/table.tsx" import { DataTable } from "@enjab-ui/data-table" export function PatientsTable({ patients }) { const columns = [ { header: "Name", accessor: "name" }, { header: "ID", accessor: "id", className: "font-data" }, { header: "Status", accessor: "status" }, ] return ( ) } ``` The table handles styling, borders, hover states, and empty states. Cell content is yours to customize, but the outer structure is enforced across all Enjab dashboards. ## Responsive behavior [#responsive-behavior] The chrome is already phone-friendly: * **Sidebar**: Collapses to a hamburger drawer below `lg` breakpoint. * **Dashboard shell**: Stacks on small screens. * **Data table**: Scrolls horizontally inside its card, no data lost. For your own page content, stack grids to one column on small screens (`grid-cols-1 sm:grid-cols-2 lg:grid-cols-4`), use responsive padding (`p-4 sm:p-6`), and avoid fixed pixel widths that overflow a phone. ## Authentication wiring [#authentication-wiring] The sidebar plugs into Enjab Auth. Pass the getUser() result and a sign-out handler: See [Sign in with Enjab Auth](/docs/sign-in-with-enjab/dashboard) for the complete getUser() setup and session handling. ```tsx const user = await getUser() // Returns { name, email, id } { "use server" await signOut() }} /> ``` ## Rules [#rules] 1. Never hand-roll the sidebar, topbar, or table. Use the components as-is. 2. The brand header and topbar must align (same height, same bottom border). 3. Every dashboard shows the "an Enjab product" byline at the bottom of the sidebar (via sidebar-footer, included in the sidebar component). 4. Navigation items inside each group need spacing between them. Wrap items in `space-y-1` or `space-y-1.5`. 5. Every data-fetching route ships a loading.tsx skeleton. Never leave the page blank while data loads. 6. The app's own brand mark (favicon + sidebar header) must be identical. Never let them drift. # Foundations (/docs/enjab-ui/foundations) The rules below define how to build any Enjab interface. Reference them in every project. ## Theme & Colors [#theme--colors] Enjab uses a light-only, carefully calibrated color system with semantic meaning. **Light mode only.** Never add a dark theme or `dark:` variants. **Primary and depth colors:** * Teal (#057C8B) is primary. * Navy (#1B3766) is depth. Always reference design tokens (`bg-teal`, `text-navy`, `bg-success`, `text-muted-foreground`, etc.), never hardcode hex values. **Status colors mean status, never used alone:** * Success #42AF48 * Warning #E0A100 * Danger #D64545 * Info #0099FF Always pair status colors with text or an icon. Never rely on color alone to communicate meaning. ## Typography [#typography] The three-font system: * **Headings:** Inter Display optical size. Never put a heading in Satoshi. * **Body and UI:** Satoshi (400, 500, 700, 900 weights). * **Data:** Fragment Mono for numbers, IDs, times, and code. Use the `font-data` class. CSS variables: `--font-heading` (Inter), `--font-sans` (Satoshi), `--font-mono` (Fragment Mono). ## Spacing [#spacing] 4px scale. Never let two sections, cards, or nav items touch. Default gap between blocks is **20px** (Tailwind `gap-5` or `space-y-5`). ## Feedback & Status [#feedback--status] There are three feedback patterns. Never hand-roll a colored box: 1. **Alerts** (@enjab-ui/alert): persistent messages tied to the current view. Use tones: `success` (it worked, safe), `info` (neutral context), `warning` (caution, needs attention), `danger` (error, blocked). Always keep the icon and a worded title. 2. **Toasts** (sonner): transient "it worked" / "it failed" feedback right after an action. 3. **Status pills** (@enjab-ui/status-pill): the status of a single row or item. A colored dot plus label. ## Buttons & Loading States [#buttons--loading-states] Buttons that trigger a slow action (network request, server action, async submit) must give feedback: disable the button and show a spinner while it runs. Use the button's `loading` prop: ```tsx ``` For a form posting to a server action, drive it with `useFormStatus()`: ```tsx const { pending } = useFormStatus(); ``` Never leave a button clickable and silent during a slow action. A button must **never change size** on hover, focus, or active/selected state, that shifts the layout around it. Change only color or background between states, never the box. The common trap: a state toggles a `border` on or off on an auto-width button, which makes it 1px wider or narrower per side. Keep a border in every state, using `border border-transparent` where you don't want a visible one: ```tsx // selected vs not, same size (the selected state keeps a transparent border) className={active ? "border border-transparent bg-teal-tint text-teal" : "border text-muted-foreground"} ``` Do not change padding, border-width, or use `scale` on hover/active either. ## Motion & Animation [#motion--animation] Animations belong on landing and marketing pages only, implemented with Framer Motion. Dashboards and internal tools stay still (instant color and state changes only) for usability. ## Responsive Design [#responsive-design] **Landing / marketing pages:** fully responsive, mobile-first, look right down to 360px. **Dashboards and internal tools:** desktop-optimized but must stay phone-usable, never desktop-only or broken. The shared chrome already handles most of it: `@enjab-ui/sidebar` collapses to a hamburger drawer below `lg`, `@enjab-ui/dashboard-shell` stacks, and `@enjab-ui/data-table` scrolls horizontally inside its card. **Same functionality on every screen.** A small screen must do everything a desktop can, exactly. Collapse or restyle the layout to fit (stack grids, a hamburger drawer, icon buttons, horizontal scroll), but never hide, drop, or break a control on a phone. If a labelled button doesn't fit, make it an icon button, do not remove it or bury it behind something broken. For your own content on dashboards: stack grids to one column on small screens (`grid-cols-1 sm:grid-cols-2 lg:grid-cols-4`), shrink page padding (`p-4 sm:p-6`), never set fixed pixel widths that overflow a phone, keep tap targets comfortable (at least `size-9`). Do NOT build a separate mobile design, and never leave a feature out of the phone layout. ## Logo & Branding [#logo--branding] * **Enjab logo** (@enjab-ui/logo): light backgrounds only. No reversed or white logo exists. * **Tool brand mark** (@enjab-ui/app-mark): a tool's own square icon/logo. Use at the top of the sidebar and on landing navbars. Pass the same glyph as your favicon. * **Favicon** (@enjab-ui/favicon): code-generated PNG icons via `app/icon.tsx` + `app/apple-icon.tsx`. The Enjab gradient square plus a glyph (default letter "E"). Edit the GLYPH constant to brand a tool with a different letter or an icon's inline SVG. Delete any old static `app/icon.svg` so it does not clash. * **Favicon and in-app mark must be identical.** The favicon glyph is canonical. Wherever the app shows its own square icon/logo, render the same mark, same gradient square, same glyph. Mirror the glyph SVG into one small component so they can never drift. The Enjab parent logo appears in exactly one place: the "an Enjab product" byline. Never put it in the navbar or sidebar top. ## Navigation & Instant Loading [#navigation--instant-loading] Navigation must be instant. Every data-fetching route ships a Next.js `loading.tsx` skeleton that mirrors the page and shows immediately while data loads. Never a blank or frozen screen. Skeletons use `animate-pulse` on `bg-muted`, sized to match the real content with no layout shift. ## App Shell & Layout [#app-shell--layout] Build the dashboard chrome from ready components, never hand-roll the sidebar, topbar, or table: * `@enjab-ui/dashboard-shell`: the dashboard layout (fixed sidebar + scrollable main column). * `@enjab-ui/sidebar`: the dashboard sidebar (grouped nav with active state, account footer, byline). * `@enjab-ui/page-header`: the dashboard topbar (title, subtitle, action slot). * `@enjab-ui/data-table`: fully self-styled data table (bordered card, mono uppercase headers, comfortable rows). The sidebar brand header and topbar share the same height (`h-15`) and bottom border, so the top reads as one connected bar. The sidebar-footer (account block + byline) pins to the very bottom. Every dashboard shows the "an Enjab product" byline: sidebar dashboards use `@enjab-ui/sidebar-footer` pinned to the bottom; no-sidebar dashboards place `@enjab-ui/enjab-byline` anywhere sensible. Never restyle these, fixed sizes keep all dashboards identical. ## Browser tab titles [#browser-tab-titles] Every browser tab title reads `Page - Service`, never the bare service name (for example `Sign in - Enjab Auth`, not just `Enjab Auth`). Set a title template once in the root layout, then give each page its own short title: ```tsx title="app/layout.tsx" export const metadata = { title: { default: "Your Tool", template: "%s - Your Tool" }, }; ``` ```tsx title="app/settings/page.tsx" export const metadata = { title: "Settings" }; // tab reads: Settings - Your Tool ``` The home page falls back to the bare service name (the `default`); every other page sets a title. A client component page (`"use client"`) can't export `metadata`, so give it a tiny server `layout.tsx` that does. ## 404 page [#404-page] Every Enjab tool ships a custom `app/not-found.tsx`, never the framework default. Keep the same shape so all tools' 404s look alike: the tool's gradient mark, a `404` label, a **Page not found** heading, a short line, a button back home, and the "an Enjab product" byline. Install the ready one and edit its glyph + home link: ```bash npx shadcn add @enjab-ui/not-found ``` ## Language & Direction [#language--direction] English, left to right. ## Punctuation [#punctuation] Never use an em-dash (`—`) or en-dash (`–`), anywhere: UI copy, headings, code, comments, and content all included. Use a comma, period, colon, parentheses, or a spaced hyphen instead. ## Tech Stack [#tech-stack] * Next.js (App Router) * Tailwind CSS * shadcn/ui * Charts: Recharts * Icons: Lucide * Motion: Framer Motion (landing only) # Overview (/docs/enjab-ui) Enjab UI is the design system for Enjab Medical Centre, packaged as a shadcn registry. Hand an agent [the spec file](https://developers.enjab.ae/llms.txt) and it can theme any project to match Enjab with no other input. ## Registry endpoints [#registry-endpoints] * **Base URL:** `https://ui.enjab.ae/r/{name}.json` * **Namespace:** `@enjab-ui` * **Full index:** `https://ui.enjab.ae/r/registry.json` These docs are the single source of truth for how to use Enjab UI. To keep an existing project current, see [Update an existing tool](/docs/update-an-existing-tool). ## Quick start [#quick-start] Install the theme first (it sets the design tokens all components depend on): ```bash npx shadcn@latest add https://ui.enjab.ae/r/theme.json npx shadcn@latest add https://ui.enjab.ae/r/button.json ``` Or use the namespaced registry. Add this to `components.json`: ```json title="components.json" { "registries": { "@enjab-ui": "https://ui.enjab.ae/r/{name}.json" } } ``` Then install via namespace: ```bash npx shadcn@latest add @enjab-ui/button ``` ## Audience [#audience] Enjab builds employee-facing tools (dashboards, internal consoles, automation), each in its own repository, often by different developers and agents. This registry keeps them visually identical. ## Next steps [#next-steps] # Setup (/docs/enjab-ui/setup) Enjab UI is a shadcn/ui registry. Install the theme first (it sets design tokens every component depends on), then add components. ## Installation [#installation] Choose one approach: No configuration required. Install components directly from the registry: ```bash npx shadcn@latest add https://ui.enjab.ae/r/theme.json npx shadcn@latest add https://ui.enjab.ae/r/button.json ``` Replace `button.json` with any component name from the registry. Add the registry to your `components.json`: ```json title="components.json" { "registries": { "@enjab-ui": "https://ui.enjab.ae/r/{name}.json" } } ``` Then install components with the namespace: ```bash npx shadcn@latest add @enjab-ui/button ``` The `theme` component sets Enjab design tokens (colors, spacing, radius, sidebar, charts). Every other component depends on it. Add it before anything else. ## Fonts [#fonts] Load these three font families. The theme exposes them as CSS variables that components reference. | Font | Usage | Source | | ---------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Inter (Display optical size) | Headings | Google Fonts or `next/font` | | Satoshi | Body and UI text | [https://api.fontshare.com/v2/css?f\[\]=satoshi@400,500,700,900\&display=swap](https://api.fontshare.com/v2/css?f\[]=satoshi@400,500,700,900\&display=swap) | | Fragment Mono | Numbers, IDs, times | Google Fonts | In your stylesheets or layout, declare these fonts. The theme will reference them via: * `--font-heading` (Inter) * `--font-sans` (Satoshi) * `--font-mono` (Fragment Mono) Use the corresponding CSS class on text that needs a specific font: `font-heading` for titles, no class for body (Satoshi is default), `font-data` for data/numbers. ## Tech Stack [#tech-stack] Next.js (App Router) + Tailwind CSS + shadcn/ui. Charts via Recharts. Icons via Lucide. # Dashboard shell (/docs/sign-in-with-enjab/dashboard) Install the Enjab Auth dashboard chrome, sidebar, and page header components: ``` npx shadcn add @enjab-ui/dashboard-shell @enjab-ui/sidebar @enjab-ui/page-header ``` ## AppShell component [#appshell-component] Create `components/app-shell.tsx` to wrap your dashboard. This component connects your tool's navigation and icon to the shell. Edit only the NAV array and GLYPH; the rest stays as written. ```tsx title="components/app-shell.tsx" "use client"; import { LayoutDashboard, Settings } from "lucide-react"; // your tool's icons import { DashboardShell } from "@/components/enjab/dashboard-shell"; import { Sidebar, type SidebarNavGroup } from "@/components/enjab/sidebar"; import type { EnjabUser } from "@/lib/enjab-auth"; // Your tool's nav. Edit this; leave the rest. const NAV: SidebarNavGroup[] = [ { items: [ { href: "/dashboard", label: "Dashboard", icon: LayoutDashboard }, { href: "/settings", label: "Settings", icon: Settings }, ]}, ]; // Your tool's glyph. Use the SAME inline SVG you put in app/icon.tsx, so the sidebar // mark and the favicon are identical. (Don't use a Lucide React component here if you // also use it in app/icon.tsx, mirror the raw so both match.) const GLYPH = ( {/* ...your favicon's paths... */} ); export function AppShell({ user, children }: { user: EnjabUser; children: React.ReactNode }) { return ( { window.location.href = "/api/auth/logout"; }} /> } > {children} ); } ``` The sidebar top shows your tool's mark (its favicon square and name), never the Enjab logo. The Enjab logo appears only in the "an Enjab product" byline at the bottom of the sidebar. Keep `appIcon` identical to your favicon's GLYPH. ## Dashboard layout [#dashboard-layout] Create `app/dashboard/layout.tsx` as a server component that fetches the signed-in user and renders the AppShell. If the user is not signed in, redirect to Enjab Auth's login. ```tsx title="app/dashboard/layout.tsx" import { redirect } from "next/navigation"; import { getUser, loginUrl } from "@/lib/enjab-auth"; import { AppShell } from "@/components/app-shell"; export default async function DashboardLayout({ children }: { children: React.ReactNode }) { const user = await getUser(); if (!user) redirect(loginUrl("/dashboard")); return {children}; } ``` ## Page header [#page-header] Each page inside the dashboard renders its own topbar using the PageHeader component: ```tsx import { PageHeader } from "@/components/enjab/page-header"; export default function Page() { return ( <>
{/* your content */}
); } ``` ## Sign-out flow [#sign-out-flow] The sidebar footer shows the real signed-in person, and its Sign out button goes to Enjab Auth's confirm page first (so nothing happens on an accidental click), then clears only this tool's session and shows "You're signed out of ``". The central Enjab Auth session stays, so the user remains signed in to Enjab Auth and other tools and can jump back into this tool without logging in again. # The flow and endpoints (/docs/sign-in-with-enjab/flow) ## The flow [#the-flow] Follow these 5 steps to complete a sign-in: ### User hits your tool signed out [#user-hits-your-tool-signed-out] A signed-out user visits any page on your tool. Your middleware detects no session cookie and immediately redirects (no login button, no login page) to Enjab Auth. ### Redirect to /authorize [#redirect-to-authorize] Send the user to: ``` https://auth.enjab.ae/authorize?client_id=...&redirect_uri=...&response_type=code&state= ``` All parameters are required: * `client_id`: your tool's ID (from Enjab Auth admin) * `redirect_uri`: must be registered on your tool's page (default `https:///api/auth/callback`) * `response_type=code` * `state`: the page the user was trying to reach; Enjab Auth returns it unchanged Optional: add PKCE `code_challenge` (S256) if your tool supports it. ### Enjab Auth authenticates and authorizes [#enjab-auth-authenticates-and-authorizes] Enjab Auth asks for a password + two-factor (TOTP) if needed. It checks the user's access: if an admin gave them a role or direct grant for your tool, they pass. If not, Enjab Auth shows "no access" and never returns to your tool. If authorized, Enjab Auth shows a greeting + consent screen ("Welcome, Sara. You have access to ``. `` will receive your name, email, role..."), then a Continue button. ### Enjab Auth redirects back with a code [#enjab-auth-redirects-back-with-a-code] On continue, Enjab Auth sends the browser back: ``` https://?code=&state= ``` The code is single-use and valid for minutes. ### Exchange code for access token (server-to-server) [#exchange-code-for-access-token-server-to-server] Your server makes a POST request to swap the code for an access token: ```bash title="Example: POST /api/oauth/token" curl -X POST https://auth.enjab.ae/api/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code&code=&redirect_uri=&client_id=&client_secret=" ``` You get back the user's profile and an access token in one response. Store the token in a secure, httpOnly cookie. The user is now signed in to your tool. On later requests, fetch the fresh user by calling `/api/oauth/userinfo` with the token (see Endpoints below). ## Redirect instantly (no login button) [#redirect-instantly-no-login-button] Do NOT build a "Sign in with Enjab Auth" button or any login screen. The moment a signed-out user touches ANY page of your tool, your middleware redirects them straight to `/authorize`. Enjab Auth shows the login and the greeting screen. Your tool never builds a login UI. On each request, always call `/api/oauth/userinfo` with the stored access token to get the fresh user. Do NOT cache the user object beyond the request or trust your session cookie's claims. This is the only way central sign-out, account disable, and role changes reach your tool. Enjab Auth cannot modify your cookie, so if you cache the user, a signed-out or disabled person keeps working. ## Endpoints [#endpoints] ### GET /authorize [#get-authorize] Starts the OAuth flow. Redirect the user's browser here. **Query parameters:** * `client_id` (required): your tool's ID * `redirect_uri` (required): must be registered in Enjab Auth admin * `response_type=code` (required) * `state` (required): any string (typically the page the user was viewing); returned unchanged * `code_challenge` (optional): PKCE code challenge (S256) **Response:** Browser redirect to `redirect_uri?code=&state=` *** ### POST /api/oauth/token [#post-apioauthtoken] Server-to-server only. Swap the authorization code for an access token and user profile. **Request body (form-encoded or JSON):** * `grant_type=authorization_code` (required) * `code` (required): from the /authorize callback * `redirect_uri` (required): must match the one sent to /authorize * `client_id` (required) * `client_secret` (required): server only, never expose to the browser * `code_verifier` (optional): PKCE verifier, if you sent a code\_challenge **Response:** ```json { "access_token": "enjat_...", "token_type": "Bearer", "expires_in": 28800, "user": { "sub": "0f3c...", "email": "sara@enjab.ae", "name": "Sara Ahmed", "roles": ["reception"], "is_super_admin": false } } ``` Note: `roles` and `is_super_admin` are only present if your tool is marked role-aware in Enjab Auth admin. *** ### GET /api/oauth/userinfo [#get-apioauthuserinfo] Get the fresh user profile. Call this on every request to detect sign-out, account disable, and role changes. **Request header:** ``` Authorization: Bearer ``` **Response:** The fresh `user` object (same shape as the token response above). # Overview (/docs/sign-in-with-enjab) Sign in with Enjab Auth is the central login for every Enjab tool. It is an OAuth 2.0 authorization code flow (with an extra feature: the token response includes the user's profile and roles, so you usually need no extra call). * **Issuer:** [https://auth.enjab.ae](https://auth.enjab.ae) * **Discovery:** [https://auth.enjab.ae/.well-known/openid-configuration](https://auth.enjab.ae/.well-known/openid-configuration) ## What Enjab Auth does (and does not) [#what-enjab-auth-does-and-does-not] Enjab Auth handles all authentication: passwords, two-factor authentication (TOTP), sessions, lockout. Your tool never sees a password or a 2FA code and never builds a login screen. It enforces a strong password policy (12 to 24 characters, mixed character classes, never the person's name), a forced password change on first login (and after an admin reset) before anything else, and mandatory two-factor. Users manage their own password from Settings on the hub. There is exactly one super admin per organization. Enjab Auth handles authorization at the door: a user can only complete "Sign in with Enjab Auth" for your tool if an admin gave them access (via a role or a direct grant). If they cannot, Enjab Auth shows the "no access" screen and never returns to your tool. Enjab Auth shows the user a greeting plus consent screen on the way in ("Welcome, Sara. You have access to ``. `` will receive your name, email, role..."), then a Continue button sends them back to you. Your tool does not build this; Enjab Auth owns it. Sign-out is confirmed on Enjab Auth and per-tool. Your "Sign out" button just navigates to your `/api/auth/logout` route; Enjab Auth shows a confirm page first (so an accidental click never logs anyone out), and confirming drops only your tool, never the central Enjab Auth session. Signing out of Enjab Auth itself (from the Enjab Auth hub) ends everything: it revokes every access token issued to you, so each tool loses access on its next `/userinfo` check. Never call signOut or clear the session yourself. It tells your tool who the user is. By default it does NOT send roles and you must NOT gate by role: if Enjab Auth let the user in, that is the whole decision. Roles are sent only if an admin marks your tool "role-aware" (see Roles). It stores only identity plus access. It does NOT store your app's data. Key all your own records by the user's `sub` (the stable Enjab Auth user id). Your tool builds no login button and no login page. The instant a signed-out user touches any page, redirect straight to Enjab Auth (see the Flow guide). On each request, read the user with `getUser()`, which calls `/userinfo` fresh (cache: "no-store"). Do NOT cache the user object beyond the request or trust your cookie's claims. This is the ONLY way central sign-out, account disable, and role changes reach your tool. Enjab Auth cannot touch your cookie, so if you cache the user, a signed-out or disabled person keeps working. ## The user object your tool receives [#the-user-object-your-tool-receives] ```json { "sub": "0f3c...", // stable Enjab Auth user id - your foreign key for this user "email": "sara@enjab.ae", "name": "Sara Ahmed", // the person's name, set in Enjab Auth admin - greet them with it "roles": ["reception"], // ONLY present if your tool is role-aware; otherwise omitted "is_super_admin": false // ONLY present if your tool is role-aware; otherwise omitted } ``` There is no `is_active` flag: Enjab Auth blocks a disabled account at the door, so any user your tool receives is active. Most tools never see `roles` or `is_super_admin` at all (the keys are absent) and should not branch on them. Only role-aware tools receive the org's authorization details; a super admin has `is_super_admin: true` and access to every tool. ## Most tools never need roles [#most-tools-never-need-roles] Access is binary. An admin gives a user access to your tool (via a role or a direct grant); Enjab Auth enforces that at the door. If the user reaches your tool, they are allowed to use it. That is enough for almost every tool. Do not gate features by role. Turn on roles ONLY when your tool has functions that not every authorized user should reach (for example, an admin-only settings page inside the tool). Then an Enjab Auth admin flips your tool to "role-aware" (Admin -> Tools -> your tool -> Role-aware), and from then on the `user` object includes `roles` (global keys like `reception`, `doctor`). You gate with `user.roles?.includes("reception")`. If the flag is off, `roles` is omitted. # Next.js reference (/docs/sign-in-with-enjab/nextjs) Implement Enjab Auth in a Next.js App Router project using these four files. Each handles part of the OAuth flow: client initialization, the callback, sign-out, and middleware to redirect unsigned users. ## Setup [#setup] First, add environment variables to `.env.local`: ``` ENJAB_CLIENT_ID=enjci_... ENJAB_CLIENT_SECRET=enjsk_... ENJAB_REDIRECT_URI=https://yourtool.enjab.ae/api/auth/callback ENJAB_ISSUER=https://auth.enjab.ae ``` Then add these four files to your project. ## Core auth helpers [#core-auth-helpers] Helpers to fetch the user, generate the login URL, and exchange the auth code for a token. ```ts title="lib/enjab-auth.ts" import "server-only"; import { cookies } from "next/headers"; import { cache } from "react"; const ISSUER = process.env.ENJAB_ISSUER ?? "https://auth.enjab.ae"; export const SESSION_COOKIE = "enjab_session"; export type EnjabUser = { sub: string; email: string; name: string; roles?: string[]; // present only if the tool is role-aware is_super_admin?: boolean; // present only if the tool is role-aware }; export function loginUrl(returnPath = "/") { const u = new URL(ISSUER + "/authorize"); u.searchParams.set("client_id", process.env.ENJAB_CLIENT_ID!); u.searchParams.set("redirect_uri", process.env.ENJAB_REDIRECT_URI!); u.searchParams.set("response_type", "code"); u.searchParams.set("state", returnPath); return u.toString(); } // Cached per request. Returns null if signed out or the token is no longer valid. export const getUser = cache(async (): Promise => { const token = (await cookies()).get(SESSION_COOKIE)?.value; if (!token) return null; const res = await fetch(ISSUER + "/api/oauth/userinfo", { headers: { Authorization: `Bearer ${token}` }, cache: "no-store", }); return res.ok ? ((await res.json()) as EnjabUser) : null; }); export async function exchangeCode(code: string): Promise { const res = await fetch(ISSUER + "/api/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: process.env.ENJAB_REDIRECT_URI!, client_id: process.env.ENJAB_CLIENT_ID!, client_secret: process.env.ENJAB_CLIENT_SECRET!, }), cache: "no-store", }); if (!res.ok) return null; return (await res.json()).access_token as string; } ``` ## OAuth callback [#oauth-callback] Handles the redirect from Enjab Auth after sign-in. Exchanges the auth code for a token, stores it in a secure cookie, and returns the user to their requested page. ```ts title="app/api/auth/callback/route.ts" import { NextResponse, type NextRequest } from "next/server"; import { exchangeCode, SESSION_COOKIE } from "@/lib/enjab-auth"; export async function GET(req: NextRequest) { const code = req.nextUrl.searchParams.get("code"); const state = req.nextUrl.searchParams.get("state") || "/"; if (!code) return NextResponse.redirect(new URL("/", req.url)); const token = await exchangeCode(code); if (!token) return NextResponse.redirect(new URL("/", req.url)); // Only ever return to a local path. const dest = state.startsWith("/") && !state.startsWith("//") ? state : "/"; const res = NextResponse.redirect(new URL(dest, req.url)); res.cookies.set(SESSION_COOKIE, token, { httpOnly: true, secure: true, sameSite: "lax", path: "/", maxAge: 60 * 60 * 8, }); return res; } ``` ## Sign-out [#sign-out] Redirects to Enjab Auth's sign-out confirmation page first, so an accidental click never logs the user out. Only signs out of this tool, not the central Enjab Auth session. ```ts title="app/api/auth/logout/route.ts" import { NextResponse, type NextRequest } from "next/server"; import { SESSION_COOKIE } from "@/lib/enjab-auth"; const ISSUER = process.env.ENJAB_ISSUER ?? "https://auth.enjab.ae"; // Sign-out ALWAYS goes through Enjab Auth's confirm page first (so a stray/accidental click // can't log anyone out), and clears ONLY this tool's session, never the central Enjab Auth // session. Flow: the Sign out button navigates here -> we send the user to Enjab Auth's // /logout confirm -> on confirm, Enjab Auth sends them back here with ?confirmed=1 -> we // clear our cookie and show the "You're signed out of " page. Do NOT clear the // cookie or call any signout before ?confirmed=1, or you'll skip the confirmation. export async function GET(req: NextRequest) { if (req.nextUrl.searchParams.get("confirmed") === "1") { const res = NextResponse.redirect(`${ISSUER}/logged-out?client_id=${process.env.ENJAB_CLIENT_ID}`); res.cookies.delete(SESSION_COOKIE); return res; } return NextResponse.redirect(`${ISSUER}/logout?client_id=${process.env.ENJAB_CLIENT_ID}`); } ``` ## Middleware [#middleware] Redirects unsigned users straight to Enjab Auth. No login button, no login page. The moment an unsigned user touches any route, they go to Enjab Auth's login and consent flow. ```ts title="proxy.ts" import { NextResponse, type NextRequest } from "next/server"; const ISSUER = process.env.ENJAB_ISSUER ?? "https://auth.enjab.ae"; export function proxy(req: NextRequest) { const { pathname } = req.nextUrl; if (pathname.startsWith("/api/auth")) return NextResponse.next(); // callback/logout are public if (req.cookies.has("enjab_session")) return NextResponse.next(); const u = new URL(ISSUER + "/authorize"); u.searchParams.set("client_id", process.env.ENJAB_CLIENT_ID!); u.searchParams.set("redirect_uri", process.env.ENJAB_REDIRECT_URI!); u.searchParams.set("response_type", "code"); u.searchParams.set("state", pathname); return NextResponse.redirect(u); } export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\..*).*)"], }; ``` ## Using it in a page [#using-it-in-a-page] Fetch the user with `getUser()`. If it returns null, the token expired between requests, so redirect to login. Gate by role only if your tool is role-aware (an Enjab Auth admin has marked it so). ```ts title="Example page" import { redirect } from "next/navigation"; import { getUser, loginUrl } from "@/lib/enjab-auth"; export default async function Page() { const user = await getUser(); if (!user) redirect(loginUrl("/")); // token expired between requests // Most tools stop here: being signed in IS the authorization. Just use the user. return

Hello {user.name} ({user.email})

; } // ONLY if your tool is role-aware, gate an internal function like this: // if (!user.roles?.includes("reception") && !user.is_super_admin) { // return

You don't have access to this section.

; // } ``` Call `getUser()` on every request that needs the user. It fetches fresh from Enjab Auth (cache: "no-store"), so role changes, sign-outs, and account disables take effect right away. Never cache the user object beyond the request or trust your cookie's claims. # Other stacks and security (/docs/sign-in-with-enjab/other-stacks) ## Other stacks (no framework) [#other-stacks-no-framework] Implement the same three OAuth calls with any stack: 1. **Redirect to /authorize** Construct and redirect to `https://auth.enjab.ae/authorize` with these query parameters: * `client_id` (from your environment) * `redirect_uri` (must match your registered callback URL) * `response_type=code` * `state` (round-trip this back; typically the path to return to after login) 2. **Server-side POST /api/oauth/token** From your backend, call `https://auth.enjab.ae/api/oauth/token` with: * Method: `POST` * Body (form-encoded or JSON): * `grant_type=authorization_code` * `code` (from the redirect callback) * `redirect_uri` (must match the one used in step 1) * `client_id` * `client_secret` (server only, never expose to the browser) Response: ```json { "access_token": "enjat_...", "token_type": "Bearer", "expires_in": 28800, "user": { "sub": "0f3c...", "email": "sara@enjab.ae", "name": "Sara Ahmed", "roles": ["reception"], "is_super_admin": false } } ``` 3. **GET /api/oauth/userinfo (fresh roles on each request)** Call `https://auth.enjab.ae/api/oauth/userinfo` with: * Header: `Authorization: Bearer ` * Always fetch fresh (set HTTP Cache-Control to `no-store` or equivalent in your language) Returns the user object above. **Store the access token in a secure httpOnly cookie on the server.** The browser should never see this token. On each subsequent request, read it from the cookie and call `/api/oauth/userinfo` to get the fresh user object. ## Security rules (do not skip) [#security-rules-do-not-skip] The `ENJAB_CLIENT_SECRET` must never be shipped to the browser. If it appears in client-side code or reaches the browser, rotate it immediately from the tool's page in Enjab Auth admin. * **Redirect URI must be HTTPS on enjab.ae** and registered in Enjab Auth admin. Enjab Auth refuses to send authorization codes to any other domain; this is what stops an attacker from harvesting logins. * **Store the access token in an httpOnly + secure + sameSite cookie.** Do not expose it to JavaScript. Set it with: ``` httpOnly: true secure: true sameSite: lax (or strict) path: / ``` * **Always derive the user from getUser()/userinfo.** Trust the `sub` field as the user's stable identity. Never trust user info that comes from the browser or your client code. Call `/api/oauth/userinfo` fresh on each request to catch sign-outs, role changes, and account disables immediately. * **Never cache the user object beyond the request.** A cached user object means revoked roles, disabled accounts, and central sign-outs will not reach your tool. Enjab Auth cannot modify your cookie, so if you cache, a signed-out or disabled person keeps working. # Register your tool (/docs/sign-in-with-enjab/register) An Enjab Auth admin registers your tool once in Enjab Auth admin, then shares the credentials with your team. ### Add the tool [#add-the-tool] Go to [https://auth.enjab.ae/admin/apps](https://auth.enjab.ae/admin/apps) and select "Add tool". Enter: * **Name**: your tool's display name * **Slug**: URL-safe identifier (e.g. `my-tool`) * **Domain**: must be on `enjab.ae` (e.g. `my-tool.enjab.ae`) ### Copy credentials [#copy-credentials] After creation, you will see a one-time display of: * `ENJAB_CLIENT_ID` (e.g. `enjci_...`) * `ENJAB_CLIENT_SECRET` (e.g. `enjsk_...`) Copy both immediately. The secret is shown once only. ### Confirm the redirect URI [#confirm-the-redirect-uri] The default redirect URI is `https:///api/auth/callback`. Edit it if needed on the tool's page in Admin. The redirect URI must be: * HTTPS only * On your registered domain * Path can be any route your tool owns (default path is `/api/auth/callback`) Enjab Auth locks the host to your domain and refuses redirects to any other host. ### Create roles (optional) [#create-roles-optional] If your tool has features that not every authorized user should access (e.g. an admin-only settings page), go to [https://auth.enjab.ae/admin/roles](https://auth.enjab.ae/admin/roles) to: * Create roles (e.g. `reception`, `doctor`) * Attach this tool to the roles that should reach it * Assign those roles to employees By default, access is binary: if Enjab Auth lets the user in, they can use your tool. Only enable roles if you need to gate features by role inside the tool (see [Roles](/docs/sign-in-with-enjab/roles) for details on how to check roles in code). ## Environment variables [#environment-variables] Set these on your server. The client secret is server-only and must never reach the browser. ``` ENJAB_CLIENT_ID=enjci_... ENJAB_CLIENT_SECRET=enjsk_... ENJAB_REDIRECT_URI=https://yourtool.enjab.ae/api/auth/callback ENJAB_ISSUER=https://auth.enjab.ae ``` `ENJAB_CLIENT_SECRET` must never ship to the browser. If it ever reaches client code or is exposed, rotate it immediately from the tool's page in Enjab Auth admin. # Roles (/docs/sign-in-with-enjab/roles) ## When to use roles [#when-to-use-roles] Access is binary. An admin gives a user access to your tool via a role or a direct grant. Enjab Auth enforces that at the door. If the user reaches your tool, they are allowed to use it. Turn on roles only when your tool has functions that not every authorized user should reach, such as an admin-only settings page inside the tool. ## Enabling roles [#enabling-roles] An Enjab Auth admin flips your tool to "role-aware" (Admin > Tools > your tool > Role-aware). From then on, the `user` object includes `roles` (global keys like `reception`, `doctor`) and `is_super_admin`. Without the flag, `roles` and `is_super_admin` are omitted from the user object. ## The user object with roles [#the-user-object-with-roles] When your tool is role-aware, `getUser()` returns: ```json { "sub": "0f3c...", "email": "sara@enjab.ae", "name": "Sara Ahmed", "roles": ["reception"], "is_super_admin": false } ``` Most tools never see these keys and should not branch on them. Only role-aware tools receive them. ## Gating features by role [#gating-features-by-role] Use the pattern below inside pages or components: ```ts title="lib/enjab-auth.ts" const user = await getUser(); if (!user) redirect(loginUrl("/")); // ONLY if your tool is role-aware, gate an internal function like this: if (!user.roles?.includes("reception") && !user.is_super_admin) { return

You don't have access to this section.

; } ``` Check `user.roles?.includes("roleName")` or `user.is_super_admin` to control what the signed-in user can reach. A super admin has `is_super_admin: true` and access to every tool. Call `getUser()` on every request (use `cache: "no-store"`). Do NOT cache the user object beyond the request. This is the only way central sign-out, account disable, and role changes reach your tool. # Update the auth integration (/docs/update-an-existing-tool/auth) When Enjab Auth changes, the [Enjab Auth changelog](/docs/changelog/enjab-auth) lists exactly what changed and the action to adopt it. Apply the entries your tool has not adopted yet, then verify against the [Sign in with Enjab Auth](/docs/sign-in-with-enjab) reference. This page covers the points most worth re-checking. The `is_active` field was removed from the user object (Enjab Auth blocks disabled accounts at the door, so a tool only ever sees active users). Delete `is_active` from your `EnjabUser` type and any code that read it. See the [changelog](/docs/changelog/enjab-auth) for the full history. ## Re-validate every request [#re-validate-every-request] This is critical and often the first thing to get wrong when updating. On each request, call `getUser()` fresh. This must call `/userinfo` with `cache: "no-store"`. Never cache the user object beyond the request, or trust your cookie's claims. A signed-out user, a disabled user, or a user whose role changed at Enjab Auth will keep working in your tool if you cache them. Enjab Auth cannot touch your cookie. The only way central sign-out, account disable, and role changes reach you is if you call `/userinfo` every request. Always use the reference implementation exactly as written. ```ts title="lib/enjab-auth.ts" export const getUser = cache(async (): Promise => { const token = (await cookies()).get(SESSION_COOKIE)?.value; if (!token) return null; const res = await fetch(ISSUER + "/api/oauth/userinfo", { headers: { Authorization: `Bearer ${token}` }, cache: "no-store", }); return res.ok ? ((await res.json()) as EnjabUser) : null; }); ``` Call `getUser()` on every page or endpoint that needs the user, and check the result: ```ts title="app/dashboard/page.tsx" import { redirect } from "next/navigation"; import { getUser, loginUrl } from "@/lib/enjab-auth"; export default async function Page() { const user = await getUser(); if (!user) redirect(loginUrl("/dashboard")); return

Hello {user.name}

; } ``` ## No login UI of your own [#no-login-ui-of-your-own] Your tool must not have a login button or login page. The moment a signed-out user touches any page, redirect straight to Enjab Auth's `/authorize` endpoint. Enjab Auth handles passwords, two-factor, and the greeting screen. Use the middleware proxy from the reference to redirect instantly: ```ts title="proxy.ts" export function proxy(req: NextRequest) { const { pathname } = req.nextUrl; if (pathname.startsWith("/api/auth")) return NextResponse.next(); // callback/logout are public if (req.cookies.has("enjab_session")) return NextResponse.next(); const u = new URL(ISSUER + "/authorize"); u.searchParams.set("client_id", process.env.ENJAB_CLIENT_ID!); u.searchParams.set("redirect_uri", process.env.ENJAB_REDIRECT_URI!); u.searchParams.set("response_type", "code"); u.searchParams.set("state", pathname); return NextResponse.redirect(u); } export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\..*).*)"], }; ``` ## Sign-out flow [#sign-out-flow] Your tool does not clear the session or call any sign-out method. A "Sign out" button navigates to your `/api/auth/logout` route. Enjab Auth shows a confirmation page first, so an accidental click never logs anyone out. On confirm, Enjab Auth sends the user back to your logout route with `?confirmed=1`, and only then do you clear your tool's session. ```ts title="app/api/auth/logout/route.ts" export async function GET(req: NextRequest) { if (req.nextUrl.searchParams.get("confirmed") === "1") { const res = NextResponse.redirect(`${ISSUER}/logged-out?client_id=${process.env.ENJAB_CLIENT_ID}`); res.cookies.delete(SESSION_COOKIE); return res; } return NextResponse.redirect(`${ISSUER}/logout?client_id=${process.env.ENJAB_CLIENT_ID}`); } ``` Wire your sign-out button to `/api/auth/logout`: ```tsx ``` ## Role gating (only if role-aware) [#role-gating-only-if-role-aware] By default, Enjab Auth does not send roles. Access is binary: if Enjab Auth let the user in, they have access. Do not gate by role unless your tool is explicitly marked "role-aware" by an Enjab Auth admin. If your tool is role-aware, you will receive a `roles` array in the user object. Gate internal features like this: ```ts if (!user.roles?.includes("reception") && !user.is_super_admin) { return

You don't have access to this section.

; } ``` Never invent your own role names or gate on roles if your tool is not role-aware. If roles are absent from the user object, your tool is not role-aware, and you should not branch on them. ## Compare against the reference [#compare-against-the-reference] Your integration should match the reference implementation in the Enjab Auth guide at [https://developers.enjab.ae/llms.txt](https://developers.enjab.ae/llms.txt). In particular: * `getUser()` calls `/api/oauth/userinfo` with `cache: "no-store"` * Your `/api/auth/callback` route exchanges the code and stores the token in an httpOnly cookie * Your `/api/auth/logout` route waits for `?confirmed=1` before clearing the session * Your middleware redirects signed-out users straight to `/authorize`, no button * All environment variables are set: `ENJAB_CLIENT_ID`, `ENJAB_CLIENT_SECRET`, `ENJAB_REDIRECT_URI`, `ENJAB_ISSUER` If you are not using Next.js, the same principles apply: always call `/userinfo` fresh, never cache the user, redirect unsigned users instantly, and confirm sign-out before clearing the session. # Overview (/docs/update-an-existing-tool) Enjab evolves. Updating a tool means tracking two contracts, the **Enjab UI** design system and the **Enjab Auth** integration, and adopting only what changed. The principle is always the same: apply ONLY the changelog entries your tool has not adopted, and touch nothing else. Updates are not refactors. Send your agent the [Changelog](/docs/changelog), have them identify which entries your tool has not adopted yet, and apply exactly those. Stop there. Do not restyle or reorganize surrounding code. ## Two tracks [#two-tracks] ## Update everything at once [#update-everything-at-once] To bring a tool up to date on both UI and Auth in one go, use the single [update-everything prompt](/docs/changelog) on the changelog overview. It reads the central docs and both changelogs, works out what your project is missing, and applies only that. ## Live contract [#live-contract] This site is the single source of truth for every Enjab tool, published as plain text for agents: * **The contract**: [https://developers.enjab.ae/llms.txt](https://developers.enjab.ae/llms.txt) * **What changed**: the [Changelog](/docs/changelog) ([Enjab UI](/docs/changelog/enjab-ui), [Enjab Auth](/docs/changelog/enjab-auth)) Hand these to your agent and they can bring your tool up to the current standard, applying only the delta. # Update the UI (/docs/update-an-existing-tool/ui) When Enjab UI changes, the [Changelog](/docs/changelog) lists exactly what changed and the action to adopt it. Update your project by applying the entries you have not adopted yet. ## The update prompt [#the-update-prompt] Hand an agent this message: ``` Update this project to the latest Enjab UI. Read https://developers.enjab.ae/llms.txt and the changelog at https://developers.enjab.ae/docs/changelog, then apply ONLY the changelog items this project has not adopted yet. Re-install changed components with `npx shadcn add @enjab-ui/` (overwrite). Re-apply the theme if tokens changed. Do not refactor or restyle anything else, keep the change surface minimal. ``` The agent will fetch the docs and the changelog, check your project against the latest entries, and apply only what you have not already adopted. ## How updates work [#how-updates-work] ### Read the changelog [#read-the-changelog] Read the [Changelog](/docs/changelog) (or fetch `https://developers.enjab.ae/docs/changelog`). Note which components and design tokens changed. ### Identify what you need [#identify-what-you-need] Compare your project's installed components against the changelog. You only need to update the components and theme items your tool actually uses. ### Reinstall changed components [#reinstall-changed-components] For each component that changed and that your project uses, run: ```sh npx shadcn add @enjab-ui/ --overwrite ``` This pulls the latest version and overwrites the local copy. For example: ```sh npx shadcn add @enjab-ui/button --overwrite npx shadcn add @enjab-ui/data-table --overwrite ``` ### Update the theme if tokens changed [#update-the-theme-if-tokens-changed] If the changelog lists theme changes (colors, fonts, radius, or sidebar), reinstall the theme: ```sh npx shadcn add https://ui.enjab.ae/r/theme.json --overwrite ``` Always reinstall theme FIRST if you're also adding other components, since every component depends on the design tokens. ## Keep the change surface minimal [#keep-the-change-surface-minimal] Only update components your tool uses. Do not refactor or restyle anything else. The goal is to stay in sync with Enjab UI without introducing unrelated changes. If a component update requires a prop change (for example, a new required parameter), the agent will catch it during testing and show you what changed in the component's API.