# CLAUDE.md

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

## Overview

BlockRegistry is a Laravel module providing ACF-style block management for the MTC Pharmacy platform. It lets admins define reusable content blocks with configurable fields, assign them to page templates, and manage block instances on pages, products, and categories.

## Commands

Schema migrations live in `database/migrations/` and run via `make migrate` / `php artisan migrate`.

Block definitions are managed as JSON files in `blocks/` and synced via:
- `php artisan blocks:export {slug?} {--all}` — export DB definitions to JSON (blocked on production)
- `php artisan blocks:import {slug?} {--all} {--force}` — import JSON definitions into DB
- `php artisan blocks:diff {slug?}` — show differences between DB and JSON files

The admin sync page is at `/admin/blocks/sync`.

On deploy, `blocks:import --all --force` runs automatically after migrations (see `deploy.sh`).

## Architecture

### Bootstrap

The module boots via `BlockRegistryServiceProvider` (registered in `config/app.php`). It registers service singletons, loads routes, migrations, and artisan commands. Legacy hooks in `hooks.php` register Twig functions and add admin tabs.

### Two-Layer Data Model

**Definition layer** — what a block *is*:
- `BlockRegistry` — block definitions (name, slug, icon, optional Twig template path)
- `BlockRegistryField` — field definitions with self-referencing `parent_field_id` for repeater sub-fields
- `BlockRegistryCategory` — organizational grouping
- `BlockRegistryTemplateAssignment` — whitelist of which blocks are available on which page templates (with `is_default` flag for auto-add)

**Instance layer** — block data on a specific entity:
- `EntityBlock` — a block placed on a page/product/category/global, stores field values as JSON in `data` column
- `entity_type` is one of: `page`, `item`, `category`, `global`
- Global blocks (`entity_type=global`, `entity_id=0`) can be referenced from other entities via `source_block_id`

### JSON Sync System

Block definitions are stored as one JSON file per block in `blocks/{slug}.json`. Each file contains the block's metadata, fields (with nested sub-fields), category, and template assignments. The slug is the identity key.

**Workflow:**
- Edit blocks via admin UI on local or staging
- Export to JSON via admin sync page or `blocks:export`
- Commit JSON files to git branch
- Merge via normal git workflow
- Production auto-imports on deploy

**Key rules:**
- Export is blocked on production (`APP_ENV=production`)
- Import uses `updateOrCreate` by slug — safe to run repeatedly
- Fields are deleted and recreated on import (safe because `entity_blocks.data` keys by field name, not ID)
- Categories are auto-created on import if they don't exist
- Blocks in DB without JSON files are left untouched (never auto-deleted)

### Services

| Service | Responsibility |
|---------|---------------|
| `BlockRegistryService` | CRUD for block definitions, field management, duplication, validation |
| `BlockSyncService` | JSON export, import, diff, and sync status |
| `EntityBlockService` | Runtime block rendering, data access, Twig function implementations |
| `TemplateBlockService` | Template-to-block assignment management |
| `ImageProcessingService` | Image cropping, srcset generation via Intervention Image v3 (Imagick) |
| `BlockTemplateService` | Block template (bundle) management |

### Rendering Pipeline

`EntityBlockService::renderBlock()` resolves the block's Twig template (from `BlockRegistry.template` field), prepares template data, and renders via `app('twig')`. The template receives: `block`, `block_name`, `block_slug`, `block_data` (field data with value, type, label, required), `fields` (simple values), `slides` (repeater data), and `p.pagedata` (legacy-compatible format).

Frontend Twig functions (registered in `hooks.php`):
- `render_block(entityBlockId)` — single block
- `render_entity_blocks(entityType, entityId)` — all published blocks for an entity
- `render_blocks_by_type(entityType, entityId, blockSlug)` — filter by block type
- `get_entity_blocks(entityType, entityId)` — raw block data
- `render_global_block(globalBlockId)` — global block by ID

### Admin Routes

All under `/admin/blocks` with `auth` middleware. The controller handles:
- Block definition CRUD (`/`, `/create`, `/{id}/edit`)
- Entity block data editing (`/data/{id}`)
- Global blocks (`/global`, `/global/create`)
- Categories (`/categories`)
- Sync page (`/sync`) — export, import, diff with status dashboard
- AJAX endpoints for pages (`/ajax/*`) and generic entities (`/ajax/entity/*`) — adding, reordering, toggling status

Admin integration files in `admin/` inject "Blocks" tabs into product, category, and page editors.

### Field Types

`text`, `textarea`, `checkbox`, `select`, `image`, `file`, `datetime`, `colorpicker`, `product_selector`, `page_selector`, `link`, `repeater`

Repeater fields support sub-fields (but not nested repeaters). Image fields support cropping with aspect ratio enforcement, and automatic srcset generation to `/uploads/cache/blocks/{entityBlockId}/`. Icon fields provide a visual picker modal with searchable FA5 Pro icons and custom site icons (from `sites/default/css/base/icons.less`). The icon manifest is generated dynamically via `/admin/blocks/ajax/icon-manifest` — adding a new `&--iconName` entry to `icons.less` automatically makes it available in the picker.

### Conditional Field Visibility

Fields can be conditionally shown/hidden in the data editor based on a `select`-type field's value. This is configured in the block definition editor under each field's "Conditional Visibility" section, or directly in the JSON `settings` column:

```json
{"visibility": {"field": "Layout", "values": ["carousel"]}}
```

**Definition side** (`block-registry-edit.js`): `initVisibilitySelects()` populates the "depends on" dropdown with select-type fields, `updateVisibilityValues()` shows checkboxes for the controller's options. The controller's `parseFieldsFromRequest()` merges visibility into the `settings` JSON alongside any type-specific settings (image, page_selector).

**Data editor side** (`block-registry-edit-data.js`): `initFieldVisibility()` attaches change listeners to controller fields, `applyFieldVisibility()` evaluates `data-visibility-field` / `data-visibility-values` attributes on field wrappers and toggles `display: none`. Hidden fields keep their form values. Called on DOMContentLoaded and after `addRepeaterRow()` for dynamically added rows.

Works for both top-level fields and repeater sub-fields. The `settings` column is not cast on `BlockRegistryField` — it's stored as a JSON string and parsed in templates via `json_decode`.

### JavaScript

Vanilla JS files in `js/` — no build step, loaded directly in admin templates. Key files: `block-registry-edit.js` (block definition editor), `block-registry-edit-data.js` (data editor with image crop), `block-registry-entity-blocks.js` (AJAX block management on pages/entities), `block-registry-drag.js` (drag-and-drop reordering), `block-registry-sync.js` (sync page diff toggle and confirmations).

## Key Patterns

- Block definitions auto-generate UUID and slug on create, cascade-delete related data
- EntityBlock auto-sets `order` on create and initializes `data` with field defaults
- Published/draft status filtering — frontend rendering only shows published blocks
- Git history replaces in-app versioning — the `block_registry_versions` table exists but is unused
- Image paths are validated against directory traversal in `ImageProcessingService`
