This child theme of wp-bootstrap (`../wp-bootstrap/` or <https://src.bundespruefstelle.ch/magdev/wp-bootstrap>) extends wp-bootstrap and adds overrides for all theme files of the WooCommerce plugin (`../../plugins/woocommerce/` or <https://github.com/woocommerce/woocommerce.git>). All theme files from the plugin are converted to Bootstrap 5 structures and styles.
- 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)
-`json_encode|raw` in JS context is correct (not XSS) -- Twig auto-escaping would break JSON
-`styles|raw` in PDF exports is backend-generated static CSS, documented with comments
### Translation Ready
All user-facing strings use:
```php
__('Text to translate', 'wc-bootstrap')
_e('Text to translate', 'wc-bootstrap')
```
Text domain: `wc-bootstrap`
#### Text Domain Strategy
<!-- Choose ONE of these strategies based on your project needs: -->
**Option A -- Plugin domain (recommended when plugin provides Twig functions):**
All strings in theme templates use the plugin's `'woocommerce'` domain. This is necessary when the plugin registers `__()` / `_n()` as Twig functions with a default domain, because `load_child_theme_textdomain()` does not resolve at Twig render time.
**Rule:** Always use `'woocommerce'` as the text domain in all Twig templates and PHP files.
**Option B -- Theme domain (when theme handles its own Twig rendering):**
Theme templates use the theme's own `'wc-bootstrap'` domain. Ensure `load_child_theme_textdomain()` is called before Twig rendering.
#### Available Translations
<!-- List your supported locales here -->
-`en_US` -- English (base language, .pot template)
Child theme (PHP + Twig). Use a Twig extractor script that scans `.html.twig` files for `__()`, `_e()`, `_n()`, `_x()` calls, generates `twig-extracted.pot`, then merges with WP-CLI PHP extraction:
Extract untranslated strings per locale into JSON, translate (via AI or manual), then apply with `languages/patch-po.py`.
German variant derivation rules:
-`de_CH` from `de_DE`: replace `ss` where Swiss German uses `ss` instead of `ß`
-`de_DE_informal` from `de_DE`: `Sie` -> `du`, `Ihr` -> `dein`, etc.
-`de_CH_informal`: combine both transformations
##### Step 4 -- Validate and compile
```bash
for po in languages/wc-bootstrap-*.po; do msgfmt --statistics "$po" -o /dev/null; done
for po in languages/wc-bootstrap-*.po; do msgfmt -o "${po%.po}.mo" "$po"; done
```
### 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.
### CRITICAL -- Cross-Project Release Order
When a release touches both the plugin and the theme, ALWAYS release in this order:
1.**Theme first** -- full release workflow (commit -> merge -> tag -> push)
2.**Plugin second** -- update plugin's Dockerfile/config to reference the new theme version, then full release workflow
Reason: the plugin's build references the theme version. If the plugin is released first with a new theme version number, the CI build will attempt to download a theme release that does not yet exist and fail.
---
## 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
1.**MD031**: Blank lines before and after fenced code blocks
2.**MD056**: Table separators must have matching column counts
3.**MD009**: No trailing spaces
4.**MD012**: No multiple consecutive blank lines
5.**MD040**: Fenced code blocks should have a language specified
6.**MD032**: Lists surrounded by blank lines
7.**MD034**: Bare URLs in angle brackets or markdown link syntax
8.**MD013**: Disabled (line length)
9.**MD024**: `siblings_only` mode
## Known Pitfalls & Key Learnings
Recurring bugs and non-obvious behaviours discovered across sessions. **Read this before starting any task.**
### Template Override Mechanism
- Plugin's `Template` class uses Twig `FilesystemLoader`. The `inc/TemplateOverride.php` hooks `init` at priority 20 (after plugin init at 0) and uses `prependPath()` to give child theme templates priority.
- Plugin templates in `../../plugins/woocommerce/templates/` are overridden by matching files in `templates/`.
### Twig Compatibility
- **`|values` filter does NOT exist in Twig 3.x** -- use loop-based sum instead. Twig 4.x only.
- **Twig autoescape + WordPress escape filters = double encoding** -- register all `esc_*` filters with `['is_safe' => ['html']]` option in the plugin's `Template.php`.
- **`wp i18n make-pot` does NOT scan Twig templates** -- any string used exclusively in `.html.twig` files must be manually added to the `.pot` file.
- **`#, fuzzy` silently skips translations at runtime** -- always remove fuzzy flags after verifying translations.
- **`|nl2br|esc_html` is wrong filter order** -- `nl2br` outputs `<br>` tags, then `esc_html` escapes them to `<br>`. Correct: `|esc_html|nl2br`.
- **`function() is defined` is semantically wrong** -- always evaluates truthy since the function is called regardless. Use `{% if function() %}` directly.
- **HTML entities in translated strings get double-encoded** -- `…` in `__()` becomes `&hellip;`. Use Unicode `…` directly or append `|raw` for trusted filter output.
- **Filter chains producing HTML need `|raw`** -- e.g., `term_description()|wptexturize|wpautop|do_shortcode|raw`.
- **WooCommerce `shop_table` borders conflict with Bootstrap `.table`** -- reset with `.woocommerce table.shop_table { border: 0 }` and cell `border-left/right: 0`.
- **WooCommerce gallery JS requires modifier classes** -- `--with-images`, `--without-images`, `--columns-N` on `.woocommerce-product-gallery` + `style="opacity: 0"` for fade-in. Without these, zoom and photoswipe won't initialize.
- **WooCommerce float layout fights Bootstrap grid** -- `div.product div.images/summary` have `float:left/right; width:48%` in `woocommerce-layout.css`. Override with `float: none; width: 100%`.
- **Bootstrap `g-*` gutters add negative top margin** -- `g-4` sets both `--bs-gutter-x` and `--bs-gutter-y`; the `.row` gets `margin-top: calc(-1 * var(--bs-gutter-y))` pulling it upward. Use `gx-*` for horizontal-only gutters when vertical gap isn't desired.
- Security audit checklist (OWASP Top-10): open redirect validation, `|esc_url`/`|esc_attr`/`|esc_js` on all dynamic output, `|wp_kses_post` for rich text, no `innerHTML` with dynamic data.
## Multi-Project Architecture
This child theme is part of a three-project ecosystem. Each project is a separate git repo with its own `CLAUDE.md` and should be worked on in **separate Claude Code sessions**.
- **WooCommerce plugin is read-only.** We have no control over its source code. All customizations happen in the child theme via template overrides and hooks.
- **Docker environment:** Container name is `woocommerce`. Use `docker exec woocommerce ...` for commands and `docker exec woocommerce apache2ctl graceful` to clear OPcache after PHP changes.
4.**Shared Docker environment.** All three projects will be bind-mounted into the same Docker container once it is set up, so changes will be visible live without rebuilds.
The child theme overrides plugin Twig templates by prepending its own `templates/` directory to the Twig `FilesystemLoader` via `TemplateOverride` class (hooks `init` priority 20, after plugin init at priority 0).
#### Twig Template Locations
- **Plugin base templates:** `wp-content/plugins/woocommerce/templates/`
- **My Account layout** uses `col-lg-auto` (nav) + `col-lg` (content) so the nav auto-sizes to its content and the main area fills remaining space. Requires CSS resets for WooCommerce's float-based layout (`float: none; width: auto/100%`) and `max-width: none` on `.woocommerce-account main .woocommerce` to override the plugin's `max-width: 1000px`.
- **My Account navigation** uses `offcanvas-lg offcanvas-start` responsive pattern: full sidebar with icons on desktop (sticky via `position-sticky`), offcanvas slide-in on mobile with toggle button. Icons are mapped per endpoint via a Twig hash.
- **My Account dashboard** uses card-based quick actions (`row row-cols-1 row-cols-md-2 row-cols-xl-3 g-3`) with `bg-primary-subtle` icon containers that adapt to dark mode. Each action card is an `<a class="card">` for full-card clickability.
- **My Account forms** (edit-account, edit-address, lost-password, reset-password) are wrapped in `card shadow-sm` sections with icon headers for visual consistency.
- **Edit forms** use card-based section grouping (e.g., "Personal information" + "Password change" as separate cards) instead of `<fieldset>/<legend>`.
-`templates/myaccount/dashboard.html.twig` — Replaced plain `<p>` tags with welcome card (avatar + greeting) and 5 quick-action cards in responsive grid
-`templates/myaccount/view-order.html.twig` — Replaced `<mark>` tags with summary card using `list-group-flush` and `components/status-badge.html.twig`; notes wrapped in card
-`templates/myaccount/form-edit-account.html.twig` — Wrapped in two card sections (Personal info + Password change) with icons
-`templates/myaccount/form-edit-address.html.twig` — Wrapped in card with dynamic icon (`bi-receipt` billing / `bi-truck` shipping)
-`templates/myaccount/form-lost-password.html.twig` — Wrapped in card with `bi-key` icon
-`templates/myaccount/form-reset-password.html.twig` — Wrapped in card with `bi-shield-lock` icon
-`templates/myaccount/lost-password-confirmation.html.twig` — Added `text-body-secondary` styling and "Back to login" button
-`templates/myaccount/my-account.html.twig` — Changed grid from `col-lg-3`/`col-lg-9` to `col-lg-auto`/`col-lg`
- **Nav column auto-sizing (`col-lg-auto`)**: Fixed-width columns (e.g., `col-lg-3` = 285px) caused label overflow with icons. Auto-sizing lets the nav take exactly the space it needs across locales while the content fills the rest.
- **WooCommerce layout overrides require matching specificity**: The plugin uses `.woocommerce-account .woocommerce-MyAccount-navigation` (specificity `0,2,0`) — single-class selectors don't override. Also `.woocommerce-account main .woocommerce` sets `max-width: 1000px` (specificity `0,2,1`) which must be reset to `none`.
- **`offcanvas-lg` over `d-none d-lg-block`**: Bootstrap's responsive offcanvas natively handles the desktop/mobile switch without duplicating nav markup. The toggle button uses `d-lg-none` visibility.
- **`bg-primary-subtle` for icon containers**: These Bootstrap 5.3 contextual utilities automatically adapt to dark mode, unlike hardcoded colors.
- **Welcome message restructured**: Separated greeting from logout link instead of using WooCommerce's default inline-linked `__()` string. This gives full control over card layout and avoids translated strings containing HTML structure assumptions.
**Scope:** Created Bootstrap 5 two-column layout for single product pages. Then audited all ~90 Twig templates for WooCommerce CSS quirks, Twig escaping bugs, and missing Bootstrap styling.
**Single product layout (3 files):**
-`woocommerce/content-single-product.php` — Bridge file for `wc_get_template_part()` interception