Scoped guidance for Claude Code when editing under client/. For repo-wide context (RBAC roles, env vars, AI providers), see ../CLAUDE.md. For the REST API contract, see ../docs/api.md.
- Vite 5 + React 18 + TypeScript (entry: src/main.tsx →
App.tsx) - Tailwind CSS v3 via PostCSS — no CSS-in-JS
- react-router-dom v7 —
BrowserRouterinApp.tsx - Zustand for global state (no Redux, no Context)
- axios via src/api/index.ts — single canonical API client; all pages import from this namespace
- @dnd-kit/core + @dnd-kit/sortable with
PointerSensorfor card and lane DnD - recharts —
LineChartfor burndown (SprintsPage), bar/line for ReportsPage - react-hot-toast — universal toast surface
<ProtectedRoute> wraps every route except /login; it redirects to /login when authStore.user is null. <Layout> is applied to project-scoped pages only (Dashboard + project subpages); ProjectSetupPage and AdminPage render outside Layout.
| Path | Component | Layout |
|---|---|---|
/login |
LoginPage |
— (public) |
/ |
RootRedirect (inline) |
Layout |
/projects/new |
ProjectSetupPage |
— |
/admin |
AdminPage |
— |
/dashboard |
DashboardPage |
Layout |
/projects/:id/board |
BoardPage |
Layout |
/projects/:id/backlog |
BacklogPage |
Layout |
/projects/:id/epics |
EpicsPage |
Layout |
/projects/:id/sprints |
SprintsPage |
Layout |
/projects/:id/tests |
TestSuitePage |
Layout |
/projects/:id/roadmap |
RoadmapPage |
Layout |
/projects/:id/reports |
ReportsPage |
Layout |
/projects/:id/admin |
ProjectAdminPage |
Layout |
/projects/:id/retrospective |
RetrospectivePage (gated by retrospective flag) |
Layout |
/projects/:id/calendar |
CalendarPage (gated by calendar flag) |
Layout |
* |
NotFoundPage |
— |
| File | Purpose |
|---|---|
LoginPage |
Renders Google / GitHub / email-password sign-in, each gated by its auth_* flag. OAuth buttons full-page-navigate to /api/auth/<provider>/start; the password form posts to /api/auth/login and then calls /api/auth/me. Surfaces ?error=… query params (e.g. email_not_verified) as toasts and strips them from the URL |
DashboardPage |
All-projects overview — stats, active sprints, recent activity, project create/edit/delete |
ProjectSetupPage |
Project creation wizard; lane preset chooser or custom-lane builder with live DnD preview |
BoardPage |
Kanban board (lanes + cards) with @dnd-kit DnD; Epic/Feature/Sprint filter dropdowns; ManageLanesModal; opens CardModal on card click |
BacklogPage |
Re-exports from components/; type-filter tabs (All / Epics / Features / Stories / Tasks) + collapsible hierarchy in "All" |
EpicsPage |
Drill-down tree: Epic → Feature → Story → Task with inline create/edit/delete |
SprintsPage |
Re-exports from components/; sprint list with progress bars + recharts burndown; activate/complete |
TestSuitePage |
3-pane: suite navigator / test case table with bulk actions / detail panel; CSV export |
RoadmapPage |
Gantt-style timeline; epic rows with collapsible feature sub-rows; date editor popover |
ReportsPage |
Velocity chart, cycle-time chart, AI token usage chart (gated by ai + ai_usage_reporting), capacity per assignee, CSV export buttons (backlog/sprint/full) |
AdminPage |
Super-admin only; Users, Holidays (global, gated by calendar flag), and Feature Flags (grouped, searchable flag toggles) tabs |
ProjectAdminPage |
Project-scoped admin (accessible to project_admin and super_admin); Members (grant/update/revoke project-level roles with restrictions: project_admins can't change own role/remove self, can't manage other project_admins; search-and-add modal filters out super_admins and existing members), Settings (edit project name/description/color), Lanes (swim lane CRUD with inline rename, done-col toggle, reorder, delete) |
RetrospectivePage |
Per-sprint retro with three fixed columns (Went well / To improve / Action items) and @dnd-kit reorder. Gated by retrospective flag |
CalendarPage |
Month view of sprints, epics, features + holidays/events/vacations. Prev/next/today nav; + on a day or button to add an entry. Gated by calendar flag |
NotFoundPage |
404 fallback |
Layout— sidebar + outlet; renders unread notification bell (clears on click) and failed-test badge driven by SSEHeader— dark blue top bar; project name, active-sprint label, project + sprint dropdowns, "live" indicatorBoard/Card,Board/Column,Board/AddCardForm— DnD-aware Kanban primitives in src/components/Board/Retro/RetroColumn,Retro/RetroNote— DnD-aware retrospective primitives in src/components/Retro/ (mirror the Board/ pattern with three fixed categories instead of dynamic lanes)Calendar/MonthGrid,Calendar/EntryBar,Calendar/EntryFormModal— calendar surface in src/components/Calendar/.EntryFormModalis reused byAdminPage's Holidays tab viaallowedKinds={['holiday']}Board/ManageLanesModal— CRUD lanes (create, rename, recolor, reorder, delete with card-count guard)CardModal— full story editor; 6 tabs: Description (with inline Tasks checklist), Comments, Activity, Tests, Dependencies (blocks / blocked-by), Integrations (linked GitHub PRs and GitLab MRs, gated by flags). Right sidebar holds Sprint, Feature, Assignee, Priority, Story Points selectorsui/ProtectedRoute— auth gate; redirects to/loginwhen no sessionui/FeatureGate—<FeatureGate flag="ai">{children}</FeatureGate>; renders only when the flag resolves trueui/PriorityBadge— colour-coded priority pill (p0–p3 → Critical/High/Medium/Low); used on cards and CardModalui/BoardSkeleton— loading skeleton for the Kanban board; used as Suspense fallbackProfileSettingsModal— user email notification preference toggle; accessible via user avatar dropdown Settings button; readsemail_notificationsfromGET /auth/me, toggled viaPATCH /auth/me; wrapped in<FeatureGate flag="email_notifications">at mount site inLayoutNLItemInput— universal natural-language work item creation (gated byFEATURE_AI). Props:allowedTypes(array of types the parser can return),context(projectId/epicId/laneId),lanes(for story lane picker),cards(for task parent picker),onCreated(refresh callback). State machine: idle → input → loading → preview → confirming. Editable fields appear based on inferred type (priority/assignee for epics/features/stories, dates for sprints/calendar, parent selectors as needed). Wrapped in<FeatureGate flag="ai">at every mount site: BoardPage, EpicsPage, SprintsPage, CalendarPage, DashboardPageProjectAccessModal— per-user table of all projects with role dropdowns; saves inline on changeCreateUserModal(inline in AdminPage) — chainsPOST /usersthenPOST /projects/:id/accessper assigned projectFeatureFlagsTab(inline in AdminPage) — feature flag toggles grouped into collapsible categories (AI, MCP, Authentication, Integrations, Collaboration) with a search box filtering by label/flag name/description; toggle is disabled when the env var isfalse(env is the ceiling)
Stores (src/store/)
| File | Holds |
|---|---|
authStore.ts |
user (includes email_notifications?: boolean), loading; helpers isSuperAdmin(), canReadProject(id), canWriteProject(id), canManageProject(id) |
projectStore.ts |
projects, currentProject; setCurrentProject, fetchProjects() |
boardStore.ts |
lanes, cards, testCaseSummary, taskSummary, linkCount; mutations: moveCard, addCard, updateCard, deleteCard, summary setters, setLinkCount |
retroStore.ts |
retroId, items; mutations: setRetro, addItem, updateItem, removeItem, setItems, clear (mutations only apply when the incoming item belongs to the active retro) |
featureFlagStore.ts |
features (all 22 flags incl. ai, ai_ceremony_digests, ai_writing_assist, ai_planning_assist, ai_project_chat, ai_usage_reporting, auth/integration/MCP flags), loading; setFlags(...), isEnabled(flag). NOTE: several test files (App.tsx fallback, featureFlagStore.test.ts, mcpFeatureFlags.test.ts, FeatureGate.test.tsx, LoginPage.test.tsx) build full Features literals — adding a flag means updating those too |
chatStore.ts |
messagesByProject (per-project chat history, in-memory), streamingProjectId, error; sendMessage(projectId, text) (streams the assistant reply via api/stream.ts), stop(), clear(projectId) |
One canonical client: src/api/index.ts — axios namespace (api.projects.list(), api.cards.create(), …). All components and pages import from the explicit '../api/index' or '../../api/index' path.
When adding new endpoints: add to the axios namespace in api/index.ts. Always use the explicit import path (not '../api').
AI methods and types exported from api/index.ts:
-
api.ai.parseItem(data)—(input: string, context?: { projectId?, epicId?, laneId?, allowedTypes? }) => Promise<ParsedIntent>— parses natural-language input into a work item -
type ParsedIntent = { type, payload } | { type: "unknown", reason }— discriminated union returned by the/api/ai/parse-itemendpoint;typeis one of"epic","feature","story","task","project","sprint","calendar", or"unknown"; each type carries different fields inpayload(e.g., epic/feature/story have priority + assignee, sprint has dates + goal, task has assignee only) -
type NLAllowedType = "epic" | "feature" | "story" | "task" | "project" | "sprint" | "calendar"— work item types the parser can infer -
api.ai.getSprintDigest/generateSprintDigest(sprintId),api.ai.getStandupDigest/generateStandupDigest(projectId, opts?)— ceremony digests (AiDigesttype);api.ai.synthesizeRetro(retroId)→RetroSynthesis -
api.ai.generateAcceptanceCriteria(cardId)→{ criteria: AcceptanceCriterion[] };api.ai.summarizeComments(cardId)→CommentThreadSummary -
api.ai.suggestAssignee(cardId),api.ai.suggestEstimate(cardId),api.ai.planSprint(projectId, sprintId)→SprintPlan,api.ai.groomBacklog(projectId)→BacklogGrooming -
Streaming:
api/stream.tsexportspostSSE(url, body, callbacks, signal?)— raw fetch + ReadableStream consumption of POST SSE endpoints (axios buffers; EventSource is GET-only). Used bychatStoreforPOST /ai/projects/:id/chat.
AI UI surfaces (all behind nested <FeatureGate flag="ai"> + group flag): Reports/SprintDigestPanel (ReportsPage), Board/StandupDigestPanel (BoardPage slide-over), Retro/RetroSynthesisPanel (RetrospectivePage), CardModal/AcceptanceCriteriaGenerator (description tab), CardModal/CommentSummaryCard (comments tab, ≥5 comments), CardModal/SuggestAssigneePopover + SuggestEstimatePopover (modal sidebar), Sprints/SprintPlanModal (planned sprints), Backlog/GroomingPanel (BacklogPage), Chat/ProjectChatPanel (right slide-over mounted in Layout, toggled from Header via Chat/chatPanelContext).
Card Links methods exported from api/index.ts:
api.cardLinks.list(cardId)— fetches all linked PRs/MRs for a cardapi.cardLinks.add(cardId, { url })— adds a new link by URL; parses provider/type/number; optionally fetches metadataapi.cardLinks.remove(cardId, linkId)— deletes a link
Vite proxies /api → localhost:3000 in dev (see vite.config.ts). The axios client sends credentials so the sf_token cookie is included.
The email notifications feature is gated by the email_notifications feature flag and surfaces a preference toggle in the user profile modal (accessible via the user avatar dropdown). The toggle:
- Reads the current user's
email_notificationspreference fromGET /auth/me(fetched onLayoutmount) - Calls
PATCH /auth/mewith{ email_notifications: boolean }on toggle - Is hidden when the
email_notificationsflag is disabled (via<FeatureGate>) - Sends in-app toasts on success/error (uses existing
react-hot-toastsurface)
Users control whether they receive emails for mentions, assignments, and due-date reminders. In-app notifications always appear regardless of the email preference.
Hooks (src/hooks/)
useBoardEvents(projectId)— opens anEventSourceagainst/api/events, dispatchescard:*andepic:*events intoboardStore,retro:item:*intoretroStore, incrementsnotificationcounts onLayout. Use this hook in any new page that needs live board updates. TheCalendarPageopens its ownEventSourceforcalendar:entry:*(because it triggers a refetch instead of patching a store).
- AI-gated UI: wrap with
<FeatureGate flag="ai">; do NOT checkfeatureFlagStoredirectly in JSX. - Toasts:
toast.success(...),toast.error(...)from react-hot-toast. The<Toaster />is mounted once inApp.tsx. - DnD pattern: see
BoardPagefor the canonicalDndContext+useSensor(PointerSensor)+SortableContextsetup. - Tailwind: prefer composition with utility classes; avoid
@applyand custom CSS files. Common patterns:rounded-md border border-slate-200 bg-white shadow-sm, indigo (indigo-600) is the accent, slate for neutrals. - API errors: the response envelope is
{ data, error }. The axios client inapi/index.tsthrows onerrornon-null; catch +toast.error(err.message).