# KROW Demo
AI-powered hiring for event and hospitality staffing — a standalone demo build of KROW Forge.
Runs with no backend, no API keys and no real login. `npm install && npm run dev` and the app is fully
populated.
```bash
npm install
npm run dev # http://localhost:5173 — opens on /admin/login
```
## Stack
Vite 6 · React 19 · React Router 6 · React Query 5 · Tailwind + shadcn/ui · Framer Motion ·
Recharts + MUI X Charts · react-hot-toast.
## Structure
```
src/
main.jsx entry — mounts , imports index.css
App.jsx providers + the entire route table
index.css design tokens (HSL CSS variables) + utility layers
api/ the backend seam — see below
base44Client.js the contract the production app talks to
store.js in-memory entity store, mirrored to localStorage
aiEngine.js local, deterministic InvokeLLM
seed.js demo dataset
attendanceSeed.js shift/attendance dataset
lib/ domain logic and data hooks
krowHooks.js React Query hooks over the entity store
krowAi.js AI workflows
krowScore.js score engine
AuthContext.jsx auth provider
admin/ admin session, permissions, position insights
skills/ the Owliver skill system (registry, resolver, flows)
agents/ the agent system (registry, runtime, lifecycle)
skills/ *.md skill definitions — loaded by import.meta.glob
agents/ *.md agent definitions — loaded by import.meta.glob
components/
ui/ 23 shadcn primitives
ds/ 30 design-system components (the app's real vocabulary)
admin/ admin page shell + role glyphs
krow/ domain components (+ talent/, proving/)
charts/ MUI X Charts wrappers and theme
agents/ agent configure / canvas / test panel
forge/ KROW Forge skill + challenge components
skills/ skill authoring surfaces
ai-assistant/ Owliver — panel, routing, capabilities, history
layouts/ AdminLayout — header nav, ⌘K search, assistant panel
pages/ route components (admin/ holds the live product)
hooks/ shared hooks
assets/ brand asset references
```
## Routes
The app opens on **`/admin/login`**. Everything else lives under `/admin/*` behind two guards:
`ProtectedRoute` (auth) and `AdminRoute` (admin session), rendered inside `AdminLayout`.
| Path | Page |
| --- | --- |
| `/admin/login` | Admin sign-in — outside the protected boundary |
| `/admin` | Control Center |
| `/admin/positions`, `positions/new`, `positions/:id` | Positions, Create, Detail |
| `/admin/candidates`, `candidates/:id` | Candidates, Candidate Intelligence profile |
| `/admin/hired` · `/admin/talent-pool` | Hired History, Talent Pool |
| `/admin/university`, `university/:id` | KROW Forge, challenge detail |
| `/admin/analytics` · `/admin/activity` | Analytics, Activity |
| `/admin/profile` · `/admin/settings` | Profile, Settings |
| `/admin/workspace` | Workspace hub |
| `/admin/workspace/agents`, `agents/new`, `agents/:id` | Agent list, agent editor |
| `/admin/workspace/skills`, `skills/new`, `skills/:id` | UI skill list and editor |
| `/admin/workspace/skills/owliver/new`, `owliver/:id` | Owliver skill editor |
| `/admin/workspace/skill-development` | Skill Development |
| `*` | `lib/PageNotFound.jsx` |
Legacy top-level paths (`/overview`, `/positions`, `/candidates`, `/hired`, `/talent-pool`,
`/university`, `/analytics`, `/activity`, `/tracking`) all `Navigate` into their `/admin/*`
equivalent, id preserved. Static route segments are declared before dynamic ones so
`workspace/agents/new` cannot be read as an agent whose id is `"new"`.
> **Note.** `App.jsx` still imports a set of pre-redesign Employer/Talent pages
> (`Overview`, `Apply`, `WorkerProfile`, `KrowIdentity`, `EmployeeDashboard`, `DesignSystem`, …)
> and `layouts/Layout.jsx`. None of them are mounted on a route — they are unreachable at runtime
> and retained pending a decision on whether to re-route or remove them.
## The backend seam
The production app talks to a Base44 backend through `src/api/base44Client.js`. This demo keeps that
module path, export name and full method contract, and swaps only the transport:
| Contract | Demo implementation |
| --- | --- |
| `base44.entities..list/filter/get/create/update/delete` | `api/store.js` — in-memory records, mirrored to `localStorage` |
| `base44.integrations.Core.InvokeLLM` | `api/aiEngine.js` — local, deterministic |
| `base44.integrations.Core.UploadFile` | blob URLs, session-scoped |
| `base44.auth.*` | seeded employer/admin user, always signed in |
Because the seam did not move, everything above it — `lib/krowHooks.js`, `lib/krowAi.js`,
`lib/krowScore.js`, every page and component — is unchanged from the reference implementation.
## The AI engine
Every AI workflow funnels through one call, `InvokeLLM({ prompt, response_json_schema })`.
`api/aiEngine.js` reimplements it locally: it recognises each workflow by the phrase its prompt opens
with, reads the structured fields the prompt already carries (`Years Experience: 6`,
`Required Certifications: …`, the talent-pool JSON block) and scores them deterministically.
Nine workflows are implemented: candidate screening, job-description generation, résumé building,
interview questions, interview evaluation, talent matching, Owliver's conversation, Career DNA
building and Proving Ground evaluation. Results respond to the data rather than being canned — the
same input always returns the same output.
## Skills and agents
Two registries discover their definitions from markdown at build time:
| Registry | Glob | Files |
| --- | --- | --- |
| `lib/skills/registry.js` | `import.meta.glob('/src/skills/**/*.md')` | 18 Owliver skills, 5 workforce training skills |
| `lib/agents/registry.js` | `import.meta.glob('/src/agents/**/*.md')` | 9 agent definitions |
**These globs are absolute paths.** Moving or renaming `src/skills/` or `src/agents/` makes the
registry silently return nothing — no build error, no import failure, just an empty registry. Add
definitions by dropping a new `.md` file into the right folder; nothing else needs to change.
## Demo data
Seeded in `src/api/seed.js` — positions, applicants, AI-scored candidates, interviews, hires,
talent-pool profiles, Proving Ground courses and an activity log; shift data in
`src/api/attendanceSeed.js`.
Edits persist to `localStorage`. To restore the shipped dataset:
```js
// in the browser console
localStorage.removeItem('krow_demo_db'); location.reload();
```
Bumping `STORAGE_VERSION` in `src/api/store.js` (currently `8`) invalidates saved snapshots
automatically, so changing the seed does not require a manual clear.
## Design system
Tokens live in `src/index.css` as HSL CSS variables consumed by `tailwind.config.js`.
| Token | Value |
| --- | --- |
| Primary — KROW Blue | `#0A39DF` |
| Accent — KROW Yellow | `#F9E547` |
| Pale Yellow | `#F8E08E` |
| Dark Navy | `#333F48` |
| Mint | `#D1E0D7` |
| Radius | `0.75rem` |
| Headings | Sora |
| Body | Inter |
`src/components/ds/` is the vocabulary the app actually writes in — `Surface`, `KpiCard`,
`DataTable`, `PageHeader`, `Modal`, `toast` and the rest, exported through `ds/index.js`.
`src/components/ui/` holds the shadcn primitives those are built on. Surfaces use `glass` and
`glass-card`; the page background is the `gradient-mesh` utility.
Toasts go through `ds/toast` (a wrapper over `react-hot-toast`), rendered by `` in
`App.jsx`.
## Scripts
| Command | Purpose |
| --- | --- |
| `npm run dev` | Dev server (honors `PORT`, default 5173) |
| `npm run build` | Production build |
| `npm run preview` | Serve the build |
| `npm run lint` | ESLint — currently clean |
| `npm run lint:fix` | ESLint with `--fix` |
| `npm test` | `scripts/skill-check.mjs` — 835 assertions over the skill and agent systems |
| `npm run typecheck` | `tsc -p ./jsconfig.json` with `checkJs` |
### The test harness
`npm test` boots a real Vite dev server in middleware mode and `ssrLoadModule`s the actual
application modules, so it exercises the real `@/` alias and `import.meta.glob` pipeline rather than
mocks. It also diffs Owliver's behaviour across eleven page contexts against a committed snapshot,
`scripts/__baseline__/owliver-baseline.json`; regenerate that deliberately with
`node scripts/owliver-baseline.mjs --write`, never to turn a red check green.
Because it loads modules by absolute path, the harness pins the locations of `src/api/`, `src/lib/`,
`src/lib/skills/`, `src/lib/agents/`, `src/components/ai-assistant/`, `src/App.jsx`,
`src/layouts/AdminLayout.jsx` and a handful of agent files. Move any of them and update
`scripts/owliver-capture.mjs` and `scripts/skill-check.mjs` in the same change.
### Known-failing checks
Two checks fail on a clean checkout and are tracked as separate work, not regressions:
- `npm test` — 834/835 pass; `the seeded overtime climb is found` fails.
- `npm run typecheck` — 59 errors, all pre-existing JSDoc/inference gaps in `lib/krowAi.js`,
`lib/skills/*`, `lib/positionModel.js` and a few components.
## Deployment
`Dockerfile` builds with `node:22-alpine` (capped at 1 GB heap for small hosts) and serves the
static `dist/` from `nginx:alpine` using `nginx.conf` — gzip, immutable `/assets/` caching and an
SPA fallback to `index.html` so client-side routes resolve on refresh.