# AllSee Displays — Development Context

## Project Overview

**Project:** AllSee Displays — WooCommerce child theme for a UK B2B dropshipper of AllSee Technologies commercial digital signage displays.

**Stack:** Bedrock WordPress (Composer-managed), ddev local dev (`https://allseereseller.ddev.site`), parent theme `silvanus` (Timber 2.0 / Twig, ACF Pro blocks, Bootstrap 5), child theme `apexl-allsee`.

---

## Silvanus Parent Theme Architecture

Silvanus is a **parent theme framework** at `web/app/themes/silvanus/`. It is NOT a conventional WordPress theme — it uses Timber (Twig templating) and ACF Pro blocks exclusively. Understanding its architecture is essential.

### Core Dependencies
- **Timber 2.0** — Twig template engine for WordPress
- **ACF Pro** — Advanced Custom Fields for block field management
- **Asset Loader plugin** — Manifest-based CSS/JS enqueueing from `dist/manifest.json`
- **Bootstrap 5** — Grid, utilities, and components
- **StoutLogic AcfBuilder** — PHP DSL for ACF field definitions

### Template Hierarchy
Silvanus has only two PHP template files:
- **`index.php`** — Handles all archive/index queries → renders `views/index.twig`
- **`single.php`** — Handles all single post/page queries → renders `views/single-{post_type}.twig` falling back to `views/single.twig`

There is **NO `header.php`, `sidebar.php`, `footer.php`, `page.php`, or `archive.php`**. These are all handled via Twig:
- `views/base.twig` — HTML document wrapper with `{% block header %}`, `{% block content %}`, `{% block footer %}`
- `views/parts/header.twig` — Navbar with logo, contact bar, primary + secondary menus
- `views/parts/footer.twig` — Multi-column footer driven by ACF options
- `views/parts/menu.twig` — Generic menu renderer (navbar-nav, inline, column styles)

### Global Twig Context
Every `Timber::context()` call automatically includes (via `timber/context` filter in `Silvanus.php`):

```
menu              → Timber menu object for 'primary' location
second_menu       → Timber menu for 'second_menu' location
contact_menu      → Path-routed menu (theme_settings routing rules)
logo              → Path-routed logo ID (theme_settings routing rules)
theme_settings    → ACF fields from 'options-theme-settings'
site_settings     → ACF fields from 'options-site-settings'
footer_settings   → ACF fields from 'options-footer-settings'
menus_by_id       → All WP menus keyed by term_id
```

### Menu Locations
Registered in `Silvanus.php`:
- `primary` — Main navigation menu
- `second_menu` — Secondary menu (right side of navbar)
- `contact_menu` — Top contact bar menu (supports path-based routing)
- `policy_menu` — Footer policy links
- `footer_menu` — Footer navigation

### ACF Options Pages
Three options pages under "Site Settings" in WP admin:
1. **Site Settings** (`options-site-settings`) — Company details, social accounts
2. **Theme Settings** (`options-theme-settings`) — Header logo routing, top menu routing, global container setting, global CTAs
3. **Footer Settings** (`options-footer-settings`) — Footer column configuration (logo/tagline, menus, contact, social, text)

### Important: `theme_settings["General Settings"].container`
This global setting controls whether blocks get a Bootstrap `.container` wrapper. If not set, blocks render without a container. The child theme overrides `block-settings.twig` to default to `'container'` when this is empty.

---

## Silvanus Block System

### Block Discovery
Silvanus auto-discovers blocks from `blocks/` directories in both parent and child themes. The scan pattern is:
```
blocks/{category}/{block_name}/{slug}.twig      (required — contains file headers)
blocks/{category}/{block_name}/{slug}.acf.php   (field definitions)
blocks/{category}/{block_name}/{slug}.block.php (optional Module class)
```

Child theme blocks at the same slug override parent blocks.

### Twig File Headers
Every block `.twig` file must start with a comment block containing metadata:
```twig
{#
Title: Block Title
Description: Block description
Category: formatting
Icon: admin-comments
Keywords: keyword1 keyword2
Mode: preview
Align: full
SupportsAlign: full false
#}
```

### Block Rendering (Parent Theme Blocks)
Parent theme blocks use `TimberAcfWpBlocks::timber_blocks_callback()`:
1. Calls `Timber::context()` → gets full global context
2. Adds `$context['fields'] = get_fields()` → block ACF data
3. Applies `timber/acf-gutenberg-blocks-data` filter
4. Renders the `.twig` template

### Block Rendering (Child Theme Blocks)
Child theme blocks use their OWN render callbacks (NOT silvanus's shared callback). The pattern:
```php
'render_callback' => function ( array $block, string $content, bool $is_preview ): void {
    $context = \Timber\Timber::context();        // Gets global context
    $context['block']      = $block;
    $context['is_preview'] = $is_preview;
    $context['fields']     = get_fields() ?: []; // Block ACF fields

    // Flatten fields for cleaner Twig access: {{ heading }} instead of {{ fields.heading }}
    foreach ( $context['fields'] as $key => $value ) {
        $context[ $key ] = $value;
    }

    \Timber\Timber::render( 'blocks/hero/hero-banner.twig', $context );
},
```

**Key difference:** Child blocks flatten fields to top-level context, so templates use `{{ heading }}` not `{{ fields.heading }}`. Parent blocks use `{{ fields.title }}`.

### AcfBlockFields Class
Parent theme blocks define fields using `AcfBlockFields`:
```php
$fields = new AcfBlockFields('Title & Text', __FILE__);
$fields->addContainerSettings();  // Adds vheight + container group
$fields->addText('Title');
$fields->addWysiwyg('Text', ['rows' => 3]);
return $fields->build();
```

The `__call()` magic method auto-generates field names and keys:
- Field name: `generateKey('Title')` → `title`
- Field key: `field_{blockKey}_{fieldName}` → `field_title__text_title`

Child theme blocks use raw `StoutLogic\AcfBuilder\FieldsBuilder` directly and manually define the `block_settings` group.

### Block Settings (Container + VHeight)
Every block should extend `block-settings.twig`:
```twig
{% extends '/views/parts/logic/block-settings.twig' %}
{% block blocksettings %}
  <section class="my-block {{ outer_classes }}">
    <div class="{{ inner_classes }}">
      {# block content #}
    </div>
  </section>
{% endblock %}
```

This provides:
- `outer_classes` → `"silvanus-block silvanus-core-block {vheight-class}"`
- `inner_classes` → `"block-content {container-class}"`

The child theme overrides `block-settings.twig` to default container to `'container'` when `theme_settings` is not configured.

### Core Gutenberg Blocks Are HIDDEN
Silvanus filters `allowed_block_types_all` to only allow `acf/*` blocks. All core WordPress blocks (paragraphs, headings, columns) are hidden in the editor. **All page content must be in ACF blocks.**

### Available Silvanus Blocks
| Block | Slug | Purpose |
|-------|------|---------|
| Title & Text | `title-and-text` | Heading + WYSIWYG + buttons |
| Two Column Media + Text | `two-column-media-text` | Image/video + content side-by-side |
| Hero Plain | `hero-plain` | Page header with title + tagline |
| Columns | `columns` | Grid container (InnerBlocks) |
| Column Single | `column-single` | Responsive column within Columns |
| Content Container | `content-container` | Wrapper for InnerBlocks |
| Single Image | `single-image-block` | Positioned image with caption |
| General CTA | `general-cta` | Global CTA from theme settings |
| Basic Title Link Block Grid | `basic-title-link-block-grid` | Card grid with headers + links |
| Image Slider | `image-slider` | Carousel with images/videos |
| Logo Slider | `logo-slider` | Auto-scrolling logo marquee |
| Testimonial Slider | `testimonial-slider` | Testimonials carousel |
| Latest Posts Block Grid | `latest-posts-block-grid` | Filterable post grid |
| Standard Video | `standard-video` | Upload or embed video |
| Gravity Form | `gravity-form` | Gravity Forms embed |
| Two Column Link Rows | `two-column-link-rows` | Structured link lists |

---

### SCSS Import Order
The import chain is carefully ordered for proper variable/mixin cascading:
1. Bootstrap functions
2. Silvanus variables → **Child variables** (overrides)
3. Bootstrap variables + dark mode
4. Bootstrap maps → mixins → **Child mixins**
5. Bootstrap utilities → Silvanus utilities → Utilities API
6. Full Bootstrap
7. Silvanus main → Silvanus block-editor
8. **Child main** (dark theme component styles)
9. Silvanus parts + blocks
10. **Child block styles**
11. **WooCommerce overrides**

**Critical rule:** Use `@import` convention throughout (not `@use`) because Bootstrap 5 requires it. Silence deprecations in sass-loader config.

---

## Adding a New ACF Block

### 1. Create block directory
```
blocks/{category}/{block-name}/
├── {block-name}.block.php
├── {block-name}.acf.php
└── {block-name}.scss
```

### 2. Register the block (`{block-name}.block.php`)
```php
<?php
declare(strict_types=1);

if ( ! defined( 'ABSPATH' ) ) exit;

require_once __DIR__ . '/{block-name}.acf.php';

if ( function_exists( 'acf_register_block_type' ) ) {
    add_action( 'acf/init', function (): void {
        acf_register_block_type( [
            'name'            => '{block-name}',
            'title'           => __( 'Block Title', 'apexl-allsee' ),
            'description'     => __( 'Block description.', 'apexl-allsee' ),
            'category'        => 'apexl-allsee',
            'icon'            => 'admin-generic',
            'keywords'        => [ 'keyword1', 'keyword2' ],
            'supports'        => [ 'align' => [ 'full', 'wide' ] ],
            'render_callback' => function ( array $block, string $content, bool $is_preview ): void {
                $context = \Timber\Timber::context();
                $context['block']      = $block;
                $context['is_preview'] = $is_preview;
                $context['fields']     = get_fields() ?: [];

                foreach ( $context['fields'] as $key => $value ) {
                    $context[ $key ] = $value;
                }

                \Timber\Timber::render( 'blocks/{category}/{block-name}.twig', $context );
            },
        ] );
    }, 20 );
}
```

### 3. Define ACF fields (`{block-name}.acf.php`)
```php
<?php
declare(strict_types=1);
use StoutLogic\AcfBuilder\FieldsBuilder;

$block = new FieldsBuilder( '{block_name}', [ 'title' => 'Block Title' ] );
$block
    ->addText( 'heading', [ 'label' => 'Heading' ] )
    // ... more fields ...
    ->addGroup( 'block_settings', [ 'layout' => 'row' ] )
        ->addSelect( 'vheight', [
            'label' => 'Vertical Height',
            'choices' => [ '' => 'Ignore', 'has-vh vh-50' => '50%', 'has-vh vh-100' => '100%' ],
        ] )
        ->addSelect( 'container', [
            'label' => 'Container',
            'choices' => [ 'default' => 'Site Default', 'container' => 'Yes', '' => 'No' ],
            'default_value' => 'default',
        ] )
    ->endGroup()
    ->setLocation( 'block', '==', '{category}/{block-name}' );

add_action( 'acf/init', function () use ( $block ): void {
    acf_add_local_field_group( $block->build() );
} );
```

### 4. Create Twig template (`views/blocks/{category}/{block-name}.twig`)
```twig
{#
Title: Block Title
Description: Block description
Category: apexl-allsee
Icon: admin-generic
Keywords: keyword1 keyword2
#}

{% extends '/views/parts/logic/block-settings.twig' %}

{% block blocksettings %}
<section class="my-block {{ outer_classes }}" {% if block.anchor %}id="{{ block.anchor }}"{% endif %}>
  <div class="{{ inner_classes }}">
    {# Block content here — use {{ heading }}, {{ body_text }}, etc. #}
  </div>
</section>
{% endblock %}
```

### 5. Add SCSS import to `assets/styles/style.scss`
```scss
@import "../../blocks/{category}/{block-name}/{block-name}";
```

### 6. Auto-discovery
The block is automatically discovered by `functions.php`'s glob scan at `blocks/*/*/*block.php`. No additional registration needed.

---

### Database Access
```bash
# WP-CLI (from project root)
ddev exec 'wp post get 259 --field=post_content'
ddev exec 'wp post update 259 --post_content="..."'

# Direct MySQL
ddev exec mysql -u db -pdb db -e "SELECT post_content FROM wp_posts WHERE ID=259;"
```

---

## Critical Rules

1. **All page content must be in ACF blocks** — Core WordPress blocks are hidden in the editor. Use silvanus blocks (title-and-text, hero-plain, columns, etc.) for content pages.

2. **Every block must extend `block-settings.twig`** — This provides the container wrapper and vheight classes. Always include the `block_settings` ACF group in field definitions.

3. **WooCommerce templates must use Timber** — Never use `get_header()`/`get_sidebar()` in WooCommerce template overrides. Always render via `Timber::render()` with templates that extend `base.twig`.

4. **Asset enqueueing is handled by silvanus's Asset_Loader plugin** — Do NOT add manual `wp_enqueue_scripts` in functions.php. Build to `dist/` with content-hash filenames and the Asset_Loader reads `manifest.json`.

5. **SCSS uses `@import` not `@use`** — Bootstrap 5 requires it. Silence deprecation warnings in sass-loader config.