- **Empty page title on catalog pages** (`header.html.twig`): Replaced `page_title` context variable (never passed by WC) with direct `fn('woocommerce_page_title', false)` call
- **Missing breadcrumbs on catalog pages** (`archive-product.php`): Added `woocommerce_breadcrumb()` call before shop loop header
- **Missing product categories on catalog pages** (`content-product-cat.html.twig`): Renamed template from hyphen (`content-product-cat`) to underscore (`content-product_cat`) to match WC's `wc_get_template()` filename convention
- **Product grid 4 columns instead of 3** (`functions.php`, `loop-start.html.twig`): Changed default columns from 4 to 3 for better card proportions with sidebar
- **Double chevron on sort dropdown** (`wc-bootstrap.css`): Removed conflicting `appearance: auto` rule; set `appearance: none` to let Bootstrap's `form-select` class handle the dropdown arrow exclusively
- **Variable product add-to-cart button stays disabled** (`variable.html.twig`, `variation-add-to-cart-button.html.twig`): Added missing `data-product_id` and `data-product_variations` attributes to form; replaced HTML `disabled` attribute on button with CSS classes `disabled wc-variation-selection-needed` (WC JS only toggles CSS classes, never removes the HTML attribute)
- **Variable product select white background in dark mode** (`wc-bootstrap.css`): Increased dark mode override specificity to `(0,5,1)` to beat WC's `.woocommerce div.product form.cart .variations select` at `(0,4,3)` which uses `background` shorthand; also overrides `background-image` for Bootstrap's dark-mode-aware chevron SVG
- **Product gallery missing main image in thumbnail strip** (`product-image.html.twig`): Prepend main image ID to gallery IDs using `[post_thumbnail_id]|merge(gallery_image_ids)` with active state on first thumbnail; added `{% if thumb_url %}` guard to skip invalid attachment IDs
- **Related/upsells products show same product repeated** (`related.html.twig`, `up-sells.html.twig`): Added `wc_setup_product_data()` call before each product render and `wp_reset_postdata()` after loop to set global `$product` correctly for WC hooks
- **Grouped product add-to-cart button/pricing broken** (`grouped.html.twig`): Rewrote template to compute `quantites_required` and `show_add_to_cart_button` in loop (matching WC PHP logic); moved hidden `add-to-cart` input outside conditional; added `has_options()` and `is_sold_individually()` checks
- **Downloads page empty** (`downloads.html.twig`): Replaced fragile `fn('WC').customer.get_downloadable_products()` chain with direct `fn('wc_get_customer_available_downloads', get_current_user_id())` call
### Added
- **Product gallery JS** (`product-gallery.js`): Vanilla JS click handler for thumbnail-to-main-image swap with active state highlighting and gallery fade-in
- **`wc_setup_product_data()` Twig function** (`WooCommerceExtension.php`): Sets `$GLOBALS['product']` and calls `setup_postdata()` for correct product context in Twig loops
- **`wp_reset_postdata` Twig function** (`WooCommerceExtension.php`): Restores global post state after product loops
- **`sanitize_title` Twig filter** (`WooCommerceExtension.php`): Matches WC PHP's lowercase attribute name handling for variation form data attributes
- **Whitelisted functions** (`WooCommerceExtension.php`): Added `woocommerce_page_title` and `wc_get_customer_available_downloads` to `ALLOWED_FUNCTIONS`
- **Removed obsolete files**: Deleted `PLAN.md` and `SETUP.md` (superseded by CLAUDE.md)
- **fn() function whitelist** (`WooCommerceExtension`): The `callFunction()` method (exposed as `fn()` in Twig templates) now restricts callable functions to an explicit `ALLOWED_FUNCTIONS` whitelist. Previously any PHP function could be called, risking arbitrary code execution if template context were compromised. Only the 6 functions actually used in templates are permitted.
- **Notice data attribute escaping**: Changed `{{ notice.data|raw }}` to `{{ notice.data|wp_kses_post }}` in success, error, and notice Twig templates. Defense-in-depth against potential XSS via data attributes.
- **Search query escaping** (`product-searchform.html.twig`): Added `|esc_attr` filter to `get_search_query()` output in the search input value attribute.
### Performance
- **Per-request ContextBuilder caching**: New `wc_bootstrap_get_theme_context()` function with static variable caching eliminates redundant `ContextBuilder::build()` calls (10-20 DB queries each) when multiple WooCommerce render functions fire in the same request.
### Changed
- **Shared page shell helper**: New `wc_bootstrap_render_in_page_shell()` function extracts the duplicated context-injection-and-render pattern from `wc_bootstrap_render_page()`, `wc_bootstrap_render_product_archive()`, and `wc_bootstrap_render_single_product()`.
- **Removed unused constants**: Removed `WC_BOOTSTRAP_VERSION` and `WC_BOOTSTRAP_URL` constants that were defined but never referenced.
- Dark mode: text inputs and textareas showing white background due to WooCommerce's `.woocommerce form .form-row .input-text` (specificity `0,3,1`) overriding theme's checkout form rules with `var(--wc-form-color-background, #fff)` fallback
- Dark mode: `table-light` class on `<thead>` forcing white table headers in cart, checkout review, orders, and payment methods pages
- Dark mode: WooCommerce notice focus ring appearing white when `focus_populate_live_region()` JS programmatically focuses alerts for screen reader accessibility
- WooCommerce notice overrides not matching alerts rendered by Twig templates (added `.alert.woocommerce-*` compound selectors alongside `.woocommerce .woocommerce-*` descendant selectors)
- Order details table on thank-you page not wrapped in a card like other sections
- Thank-you page success message line-wrapping after icon due to block-level `<p>` inside inline alert context
- Dark mode: native `<select>` elements showing white background due to WooCommerce's `--wc-form-color-background` falling back to `#fff`
- Dark mode: SelectWoo/Select2 dropdowns (country/state pickers) rendering with hardcoded `#fff` backgrounds, text colors, and borders
- Dark mode: checkout form focus ring color for inputs, textareas, and selects
- WooCommerce notice borders not matching Bootstrap alert styles due to insufficient CSS specificity (bumped to `.woocommerce .woocommerce-*` at `0,2,0`)
- Private registry image name and restart policies for Docker stack
- **My Account polish**: endpoint icon map, `offcanvas-lg` responsive navigation with sticky sidebar, card-based dashboard with avatar welcome greeting and quick-action grid, card-wrapped forms with icon headers, view-order summary card with status badge
- **Product archive**: Bootstrap 5 card grid with responsive columns, sale badges, star ratings, offcanvas sidebar for mobile filters, shop-sidebar widget area
- **Phase 8** (Email templates): WooCommerce emails use `wc_get_template_html()` which bypasses the Twig rendering pipeline. Default email templates are sufficient; customization can be handled via dedicated email plugins.