CLAUDE.md
========

Build/Lint/Test Commands:
------------------------
- **Build**: `npm run dist`
- **Lint**: `npm run lint`
- **Test**: `npm test`
- **Single Test**: `npm run test:pw:run -- --grep="Test Name"`
- **Validate**: `npm run validate`
- **Watch**: `npm run watch`

Code Style Guidelines:
----------------------
- **Imports**: Use relative paths with `@` alias for core files
- **Formatting**: Prettier via Babel config
- **Types**: Use JSDoc for type annotations
- **Naming**: PascalCase for components, kebab-case for CSS classes
- **Error Handling**: Use try/catch with GF specific error codes
- **React**: Functional components with hooks
- **CSS**: PostCSS with CSS modules

### CSS/PostCSS Architecture

CSS is written as PostCSS files (`.pcss`) in `assets/css/src/` and must follow `@gravityforms/stylelint-config` rules:

#### File Organization
- **Source**: `assets/css/src/admin/` - organized by feature
- **Extension**: `.pcss` for PostCSS files
- **Build**: Processed via gulp tasks to `css_dist/`

#### Required Style Rules
1. **Properties in alphabetical order** (enforced by stylelint)
2. **BEM naming**: `.block__element--modifier` pattern
3. **Logical properties**: Use `block-size`/`inline-size` instead of `height`/`width`
4. **Design tokens**: Use CSS custom properties from `@gravityforms/design-tokens`
5. **Nesting**: Use PostCSS nesting for child selectors

#### Example
```css
.gform-conversational {
	block-size: calc(100vh - 46px);
	container-type: inline-size;
	
	@media (--gform-admin-viewport-wpadmin) {
		block-size: calc(100vh - 32px);
	}
	
	&__header {  /* BEM with nesting */
		background-color: var(--gform-admin-color-white);
		padding: var(--gform-admin-spacing-3);
	}
}
```

#### PostCSS Features Available
- Import statements with glob patterns
- CSS nesting
- Custom media queries
- Custom properties (CSS variables)
- Modern CSS (stage 0 features)
- Mixins and extends

## JavaScript/React Code Standards

All JavaScript code in `assets/js/src/` must follow ESLint configuration extending: `eslint:recommended`, `plugin:jsx-a11y/recommended`, `plugin:react/recommended`, `plugin:react-hooks/recommended`, `plugin:jsdoc/recommended`.

### Formatting & Style
- **Indentation**: Tabs, including JSX (`indent: ["tab"]`, `react/jsx-indent: ["tab"]`)
- **Quotes**: Single quotes, template literals allowed (`quotes: ["single", {allowTemplateLiterals: true}]`)
- **Semicolons**: Always required (`semi: error`)
- **Trailing commas**: Always in multiline (`comma-dangle: ["always-multiline"]`)
- **Line endings**: Unix style (`linebreak-style: ["unix"]`)
- **EOF**: File must end with newline (`eol-last: error`)
- **Brace style**: 1TBS (`brace-style: ["1tbs"]`)

### Spacing
- **Arrays**: `[ 'item1', 'item2' ]` (`array-bracket-spacing: ["always"]`)
- **Objects**: `{ key: 'value' }` (`object-curly-spacing: ["always"]`)
- **Computed properties**: `obj[ 'key' ]` (`computed-property-spacing: ["always"]`)
- **Template literals**: `` `${ variable }` `` (`template-curly-spacing: ["always"]`)
- **Parentheses**: `fn( arg )` (`space-in-parens: ["always"]`)
- **Functions**: No space before parens (`space-before-function-paren: [{named: "never"}]`)
- **JSX**: `{ content }` with spaces, `prop="value"` without (`react/jsx-curly-spacing: ["always"]`, `react/jsx-equals-spacing: error`)

### Variables & Functions
- **Declaration**: `const` by default, `let` if reassigned, no `var` (`prefer-const: error`, `no-var: error`)
- **Naming**: camelCase except properties (`camelcase: [{properties: "never"}]`)
- **Unused**: Not allowed (`no-unused-vars: error`)
- **Shorthand**: Use object shorthand (`object-shorthand: error`)
- **Arrow functions**: Always use parens `(x) => x` (`arrow-parens: ["always"]`)

### Control Flow
- **Equality**: Strict only (`eqeqeq: error`)
- **Curly braces**: Always required (`curly: ["all"]`)
- **Return**: No else after return (`no-else-return: error`)
- **Ternary**: No nested (`no-nested-ternary: error`)
- **Callbacks**: Must return in array methods (`array-callback-return: error`)

### JSDoc Requirements
**IMPORTANT: ALL JavaScript functions MUST have JSDoc comments. No exceptions.**

```javascript
import { React } from '@gravityforms/libraries';
import { Button } from '@gravityforms/components';

const { useState, useEffect } = React;

/**
 * @description Component description here.
 *
 * @since 1.9.5
 *
 * @param {Object}   props           Component properties.
 * @param {string}   props.title     The title prop.
 * @param {Function} props.onSave    Callback when save is clicked.
 *
 * @return {JSX.Element} The rendered component.
 */
const MyComponent = ( { title, onSave } ) => {
	const [ state, setState ] = useState( '' );
	
	/**
	 * @description Handles form submission.
	 *
	 * @since 1.9.5
	 *
	 * @param {Event} event The submit event.
	 *
	 * @return {void}
	 */
	const handleSubmit = ( event ) => {
		event.preventDefault();
		// WHY: Prevents page reload while processing
		onSave( state );
	};
	
	return <Button onClick={ handleSubmit }>{ title }</Button>;
};
```

**JSDoc Rules**:
- Use `@description` tag style (`jsdoc/require-description: [{descriptionStyle: "tag"}]`)
- Align parameters (`jsdoc/check-line-alignment: ["always", {tags: ["param", "property"]}]`)
- Version from package.json for `@since` tags
- **Required for ALL functions**: Components, event handlers, utilities, helpers

### Inline Comments Guidelines
**Only add meaningful comments that explain WHY, not WHAT.**

#### ✅ GOOD Comments (Explain WHY):
```javascript
// Debounce search to reduce API calls during typing
const debouncedSearch = debounce( handleSearch, 300 );

// Sort by date descending to show newest items first
items.sort( ( a, b ) => b.date - a.date );

// Use useLayoutEffect to measure DOM before paint
useLayoutEffect( () => {
	// Measure element height before browser paints to prevent flicker
	setHeight( ref.current.offsetHeight );
}, [] );

// Fallback to empty array to prevent map errors on null response
const data = response?.items || [];
```

#### ❌ BAD Comments (State the obvious):
```javascript
// Set loading to true
setIsLoading( true );

// Loop through items
items.forEach( item => {
	// Process each item
	processItem( item );
});

// Return the component
return <div>{ content }</div>;

// Increment counter by 1
counter++;
```

**Comment Rules**:
1. Comments should explain business logic, not code syntax
2. Focus on the "why" not the "what"
3. Document edge cases and workarounds
4. Explain non-obvious algorithmic choices
5. Never state what the code already clearly shows

### React Specific
- **Import**: Always from `@gravityforms/libraries`: `import { React } from '@gravityforms/libraries';`
- **Hooks**: Destructure from React object: `const { useState, useEffect } = React;`
- **Components**: Functional with hooks preferred
- **Props**: No prop-types required (`react/prop-types: off`)
- **Keys**: Required in lists (`react/jsx-key: error`)
- **React import**: Not required in scope (`react/react-in-jsx-scope: off`)

### React Best Practices - Avoid Unnecessary Effects
**useEffect should only be used for synchronizing with external systems. Most code doesn't need effects.**

#### DON'T use effects for:
1. **Transforming data for rendering** - Calculate during render instead
```javascript
// ❌ Bad
const [ fullName, setFullName ] = useState( '' );
useEffect( () => {
    setFullName( firstName + ' ' + lastName );
}, [ firstName, lastName ] );

// ✅ Good - Calculate during render
const fullName = firstName + ' ' + lastName;
```

2. **Handling user events** - Use event handlers
```javascript
// ❌ Bad
useEffect( () => {
    if ( submitted ) {
        showNotification( 'Form submitted!' );
    }
}, [ submitted ] );

// ✅ Good - Handle in event
const handleSubmit = () => {
    submitForm();
    showNotification( 'Form submitted!' );
};
```

3. **Expensive calculations** - Use useMemo
```javascript
// ❌ Bad
const [ filtered, setFiltered ] = useState( [] );
useEffect( () => {
    setFiltered( items.filter( item => item.active ) );
}, [ items ] );

// ✅ Good - Use useMemo
const filtered = useMemo( 
    () => items.filter( item => item.active ),
    [ items ]
);
```

#### DO use effects for:
- Fetching data from APIs
- Setting up subscriptions
- Synchronizing with non-React code
- Measuring DOM layout

**Key principle**: Effects should run because the component was displayed, not because of user interactions.

### Library Imports
Check if a library is available in `@gravityforms/libraries` before importing:

**Libraries available in @gravityforms/libraries:**
- `React`, `ReactDOM`, `PropTypes`
- `classnames`
- `zustand` (as zustand.default), `immer` (as produce)
- `ReactRouter` (with specific exports)
- `SimpleBar`, `ReactCalendar`, `ReactColorful`, `ReactPaginate`
- `ReactDND*` modules, `FileDrop`

```javascript
// For libraries in @gravityforms/libraries:
import { React, ReactDOM, classnames } from '@gravityforms/libraries';
const { useState, useEffect } = React;

// For other libraries, import normally from package.json:
import { __ } from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';
```

**Rule**: Check the @gravityforms/libraries package exports first. If not available there, import normally from the project's dependencies.

### JavaScript Utilities
Always prefer utilities from `@gravityforms/utils` and `@gravityforms/react-utils` over custom implementations:

#### @gravityforms/utils (General JavaScript)
```javascript
import { debounce, deepMerge, sprintf, slugify } from '@gravityforms/utils';

// Common utilities to use:
const merged = deepMerge( defaults, userConfig );
const debouncedSearch = debounce( handleSearch, 300 );
const message = sprintf( 'Hello %s!', userName );
const urlSlug = slugify( title );
```

Key utilities: `deepMerge`, `cloneDeep`, `debounce`, `sprintf`, `escapeHtml`, `localStorage`, `isEqual`, `isEmptyObject`

#### @gravityforms/react-utils (React-specific)
```javascript
import { ConditionalWrapper, useFocusTrap, useCache } from '@gravityforms/react-utils';

// React hooks and components:
const { cache, setCache } = useCache( 'myFeature' );
const focusTrapProps = useFocusTrap( { isActive: isModalOpen } );

// Conditional wrapper component
<ConditionalWrapper
    condition={ shouldWrap }
    wrapper={ children => <Modal>{ children }</Modal> }
>
    { content }
</ConditionalWrapper>
```

Key utilities: `ConditionalWrapper`, `useFocusTrap`, `useCache`, `useHermes`, `create` (state management)

### AJAX Requests
Use `@gravityforms/request` for all AJAX requests. Never use fetch or jQuery.ajax directly:

```javascript
import endpoint from 'ajaxUrl'; // Webpack alias to WordPress ajaxurl
import { post } from '@gravityforms/request';
import { useSnackbar } from '@gravityforms/components/react/admin/modules/SnackBar';

const MyComponent = () => {
    const addSnackbarMessage = useSnackbar();
    
    const handleSubmit = async () => {
        const body = {
            action: 'gsmtp_save_settings',    // WordPress action
            security: config.nonce,           // Always include nonce
            settings: formData,
        };
        
        try {
            const response = await post( { endpoint, body } );
            
            if ( response?.data?.success ) {
                addSnackbarMessage( __( 'Settings saved!', 'gravitysmtp' ) );
            } else {
                addSnackbarMessage( __( 'Failed to save settings', 'gravitysmtp' ), 'error' );
            }
        } catch ( error ) {
            console.error( error );
            addSnackbarMessage( __( 'An error occurred', 'gravitysmtp' ), 'error' );
        }
    };
};
```

**Key points**:
- Import endpoint from 'ajaxUrl' (webpack alias)
- Always include action and security nonce
- Use snackbar for user feedback
- Log errors to console for debugging

### State Management with Zustand
Use the custom `create` wrapper from `@gravityforms/react-utils` for all stores:

```javascript
import { immer as produce } from '@gravityforms/libraries';
import { create } from '@gravityforms/react-utils';
import { getConfig } from '@gravityforms/utils';

// 1. Define initial state
const initialState = {
    items: [],
    isLoading: false,
    selectedId: null,
};

// 2. Define actions (always as a function that receives set)
const storeActions = ( set ) => ( {
    // Simple setters
    setIsLoading: ( val ) => set( () => ( { isLoading: val } ) ),
    
    // Complex updates with immer
    updateItem: ( id, data ) =>
        set( produce( ( draft ) => {
            const item = draft.items.find( ( obj ) => obj.id === id );
            if ( item ) {
                Object.assign( item, data );
            }
        } ) ),
    
    // Multiple state updates
    resetStore: () => set( () => ( { ...initialState } ) ),
} );

// 3. Create store with custom create wrapper
const useStore = create( initialState, storeActions );

export default useStore;
```

**Pattern requirements**:
- Use `create` from `@gravityforms/react-utils` (NOT zustand directly)
- Define `initialState` object separately
- Define `storeActions` as function receiving `set`
- Use `immer` (as produce) for complex state updates
- Export as `useStore` (or feature-specific name)

### Forbidden
- `alert()`, `debugger`, `console.*`, `eval()`, `with` statements
- Bitwise operators, nested ternary, multiple empty lines
- Mixed spaces/tabs, trailing spaces, `var` declarations
