From 9975c4613823493fad753139554f2ad72681e02b Mon Sep 17 00:00:00 2001 From: Alexandru-Dan Pop Date: Fri, 8 May 2026 12:04:35 +0300 Subject: [PATCH 1/4] Add React skill --- .claude/skills/react/SKILL.md | 176 ++++++++++++++++++++++++++++++++++ CLAUDE.md | 31 +----- 2 files changed, 181 insertions(+), 26 deletions(-) create mode 100644 .claude/skills/react/SKILL.md diff --git a/.claude/skills/react/SKILL.md b/.claude/skills/react/SKILL.md new file mode 100644 index 0000000000..d841e19e19 --- /dev/null +++ b/.claude/skills/react/SKILL.md @@ -0,0 +1,176 @@ +--- +name: react +description: > + Use when creating or editing frontend React code in react-ui or ui-components packages. + Triggers on any frontend component, hook, or UI work. +--- + +# React & Frontend Development Skill + +When working on frontend code in `packages/react-ui` or `packages/ui-components`, follow these guidelines strictly. + +--- + +## 1. Modular Components + +- Break the design into independent files. Avoid large, single-file outputs. +- Each component should have one clear responsibility. +- Reusable, pure components live in `packages/ui-components` and **must** have Storybook stories in `packages/ui-components/src/stories/`. +- Application-specific components live in `packages/react-ui`. +- Do not make breaking changes to existing component interfaces (props, names) without discussion. + +--- + +## 2. Logic Isolation + +- Move event handlers and business logic into **custom hooks** (`use-*.ts`). +- Follow existing convention: hooks go in `hooks/` directories alongside features (e.g., `features/campaigns/hooks/use-campaign-charts.ts`). +- Keep component files focused on rendering; delegate logic to hooks. +- Extract complex derived state into hooks or utility functions. + +--- + +## 3. Data Fetching — react-query v5 + +- Use `QueryKeys` from `@/app/constants/query-keys.ts` — **never hardcode query key strings**. +- Use `useQuery` for reads, `useMutation` for writes. +- Invalidate related queries after mutations using `queryClient.invalidateQueries`. +- Use the `enabled` option for conditional queries. +- Keep query functions in dedicated API modules (e.g., `campaigns-api.ts`). + +**Example — existing pattern:** + +```tsx +import { QueryKeys } from '@/app/constants/query-keys'; +import { useQuery } from '@tanstack/react-query'; +import { campaignsApi } from '../campaigns-api'; + +export function useCampaignCharts(campaignId: string) { + const { data: chartData } = useQuery({ + queryKey: [QueryKeys.campaignCharts, campaignId], + queryFn: () => campaignsApi.getCharts(campaignId), + }); + + const weekData = useMemo(() => /* derive from chartData */, [chartData]); + + return { chartData, weekData }; +} +``` + +--- + +## 4. React Hooks & Performance + +- **Always extract event handlers** to memoized callbacks using `useCallback` at component scope. +- **Never define inline callbacks** in JSX (`onChange`, `onBlur`, `onClick`, etc.). +- **Use `useMemo`** for expensive computations or derived state. +- **Use `useCallback`** for all event handlers passed as props or used in dependency arrays. +- Place all hooks and memoized functions at the top of the component, after state declarations. + +**BAD** — Inline callbacks: + +```tsx + { + const num = Number.parseInt(e.target.value); + onMaxChange?.(Number.isNaN(num) ? 0 : Math.max(1, num)); + }} + onBlur={(e) => { + if (!e.target.value || Number.parseInt(e.target.value) < 1) { + onMaxChange?.(1); + } + }} +/> +``` + +**GOOD** — Extracted memoized handlers: + +```tsx +const handleMaxChange = useCallback( + (e: React.ChangeEvent) => { + const num = Number.parseInt(e.target.value); + onMaxChange?.(Number.isNaN(num) ? 0 : Math.max(1, num)); + }, + [onMaxChange], +); + +const handleMaxBlur = useCallback( + (e: React.FocusEvent) => { + if (!e.target.value || Number.parseInt(e.target.value) < 1) { + onMaxChange?.(1); + } + }, + [onMaxChange], +); + +return ; +``` + +--- + +## 5. Anti-Patterns Checklist + +❌ **Inline arrow functions in JSX** (creates new function on every render) +❌ **Inline object/array literals in JSX props** (breaks reference equality) +❌ **Complex logic inside render** (hard to test, poor separation of concerns) +❌ **Missing dependency arrays** in `useEffect`/`useCallback`/`useMemo` +❌ **Unnecessary state** (derive from props when possible) +❌ **Hardcoded query key strings** (use `QueryKeys` constants) + +✅ **Extract and memoize** all callbacks with `useCallback` +✅ **Extract complex JSX logic** into separate memoized functions or sub-components +✅ **Derive state** with `useMemo` instead of storing duplicated state +✅ **Keep component functions focused** — one clear responsibility per function +✅ **Move business logic into custom hooks** + +--- + +## 6. Styling + +- Use shadcn components as the foundation. +- Use `cn` utility to group Tailwind classnames: + +```tsx +
+ {t('Sample output data')} +
+``` + +--- + +## 7. Code Quality Constraints + +- **TDD** — red-green-refactor. No production code without a failing test first. +- **SOLID, DRY, Clean Code** — small functions, clear names, no dead code. +- **Pattern consistency** — read existing code in the target area before writing new code. +- Run build, test, lint, type-check iteratively: + +```bash +npx nx test react-ui +npx nx test ui-components +npx nx lint react-ui +npx nx lint ui-components +``` + +--- + +## 8. Project-Specific Context + +- **React 18** with functional components +- **Zustand** for state management +- **react-query v5** (`@tanstack/react-query`) for data fetching +- **shadcn** for UI components +- **Axios** via existing wrapper in `api.ts`; use `qs` package for query strings +- Tests go in `tests/` folders alongside code (Jest) +- Use `qa-agent` subagent for browser testing diff --git a/CLAUDE.md b/CLAUDE.md index 2c4ec8c0f8..69d1f25553 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,6 +11,7 @@ The repository is using nx, with the source code under the "packages" directory. Notable packages (nx projects) include: + - **packages/server/api** - Main Fastify-based API server (port 3000) - **packages/server/shared** - Shared server utilities (logging, caching, encryption) - **packages/engine** - Workflow execution engine (runs as separate service or AWS Lambda) @@ -19,7 +20,7 @@ Notable packages (nx projects) include: - **packages/blocks/** - 50+ integration blocks (AWS, Azure, GCP, Slack, etc.) - **packages/blocks/framework** - Base framework for creating blocks/actions/triggers - **packages/ui-components** - Reusable UI component library (documented in Storybook) - +- **packages/ui-kit** - UI kit with frontend tools, socket context, and API utilities ## Code Style @@ -90,29 +91,7 @@ export function getUserProfile(userId: string): UserProfile { ### Best Practices -- Follow best practices for React hooks -- Prefer small, composable components -- Extract helper functions where possible -- Do not make breaking changes to existing code interfaces (component props, names) without discussion -- Ensure compliance with strict linter setups (including Sonar) -- Use `cn` utility to group Tailwind classnames: - -```tsx -
- {t('Sample output data')} -
-``` +**Always use the `react` skill** when creating or editing frontend code in `react-ui` or `ui-components`. The skill covers component organization, hooks/performance, data fetching, styling, TDD, and anti-patterns. ## Testing @@ -136,7 +115,6 @@ npx nx lint react-ui - Keep commits small and focused on a single change - Write descriptive commit messages that explain what and why, not how - ### Pull Requests #### Size and Scope @@ -155,7 +133,8 @@ npx nx lint react-ui All PRs must reference a linear issue in their body. -Examples: +Examples: + - Fixes OPS-100. - Resolves OPS-101. - Part of CI-102. From bd6af706ac11d68994dc63836743816609cb6f2e Mon Sep 17 00:00:00 2001 From: Alexandru-Dan Pop Date: Fri, 8 May 2026 12:09:56 +0300 Subject: [PATCH 2/4] Update agent files with React skill --- .claude/skills/react/SKILL.md | 1 - AGENTS.md | 67 ++++++++++++----------------------- CLAUDE.md | 3 ++ 3 files changed, 26 insertions(+), 45 deletions(-) diff --git a/.claude/skills/react/SKILL.md b/.claude/skills/react/SKILL.md index d841e19e19..10a1f856cb 100644 --- a/.claude/skills/react/SKILL.md +++ b/.claude/skills/react/SKILL.md @@ -151,7 +151,6 @@ return ; ## 7. Code Quality Constraints -- **TDD** — red-green-refactor. No production code without a failing test first. - **SOLID, DRY, Clean Code** — small functions, clear names, no dead code. - **Pattern consistency** — read existing code in the target area before writing new code. - Run build, test, lint, type-check iteratively: diff --git a/AGENTS.md b/AGENTS.md index 26cd6745d1..9605793399 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,15 +7,25 @@ - Update documentation for user-facing changes - Do not introduce new dependencies without discussion - ## Structure The repository is using nx, with the source code under the "packages" directory. -Use nx to run tests (e.g. npx nx test server-shared). -Run "npx nx lint" before committing. +Notable packages (nx projects) include: + +- **packages/server/api** - Main Fastify-based API server (port 3000) +- **packages/server/shared** - Shared server utilities (logging, caching, encryption) +- **packages/engine** - Workflow execution engine (runs as separate service or AWS Lambda) +- **packages/react-ui** - React frontend application (Vite + TailwindCSS) +- **packages/shared** - Shared types and utilities across frontend/backend +- **packages/blocks/** - 50+ integration blocks (AWS, Azure, GCP, Slack, etc.) +- **packages/blocks/framework** - Base framework for creating blocks/actions/triggers +- **packages/ui-components** - Reusable UI component library (documented in Storybook) +- **packages/ui-kit** - UI kit with frontend tools, socket context, and API utilities ## Code Style +Run "npx nx lint" to verify code style before committing. + ### Formatting - **Indentation:** 2 spaces (TypeScript/JavaScript, shell scripts) @@ -81,45 +91,24 @@ export function getUserProfile(userId: string): UserProfile { ### Best Practices -- Follow best practices for React hooks -- Prefer small, composable components -- Extract helper functions where possible -- Do not make breaking changes to existing code interfaces (component props, names) without discussion -- Ensure compliance with strict linter setups (including Sonar) -- Use `cn` utility to group Tailwind classnames: - -```tsx -
- {t('Sample output data')} -
-``` +**Always use the `react` skill** when creating or editing frontend code in `react-ui` or `ui-components`. The skill covers component organization, hooks/performance, data fetching, styling, TDD, and anti-patterns. ## Testing +- **TDD** — red-green-refactor. No production code without a failing test first. - Use Jest for testing - Place unit tests in a `tests` folder alongside the code - Run tests using Nx commands: ```bash -nx test react-ui -nx test ui-components -nx test-unit server-api -nx test engine -nx lint react-ui +npx nx test react-ui +npx nx test ui-components +npx nx test-unit server-api +npx nx test engine +npx nx lint react-ui ``` -## Git Workflow +## Git and Pull Request Guidelines ### Commits @@ -127,17 +116,6 @@ nx lint react-ui - Keep commits small and focused on a single change - Write descriptive commit messages that explain what and why, not how -**Commit message format:** - -``` -Short summary in imperative mood (under 50 chars) - -More detailed explanation if necessary. Wrap at around 72 -characters. Explain what and why, not how (the code shows that). - -Fixes #issue_number -``` - ### Pull Requests #### Size and Scope @@ -156,7 +134,8 @@ Fixes #issue_number All PRs must reference a linear issue in their body. -Examples: +Examples: + - Fixes OPS-100. - Resolves OPS-101. - Part of CI-102. diff --git a/CLAUDE.md b/CLAUDE.md index 69d1f25553..5f19951373 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -95,10 +95,13 @@ export function getUserProfile(userId: string): UserProfile { ## Testing +- **TDD** — red-green-refactor. No production code without a failing test first. - Use Jest for testing - Place unit tests in a `tests` folder alongside the code - Run tests using Nx commands: +Examples: + ```bash npx nx test react-ui npx nx test ui-components From cae5ba1398de6c9d8ef28c7a4d509c14f7eb385b Mon Sep 17 00:00:00 2001 From: Alexandru-Dan Pop Date: Fri, 8 May 2026 12:25:40 +0300 Subject: [PATCH 3/4] Address PR review comments on react skill - Soften useCallback/inline handler guidance to prefer extraction only when referential stability matters or logic is non-trivial - Add radix 10 to all Number.parseInt calls in examples - Replace non-existent qa-agent reference with generic browser testing guidance - Update anti-patterns checklist to match softened rules Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .claude/skills/react/SKILL.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.claude/skills/react/SKILL.md b/.claude/skills/react/SKILL.md index 10a1f856cb..5ac8f2b329 100644 --- a/.claude/skills/react/SKILL.md +++ b/.claude/skills/react/SKILL.md @@ -61,22 +61,22 @@ export function useCampaignCharts(campaignId: string) { ## 4. React Hooks & Performance -- **Always extract event handlers** to memoized callbacks using `useCallback` at component scope. -- **Never define inline callbacks** in JSX (`onChange`, `onBlur`, `onClick`, etc.). +- **Prefer extracted event handlers** at component scope when they are passed to memoized children, reused across multiple elements, used in dependency arrays, or contain non-trivial logic. +- **Inline JSX callbacks are acceptable** for simple, local interactions when they keep the code clearer and are not causing avoidable re-renders in hot paths. - **Use `useMemo`** for expensive computations or derived state. -- **Use `useCallback`** for all event handlers passed as props or used in dependency arrays. -- Place all hooks and memoized functions at the top of the component, after state declarations. +- **Use `useCallback`** selectively for handlers where referential stability matters (for example, props to memoized children or values used in hook dependency arrays). +- Place hooks and any memoized values/functions near the top of the component, after state declarations, following existing file conventions. -**BAD** — Inline callbacks: +**Prefer extraction when logic is non-trivial or stability matters**: ```tsx { - const num = Number.parseInt(e.target.value); + const num = Number.parseInt(e.target.value, 10); onMaxChange?.(Number.isNaN(num) ? 0 : Math.max(1, num)); }} onBlur={(e) => { - if (!e.target.value || Number.parseInt(e.target.value) < 1) { + if (!e.target.value || Number.parseInt(e.target.value, 10) < 1) { onMaxChange?.(1); } }} @@ -88,7 +88,7 @@ export function useCampaignCharts(campaignId: string) { ```tsx const handleMaxChange = useCallback( (e: React.ChangeEvent) => { - const num = Number.parseInt(e.target.value); + const num = Number.parseInt(e.target.value, 10); onMaxChange?.(Number.isNaN(num) ? 0 : Math.max(1, num)); }, [onMaxChange], @@ -96,7 +96,7 @@ const handleMaxChange = useCallback( const handleMaxBlur = useCallback( (e: React.FocusEvent) => { - if (!e.target.value || Number.parseInt(e.target.value) < 1) { + if (!e.target.value || Number.parseInt(e.target.value, 10) < 1) { onMaxChange?.(1); } }, @@ -110,14 +110,14 @@ return ; ## 5. Anti-Patterns Checklist -❌ **Inline arrow functions in JSX** (creates new function on every render) +❌ **Inline arrow functions in JSX for non-trivial logic or props to memoized children** (can cause avoidable re-renders) ❌ **Inline object/array literals in JSX props** (breaks reference equality) ❌ **Complex logic inside render** (hard to test, poor separation of concerns) ❌ **Missing dependency arrays** in `useEffect`/`useCallback`/`useMemo` ❌ **Unnecessary state** (derive from props when possible) ❌ **Hardcoded query key strings** (use `QueryKeys` constants) -✅ **Extract and memoize** all callbacks with `useCallback` +✅ **Extract and memoize** callbacks with `useCallback` when referential stability matters ✅ **Extract complex JSX logic** into separate memoized functions or sub-components ✅ **Derive state** with `useMemo` instead of storing duplicated state ✅ **Keep component functions focused** — one clear responsibility per function @@ -172,4 +172,4 @@ npx nx lint ui-components - **shadcn** for UI components - **Axios** via existing wrapper in `api.ts`; use `qs` package for query strings - Tests go in `tests/` folders alongside code (Jest) -- Use `qa-agent` subagent for browser testing +- Perform browser testing with the available project tooling before finalizing UI changes From a7b90a8f9dda326639e44800bfbe92ec90497783 Mon Sep 17 00:00:00 2001 From: Alexandru-Dan Pop Date: Fri, 8 May 2026 12:26:33 +0300 Subject: [PATCH 4/4] Generalize useCallback examples in react skill Replace domain-specific Number.parseInt examples with generic inline vs extracted handler patterns that illustrate the guideline more clearly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .claude/skills/react/SKILL.md | 41 +++++++++-------------------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/.claude/skills/react/SKILL.md b/.claude/skills/react/SKILL.md index 5ac8f2b329..5ea927eca7 100644 --- a/.claude/skills/react/SKILL.md +++ b/.claude/skills/react/SKILL.md @@ -70,40 +70,19 @@ export function useCampaignCharts(campaignId: string) { **Prefer extraction when logic is non-trivial or stability matters**: ```tsx - { - const num = Number.parseInt(e.target.value, 10); - onMaxChange?.(Number.isNaN(num) ? 0 : Math.max(1, num)); - }} - onBlur={(e) => { - if (!e.target.value || Number.parseInt(e.target.value, 10) < 1) { - onMaxChange?.(1); - } - }} -/> -``` - -**GOOD** — Extracted memoized handlers: - -```tsx -const handleMaxChange = useCallback( - (e: React.ChangeEvent) => { - const num = Number.parseInt(e.target.value, 10); - onMaxChange?.(Number.isNaN(num) ? 0 : Math.max(1, num)); - }, - [onMaxChange], -); - -const handleMaxBlur = useCallback( - (e: React.FocusEvent) => { - if (!e.target.value || Number.parseInt(e.target.value, 10) < 1) { - onMaxChange?.(1); - } +// ✅ Simple inline callback — fine when the handler is trivial and local +; + +// ✅ Extracted handler — preferred when logic is non-trivial or passed to memoized children +const handleSubmit = useCallback( + (values: FormValues) => { + const sanitized = sanitizeInput(values); + onSubmit?.(sanitized); }, - [onMaxChange], + [onSubmit], ); -return ; +return
; ``` ---