# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

IvyForms is a WordPress form builder plugin with three distinct areas:
- `backend/src/` — PHP 7.4+ with dependency injection (PHP-DI)
- `frontend/src/` — Vue 3 + TypeScript, built with Vite
- `e2e/` — Playwright end-to-end tests (see `e2e/CLAUDE.md` for details)

## Commands

All commands run from the repo root. PHP quality checks and `composer install` run **inside the Docker container**.

### Setup
```bash
make setup          # Install all dependencies (PHP + Node + E2E)
make install        # Composer install only (inside container)
```

### Frontend development
```bash
make dev            # Vite dev server with HMR
make build          # Production build + type check
make build-only     # Production build only
make type           # TypeScript type check only
make lint           # oxlint
make format         # oxfmt
```

### PHP quality checks
```bash
make quality-check  # All three checks in sequence (aliases: make q)
make phpcs          # PSR12 style check (aliases: make cs)
make phpcs-fix      # Auto-fix style issues
make phpstan        # Static analysis at level 6 (aliases: make stan)
make phpmd          # Mess detector
```

### E2E tests (run from repo root)
```bash
make test              # Headless — builds frontend + sets up wp-docker environment
make test-remote       # Headless, skips build + setup (remote env)
make test-open         # Same as test but with visible browser
make test-open-remote  # Visible browser, skips build + setup
make test-ui           # Interactive Playwright UI mode
make test-ui-remote    # UI mode, skips build + setup
```

### Docker / WordPress
```bash
make ssh            # Shell into WordPress container
make debug-log      # Tail wp-content/debug.log
make error-log      # Tail PHP error log
make dev-on / dev-off  # Toggle IVYFORMS_DEV constant in ivyforms.php
make dev-status     # Check current dev mode
```

## Architecture

### PHP backend

Follows a strict layered architecture: **Routes → Controllers → Services → Repositories → Entities/ValueObjects**

- **`Plugin.php`** — singleton entry point; builds the PHP-DI container, registers all WordPress hooks
- **`Config/`** — DI wiring: `container.php`, `services.php`, `repository.php`
- **Controllers** — one class per action, always named `handle()`, always return `WP_REST_Response`. Must verify nonce first via `Sanitizer::verifyNonce()`, then sanitize all input via `Sanitizer::` methods before any processing.
- **Services** — business logic, injected into controllers via constructor
- **Repositories** — data access only; always bound via interface in `Config/repository.php`
- **Entities** — wrap ValueObjects and expose domain methods (e.g. `Form`, `Field`)
- **ValueObjects** — immutable data holders; all have a `toArray()` method with properties aligned for readability
- **Factories** — construct Entities from raw arrays

### Frontend

Vue 3 Composition API throughout. Key entry points: `frontend/src/assets/js/admin/admin.ts` and `public/public.ts`.

- **Stores** (`stores/`) — Pinia, one store per domain
- **Composables** (`composables/`) — shared reactive logic
- **Router** (`router/index.ts`) — routes are conditionally loaded based on the WordPress `page` query param (e.g. `ivyforms-builder`)
- **Components** — auto-imported; all use `<script setup lang="ts">` (never Options API)
- **SCSS** — design token system: primitives → themes → maps. Always use `--map-*` CSS variables in components (supports light/dark mode); never hardcode colors.

## Key conventions

### PHP

- **No `ABSPATH` guard** in class files — only in non-class files (interfaces, traits, config)
- **Settings**: always use `SettingsService` — never `get_option('ivyforms_settings')` directly
- **Strings**: use `BackendStrings::get*Strings()['key']` — check the right method before adding a new key; never create duplicates across methods
- **Hook naming**: `ivyforms/category/action_name` (e.g. `ivyforms/form/after_submission`)
- **Variable names**: descriptive (`$formSettings`, not `$arr`; `$serviceClass`, not `$fqcn`)
- **WP_REST_Request param**: name it `$data`, not `$request`

### Frontend

- **No `any` type** — use specific types or `unknown`
- **Translatable strings**: `getLabel('key')` from `useLabels()` composable — never hardcoded UI strings
- **External links**: use `openPricingPage()` from `useProFeatureUpgrade.ts` — never `window.open(url, '_blank')` directly
- **Colors**: use `--map-base-*` CSS variables from the design token system

### Global

- Keep changes small and scoped to the area being touched
- Run the relevant quality checks for the changed area before finalizing
- Preserve architecture boundaries — don't reach across layers
