| name | client-react-router |
| description | Use when adding or changing routes in apps/client. |
Generated from ai/registry.json. Do not edit manually.
Canonical skill: ../../../ai/skills/client-react-router.md.
Referenced context:
../../../ai/rules/client-routing-rules.md
../../../ai/rules/client-component-rules.md
../../../ai/architecture/client-app.md
This file is compiled from canonical AI knowledge files. Edit canonical files under ai, then run npm run ai:sync.
Compiled AI Skill: client-react-router
Canonical Skill: ai/skills/client-react-router.md
Client React Router
Use this skill when adding or changing routing in apps/client.
Goal
Add or change routes without breaking provider ownership, direct URL loading, or page/component boundaries.
Read First
ai/rules/client-routing-rules.md
ai/rules/client-component-rules.md
ai/architecture/client-app.md
Workflow
- Inspect
src/main.tsx, src/router.tsx, and the target page before editing.
- Put route definitions in
src/router.tsx.
- Put final route screens under
src/pages and route layout wrappers under src/layouts.
- Move reusable UI into
src/components only when it is shared or clarifies composition.
- Use internal router navigation for app routes and plain anchors only for external URLs.
Expected Output
- Every route can load from a cold URL.
- Top-level providers remain outside route-specific UI.
- Route components compose page sections instead of becoming broad shared components.
Verification
- Run
npm --workspace @capture-flag/client run build.
- Manually check
/ and every new route from a cold page load when feasible.
Referenced Context
Reference: ai/rules/client-routing-rules.md
Client Routing Rules
Rules for route changes in apps/client.
Always
- Use React Router for page navigation and route rendering.
- Keep top-level providers in
src/main.tsx.
- Keep the router definition in
src/router.tsx.
- Add route modules as named exports compatible with the local
lazyRoute() helper in src/router.tsx.
- Keep route layouts that own shared shells, sidebars, headers, and nested
<Outlet /> rendering in src/layouts.
- Keep route-level screens under
src/pages.
- Prefer folder route modules with
index.ts named exports for multi-file pages; simple one-file pages may stay as direct page files.
- Prefer
Link and NavLink for internal navigation.
- Preserve direct URL loading for every route.
- Keep
QueryClientProvider outside the router so routes share one React Query client.
- Keep route path builders, search-param keys, and search-param mutation helpers in
src/routing/routePaths.ts.
- Use
src/routing/useRouteContext.ts to compose route params, search params, and server state for selected organization/project/config/environment resources.
- Use React Router search params for route-specific selection state that should survive reloads or links, such as SDK key config/environment selection and audit-log project scope.
Never
- Do not use plain anchors for internal client navigation.
- Do not move reusable UI into route files when it belongs under
src/components.
- Do not place route layout wrappers under
src/pages; reserve src/pages for final route screens.
- Do not add redirects unless they encode real product behavior; existing product redirects are
/ to /organizations and catch-all * to /.
- Do not put route-specific screen logic into shared components.
- Do not duplicate selected route-resource derivation in pages when an existing
useRouteContext helper covers it.
Verification
- Run
npm --workspace @capture-flag/client run build after route changes.
- Manually check
/ and every new route from a cold page load when feasible.
Reference: ai/rules/client-component-rules.md
Client Component Rules
Rules for React component boundaries in apps/client.
Always
- Extract components when UI repeats or a named component makes screen composition clearer.
- Keep shared client components in
src/components.
- Keep context-independent utilities and reusable hooks in
src/core/<category>/<name>.ts, with one exported function or hook per file.
- Keep route layouts that wrap nested routes in
src/layouts/<LayoutName>.
- Keep route-level screens in
src/pages.
- Keep page-specific components and hooks under
src/pages/<PageName> when they are not shared outside that page.
- Import
src/core utilities and hooks from their direct alias file path such as @core/json/formatJson; do not add index.ts barrels under src/core.
- Import shared components directly through aliases such as
@components/Button; do not assume a central src/components/index.ts barrel exists.
- Keep new or substantially changed React component files in
apps/client at or below 400 lines; when touching larger existing files, prefer splitting real UI responsibilities instead of expanding them further.
- Keep component props small and explicit.
- Prefer
children for layout wrappers such as cards, shells, and empty states.
- Prefer explicit JSX over array-driven rendering for a small, fixed set of known UI items.
- Extract custom hooks for repeated or stateful UI behavior.
- Turn repeated form field label/control/hint/error markup into small primitives before copying it again.
- Shared form controls must accept native props, extra
className, aria-invalid, and ref.
- Add or update Storybook stories when adding or changing reusable, layout, page-specific, or route-level React components in
apps/client.
- Keep Storybook stories in a
stories/ child folder next to the component folder they cover, using *.stories.tsx; route/panel grouping stories belong in the owning route folder's stories/ folder.
- Add Storybook controls or actions for every public prop explicitly declared by the component; use controls for data props and actions for callbacks.
- Keep shared component stories in
src/components/stories and member component stories in src/components/members/stories.
- Keep layout stories in
src/layouts/<LayoutName>/stories.
- Keep page or page-section stories in
src/pages/<PageName>/stories or src/pages/<PageName>/<section>/stories.
- Keep cross-page route or panel grouping stories in
src/pages/stories.
- Keep Storybook fixtures, route constants, and API mocks in
src/stories; do not put component stories there.
Never
- Do not extract one-off UI when it adds indirection without reuse, naming clarity, or state-boundary value.
- Do not turn every extraction into a broad component library.
- Do not move page, domain, route, or API-specific helpers into
src/core.
- Do not let route components become god components.
- Do not fix a god component by moving all state and effects into a god provider or god hook.
- Do not build artificial arrays just to render a handful of fixed, known navigation or action items.
- Do not use React Context for mutable UI state.
- Do not copy fetched React Query data into component state just to pass it down.
- Do not leave component prop changes without matching Storybook
args and argTypes updates.
- Do not place
*.stories.tsx beside component files when a nearby stories/ folder is available.
Data-Driven Rendering
- Use arrays and
.map() when rendering API data, dynamic collections, long repeated groups, or lists whose members are not all known at author time.
- Render items directly when the UI is a short, fixed set of known product actions or navigation entries.
- Split large files by ownership such as layout shell, sidebar, selectors, form, list, detail, and helper hooks; do not hide a large component behind a single large hook.
Core Utilities
- Use
src/core/date, src/core/json, src/core/strings, src/core/validation, or src/core/hooks only for helpers that are independent of Capture Flag domain context.
- Keep tests for each core helper in
src/core/<category>/__tests__/<name>.test.ts.
- Prefer direct alias imports such as
@core/json/formatJson over barrels or grouped core imports.
Storybook Layout
- Use one
*.stories.tsx file per component or cohesive route grouping.
- Use shared mock entities and route strings from
src/stories/mockData.ts when stories need realistic Capture Flag data.
- Use the Storybook API mock installed by
.storybook/preview.tsx for route and panel stories that call client API hooks.
- Set route-specific
parameters.router.initialEntries when a story depends on React Router params.
- Prefer
parameters.layout = "fullscreen" for route, layout, shell, and panel stories that need app-width context.
Verification
- Ensure extracted components do not change behavior.
- Check the related Storybook story was added or updated and exposes controls/actions for every public declared prop.
- Run
npm --workspace @capture-flag/client run storybook:build after Storybook, component, or story changes.
- Run
npm --workspace @capture-flag/client run build after component moves.
Reference: ai/architecture/client-app.md
Client App Architecture
apps/client is a Vite React application for the Capture Flag platform UI.
Entry And Routing
src/main.tsx owns top-level providers.
src/router.tsx owns React Router route definitions.
- Route modules are lazy-loaded through the local
lazyRoute() helper and should expose named exports.
src/layouts contains route layout wrappers that render shared shells, navigation, headers, and nested <Outlet /> regions.
src/pages contains route-level screens. Multi-file route screens use folder modules with index.ts named exports; simple one-file screens may stay as direct page files.
src/components contains shared UI used by multiple pages or sections.
src/core contains context-independent client utilities and reusable hooks organized by category.
src/api contains client request functions, React Query hooks, operation barrels, domain barrels, and domain query keys.
src/routing contains route path and route context helpers shared by pages and layouts.
src/stories contains shared Storybook fixtures and API mocks, not component stories.
src/test contains shared Vitest and Testing Library helpers.
PlatformLayout owns the authenticated shell, navigation frame, sidebar/header state, logout flow, and nested route outlet.
- Selected organization, project, config, and environment state is derived by route helpers such as
useRouteContext, not stored in a mutable layout context.
Route Map
/: redirects to /organizations.
/login: GitHub login screen.
/account: authenticated user account details and display name editing.
/organizations and /organizations/:organizationId: organization selection and organization members.
/organizations/:organizationId/projects and /organizations/:organizationId/projects/:projectId: project selection and project members.
/organizations/:organizationId/projects/:projectId/environments: environments for the selected project.
/organizations/:organizationId/projects/:projectId/configs and /organizations/:organizationId/projects/:projectId/configs/:configId: configs and public Config JSON preview.
/organizations/:organizationId/projects/:projectId/configs/:configId/flags: feature flags and remote config values.
/organizations/:organizationId/projects/:projectId/configs/:configId/segments: reusable targeting segments.
/organizations/:organizationId/projects/:projectId/sdk-keys: SDK key lifecycle for project configs/environments, with selected config/environment in ?configId= and ?environmentId= when needed.
/organizations/:organizationId/audit-logs: organization/project audit log timeline, with selected project in ?projectId= when needed.
*: redirects to /.
Data Flow
- React Query owns server state such as authenticated user, organizations, projects, configs, environments, SDK keys, members, and feature flags.
- API operations live under
src/api/<domain>/<operation>.
- Request functions perform HTTP calls and contain no React imports.
- Query and mutation hooks are the UI-facing API.
- Query hooks may use
useQuery or useInfiniteQuery according to the API shape.
- Mutation hooks invalidate affected query keys.
- Mutations that affect derived server state may invalidate query keys from multiple API domains inside the mutation hook.
- API request and hook tests mock successful responses and API errors instead of reaching a real backend.
- Route params, search params, and server state are combined by
useRouteContext for selected resources and redirect-safe navigation paths.
- Permission gates in the client are UX only; API guards and services remain authoritative.
UI Composition
- Route components compose page sections and own screen-level flow.
- Repeated panels, forms, controls, lists, and empty states move into named components.
- React component files should stay at or below 400 lines by splitting real responsibilities into focused files.
- Short, fixed navigation or action sets should be rendered explicitly instead of through artificial arrays.
- Page-specific components stay colocated under the page folder until reused elsewhere.
- Layout-specific components stay colocated under
src/layouts/<LayoutName> until reused by another layout or page.
- Shared primitives live under
src/components and are imported directly through aliases such as @components/Button.
- Member management uses shared
components/members primitives with page-specific role options.
- Feature flag and segment page internals stay colocated under their page folders until reused.
- Storybook stories live in a
stories/ child folder beside the source area they cover.
- Route-level and cross-page grouping stories live in
src/pages/stories.
- Shared Storybook data and fetch mocks live in
src/stories and are reused by stories and page tests.
Shared Core Utilities
- Context-independent helpers and reusable client hooks live under
src/core/<category>/<name>.ts.
- Current core categories include
date, json, strings, validation, and hooks.
- Each core file exports one function or hook; import it from the direct file path such as
src/core/date/toDate.
- Do not add
index.ts barrels under src/core; multiple helpers require multiple explicit imports.
- Core tests live under
src/core/<category>/__tests__/<name>.test.ts next to the category they cover.
- Client tests live in
__tests__/ child folders beside the source area they cover, mirroring Storybook stories/ folders.
- Shared test setup and helpers live under
src/test and provide Testing Library render helpers, React Query providers, and fetch response mocks.
- Coverage is run with
npm --workspace @capture-flag/client run test:coverage and should stay at 90% or higher for configured client targets.
- Page, domain, API, or route-specific helpers stay colocated with their owning feature until they become context-independent reuse.
Client Test And Story Layout
- API request and hook tests live in
src/api/__tests__ and cover operation behavior through mocked fetch responses.
- Shared component tests live in
src/components/__tests__; member component tests live in src/components/members/__tests__.
- Page tests live in
src/pages/<PageName>/__tests__ when they cover one page folder.
- Page subsection tests live in
src/pages/<PageName>/<section>/__tests__ when they cover colocated page internals such as feature flags or segments.
- Root-level client tests live in
src/__tests__ only for source files owned directly by src, such as permissions.ts.
- Shared component stories live in
src/components/stories; member component stories live in src/components/members/stories.
- Layout stories live in
src/layouts/<LayoutName>/stories.
- Page and page subsection stories live in the owning page folder's
stories/ child folder.
- Cross-page route or panel grouping stories live in
src/pages/stories and use route parameters plus shared mock data to render realistic page states.
- Do not place component stories in
src/stories; that folder is reserved for shared Storybook data, routes, and API mocks.
Form Flow
- React Hook Form owns field state and submission.
- Zod schemas parse and validate form values.
- API mutation hooks submit normalized payloads and refresh server state.