Instruction file imported from williamisnotdefined/capture-flag (
.github/instructions/client-react-router.instructions.md). Copyright stays with the author.
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.mdai/rules/client-component-rules.mdai/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/pagesand route layout wrappers undersrc/layouts. - Move reusable UI into
src/componentsonly 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 insrc/router.tsx. - Keep route layouts that own shared shells, sidebars, headers, and nested
<Outlet />rendering insrc/layouts. - Keep route-level screens under
src/pages. - Prefer folder route modules with
index.tsnamed exports for multi-file pages; simple one-file pages may stay as direct page files. - Prefer
LinkandNavLinkfor internal navigation. - Preserve direct URL loading for every route.
- Keep
QueryClientProvideroutside 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.tsto 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; reservesrc/pagesfor final route screens. - Do not add redirects unless they encode real product behavior; existing product redirects are
/to/organizationsand 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
useRouteContexthelper covers it.
Verification
- Run
npm --workspace @capture-flag/client run buildafter 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/coreutilities and hooks from their direct alias file path such as@core/json/formatJson; do not addindex.tsbarrels undersrc/core. - Import shared components directly through aliases such as
@components/Button; do not assume a centralsrc/components/index.tsbarrel exists. - Keep new or substantially changed React component files in
apps/clientat 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
childrenfor 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, andref. - 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'sstories/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/storiesand member component stories insrc/components/members/stories. - Keep layout stories in
src/layouts/<LayoutName>/stories. - Keep page or page-section stories in
src/pages/<PageName>/storiesorsrc/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
argsandargTypesupdates. - Do not place
*.stories.tsxbeside component files when a nearbystories/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, orsrc/core/hooksonly 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/formatJsonover barrels or grouped core imports.
Storybook Layout
- Use one
*.stories.tsxfile per component or cohesive route grouping. - Use shared mock entities and route strings from
src/stories/mockData.tswhen stories need realistic Capture Flag data. - Use the Storybook API mock installed by
.storybook/preview.tsxfor route and panel stories that call client API hooks. - Set route-specific
parameters.router.initialEntrieswhen 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:buildafter Storybook, component, or story changes. - Run
npm --workspace @capture-flag/client run buildafter 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.tsxowns top-level providers.src/router.tsxowns React Router route definitions.- Route modules are lazy-loaded through the local
lazyRoute()helper and should expose named exports. src/layoutscontains route layout wrappers that render shared shells, navigation, headers, and nested<Outlet />regions.src/pagescontains route-level screens. Multi-file route screens use folder modules withindex.tsnamed exports; simple one-file screens may stay as direct page files.src/componentscontains shared UI used by multiple pages or sections.src/corecontains context-independent client utilities and reusable hooks organized by category.src/apicontains client request functions, React Query hooks, operation barrels, domain barrels, and domain query keys.src/routingcontains route path and route context helpers shared by pages and layouts.src/storiescontains shared Storybook fixtures and API mocks, not component stories.src/testcontains shared Vitest and Testing Library helpers.PlatformLayoutowns 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./organizationsand/organizations/:organizationId: organization selection and organization members./organizations/:organizationId/projectsand/organizations/:organizationId/projects/:projectId: project selection and project members./organizations/:organizationId/projects/:projectId/environments: environments for the selected project./organizations/:organizationId/projects/:projectId/configsand/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
useQueryoruseInfiniteQueryaccording 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
useRouteContextfor 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/componentsand are imported directly through aliases such as@components/Button. - Member management uses shared
components/membersprimitives 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/storiesand 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, andhooks. - 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.tsbarrels undersrc/core; multiple helpers require multiple explicit imports. - Core tests live under
src/core/<category>/__tests__/<name>.test.tsnext to the category they cover. - Client tests live in
__tests__/child folders beside the source area they cover, mirroring Storybookstories/folders. - Shared test setup and helpers live under
src/testand provide Testing Library render helpers, React Query providers, and fetch response mocks. - Coverage is run with
npm --workspace @capture-flag/client run test:coverageand 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 insrc/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 bysrc, such aspermissions.ts. - Shared component stories live in
src/components/stories; member component stories live insrc/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/storiesand 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.