# RJ Laravel Admin · design system (round-1 pack)

**Brand:** PIÑA `brand-006-rj-laravel-admin` · **Product:** the new standalone admin at `admin.rjmtm.com` (staging `stg-admin.rjmtm.com`) · **Pack date:** 2026-10-18 · **Status:** foundations, kit, archetypes, power-ups and menu are decided; the flagships are in review (see `HANDOFF-TO-FRONTEND.md`).
**Front door (every live board):** https://pina-brand-006-rj-laravel-admin.pages.dev/ (phone: `/m/`)
**Source of every value:** `tokens.css` in this folder (a copy of `phase-1/kit/tokens.css`). If this document and `tokens.css` disagree, `tokens.css` wins.

Contents: 1 Start here · 2 Foundations · 3 Components · 4 Patterns · 5 Implementation · 6 Where everything is

---

## 1. Start here

- **What it is.** The F1 sibling of the DTC admin ("Fine Worsted · Satin Lapel"): about 90% shared DNA, the same token architecture and component vocabulary, tuned for a monster-size internal tool (325 pages, 21 modules, 1,083 actions).
- **Direction 4b·R (Frank, 2026-10-18).** White 48px header with the white **RJ HQ · Master admin** corner joining it; the exact DTC navy `#0E1729` sidebar ("the Laravel app is the parent app, so it gets the choice"); a **76px collapsed rail with a label under every icon**; 2b ultra density; a light tape-measure motif under the header.
- **One kit, two apps.** The Laravel admin is the `:root` theme (navy pinned). The DTC admin sets `data-theme="dtc"` (Midnight green `#08120E`, shipped to DTC production 2026-10-18) and `data-density="comfortable"`. Same components, no forks.
- **Rule zero: invent nothing.** Real statuses, real fields, real buttons. The truth docs (`reference/TRUTH-*.md`) are the source; anything beyond today's admin is a tagged proposal (`PROPOSALS.md`).

## 2. Foundations

### 2.1 Colour (Laravel theme, `:root`)

| Role | Token | Value | Use |
|---|---|---|---|
| Primary / sidebar | `--primary`, `--sidebar` | `#0E1729` | every primary action; the expanded menu and the rail. Pinned literally (TOKENS-DELTA §3) |
| Sidebar sheen | `--sidebar-sheen` | `#131D33 → #0E1729 → #0C1425` | sidebar only, never on buttons |
| Menu text / active | `--sidebar-foreground`, `--sidebar-primary`, `--sidebar-item-on-icon` | `#C9CEDA`, `#9FB4E2`, `#B7C7EA` | rail labels, active rail, active icon |
| Steel | `--steel`, `--ring` | `#2C4B86` | **selection and focus only**: focus ring, selection rail, sorted column, tape ticks. Never the sidebar, never a primary action |
| Selection | `--accent`, `--selected-row` | `#E9EEF8` | selected row, active filter |
| Ground | `--background` | `#EDF0F4` | page ground; containers stay white (`--card #FFFFFF`) |
| Wells / table head | `--muted` | `#F5F6F9` | |
| Hairlines | `--border` | `#E2E6EC` | |
| Ink / quiet ink | `--foreground`, `--muted-foreground` | `#0F1626`, `#505A70` | |
| Status | `--success` `--warning` `--destructive` `--info` `--neutral` | `#21654C` `#83550F` `#A2323F` `#335794` `#575F72` | soft fill = status at 12% (`--status-soft-alpha`) with the status as text |
| Real domain colours | `--os-*` (11 order statuses), `--ph-*` (9 gantt phases), `--tk-*` (6 ticket statuses) | from `OrderStatus::getStatusList()`, the gantt legend and `tickets_statuses` | status chips, piece pills, gantt bars. Never invent a status colour |
| Annotation only | `--tag-proposal`, `--tag-needs-backend`, `--tag-api` | | design-board tags, never product UI |

Colour carries meaning only. Never: dark canvas, gold or brass, plum/purple in product UI, serif titles, uppercase letter-spaced labels, a colour flip of the DTC admin.
Contrast: the pack gate checks 14 ink-on-ground pairings in both themes at ≥ 4.5:1 (`pack-gate.txt`, all pass).

### 2.2 Type

- **Archivo** (600) for titles, buttons, menu, labels, table headers. Always normal width: `font-stretch:100%`, tracking 0 or negative, never wide.
- **IBM Plex Sans** for reading text, tables, prices. **IBM Plex Mono** for ids, codes, versions, dates in grids (tabular numbers everywhere numbers line up).
- **中文:** `--font-cjk` = PingFang SC, Noto Sans SC, Microsoft YaHei, after the Latin face in both stacks. Never letter-space CJK.
- **Scale at compact (default):** body 12.5px · small 11.5px · button 13px · nav 13px · page title 20px · section 16px · rail label 10px (Archivo 500, 600 active, up to 2 lines, never truncated). Comfortable: body 13px, small 12px.

### 2.3 Density (a token mode, not a restyle)

| Token | Compact (`:root`, Laravel default) | Comfortable (`[data-density="comfortable"]`) |
|---|---|---|
| `--row` / `--row-th` | 30 / 30px | 34 / 32px |
| `--control-h` / `--control-h-sm` | 30 / 26px | 32 / 28px |
| `--tab-h` · `--btn-px` · `--label-col` · `--cell-w` | 36 · 13 · 180 · 92px | 40 · 14 · 200 · 100px |
| `--text-body` / `--text-small` | 12.5 / 11.5px | 13 / 12px |

Per person (density switch, power-up #15; stored in the prefs store once H01 exists, browser storage until then). The `/m/` shell is always comfortable. `[data-density="compact"]` restates compact for an island inside a comfortable page.

### 2.4 Space, shape, elevation, motion

- **Space:** 4px grid (`--s-1` 4 … `--s-18` 72px); page gutter `--page-pad` 20px.
- **Radius:** `--radius` 6px (shadcn); chips 4px, controls 6px, cards / popovers 8px, panels 15px, pills 999px.
- **Shell sizes:** header `--topbar-h` 48px · rail `--rail-w` 76px (item 66 × ≥ 48px) · expanded sidebar `--sidebar-w` 232px · flyout column `--fly-w` 240px.
- **Elevation:** `--e-1` card · `--e-pop` menus · `--e-sheet` side sheet · `--e-dialog` dialog · `--e-toast`.
- **Motion:** hover 120ms · press 80ms · pop in 160ms / out 110ms · sheet in 260ms / out 180ms · toast in 200ms, dwell 2.6s (5.2s with Undo). Entering curve `--ease-enter`, leaving `--ease-exit`. `prefers-reduced-motion` makes every move instant (tokens already do it).
- **Tape measure (light touch):** steel ticks (minor 8px, major 80px, 6px tall, opacity .55) under the header and HQ corner, on the sorted column and on progress. Off in the DTC theme.
- **Icons:** Lucide at stroke 1.5 (`--icon-stroke`).

## 3. Components

The component spec is **`CONTRACT.md`** (copy of `phase-1/kit/CONTRACT.md`): for each of the 36 F1 families, the export name, props, states, shadcn / Radix mapping, package underneath and phone variant; plus the 86 components inherited by name from the DTC kit (§4 of the contract). Live specimens: https://kit.pina-brand-006-rj-laravel-admin.pages.dev/families/ (phone: `/families/m/`), each in both densities, EN + 中文, long strings and permission-denied.

| Group | Families (kit folder names) |
|---|---|
| Shell | `shell` (page frame, rail, header, HQ corner, phone app bar), `job-tray` (Jobs list + notification centre), `locale-tz` (globe EN ↔ 中文, clock → timezone) |
| Tables and lists | `data-table`, `pagination`, `filter-bar` (+ saved views), `bulk-action-bar` (batches of 100), `split-pane`, `sortable-list`, `tree-view` |
| Pickers and inputs | `combobox` (async, cloth picker, ⌘K), `money-input` (wallet before → after), `datetime`, `bilingual-field`, `rich-text`, `cron-builder`, `swatch-picker`, `measurement-grid`, `price-group`, `matrix-editor` |
| Forms and flows | `form-section` (section nav, save bar, unsaved guard), `stepper`, `import-flow` (upload → map → preview → confirm → progress → result) |
| Records | `record-header` (action model, split button), `timeline-audit`, `diff-viewer`, `comment-thread`, `json-viewer`, `qr-display` |
| Safety and states | `confirm-ladder` (incl. typed confirm), `states` (empty, loading, error, offline, stale, conflict, not available to your role, needs backend) |
| Data display and media | `kpi-chart`, `gantt`, `document-print`, `gallery-picker`, `photo-review` |

Rules every component follows (CONTRACT §2): density and theme are attributes; permission-shaped (absent if the role cannot use it); en / 中文 from messages; long strings truncate with a title in single-line cells and wrap elsewhere; times in the staff timezone; real data only; job states designed; bulk in batches of 100.

## 4. Patterns

### 4.1 Permission-shaped shell from `/me/menu`
- On sign-in the shell calls `admin.me.show` (identity, permissions, preferences) and `admin.me.menu` (the server-filtered tree). The sidebar renders **exactly** that tree, depth ≤ 3; no client-side menu config.
- A control a role cannot use is absent. Use the "Not available to your role" state only where hiding would confuse (T003 ruling).
- Sign-in is **Clerk** (T001 Q1); design the signed-out and expired-session screens. Staging role accounts use `POST /api/v1/login` behind a dev-only flag.

### 4.2 Menu mechanics (Frank: T-d + T-f)
- **Collapsed (76px rail):** hover (~140ms intent) or click opens the **T-d mega flyout**: level-2 headings over level-3 links, 1–3 columns of `--fly-w`. Keyboard and touch twins required.
- **Header:** **T-f breadcrumb dropdowns**: each crumb ▾ opens its siblings.
- **Expanded (232px):** accordion, level 2 as headings. Deeper than 3 = in-page tabs.
- Menu regroup is out of design scope (separate exercise with backend, PR-074). cn labels fall back to English until PR-073.

### 4.3 Dedicated phone routes (`/m/`)
- Every page with a phone answer has its own `/m/…` route and view file; only the view layer forks (`features/*/desktop` vs `features/*/mobile`). URLs are identical apart from the `/m` prefix.
- Middleware routes on `Sec-CH-UA-Mobile` / user agent, remembers `rj_surface=m|d`, offers a switch in the profile drawer. Never route on viewport width.
- Phone modes per page are in `PAGE-MAP.md`: **dedicated** (15), **read-mostly** (156), **desktop-only-notice** (154, `/m/not-on-phone?to=…`).

### 4.4 Live data: polling, not websockets (T001 Q2)
- Jobs (exports, imports, print runs, job batches, vision re-runs) and the notification count are **polled every few seconds** with `@tanstack/react-query` `refetchInterval`. Recommended default: 3s while something is queued or running, slower when idle, paused when the tab is hidden.
- States per job: queued · running % · partial failure · failed + retry · done + download. Finished jobs hand off from the toast to the Jobs list in the header (job tray).
- `onlineManager` drives the live / stale / offline banner (power-up #20).

### 4.5 Server PDFs via signed links (T001 Q26)
- The printed order documents stay server-rendered PDFs. The UI is **prepare → open**: request, show progress, then open the signed URL in a new tab (`document-print` family, M04). No client PDF library. Cloud print shows its own status.

### 4.6 Confirmation ladder and typed confirm (T001 Q14)
- Level by consequence: no confirm (reversible, with an Undo toast) → simple confirm → review dialog (shows what changes, counts) → **typed confirm** for the scary ones (money, voids, bulk deletes, revoke-all): the person types the value shown (amount, order id or count) before Apply is enabled.
- Wallet money keeps the real two-step (Review → Confirm → Apply) unless Frank picks otherwise (TICKETS-R1 Q5).

### 4.7 Tables at scale
- Server pagination with totals (50 / 100 per page), sticky header and sticky first column, column chooser, saved views, sort, keyboard navigation, tabular numbers, inline edit with saved / error states and an Undo toast.
- Select-all-across-pages, then **batches of 100** per request (T003 #13): `batch n of m`, *Stop after this batch*, partial-failure summary.
- Filters live in the URL (`nuqs`); saved views ship on URL state + browser storage now, per-person sync with H01.

### 4.8 Forms
- Section nav on the left (A03·a), sticky save bar, unsaved-change guard, validation summary.
- "Someone else changed this": measurement writes carry a version (`If-Match` / `expected_version` → 409 `version_conflict`, H03); show the conflict banner with compare.

### 4.9 i18n en / 中文 and per-staff timezone
- `next-intl`, no locale URL prefix; locale and timezone come from `PUT /admin/me/preferences` (`{locale, timezone}`, T001 Q25).
- The API returns UTC; format in the staff zone (`date-fns` + `@date-fns/tz`). Relative up to 7 days, then absolute; both zones on hover (power-up #21).

### 4.10 The 24 adopted power-ups (Frank, "loved all")
⌘K command palette · Find order always in the header · keyboard shortcuts + `?` cheat sheet · recently viewed + pinned records · saved views & column presets · filter chips in the URL · job tray in the corner · bulk action bar with batch progress · prepare → open PDF with cloud-print status · peek side-sheet (pin to split view) · hover cards · copy any ID in one click · activity side panel · empty states that offer the next step · density switch · inline edit with an undo toast · "someone else changed this" banner · confirmation ladder (typed for the scary ones) · floating unsaved-changes bar · live / stale / offline banner · times in my timezone, both on hover · English / 中文 switch · motion set · skeleton loading.
Needs backend: saved views sync (PR-001 / H01), activity panel (PR-003 / H02); the conflict banner is backed (PR-004 / H03, verify the 409). Advanced power-ups: awaiting Frank's picks (https://powerups.pina-brand-006-rj-laravel-admin.pages.dev/advanced/).

### 4.11 Archetypes
11 page patterns (A07 folded into A05). Locked layouts: A01·a full-width table · A02·a tabs + front matter + side panel · A03·a section nav left · A06·a table of runs · A11·a 3-pane. Every non-flagship page copies its archetype hero; flagship pages copy `phase-3/flagships/<area>/`. Details: `reference/ARCHETYPES.md`, live sheet https://archetypes.pina-brand-006-rj-laravel-admin.pages.dev/.

## 5. Implementation

- **Stack (decided, Frank 2026-10-18):** Next.js App Router (15.5, DTC's major) + React 19 + TypeScript + shadcn/ui on Radix + Tailwind 3.4 over the CSS-variable tokens. Same as the DTC admin, so the kit is written once.
- **Shared package `@rj/admin-kit`:** base tokens + one theme layer per app, the shadcn / Radix primitives, the F1 composites (each wrapping the chosen third-party package). Repo location is **deferred** (recommended default `RJmtm/admin-kit`, STACK §5.2). Semver with changesets; the kit manifest gains `themes` and `consumers`; CI fails a component with no manifest row, demo or required phone variant.
- **Packages (recommended defaults, the team confirms):** `@tanstack/react-table` 8 + `@tanstack/react-virtual`, `@tanstack/react-query`, `zustand`, `nuqs`, `cmdk`, `react-hotkeys-hook`, `react-hook-form` + `zod`, `sonner`, `motion`, `recharts`, `react-resizable-panels`, `@dnd-kit`, `date-fns` + `@date-fns/tz`, `react-day-picker`, `next-intl`, `react-datasheet-grid` (after a one-day spike), gantt built in the kit on CSS grid + react-virtual, Tiptap MIT extensions, `vaul`, `@clerk/nextjs`, `@rjmtm/newadmin-client`, `msw`, `vitest`, `@playwright/test`. No paid licence. Full map: `reference/STACK-AND-PACKAGES.md` §3.
- **API access** only through `@rjmtm/newadmin-client`, wrapped once in query hooks per feature; never `fetch` in a component. The mock allows `localhost:5173`: run `next dev -p 5173` or add `:3000` to the mock's origins.
- **Drop-in files:** `implementation-files/` (`globals.css`, `tailwind-preset.ts`, `components.json`, README). `tokens.css` goes beside `globals.css`; `tokens.json` is the W3C copy (both themes and the comfortable mode under `$extensions.com.rj-admin-kit.modes`).
- **Token sync:** keep the DTC pattern (`sync-tokens.mjs --check` in CI), moved into the kit CLI with two sources (base + theme). STACK §5.5.

## 6. Where everything is

| What | Where |
|---|---|
| Hand-off (start here) | `../HANDOFF-TO-FRONTEND.md` |
| Integration checklist | `PAGE-MAP.md` (+ `page-map.seed.json`) |
| Proposals and decisions | `PROPOSALS.md`, `PROPOSALS.json`; ready-to-file API requests `API-REQUESTS.md` |
| Backend helper build order | `BACKEND-HELPERS.md` |
| Component spec | `CONTRACT.md`, `KIT-CHANGELOG.md` |
| Tokens | `tokens.css`, `tokens.json`, `base.css` |
| Truth docs and flagship notes | `reference/` |
| Live boards | https://pina-brand-006-rj-laravel-admin.pages.dev/ |
