Phase 8, first batch: 56 files, plus three supporting edits outside the folder.
All 56 emit byte-identical JavaScript and the production bundle is byte-identical
to 543da9d.
This batch establishes the pattern for the remaining component work: real prop
interfaces built from actual call sites, reusing the generated entity types.
Fifty-four components now declare what they take -
`CandidateCard({ application: JobApplication, jobTitle?: string, rank: number })`,
`MatchedCandidates({ job: JobPosting, profiles: ProfileView[], ... })` - rather
than carrying a compatibility bag. Callbacks are optional because call sites
omit them; entity props are required because call sites always pass them. Where
a call site proved otherwise, the call site won: `ScheduleInterviewModal`'s
`position`, `CandidateCard`'s `jobTitle`, `TalentDetailModal`'s `matchScore` and
`matchReasons` are optional because real callers omit them.
Two view types absorb what the registry cannot describe. `ProfileView` and
`CourseView` narrow the `jsonb` columns these components read through -
`experience`, `completed_courses`, `earned_badges`, `capabilities`,
`score_breakdown`, `challenge`, `quiz` - to arrays and objects, while every
field backed by a real column keeps the registry's type. They are declared once
for the folder, and their narrowed fields stay REQUIRED: the registry has those
columns NOT NULL, and making them optional broke assignment back to the entity
where `ChallengeRunner` hands a course to a mutation.
TWO REAL DEFECTS FOUND, BOTH PRESERVED RATHER THAN FIXED:
1. `SuggestedTalent` calls `matchTalent.mutate({...}).then(...)`. React
Query's `mutate` returns `void`, so that `.then` throws at run time, and
`runMatch` is reachable from a button. `mutateAsync` is what the code
means. Swapping it turns a crash into a working feature, which is a
product change, not a migration one - so it is cast to compile and left
behaving exactly as it did. This one deserves a fix on its own terms.
2. `TalentPoolCard` renders a location row behind `profile.location &&`, but
`worker_profiles` has no `location` column - the registry has none and the
API cannot send one, so the row has never rendered. Recorded as an
optional field on `ProfileView` with a note, the same treatment as
`user.avatar_url` in Phase 7.
Also fixed, all type-only: the Web Speech API declared as the optional `Window`
members `AIInterviewModal` already feature-detects; four `new Promise<void>`
where `resolve()` takes no argument; `toast`'s options bag made optional on
every method, which callers had always omitted; five `krowHooks` query
arguments made optional, which callers had always omitted.
One automated pass was reverted rather than shipped. A local-component
annotator captured words out of the preceding JSDoc as prop names - producing
`interface FrameProps { one?: any; every?: any }` from a sentence about
"one surface, one padding, every step" - the same class of regex error as
Phase 6's. The whole folder was restored from the index and the sound steps
re-run, then local components were annotated with an index-spliced rewrite that
reads only the destructuring.
Verified: tsc 35 -> 22, set-difference showing thirteen removed and none added;
zero errors in any of the 56 files; all 56 emit byte-identical JavaScript;
production bundle byte-identical; npm test 1684/1691 with the same seven
failures; Owliver baseline 59/59; lint 0 errors; baseline artifacts untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
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.
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 <App/>, 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.jsxstill imports a set of pre-redesign Employer/Talent pages (Overview,Apply,WorkerProfile,KrowIdentity,EmployeeDashboard,DesignSystem, …) andlayouts/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.<Name>.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:
// 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 <HotToaster> 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 ssrLoadModules 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 foundfails.npm run typecheck— 59 errors, all pre-existing JSDoc/inference gaps inlib/krowAi.js,lib/skills/*,lib/positionModel.jsand 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.