Skip to content

AI Developer Guide

Instructions for AI coding assistants (Claude Code, Cursor, Copilot, etc.) working on the Bast platform monorepo.


Repository Structure

bast-admin-ui/                    # pnpm monorepo root
├── apps/
│   ├── admin-ui/                 # Main admin dashboard (React 19 + Vite 6, :3000)
│   ├── bff/                      # Backend-for-frontend (Express + tRPC, :4000)
│   ├── chat-ui-beta/             # Chat interface (React 18 + Vite 6, :3001)
│   ├── bast-ux/                  # UX component playground (React 18 + Vite 6, :3002)
│   └── patient-journey/          # Patient journey app (React 19 + Vite 7, :5173)
├── packages/
│   └── shared-styles/            # Design system (tokens, components, CSS)
├── tests/
│   └── e2e/                      # Playwright E2E tests (release-gate, smoke, nightly)
├── openspec/                     # OpenSpec change management (specs, changes, sequencing)
├── docs/                         # Architecture docs, decisions, guides
│   └── architecture/YYYY-MM-DD/  # Date-stamped architecture decision records
└── .claude/
    └── launch.json               # Dev server configurations

Package manager: pnpm with workspaces Branch: dev → remote origin/dev (Bitbucket) Node version: Check .nvmrc if present; otherwise use latest LTS


Starting the Development Environment

All five services should be running during development. Start them using the launch configurations:

# If using Claude Code with preview_start:
preview_start("admin-ui")      # → :3000
preview_start("bff")           # → :4000
preview_start("chat-ui-beta")  # → :3001
preview_start("bast-ux")       # → :3002
preview_start("patient-journey") # → :5173

# If using terminal directly:
cd bast-admin-ui
pnpm --filter @bast/admin-ui dev    # → :3000
pnpm --filter @bast/bff dev         # → :4000
pnpm --filter @bast/chat-ui-beta dev --host 0.0.0.0 --port 3001
pnpm --filter bast-ux dev --host 0.0.0.0 --port 3002
pnpm --filter @bast/patient-journey dev  # → :5173

The BFF proxies /trpc, /api, and /health requests from admin-ui on :3000 to :4000.


Build & Test

# Build
pnpm --filter @bast/admin-ui build

# Unit tests (vitest)
pnpm --filter @bast/admin-ui test          # single run
pnpm --filter @bast/admin-ui test:watch    # watch mode
pnpm --filter @bast/admin-ui test:coverage # with coverage

# UI contract tests (custom scripts)
pnpm --filter @bast/admin-ui test:bast-ux

Always verify build passes after making changes. The build catches TypeScript errors that the dev server may not surface.


Verification Workflow

After editing code while a preview server is running:

  1. Check server logs for build/compilation errors
  2. Check browser console for runtime errors
  3. Take a screenshot to confirm the UI renders correctly
  4. Note: The preview browser does NOT share cookies with the real browser. Auth uses httpOnly cookies set by the BFF, so you cannot log in through the preview. Verify via server logs and code review instead.

Code Conventions

Component Patterns

  • Named exports only — export function ComponentName(), never export default
  • Barrel files — Each component subdirectory has an index.ts re-exporting public components
  • Existing: auth/, customization/, platform/, operational/, setup/, cat-builder/
  • Shared design system components imported from @bast/shared-styles/components
  • NeuCard, NeuButton, NeuInput, NeuSelect, NeuCheckbox, NeuRadio, NeuToggle, NeuAlert
  • Pill, StatusPill, ThemeLifecyclePill, ThemeVersionPill
  • IconActionButton, InfraCard, Button

String Constants

All user-facing strings live in apps/admin-ui/src/constants/strings.ts, organized by feature area: - COMMON, NAV, TOP_BAR, CAT, ORG, DELETE_MODAL, ABOUT, INVITATIONS, SETUP, TERMS, AUTH, ERRORS, FOOTER, INFRA_CARD - Dynamic strings use template functions: TOP_BAR.PENDING_INVITATIONS(count) - When adding new UI text, add the string constant first, then reference it in the component.

Styling

  • Tailwind CSS v4 with design system tokens via CSS custom properties
  • Use Tailwind arbitrary values for CSS variables: bg-[var(--bg-elevated)], text-[var(--primary-text)]
  • Do NOT use inline style={{}} for static CSS variable references — convert to Tailwind arbitrary values
  • Inline styles are acceptable only for truly dynamic/computed values (e.g., runtime-determined colors)

Typography — Semantic Heading Classes

Use semantic heading classes from @bast/shared-styles rather than composing raw type-ramp + font-weight. The hierarchy:

Class Scale Weight Use
screen-title h3 (24px) 200 Page-level titles (<h2>)
heading-3-strong h3 (24px) 700 Dashboard section headings with icon (<h3>)
heading-4 h4 (19px) 500 Wizard steps, modal headers, auth state headings (<h4>)
heading-5 h5 (15px) 600 Card item labels, form sub-sections, empty state titles (<h3>–<h5>)

The raw type ramp classes (type-h1–type-h5) remain available for font-size-only control. For body text use type-body (14px) and type-body-sm (12px).

State Management

  • tRPC + React Query for server state (via BFF)
  • Auth context (useAuth()) for authentication state — tokens stored in httpOnly cookies
  • localStorage accessed exclusively through lib/typed-storage.ts — never use raw localStorage
  • Demo data gated behind DEMO_MODE from lib/demo-mode.ts (env var: VITE_DEMO_MODE, defaults true)

Form Validation

Use shared utilities from lib/validation.ts: - validateEmail(), validateSlug(), validateRequired(), getPasswordRequirements() - EMAIL_REGEX, SLUG_REGEX

Accessibility

  • All icon-only buttons must have aria-label
  • Modals/dialogs must include: role="dialog", aria-modal="true", aria-labelledby="<title-id>"
  • Reference DeleteConfirmModal.tsx as the gold standard (focus trap, initial focus, escape key, full ARIA)
  • AppShell includes a skip-to-content link targeting #main-content

Lazy Loading

Route-level components are lazy-loaded via React.lazy(): - App.tsx — auth screens and ScreensGallery - ContentRenderer.tsx — all dashboard and configuration tab components - Pattern for named exports: const Comp = lazy(() => import('./path').then(m => ({ default: m.Comp }))) - Always wrap in <Suspense fallback={<Spinner />}>


Git & Deployment

Committing

# Stage specific files (preferred over git add -A)
git add <file1> <file2>

# Commit with descriptive message
git commit -m "$(cat <<'EOF'
<type>: <short description>

<optional body>

Co-Authored-By: <agent name> <noreply@anthropic.com>
EOF
)"

Commit types: feat, fix, chore, docs, refactor, test, style

Pushing

git push origin dev

If push fails with permission errors, retry with escalated permissions (the developer will grant access).

Rules

  • Work on the dev branch
  • Never force-push to dev
  • Never commit .env files, credentials, or secrets
  • Include Co-Authored-By when an AI assistant authored or co-authored the changes

Documentation

When to Update Docs

  • After completing a set of related changes — create an architecture decision record
  • After adding new utilities, patterns, or conventions — update this guide
  • After changing project structure — update the Repository Structure section above

Architecture Decision Records

Place in docs/architecture/YYYY-MM-DD/ with a descriptive filename:

# Title

**Date:** Month Year
**Status:** Complete | In Progress
**Scope:** Brief scope description

---

## Summary
Numbered list of changes with one-line descriptions.

## Fix N: Title
### Problem
What was wrong.
### Solution
What was done, which files were changed, what pattern was used.

## Files Changed
Table or list of new and modified files.

## Verification
How to confirm the changes work.

This Guide

Update this file when you establish new conventions, add new tools/utilities, or change workflows. Keep it as the single source of truth for AI assistants working on this codebase.


Key Files Reference

File Purpose
apps/admin-ui/src/constants/strings.ts All UI string constants
apps/admin-ui/src/lib/auth-context.tsx Auth provider, useAuth() hook
apps/admin-ui/src/lib/trpc.ts tRPC client
apps/admin-ui/src/lib/trpc-provider.tsx tRPC provider with auto-refresh
apps/admin-ui/src/lib/platform-hooks.ts Platform data query hooks
apps/admin-ui/src/lib/deployment-hooks.ts Deployment CRUD query hooks
apps/admin-ui/src/lib/api-key-hooks.ts API key management hooks
apps/admin-ui/src/lib/cat-builder-hooks.ts CAT builder data hooks
apps/admin-ui/src/lib/analytics-hooks.ts Analytics dashboard hooks
apps/admin-ui/src/lib/validation.ts Shared form validation
apps/admin-ui/src/lib/authenticated-fetch.ts Shared fetch wrapper with credentials: 'include' for non-tRPC BFF calls
apps/admin-ui/src/lib/org-services-hooks.ts Org services query hooks (KB, deployments, CATs, useApiInfo)
apps/admin-ui/src/lib/typed-storage.ts Typed localStorage wrapper
apps/admin-ui/src/lib/demo-mode.ts DEMO_MODE feature flag
apps/admin-ui/src/lib/csv-export.ts CSV export utility for tabular data download
apps/admin-ui/src/lib/owl-export.ts OWL ontology export utility for knowledge domain graphs
apps/admin-ui/src/hooks/useDemoData.ts Demo data hook
apps/admin-ui/src/components/AppShell.tsx Authenticated layout shell
apps/admin-ui/src/components/ContentRenderer.tsx Route → component resolver
apps/admin-ui/src/App.tsx Router configuration
apps/admin-ui/src/globals.css Global styles + CSS fallbacks
apps/admin-ui/vitest.config.ts Test configuration
packages/shared-styles/src/components/index.ts Design system component exports
apps/bff/src/services/jwt-utils.ts verifyAccessToken<T>() — uses jwt.verify() when JWT_SECRET is set, falls back to jwt.decode() for local dev
.claude/launch.json Dev server configurations

Authenticated Fetch Pattern

Non-tRPC BFF calls (e.g., file upload, rate-limit policies) must use the shared authenticatedFetch helper from lib/authenticated-fetch.ts:

import { authenticatedFetch, getBffBaseUrl } from '@/lib/authenticated-fetch';

const res = await authenticatedFetch(`${getBffBaseUrl()}/api/kb/upload`, {
  method: 'POST',
  body: formData,
});

This ensures credentials: 'include' is always set for cross-origin requests. Never use raw fetch() for BFF endpoints.


Known Issues

  • GuestGuard AuthProvider error — useAuth must be used within an AuthProvider errors appear in console on guest routes (login/register). This is a pre-existing routing issue where GuestGuard renders outside the AuthProvider tree. The UI still renders correctly via the error boundary recovery.

Rules of Hooks — Common Pitfalls

Two violations have burned us. Both cause "Rendered more hooks than during the previous render" on re-render:

  1. Never call a hook inside .map() — hook count changes with array length. Use trpc.useQueries() for dynamic parallel queries:

    // ❌ Broken — hook count varies with ids.length
    const queries = ids.map(id => trpc.foo.useQuery({ id }));
    
    // ✅ Correct — stable hook count
    const queries = trpc.useQueries(t => ids.map(id => t.foo({ id })));
    

  2. Never declare hooks after a conditional return — hooks after an early-return are skipped when the condition is true. Move ALL useState/useEffect/custom hooks above every if (...) return guard.