AMS Frontend
React admin dashboard — Vite, TypeScript, TanStack Router, TanStack Query, Shadcn UI, Tailwind, Zustand. GraphQL via OIDC auth (Keycloak).
Key Commands
- Dev:
pnpm run dev(port 5173) — kill all other Vite processes first:ps aux | grep vite→kill -9 <PIDs> - Build:
pnpm run build| Lint:pnpm run lint| Format:pnpm run format - Codegen:
npx graphql-codegen(after schema changes → updatessrc/graphql/generated/)
Build errors vs. warnings: See
docu/docs/ai/claude/rules/frontend-build.mdfor what must be fixed, what to ignore (chunk size warnings), known intentionalas anypatterns, and how to confirm a clean build.
Project Conventions
- Imports:
@/alias forsrc/ - GraphQL:
TypedDocumentStringfrom generated types, OIDC token fromuseAuth().user?.access_token - Queries:
useGraphQLQuery+ centralized keys fromsrc/lib/query-keys.ts - Mutations:
useGraphQLMutationNewwith selector - Auth:
RequireAuthwraps all routes; authenticated routes underroutes/_authenticated/ - Workspaces:
useWorkspace()context for active workspace - Tables: Follow
src/features/members/andsrc/features/applicants/for CRUD patterns - Routing:
getRouteApi('/path/').useParams()— no path duplication - i18n:
src/lib/i18n/translations/(en.ts, ja.ts, de.ts), uset()hook — all user-facing text must be translated - Styling: Tailwind + custom theme variables in
src/styles/theme.css - Error handling:
src/lib/handle-server-error.ts, 401 redirects to sign-in
Layout & UI
- Page headers:
h2 text-2xl font-bold tracking-tight, layoutflex flex-wrap items-end justify-between gap-2 - Full-width pages:
<Main fixed fluid> - Sidebar layouts:
skipMainWrapper={true}inDataTableSimplePageto avoid double Main wrapper - ContentSection:
lg:max-w-4xl| Table pages:fluid={true}inDataTableSimplePage - Sidebar structure:
flex flex-1 flex-col space-y-2 overflow-hidden md:space-y-2 lg:flex-row lg:space-y-0 lg:space-x-12
GraphQL & Query Keys
// Centralized query keys
queryKeys.bootstrap() // ['bootstrap']
queryKeys.workspaces() // ['workspaces']
queryKeys.team(teamId) // ['teams', teamId]
// Mutation invalidation pattern
onSuccess: (data, variables) => {
handleServerSuccess(variables.input, data, t('success.message'), [
queryKeys.bootstrap(),
queryKeys.userAppearance()
])
}
Bootstrap & Workspace Management
App Start → useBootstrap → useWorkspaces → setWorkspaces → loadTeams (org only) → selectActive → App Ready
User Switch → WorkspaceSwitcher → setActiveWorkspace → clearTeams → reloadTeams (org only) → UI Updates
useBootstrap: one-time init + store orchestrationrequireActiveWorkspace: type-safe access to guaranteed non-null workspace
Workspace resolution from URL (use-bootstrap.ts):
/o/.../w/{slug}/...→ match by workspaceslug/o/{orgSlug}/...(no/w/) → match byentitySlug— covers org-level admin paths (settings, users, teams, etc.). Without this, refreshing an admin page redirects to the personal workspace./p/{slug}/...→ match by workspaceslug
Never remove step 2. It is what makes page refresh work on all org-level paths that have no
/w/segment.
Sidebar Navigation
Always read docu/docs/domain/navigation.md before modifying sidebar structure, URLs, or groups.
Key facts:
- Three sidebar types: Staff (org STAFF/MIXED), Client (org CLIENT), Personal
- Staff sidebar has a Workspace / Admin context toggle — nav groups are tagged
context: 'workspace' | 'admin' | 'always' - Configuration group (Documents, Templates, Roles & Permissions) is flat — do not restore the old "Application Setup" collapsible
- Personal sidebar: section 1 = "Overview" (
sidebar.general), section 2 = "Account" (user.sidebar.sectionTitle) - Personal billing uses
getUserBillingNavItems()— triggered by/settings/billingor/settings/subscription.getUserSettingsNavItems()does not include billing/subscription items - Source of truth:
src/components/layout/data/sidebar-data.tsx
Routing
// Standard pattern
import { getRouteApi } from '@tanstack/react-router'
const route = getRouteApi('/_authenticated/w/$workspaceSlug/teams/$teamSlug/members/')
export function Members() {
const { workspaceSlug, teamSlug } = route.useParams()
}
Route folder structure:
src/routes/_authenticated/
├── p/$workspaceSlug/ # Personal workspace
└── o/$workspaceSlug/ # Organization workspace
├── members/
├── applicants/
│ ├── index.tsx
│ └── $applicantId/
│ ├── index.tsx
│ ├── route.tsx
│ └── applications/index.tsx
├── settings/
└── teams/$teamSlug/
├── index.tsx
├── members/
└── route.tsx
- Workspace slugs: unique within a user
- Team slugs: unique within a workspace
- URL:
/p/$workspaceSlug/...(personal) or/o/$orgSlug/...(organization)
Specialist Guides
| Task | Agent/Rule file |
|---|---|
| Notification system | rules/notifications.md |
| CRUD table / listing feature | agents/frontend-table-feature.md |
| Sidebar navigation (settings-style) | agents/frontend-sidebar-feature.md |
| Context providers / shared data | agents/frontend-context-patterns.md |
| Application detail page | agents/application-detail.md |
| Full CRUD feature (listing + detail) | agents/complete-feature.md |
| Listing-only table | agents/simple-table.md |
| Multi-step wizard form | agents/create-application-process.md |
| Viewport-locked split panel | agents/frontend-split-panel.md |
| Messages inbox | agents/messages-feature.md |
| Template statuses / ReactFlow workflow | .claude/agents/workflow-statuses-feature.md |