# BW Pre-Heading — Architecture

## Overview

Single-block plugin. One PHP entry point registers the block from its `block.json` manifest; everything else is self-contained inside `blocks/bw-pre-heading/`. The block is server-rendered so the stored post content is just `<!-- wp:bw/pre-heading {...attributes} /-->` — the final HTML is regenerated on every page load from the current PHP template. That means CSS or markup tweaks in a future plugin version apply retroactively to every existing use of the block, with no content migration needed.

## File Layout

```
wp-content/plugins/bw-pre-heading/
├── bw-pre-heading.php            Main file — plugin header, constants, hooks
├── uninstall.php                 No-op (no persistent state to clean up)
├── README.md                     User-facing
├── CLAUDE.md                     Dev guide
├── CHANGELOG.md                  Keep-a-Changelog
├── LICENSE                       GPL-2.0-or-later
├── blocks/
│   └── bw-pre-heading/
│       ├── block.json            Manifest, attributes, asset file refs
│       ├── index.js              Editor UI — window.wp.* globals, no JSX
│       ├── index.asset.php       Declares editor script dependencies
│       ├── render.php            Server-side render callback
│       ├── style.css             Frontend + editor shared styles
│       └── editor.css            Editor-only tweaks
└── docs/                         Dev docs, not shipped to end users
```

## Core Components

| Component | Responsibility | File |
|---|---|---|
| `bw_pre_heading_register_block()` | Registers the block from block.json on `init` | `bw-pre-heading.php` |
| `bw_pre_heading_load_textdomain()` | Loads translations on `plugins_loaded` | `bw-pre-heading.php` |
| Editor UI | InspectorControls: two color dropdowns + alignment dropdown; inline RichText for the label | `blocks/bw-pre-heading/index.js` |
| Render template | Validates attributes against enum, emits the eyebrow markup | `blocks/bw-pre-heading/render.php` |

## Attributes

Declared in `block.json` under `"attributes"`:

| Name | Type | Default | Constraint |
|---|---|---|---|
| `eyebrowText` | string | `""` (empty; RichText placeholder shows `Enter text here` until typed) | plain text (RichText allowedFormats: []) |
| `promptColor` | string | `"#f58220"` | enum: `["#f58220", "#FFFFFF"]` |
| `textColor` | string | `"#f58220"` | enum: `["#f58220", "#FFFFFF"]` |
| `alignment` | string | `"left"` | enum: `["left", "center", "right"]` |

## Hooks

### Actions
- `init` — `bw_pre_heading_register_block` registers the block.
- `plugins_loaded` — `bw_pre_heading_load_textdomain` loads translations.

### Filters
None.

## Data Storage

No options, transients, custom tables, or post meta. Attribute values are serialized inside post content as standard block-comment delimiters.

## External Dependencies

None. Uses only built-in WordPress script handles (`wp-blocks`, `wp-element`, `wp-block-editor`, `wp-components`, `wp-i18n`) declared in `blocks/bw-pre-heading/index.asset.php`.

## Security Notes

- Inline `style=""` values are whitelisted in `render.php` against `block.json` enum lists before being echoed through `esc_attr()`.
- Eyebrow text is filtered through `wp_kses( $text, array() )` to strip any HTML while preserving entities, even though the editor's RichText is configured with `allowedFormats: []`.
- Wrapper attributes come from WordPress's own `get_block_wrapper_attributes()` which handles escaping.
