Files
wp-bootstrap/CLAUDE.md

417 lines
23 KiB
Markdown
Raw Normal View History

# WordPress Theme using Bootstrap 5
**Author:** Marco Graetsch
**Author URL:** <https://src.bundespruefstelle.ch/magdev>
**Author Email:** <magdev3.0@gmail.com>
**Repository URL:** <https://src.bundespruefstelle.ch/magdev/wp-bootstrap>
**Issues URL:** <https://src.bundespruefstelle.ch/magdev/wp-bootstrap/issues>
## Project Overview
This WordPress-Theme is built from scratch employing Bootstrap 5. Build modern Websites using the state-of-the-art Framework. All basic WordPress components are converted and are fully working. The theme uses Twig for rendering. It is also a good starting point as base theme for complex WordPress websites.
### Features
- [ ] All native WordPress theme files converted to Boostrap 5
- [ ] Works seemlessly on default WordPress installations
- [ ] Installable via WordPress admin
#### Frontend
- [ ] Fully responsive Design
- [ ] Supports darkmode
#### Administration
- [ ] Compatible with the Gutenberg-Editor
- [ ] Customizable with the Design-Editor
### Key Fact: 100% AI-Generated
This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase was created through AI assistance.
## Temporary Roadmap
**Note for AI Assistants:** Clean this section after the specific features are done or new releases are made. Effective changes are tracked in `CHANGELOG.md`. Do not add completed versions here - document them in the Session History section at the end of this file.
Current version is **v1.0.2**. See `PLAN.md` for details.
## Technical Stack
- **Language:** PHP 8.3.x
- **PHP-Standards:** PSR-4
- **Framework:** Latest WordPress Theme API
- **Template Engine:** Twig 3.0 (via Composer)
- **Frontend:** Bootstrap 5 Javascript & Vanilla JavaScript
- **Styling:** Bootstrap 5 & Custom CSS (if necessary)
- **Dependency Management:** Composer (PHP), npm (JS/CSS)
- **Internationalization:** WordPress i18n (.pot/.po/.mo files)
- **Canonical Plugin Name:** `wp-bootstrap`
### Security Best Practices
- All user inputs are sanitized (integers for quantities/prices)
- Nonce verification on form submissions
- Output escaping in templates (`esc_attr`, `esc_html`, `esc_js`)
- Direct file access prevention via `ABSPATH` check
- XSS-safe DOM construction in JavaScript (no `innerHTML` with user data)
### Translation Ready
All user-facing strings use:
```php
__('Text to translate', 'wp-bootstrap')
_e('Text to translate', 'wp-bootstrap')
```
Text domain: `wp-bootstrap`
#### Translation Template
- Base `.pot` file created: `languages/wp-bootstrap.pot`
- Ready for translation to any locale
- All translatable strings properly marked with text domain
#### Available Translations
- `en_US` - English (United States) [base language - .pot template]
- `de_CH` - German (Switzerland, formal)
There is no need to compile translation to *.mo locally as it will be done in the Gitea CD/CI pipeline
### Create Releases
**Important Git Notes:**
- Default branch while development is `dev`
- Create releases from branch `main` after merging branch `dev`
- Tags should use format `vX.X.X` (e.g., `v1.1.22`), start with v0.1.0
- Use annotated tags (`-a`) not lightweight tags
- **ALWAYS push tags to origin** - CI/CD triggers on tag push
- Commit messages should follow the established format with Claude Code attribution
- `.claude/settings.local.json` changes are typically local-only (stash before rebasing)
**CRITICAL - Release Workflow:**
On every new version, ALWAYS execute this complete workflow:
```bash
# 1. Commit changes to dev branch
git add <files>
git commit -m "Description of changes (vX.X.X)"
# 2. Merge dev to main
git checkout main
git merge dev --no-edit
# 3. Create annotated tag
git tag -a vX.X.X -m "Version X.X.X - Brief description"
# 4. Push everything to origin
git push origin dev main vX.X.X
# 5. Switch back to dev for continued development
git checkout dev
```
Never skip any of these steps. The release is not complete until all branches and the tag are pushed to origin.
---
**For AI Assistants:**
When starting a new session on this project:
1. Read this CLAUDE.md file first
2. Semantic versioning follows the `MAJOR.MINOR.BUGFIX` pattern
3. Check git log for recent changes
4. Verify you're on the `dev` branch before making changes
5. Run `git submodule update --init --recursive` if lib/ is empty (only if submodules present)
6. Run `composer install` if vendor/ is missing
7. Test changes before committing
8. Follow commit message format with Claude Code attribution
9. Update this session history section with learnings
10. Never commit backup files (`*.po~`, `*.bak`, etc.) - check `git status` before committing
11. Follow markdown linting rules (see below)
Always refer to this document when starting work on this project.
### Markdown Linting Rules
When editing CLAUDE.md or other markdown files, follow these rules to avoid linting errors:
1. **MD031 - Blank lines around fenced code blocks**: Always add a blank line before and after fenced code blocks, even when they follow list items. Example of correct format:
- **Item label**:
(blank line here)
\`\`\`php
code example
\`\`\`
(blank line here)
2. **MD056 - Table column count**: Table separators must have matching column counts and proper spacing. Use consistent dash lengths that match column header widths.
3. **MD009 - No trailing spaces**: Remove trailing whitespace from lines
4. **MD012 - No multiple consecutive blank lines**: Use only single blank lines between sections
5. **MD040 - Fenced code blocks should have a language specified**: Always add a language identifier to code blocks (e.g., `txt`, `bash`, `php`). For shortcode examples, use `txt`.
6. **MD032 - Lists should be surrounded by blank lines**: Add a blank line before AND after list blocks, including after bold labels like `**Attributes:**`.
7. **MD034 - Bare URLs**: Wrap URLs in angle brackets (e.g., `<https://example.com>`) or use markdown link syntax `[text](url)`.
8. **Author section formatting**: Use a heading (`### Name`) instead of bold (`**Name**`) for the author name to maintain consistent document structure.
### Build Pipeline
The build uses npm scripts defined in `package.json`:
```bash
# Full build (CI and local)
npm run build
# Development watch mode
npm run dev
```
Build steps (in order):
1. `copy:js` — Copy Bootstrap JS bundle from `node_modules` to `assets/js/`
2. `copy:theme-js` — Copy theme JS (e.g., `dark-mode.js`) from `src/js/` to `assets/js/`
3. `scss` — Compile SCSS (`src/scss/`) to CSS (`assets/css/`)
4. `postcss` — Autoprefixer + cssnano minification → `assets/css/style.min.css`
**CI/CD note:** The Gitea workflow uses `npm install` and `npm run build`.
### Architecture Notes
- **Dark mode:** Uses Bootstrap 5.3 `data-bs-theme` attribute on `<html>`. An inline anti-flash script runs synchronously in `<head>` (via `wp_add_inline_script` with `'before'`), while the full `dark-mode.js` is deferred. Preference stored in `localStorage` key `wp-bootstrap-theme`.
- **Block styles:** Registered via `register_block_style()` with `inline_style` parameter in `functions.php`. Dark mode overrides for alert/card styles are in `src/scss/_custom.scss`.
- **Style variations:** JSON files in `styles/` directory. All 15 variations (7 light, 7 dark, plus default) use the same 10 color slug names (base, contrast, primary, secondary, success, danger, warning, info, light, dark) to ensure patterns work across all schemes.
- **Fonts:** Inter (sans-serif) and Lora (serif) variable fonts bundled as `.woff2` in `assets/fonts/`. Declared via `fontFace` in `theme.json` with `font-display: swap`.
- **Patterns:** PHP files in `patterns/` with WordPress block markup and i18n. Hidden patterns (prefixed `hidden-`) are reusable components not shown in the pattern inserter.
- **Twig frontend rendering:** `TemplateController` hooks `template_redirect` to intercept frontend requests and render Bootstrap 5 HTML via Twig, bypassing FSE block markup. FSE templates remain for the Site Editor. WordPress functions that produce output (`wp_head`, `wp_footer`, `body_class`, `language_attributes`) are captured via `ob_start()`/`ob_get_clean()` and passed to Twig as safe HTML strings.
- **Navigation menus:** `NavWalker` converts flat `wp_get_nav_menu_items()` into a nested tree for Bootstrap dropdown rendering. Falls back to listing published pages when no menu is assigned.
- **Docker development:** WordPress runs in Docker container `jobroom-wordpress`. The theme directory must be bind-mounted via `compose.override.yaml` (absolute path) for live changes to be visible.
## Session History
### Session 11 — v1.0.3 Conditional Page Title (2026-02-11)
**Completed:** Made `<h1>` on page template conditional to prevent double headings when plugins provide their own titles.
**What was fixed:**
- `views/pages/page.html.twig` now wraps `<h1>{{ post.title }}</h1>` in `{% if post.title is not empty %}` guard
- When a plugin passes empty `post.title` via `render_via_theme_twig()`, the theme's `<h1>` is skipped
- Prevents duplicate headings on pages where plugin templates render their own `<h1>` with richer context (icons, badges, meta)
**Key learnings:**
- Plugins that delegate rendering to the parent theme via `TwigService` should be able to opt out of the theme's `<h1>` by passing empty `post.title`
- The `is not empty` Twig test correctly handles both `null` and empty string `''`
### Session 10 — v1.0.2 Title Tag Fix (2026-02-10)
**Completed:** Fixed missing HTML `<title>` tag on all pages rendered by the theme's Twig pipeline.
**What was fixed:**
- Added `add_theme_support('title-tag')` to `wp_bootstrap_setup()` in `functions.php`
**Root cause:**
- The theme's `base.html.twig` calls `{{ wp_head() }}` which fires the `wp_head` action
- WordPress hooks `_wp_render_title_tag()` to `wp_head` at priority 1, which outputs the `<title>` tag
- However, this hook only fires when the theme declares `add_theme_support('title-tag')`
- The theme never made this declaration, so `wp_head()` output included styles and scripts but no `<title>` element
- All pages rendered by `TemplateController` (via `base.html.twig`) were affected
**Key learnings:**
- `add_theme_support('title-tag')` is required even for themes that render `wp_head()` via Twig — WordPress does not output `<title>` without it
- The absence of a `<title>` tag is invisible in the rendered page but affects SEO, browser tab display, and bookmarking
- This support declaration has been standard since WordPress 4.1 and should always be included in `after_setup_theme`
### Session 9 — v1.0.1 Bootstrap Icons (2026-02-09)
**Completed:** Bootstrap Icons web font integration via SCSS build pipeline.
**What was built:**
- Added `bootstrap-icons` npm dependency (v1.13.1)
- Imported Bootstrap Icons SCSS in both `style.scss` and `editor-style.scss`
- Added `$bootstrap-icons-font-src` variable override in `_variables.scss` to point `@font-face` at `assets/fonts/`
- Added `copy:icons` npm script to copy `.woff`/`.woff2` font files from `node_modules` to `assets/fonts/`
- Updated `build` script to include `copy:icons` step
**Key learnings:**
- Bootstrap Icons SCSS uses `$bootstrap-icons-font-src` to allow overriding the `@font-face` `src` declaration — set it before the import to control font file paths
- The existing `--load-path=node_modules` Sass flag resolves `@import "bootstrap-icons/font/bootstrap-icons"` without any extra configuration
- Font files (`.woff2` at 131KB, `.woff` at 176KB) are small enough to serve as web fonts without performance concern
### Session 8 — v1.0.0 Release (2026-02-08)
**Completed:** Sidebar widget area registration, Twig widget rendering with fallback, documentation refresh, v1.0.0 release.
**What was built:**
- `register_sidebar()` for `primary-sidebar` widget area with Bootstrap-styled wrapper markup
- Widget area rendering in `ContextBuilder::getSidebarData()` via `ob_start()` + `dynamic_sidebar()` with fallback to built-in content
- Twig sidebar template conditional: renders WordPress widgets when assigned, falls back to recent posts/search/tags otherwise
- Updated README.md with accurate feature counts (15 variations, 41 patterns, 3 translations, accessibility, RTL, widget area)
- Updated all translation files (.pot, de_CH.po, fr_FR.po) with widget area strings
**Key learnings:**
- `is_active_sidebar()` returns true only when widgets are assigned to the area, making it the right condition for fallback logic
- `dynamic_sidebar()` outputs widget HTML directly, so `ob_start()`/`ob_get_clean()` is needed to capture it for Twig
- Widget area `before_widget`/`after_widget` markup should use Bootstrap utility classes (`widget mb-4`) for consistent spacing
- Widget title markup (`before_title`/`after_title`) should match existing sidebar heading styles (`sidebar-heading h6 text-uppercase fw-semibold`)
### Session 7 — v0.3.2/v0.3.3 Dark Mode & Style Variation Bridge (2026-02-08)
**Completed:** Fixed dark mode rendering conflicts between WordPress global styles and Bootstrap, fixed form element styling in dark mode, bridged style variation colors to Bootstrap CSS custom properties, fixed variation detection to read from correct palette origin.
**What was built:**
- Style variation bridge function (`wp_bootstrap_variation_colors`) that maps WordPress palette colors to Bootstrap CSS custom properties via `wp_enqueue_scripts`
- Helper functions for color manipulation: `wp_bootstrap_hex_to_rgb()`, `wp_bootstrap_build_surface_css()`, `wp_bootstrap_mix_hex()`, `wp_bootstrap_hex_to_rgb_array()`, `wp_bootstrap_relative_luminance()`
- Variation detection comparing `theme` origin palette against hardcoded base defaults (base, contrast, primary)
- Dark mode body override in `_custom.scss` using `!important` to defeat WordPress global styles specificity
- Broad dark mode rules for all native form elements (`select`, `input`, `textarea`) to catch plugin-generated controls
- Fixed `footer-columns.html.twig` to use semantic `bg-body-tertiary` instead of hardcoded `bg-dark text-light`
**Key learnings:**
- WordPress puts style variation colors in the `theme` palette origin, NOT `custom` -- `wp_get_global_settings(['color', 'palette', 'theme'])` returns the base theme.json merged with the active variation
- The `custom` palette origin contains user manual edits from the Site Editor, but its data structure may lack expected `slug`/`color` keys
- To detect an active variation, compare `theme` origin colors against known base theme.json defaults rather than checking for slugs in `custom`
- WordPress `theme.json` `styles.color` generates `body { background-color: var(--wp--preset--color--base) }` directly on `body`, which overrides inherited CSS variables from `html[data-bs-theme="dark"]` -- removing `styles.color` from theme.json is the cleanest fix
- CSS variables defined directly on `body` beat inherited values from `html` due to specificity, requiring `!important` on `html[data-bs-theme="dark"] body` to ensure Bootstrap dark mode works
- Plugin-generated form elements (e.g., `<select class="jr-search-form__filter-select">`) lack Bootstrap classes and need explicit dark mode styling via element selectors
### Session 6 — v0.3.1 Style Variations (2026-02-08)
**Completed:** Added 8 new style variations (4 light, 4 dark) to the Design Editor.
**What was built:**
- 4 new light palettes: Rose (pink/fuchsia), Sand (warm amber/beige), Lavender (soft purple), Mint (fresh teal)
- 4 new dark palettes: Slate (neutral blue-gray), Mocha (coffee/warm brown), Nebula (space teal-cyan), Obsidian (near-black with red)
- All variations follow the established 10-slug color pattern for cross-variation pattern compatibility
**Key learnings:**
- Dark style variations swap base/contrast (dark base, light contrast) and use darker shades for the `light` and `dark` slugs to maintain proper surface hierarchy
- Button and link hover states in dark palettes need explicit `color` and `text` overrides since the default theme.json assumes light base
### Session 5 — v0.3.0 Polish (2026-02-08)
**Completed:** Accessibility audit and fixes, security hardening, performance optimization, RTL language support, French translation, inline style cleanup.
**What was built:**
- Skip-to-content link in `base.html.twig` with visually-hidden CSS class (visible on focus)
- ARIA labels on all `<nav>` elements across 4 header variants, 2 footer variants, and sidebar
- `aria-current="page"` on active dropdown items in all header variants
- `loading="lazy"` on post thumbnails, card images, and comment avatars
- Screen reader announcement (`aria-live="polite"`) in `dark-mode.js` for theme toggle
- Font preload `<link>` tags for Inter and Lora `.woff2` files via `wp_head` priority 1
- RTL stylesheet (`src/scss/rtl.scss`) conditionally loaded when `is_rtl()` is true
- Logical CSS properties (`border-inline-start`, `padding-inline-start`) replacing physical directions in block styles
- French translation (`fr_FR.po`) covering all ~216 translatable strings
- CSS classes replacing inline styles: `.post-thumbnail`, `.card-thumbnail`, `.sidebar-heading`, `.hero-overlay`
- XSS fix in search template: `search_query|e('html')` before `|raw` output
- Explicit `esc_url()` on comment author URLs in Twig
- Updated `.pot` and `de_CH.po` with 4 new accessibility strings
- RTL build step added to npm scripts
**Key learnings:**
- WordPress `is_rtl()` detects RTL locales, allowing conditional stylesheet loading without doubling CSS bundle size
- CSS logical properties (`border-inline-start`, `padding-inline-start`) are the modern approach to RTL support, replacing physical left/right properties
- Twig `|e('html')` filter must be applied *before* concatenation with `|raw` HTML to prevent XSS -- `search_query|e('html')` inside a `|format()` call
- Font preloading via `wp_head` at priority 1 ensures preload hints appear before render-blocking stylesheets
- `aria-live="polite"` with `role="status"` announces dynamic changes to screen readers without interrupting current reading flow
### Session 4 — v0.2.0 Design Editor (2026-02-08)
**Completed:** Full Design Editor compatibility, custom block categories, page templates, header/footer/navigation variations.
**What was built:**
- Enhanced editor stylesheet importing full Bootstrap SCSS for WYSIWYG fidelity
- Editor overrides SCSS (`_editor-overrides.scss`) for alignment and spacing
- Bootstrap JS loaded in block editor via `enqueue_block_editor_assets`
- 3 custom block categories (`block_categories_all` filter): Bootstrap Layout, Components, Navigation
- 3 custom pattern categories: Layout, Components, Navigation
- 6 layout/component patterns: container, 2-col, 3-col, full-width section, card group, accordion
- 3 full-page patterns: about, services, contact
- 4 custom page templates (FSE + Twig): landing, full-width, hero, sidebar
- 2 header variations (FSE parts + patterns + Twig): centered, transparent
- 2 footer variations (FSE parts + patterns + Twig): minimal, multi-column
- 2 navigation patterns: dark navbar, offcanvas
- Offcanvas navigation Twig partial with Bootstrap offcanvas component
- Twig block inheritance in `base.html.twig` for header/footer variant overrides
- Header/footer variant support via `get_theme_mod()` in `ContextBuilder`
- Custom page template routing via `get_page_template_slug()` in `TemplateController`
- Shadow presets, aspect ratios, custom layout values in `theme.json`
- Transparent header and offcanvas dark mode SCSS styles
- Updated translations (`.pot` and `de_CH.po`) with ~70 new translatable strings
**Key learnings:**
- WordPress `block_categories_all` filter (block inserter categories) and `register_block_pattern_category()` (pattern inserter categories) are separate APIs serving different parts of the editor UI
- FSE template parts (`parts/`) use pattern references (`<!-- wp:pattern {"slug":"..."} /-->`) to keep markup in PHP pattern files for i18n support
- Twig blocks (`{% block header %}`) enable page-level templates to override header/footer without modifying `base.html.twig`
- `get_page_template_slug()` returns the custom template slug assigned in the page editor, used in `TemplateController` for routing to the correct Twig template
- Bootstrap SCSS deprecation warnings (Dart Sass 3.0 migration) are upstream issues, not blocking — the build succeeds
### Session 3 — v0.1.1 Bootstrap Frontend Rendering (2026-02-08)
**Completed:** Full Twig-based Bootstrap 5 frontend rendering, replacing FSE block markup on the public-facing site.
**What was built:**
- `TemplateController` class hooking `template_redirect` to render Twig templates for all page types
- `ContextBuilder` class gathering WordPress data (posts, menus, pagination, comments, sidebar, archive info) into structured arrays
- `NavWalker` class converting flat menu items to nested tree for Bootstrap dropdown menus
- 20 Twig templates: base layout, 5 page templates (index, single, page, archive, search, 404), 9 partials (header, footer, pagination, sidebar, comments, search form, dark mode toggle, meta, post navigation), 3 components (post card, grid card, post loop)
- Enhanced `TwigService` with WordPress output-buffering functions, globals, and filters
- Navigation menu locations (primary, footer) with pages fallback
- Comment form Bootstrap styling filter
- README.md project documentation
**Key learnings:**
- `template_redirect` + `exit()` cleanly bypasses FSE rendering on frontend while preserving Site Editor functionality
- WordPress functions that produce output (`wp_head`, `wp_footer`, `body_class`, `language_attributes`) must be captured via `ob_start()`/`ob_get_clean()` for use in Twig, and marked with `is_safe => html`
- With `optimize-autoloader: true` in `composer.json`, new PSR-4 classes require `composer dump-autoload` to regenerate the static classmap
- Docker bind mounts require absolute paths in `compose.override.yaml` -- relative paths create empty directories
- Post content is rendered via `apply_filters('the_content', get_the_content())` which processes Gutenberg blocks into standard HTML that Bootstrap CSS handles natively
### Session 2 — v0.1.0 Core Theme (2026-02-08)
**Completed:** Full v0.1.0 milestone implementation.
**What was built:**
- 16 block patterns across 7 new categories (hero, features, CTA, testimonials, pricing, contact, text)
- Dark mode toggle with SVG sun/moon icons, localStorage persistence, `prefers-color-scheme` detection
- 17 custom block styles mapping Bootstrap components to WordPress blocks
- 4 style variations: Ocean, Forest, Sunset, Midnight
- Sidebar template part + "Blog with Sidebar" custom template
- Inter + Lora variable web fonts with Display font size
**Key learnings:**
- CI uses `npm install` / `npm run build``yarn` is not available in the CI runner
- Bootstrap 5 alert colors are hardcoded (not CSS variables), so explicit dark mode overrides are needed in SCSS
- Anti-flash for dark mode requires a synchronous inline script via `wp_add_inline_script('handle', '...', 'before')` — the deferred JS alone causes a flash
- Variable fonts use `fontWeight: "100 900"` range syntax in `theme.json` `fontFace` declarations
- Style variation JSON files must maintain identical color slug names across all variations for patterns to work correctly
- When re-tagging a release, delete the remote tag first (`git push origin :refs/tags/vX.X.X`) then recreate and push
### Session 1 — v0.0.1 Getting Started (2026-02-08)
**Completed:** Initial theme scaffolding, Bootstrap 5 integration, SASS pipeline, Twig setup, CI/CD workflow, basic FSE templates and patterns, i18n support.