Files
wc-bootstrap/CLAUDE.md
magdev 4031a1c8aa
All checks were successful
Create Release Package / PHP Lint (push) Successful in 1m2s
Create Release Package / PHPUnit Tests (push) Successful in 46s
Create Release Package / Build Release (push) Successful in 50s
Add PHPUnit test suite with Brain\Monkey (v0.1.6)
Add test infrastructure for isolated unit testing without WordPress/WooCommerce:
- 27 tests (54 assertions) covering TemplateOverride and WooCommerceExtension
- Brain\Monkey for WordPress function mocking, class stubs for TwigService and WC_Product
- PHPUnit test job added to Gitea CI pipeline between lint and build-release
- Test artifacts excluded from release packages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:48:19 +01:00

37 KiB

WooCommerce Bootstrap

Author: Marco Grätsch Author URL: https://src.bundespruefstelle.ch/magdev Author Email: magdev3.0@gmail.com Repository URL: ssh://git@src.bundespruefstelle.ch:2022/magdev/wc-bootstrap.git Issues URL: ssh://git@src.bundespruefstelle.ch:2022/magdev/wc-bootstrap.git/issues

Project Overview

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.

Technical Stack

  • Language: PHP 8.3.x
  • Framework: Latest WordPress Theme API
  • Template Engine: Twig 3.0 (via Composer)
  • WordPress Base Theme: wp-bootstrap
  • 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)

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)
  • 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:

__('Text to translate', 'wc-bootstrap')
_e('Text to translate', 'wc-bootstrap')

Text domain: wc-bootstrap

Text Domain Strategy

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

  • en_US -- English (base language, .pot template)
  • de_DE -- German (Germany, formal)
  • de_CH -- German (Switzerland, formal)
  • fr_FR -- French (France)

Translation file naming convention: wc-bootstrap-{locale}.po (e.g., wc-bootstrap-de_CH.po)

Compiled .mo files are built by the CI/CD pipeline during releases. For local development:

for po in languages/wc-bootstrap-*.po; do msgfmt -o "${po%.po}.mo" "$po"; done

Updating Translations (Fast JSON Workflow)

Step 1 -- Regenerate .pot files

wp-bootstrap (PHP sources only):

docker exec woocommerce-wordpress wp i18n make-pot \
  /var/www/html/wp-content/themes/wp-bootstrap \
  /var/www/html/wp-content/themes/wp-bootstrap/languages/wp-bootstrap.pot \
  --allow-root

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:

# Inside languages/ directory:
python3 twig-extractor.py        # generates twig-extracted.pot
docker exec woocommerce-wordpress wp i18n make-pot \
  /var/www/html/wp-content/themes/wc-bootstrap \
  /var/www/html/wp-content/themes/wc-bootstrap/languages/php-extracted.pot \
  --allow-root
msgcat --use-first php-extracted.pot twig-extracted.pot \
  -o wc-bootstrap.pot
rm php-extracted.pot
Step 2 -- Merge new strings into all .po files
for locale in de_CH de_DE fr_FR; do
  msgmerge --update --backup=none --no-fuzzy-matching \
    wc-bootstrap-${locale}.po wc-bootstrap.pot
done
Step 3 -- Translate and apply

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
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:

# 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 &lt;br&gt;. 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 -- &hellip; in __() becomes &amp;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.

Bootstrap 5 vs Plugin CSS Conflicts

  • Plugin CSS may define its own grid systems that override Bootstrap's flexbox row row-cols-*. Add theme CSS overrides as needed.
  • CSS dependency chain: woocommerce -> child theme overrides. Ensures correct cascade.
  • jQuery .show()/.hide() cannot override Bootstrap !important (d-none). Toggle both class and inline style.
  • overflow: visible !important on .wp-block-navigation__container is essential for dropdowns inside block theme navigation.
  • 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 dark mode select backgrounds -- woocommerce.css sets select { background-color: var(--wc-form-color-background, #fff) }. The custom property is never defined for dark mode, so it falls back to #fff. Override with [data-bs-theme="dark"] .woocommerce select.
  • Select2/SelectWoo dark mode -- select2.css hardcodes #fff on selection containers and dropdowns. Needs overrides for .select2-selection, .select2-dropdown, .select2-search__field, and highlighted options.
  • WooCommerce notice CSS specificity -- Uses border-top: 3px solid, background-color: #f6f5f8, and icon font ::before at specificity 0,1,0. Must use .woocommerce .woocommerce-* AND .alert.woocommerce-* (both specificity 0,2,0) to cover notices both inside .woocommerce wrappers and rendered directly by Twig templates.
  • WooCommerce notice focus ring -- woocommerce.js:focus_populate_live_region() adds tabindex="-1" and calls .focus() on the first .woocommerce-message[role="alert"] after 500ms for screen reader announcement. The default browser focus ring appears white in dark mode. Suppress with outline: 0; box-shadow: none since the focus is non-interactive.
  • WooCommerce form input specificity -- .woocommerce form .form-row .input-text (specificity 0,3,1) sets background-color: var(--wc-form-color-background, #fff). This beats theme checkout rules at 0,2,1. Needs [data-bs-theme="dark"] override at 0,4,0.
  • Bootstrap table-light breaks dark mode -- Forces a light background on <thead> regardless of data-bs-theme. Remove it and let Bootstrap's default table styling handle theming.
  • 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.

WooCommerce Variation JS

  • NEVER use HTML disabled attribute on the add-to-cart button. WC's add-to-cart-variation.js only manages CSS classes (disabled, wc-variation-selection-needed) via jQuery .addClass()/.removeClass(). It never removes the HTML disabled attribute. The HTML attribute prevents click events from firing, making the button permanently unclickable.
  • Use CSS classes disabled wc-variation-selection-needed instead. WC's onAddToCart handler checks $button.is('.disabled') (CSS class), not the HTML property.
  • WC variation form requires data-product_id and data-product_variations on <form class="variations_form">. Without these, the JS doesn't initialize.
  • Attribute select name and data-attribute_name must use sanitize_title() on the attribute name (lowercase, hyphens) to match the keys in the variation JSON data.

WooCommerce Variation Select CSS Specificity

  • WC's .woocommerce div.product form.cart .variations select at specificity (0,4,3) uses background shorthand — this resets BOTH background-color and background-image. A dark mode override must exceed (0,4,3) AND override background-image with var(--bs-form-select-bg-img) for Bootstrap's dark-mode-aware chevron SVG.
  • Previous selector [data-bs-theme="dark"] .variations .form-select at (0,3,0) lost the specificity battle.
  • _product_image_gallery post meta may contain IDs pointing to product variations or other non-attachment posts instead of image attachments. wp_get_attachment_url() returns empty/false for these.
  • Always guard thumbnail rendering with {% if thumb_url %} after calling wp_get_attachment_url(image_id).
  • The main product image should be prepended to the gallery strip so users can switch back to it after viewing gallery images.

Double Heading Prevention

  • Parent theme (wp-bootstrap) conditionally skips its <h1> when post.title is empty.
  • Plugin passes empty post.title to parent theme's Twig system.
  • Child theme layout templates always render their own <h1> with page_title context.

Theme Wrapping (_wrapped / _theme_wrapped)

  • _wrapped is set in base.html.twig when _theme_wrapped is true (parent theme provides page shell).
  • Structural blocks (wrapper, breadcrumbs, sidebar, <h1>) are suppressed when wrapped.
  • Functional blocks ({% block scripts %}) must ALWAYS render -- they contain AJAX handlers, JS initialization etc.

Plugin JS Compatibility

  • Theme must preserve plugin's JS-bound CSS class names (e.g., repeater fields, interactive widgets).
  • Theme uses plugin's global JS objects for AJAX URLs, nonces, and i18n strings.
  • Bootstrap 5 data-bs-toggle attributes replace plugin's custom dropdown mechanisms.

Security

  • 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.

Projects

Project Type Location Repository
woocommerce Plugin (read-only) wp-content/plugins/woocommerce/ https://github.com/woocommerce/woocommerce.git
wp-bootstrap Parent Theme wp-content/themes/wp-bootstrap/ https://src.bundespruefstelle.ch/magdev/wp-bootstrap
wc-bootstrap Child Theme wp-content/themes/wc-bootstrap/ ssh://git@src.bundespruefstelle.ch:2022/magdev/wc-bootstrap.git

Dependency Chain

wp-bootstrap (parent theme, Bootstrap 5 FSE + Twig rendering)
  +-- wc-bootstrap (child theme, overrides plugin Twig templates with Bootstrap 5)
        +-- woocommerce (plugin, provides post types, logic, base Twig templates)

Important Constraints

  • 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.

Cross-Project Workflow

  1. One session per project. Each session reads its own CLAUDE.md and stays within its own git repo.
  2. Plugin changes first. When a change affects templates consumed by the theme, make the plugin change first, then switch to the theme session.
  3. Communicate changes explicitly. When switching sessions, describe what changed upstream (new variables, renamed classes, new templates, etc.).
  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.

Template Contract (Plugin -> Theme)

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/
  • Theme overrides: wp-content/themes/wc-bootstrap/templates/ (mirrors plugin structure)
  • Resolution order: Theme templates take priority over plugin templates (Twig prependPath)

Theme Contract (Parent -> Child)

The child theme inherits from wp-bootstrap via WordPress Template: wp-bootstrap declaration.

  • Bootstrap 5: CSS framework and JS loaded by parent theme
  • Bootstrap Icons: Web font available via parent theme's SCSS build
  • Twig rendering: Parent theme's TwigService handles frontend rendering
  • Dark mode: Parent handles via data-bs-theme attribute; child theme inherits
  • Style variations: Color palettes available from parent, bridged to Bootstrap CSS custom properties

Architecture Decisions

  • 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>.
  • Detail pages use 8+4 column layout with sticky sidebar.
  • Cards use <article class="card h-100"> with stretched-link.
  • Form layout uses centered col-lg-8 col-xl-7 with card + shadow for auth forms.
  • Mobile patterns: offcanvas for search filters; navbar uses Bootstrap offcanvas-lg responsive pattern.
  • Email templates: use custom CSS class prefix (not Bootstrap) to avoid email client conflicts.
  • PDF exports: inline styles aligned with Bootstrap color palette (no external CSS in PDF renderers).

Version History

Current version: v0.1.6

Session History

2026-03-01 — v0.1.6 Add PHPUnit Test Suite

Scope: Added PHPUnit test infrastructure with Brain\Monkey for WordPress function mocking, covering both PHP classes (TemplateOverride, WooCommerceExtension). Added test job to Gitea CI pipeline.

Files created (7):

  • phpunit.xml.dist — PHPUnit 11 configuration (bootstrap, test suite, source coverage)
  • tests/bootstrap.php — Loads Composer autoloader and class stubs
  • tests/Stubs/TwigService.phpWPBootstrap\Twig\TwigService stub with injectable render callback and singleton reset
  • tests/Stubs/WcProduct.php — Minimal \WC_Product stub with get_id()
  • tests/Unit/TemplateOverrideTest.php — 9 tests: hook registration, Twig resolution, buffer stack (including nesting), context passing, global $product injection, exception fallback
  • tests/Unit/Twig/WooCommerceExtensionTest.php — 18 tests: function/filter registration, callFunction() whitelist enforcement, output capture wrappers, setupProductData

Files modified (4):

  • composer.json — Added require-dev (phpunit ^11.0, brain/monkey ^2.6) and autoload-dev PSR-4 mapping
  • .gitea/workflows/release.yml — Added test job between lint and build-release; excluded tests/, phpunit.xml.dist, .phpunit.cache from release packages
  • .gitignore — Changed .phpunit.cache/test-results to .phpunit.cache/
  • style.css — Version bump 0.1.5 → 0.1.6

Key decisions:

  • Brain\Monkey over WP_Mock: Lighter weight, better patchwork integration, same pattern as wp-bootstrap parent theme
  • TwigService stub with injectable callback: Allows tests to control render output without full Twig environment; includes reset() for test isolation
  • @ error suppression for error_log in fallback test: PHP internal functions can't be mocked by Brain\Monkey without patchwork.json config; suppressing is simpler than adding patchwork config for a single test
  • No patchwork.json: Avoids complexity; only error_log needed mocking and the @ operator suffices for the test assertion

Test results: 27 tests, 54 assertions, all passing

2026-03-01 — v0.1.5 Fix 10 Known Bugs

Scope: Fixed all 10 bugs from KNOWN_BUGS.md — catalog page features (title, breadcrumbs, categories, filters, sort dropdown), single product fixes (variable form, gallery, related products, grouped products), and downloads page.

Files changed (17):

  • inc/Twig/WooCommerceExtension.php — Added woocommerce_page_title and wc_get_customer_available_downloads to ALLOWED_FUNCTIONS; added wc_setup_product_data() method, wp_reset_postdata Twig function, and sanitize_title Twig filter
  • templates/single-product/add-to-cart/variable.html.twig — Added data-product_id and data-product_variations attributes to form; applied sanitize_title filter on attribute names for variation matching
  • templates/single-product/add-to-cart/variation-add-to-cart-button.html.twig — Removed HTML disabled attribute, replaced with CSS classes disabled wc-variation-selection-needed (WC JS only manages CSS classes, never the HTML attribute)
  • templates/single-product/product-image.html.twig — Prepend main image to gallery thumbnail strip; added {% if thumb_url %} guard for invalid attachment IDs
  • templates/single-product/product-thumbnails.html.twig — New empty override to suppress WC's default full-size gallery images
  • templates/single-product/related.html.twig — Added wc_setup_product_data() before each product render + wp_reset_postdata() after loop
  • templates/single-product/up-sells.html.twig — Same setup_postdata fix as related.html.twig
  • templates/single-product/add-to-cart/grouped.html.twig — Rewrote to compute quantites_required/show_add_to_cart_button in loop (matching WC PHP); moved hidden input outside conditional; added has_options() and is_sold_individually() checks
  • templates/loop/header.html.twig — Replaced page_title context variable with direct fn('woocommerce_page_title', false) call
  • woocommerce/archive-product.php — Added woocommerce_breadcrumb() call before shop loop header
  • functions.php — Changed loop columns from 4 to 3; added product gallery JS conditional enqueue
  • templates/loop/loop-start.html.twig — Changed default columns from 4 to 3
  • assets/css/wc-bootstrap.css — Replaced duplicate ordering select rules with appearance: none; increased dark mode variation select specificity to (0,5,1) to beat WC's (0,4,3) background shorthand
  • templates/content-product-cat.html.twig — Renamed to content-product_cat.html.twig (WC uses underscore)
  • assets/js/product-gallery.js — New: thumbnail click-to-swap, gallery fade-in, active state highlighting
  • templates/myaccount/downloads.html.twig — Replaced fn('WC').customer.get_downloadable_products() with direct fn('wc_get_customer_available_downloads', get_current_user_id())
  • style.css — Version bump 0.1.4 → 0.1.5

Key decisions:

  • wc_setup_product_data() over PHP bridge: Adding a Twig function that sets $GLOBALS['product'] + setup_postdata() is simpler and more maintainable than creating PHP bridge files for related/upsells rendering
  • json_encode|esc_attr over wc_esc_json: Avoids adding a custom filter; esc_attr performs the same HTML entity encoding as wc_esc_json
  • 3 columns default: With sidebar taking col-lg-3, 3 product columns in col-lg-9 gives better card proportions than 4
  • Vanilla JS gallery: Lightweight click-to-swap handler instead of WC's built-in flexslider/photoswipe (which requires specific PHP setup for wp_get_attachment_image_src data attributes)
  • CSS class disabled over HTML disabled attribute: WC's add-to-cart-variation.js onShow/onHide only toggle CSS classes (disabled, wc-variation-selection-needed). The onAddToCart handler checks .is('.disabled'). Using the HTML disabled attribute prevents click events entirely and WC JS never removes it.
  • CSS specificity (0,5,1) for dark mode selects: WC's .woocommerce div.product form.cart .variations select at (0,4,3) uses background shorthand which resets background-color and background-image. Must exceed this specificity AND override background-image for Bootstrap's dark-mode SVG chevron.

Key findings (WC variation JS):

  • WC's add-to-cart-variation.js event flow: onChangecheck_variationsfindMatchingVariationsfound_variationonFoundVariation (300ms setTimeout) → show_variationonShow (removes CSS class only)
  • onShow calls $button.removeClass('disabled wc-variation-selection-needed') — never touches the HTML disabled attribute/property
  • onAddToCart checks $button.is('.disabled') — CSS class, not HTML attribute
  • Gallery _product_image_gallery meta may contain IDs of product variations or posts instead of image attachments — always guard with {% if thumb_url %} after wp_get_attachment_url()

2026-03-01 — v0.1.4 Security Audit & Performance Fixes

Scope: Cross-theme security audit (12 findings), all fixed. Covers fn() whitelist, notice data escaping, search query escaping, per-request context caching, shared render helper, and unused constant removal.

Files changed (6):

  • inc/Twig/WooCommerceExtension.php — Added ALLOWED_FUNCTIONS whitelist to callFunction(). Only 6 functions (WC, _n, get_pagenum_link, wc_review_ratings_enabled, wc_get_product_category_list, wc_get_product_tag_list) are permitted.
  • templates/notices/success.html.twignotice.data|rawnotice.data|wp_kses_post
  • templates/notices/error.html.twignotice.data|rawnotice.data|wp_kses_post
  • templates/notices/notice.html.twignotice.data|rawnotice.data|wp_kses_post
  • templates/product-searchform.html.twig — Added |esc_attr on get_search_query() value
  • functions.php — Removed unused WC_BOOTSTRAP_VERSION/WC_BOOTSTRAP_URL constants. Added wc_bootstrap_get_theme_context() with static caching and wc_bootstrap_render_in_page_shell() helper. Refactored 3 render functions to use shared helpers.

Key decisions:

  • fn() whitelist defense-in-depth: Template files are static PHP, not user-editable, so exploitation requires file write access. Whitelist added anyway as defense-in-depth to prevent exec(), system(), etc. if template context were ever compromised.
  • |wp_kses_post over |raw for data attributes: WooCommerce sanitizes notice data, but belt-and-suspenders approach prevents XSS if upstream behavior changes.
  • Static variable caching over transients: Per-request static $cached_context is sufficient since WooCommerce pages build context once. No transient overhead or invalidation needed.

2026-02-28 — My Account Bootstrap 5 Polish

Scope: Redesigned 8 my-account Twig templates + CSS overrides to feel like a polished Bootstrap 5 application.

Files changed (10):

  • templates/myaccount/navigation.html.twig — Added endpoint icon map, offcanvas-lg responsive pattern, sticky sidebar
  • 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
  • assets/css/wc-bootstrap.css — Reset WooCommerce float layout, override max-width: 1000px, avatar rounding, card hover lift

Design decisions:

  • 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.
  • Templates NOT changed (already well-done): orders.html.twig, my-address.html.twig, form-login.html.twig, payment-methods.html.twig, form-add-payment-method.html.twig, downloads.html.twig

2026-02-28 — Single Product Bootstrap 5 Layout + Template Quirks Audit

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
  • templates/content-single-product.html.twig — Two-column row gx-4 gx-lg-5 grid (images left, summary right)
  • assets/css/wc-bootstrap.css — Float/width reset, sale badge positioning, shop_table border reset, gallery opacity fallback

Template quirks audit (14 files fixed):

  • order/order-details.html.twig — Fixed |nl2br|esc_html filter order (was escaping <br> tags)
  • single-product/product-image.html.twig — Added WC gallery modifier classes + opacity:0 for JS init
  • brands/brand-description.html.twig — Added |raw to HTML-producing filter chain
  • single-product/up-sells.html.twig, cart/cross-sells.html.twig, single-product/related.html.twig — Fixed &hellip; double-encoding in headings
  • myaccount/dashboard.html.twig — Removed duplicate deprecated hook fires
  • product-searchform.html.twig — Replaced &hellip; entity with Unicode
  • cart/cart-totals.html.twig, checkout/review-order.html.twig, checkout/form-login.html.twig, checkout/terms.html.twig — Removed wrong function() is defined guards
  • wc-base.html.twig — Added |esc_attr on notification type in class attribute
  • global/form-login.html.twig — Added is defined guard on hidden variable
  • single-product/add-to-cart/variation.html.twig — Added role="alert" for accessibility
  • cart/mini-cart.html.twig — Changed remove link to Bootstrap btn btn-sm btn-outline-danger
  • cart/cart-shipping.html.twig — Made form-check class conditional on multiple shipping methods

Infrastructure:

  • Created .claude/settings.json with allowed commands (git, docker, composer, npm, etc.)

2026-02-28 — v0.1.0 Changelog & Re-Release

Scope: Added CHANGELOG.md entry for v0.1.0 and re-released the tag.

Files changed (1):

  • CHANGELOG.md — Added [0.1.0] section documenting all features and fixes between v0.0.1 and v0.1.0

2026-02-28 — v0.1.1 Dark Mode & Notice CSS Bugfixes

Scope: Fixed dark mode select backgrounds, WooCommerce notice styling conflicts, and product card image overflow.

Files changed (2):

  • assets/css/wc-bootstrap.css — Dark mode overrides for native <select> and Select2/SelectWoo widgets; bumped notice selectors to .woocommerce .woocommerce-* (specificity 0,2,0) to beat woocommerce.css; suppressed WooCommerce icon font ::before on notices; added checkout form focus color for dark mode
  • templates/content-product.html.twig — Added overflow-hidden to product card <article> for border-radius clipping

Key learnings:

  • WooCommerce CSS sets select { background-color: var(--wc-form-color-background, #fff) } — the custom property is never defined for dark mode, so it falls back to white. Override with [data-bs-theme="dark"] .woocommerce select.
  • Select2/SelectWoo hardcodes #fff backgrounds in select2.css — needs comprehensive overrides for selection containers, dropdowns, search fields, and highlighted options.
  • WooCommerce notice CSS uses border-top: 3px solid, background-color: #f6f5f8, and WooCommerce icon font ::before at specificity 0,1,0. Single-class overrides don't win — must use .woocommerce .woocommerce-* (specificity 0,2,0) and explicitly reset border-top.
  • Never set background-image without background-repeat: no-repeat / background-size / background-position — SVGs will tile.

2026-02-28 — v0.1.2 Dark Mode Deep Fixes (Tables, Inputs, Notices)

Scope: Fixed dark mode rendering issues across checkout, thank-you, cart, and account pages — white table headers, white form inputs, notice focus rings, and missing card wrappers.

Files changed (7):

  • assets/css/wc-bootstrap.css — Added [data-bs-theme="dark"] override for .input-text and textarea (WooCommerce specificity 0,3,1 beats theme at 0,2,1); added .alert.woocommerce-* compound selectors for notice overrides outside .woocommerce wrapper; added focus ring suppression for programmatically focused notices
  • templates/order/order-details.html.twig — Wrapped product table in card shadow-sm with card-header; removed table-light from <thead>
  • templates/checkout/thankyou.html.twig — Added d-flex align-items-center to success alerts to fix icon/text line wrap
  • templates/checkout/review-order.html.twig — Removed table-light from <thead>
  • templates/cart/cart.html.twig — Removed table-light from <thead>
  • templates/myaccount/orders.html.twig — Removed table-light from <thead>
  • templates/myaccount/payment-methods.html.twig — Removed table-light from <thead>

Key findings:

  • WooCommerce's .woocommerce form .form-row .input-text at specificity 0,3,1 sets background-color: var(--wc-form-color-background, #fff) which beats theme rules. The textarea generated by woocommerce_form_field() has class input-text, so it matches this rule. Needs [data-bs-theme="dark"] override at 0,4,0.
  • WooCommerce's focus_populate_live_region() in woocommerce.js adds tabindex="-1" and calls .focus() on .woocommerce-message[role="alert"] after 500ms for screen reader accessibility. The default browser focus ring appears white in dark mode.
  • Notice overrides using .woocommerce .woocommerce-* descendant selectors don't match notices rendered by Twig templates where .alert and .woocommerce-message are on the same element without a .woocommerce wrapper ancestor. Both .woocommerce .woocommerce-* and .alert.woocommerce-* patterns needed.
  • Bootstrap's table-light class forces a light background on <thead> regardless of dark mode. Remove it entirely — let the card-header or default table styling handle visual separation.