# WooCommerce Tier and Package Prices - AI Context Document

**Last Updated:** 2025-12-30
**Current Version:** 1.2.8
**Author:** Marco Graetsch
**Project Status:** Production-ready WordPress plugin

## Project Overview

This is a WooCommerce plugin that adds flexible pricing capabilities to products through two distinct pricing models:

1. **Tier Pricing (Volume Discounts)**: Progressive discounts based on quantity ranges (e.g., 1-9 items @ $12, 10-24 @ $10, 25+ @ $8)
2. **Package Pricing (Fixed Bundles)**: Exact quantity packages at fixed prices (e.g., exactly 10 items for $95, exactly 25 for $200)

### Key Fact: 100% AI-Generated

This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase was created through AI assistance.

## Technical Stack

- **Language:** PHP 7.4+
- **Framework:** WordPress Plugin API
- **E-commerce:** WooCommerce 8.0+ (tested up to 10.x)
- **Template Engine:** Twig 3.0 (via Composer)
- **Frontend:** Vanilla JavaScript + jQuery
- **Styling:** Custom CSS
- **Dependency Management:** Composer
- **Internationalization:** WordPress i18n (.pot/.po/.mo files)

### Dependencies

```json
{
  "twig/twig": "^3.0",
  "symfony/polyfill-ctype": "^1.x",
  "symfony/polyfill-mbstring": "^1.x"
}
```

## Architecture

### Directory Structure

```txt
wc-tier-and-package-prices/
├── wc-tier-and-package-prices.php    # Main plugin file (entry point)
├── includes/                         # PHP classes
│   ├── class-wc-tpp-admin.php        # Admin settings integration
│   ├── class-wc-tpp-settings.php     # WooCommerce settings page
│   ├── class-wc-tpp-product-meta.php # Product edit page meta boxes
│   ├── class-wc-tpp-frontend.php     # Product page display logic
│   ├── class-wc-tpp-cart.php         # Cart price calculations
│   └── class-wc-tpp-template-loader.php # Twig template loader
├── templates/                        # Twig templates
│   ├── admin/                       # Admin interface templates
│   │   ├── tier-row.twig           # Single tier input row
│   │   └── package-row.twig        # Single package input row
│   └── frontend/                    # Customer-facing templates
│       ├── pricing-table.twig       # Main pricing display wrapper
│       ├── tier-pricing-table.twig  # Tier pricing display
│       └── package-pricing-display.twig # Package buttons/cards
├── assets/
│   ├── css/
│   │   ├── admin.css               # Backend styling
│   │   └── frontend.css            # Product page & cart styling
│   └── js/
│       ├── admin.js                # Meta box interaction (add/remove rows)
│       └── frontend.js             # Dynamic price updates, package selection
├── languages/                       # Translation files
│   ├── *.pot                       # Translation template
│   ├── *.po                        # Translation sources
│   └── *.mo                        # Compiled translations
├── vendor/                          # Composer dependencies (included in releases)
├── releases/                        # Release packages (not in git)
└── *.md                            # Documentation files

```

### Class Responsibilities

#### 1. `WC_Tier_Package_Prices` (Main Plugin Class)

- **Location:** `wc-tier-and-package-prices.php`
- **Pattern:** Singleton
- **Responsibilities:**
  - Plugin initialization and bootstrapping
  - Loading all component classes via `includes()`
  - HPOS (High-Performance Order Storage) compatibility declaration
  - Text domain loading for internationalization
  - Activation/deactivation hooks

#### 2. `WC_TPP_Admin`

- **Location:** `includes/class-wc-tpp-admin.php`
- **Pattern:** Singleton
- **Responsibilities:**
  - Enqueues admin CSS/JS
  - Registers WooCommerce settings page via filter
  - Manages settings page instance (cached to prevent duplicates)
  - Product meta box asset loading

#### 3. `WC_TPP_Settings`

- **Location:** `includes/class-wc-tpp-settings.php`
- **Extends:** `WC_Settings_Page` (WooCommerce core)
- **Responsibilities:**
  - Creates "Tier & Package Prices" tab in WooCommerce settings
  - Defines global plugin settings (enable/disable features, display position, etc.)
  - Setting persistence through WooCommerce options API

**Global Settings:**

- `wc_tpp_enable_tier_pricing` (yes/no)
- `wc_tpp_enable_package_pricing` (yes/no)
- `wc_tpp_display_table` (yes/no) - Show pricing tables on product pages
- `wc_tpp_display_position` (before_add_to_cart / after_add_to_cart / after_price)
- `wc_tpp_restrict_package_quantities` (yes/no) - Global quantity restrictions

#### 4. `WC_TPP_Product_Meta`

- **Location:** `includes/class-wc-tpp-product-meta.php`
- **Responsibilities:**
  - Adds tier/package pricing fields to product edit page
  - Renders Twig templates for meta box rows
  - Saves tier/package data to post meta
  - Nonce verification and capability checks for security
  - Prevents autosave from corrupting data

**Product Meta Keys:**

- `_wc_tpp_tiers` - Array of tier objects `[{min_qty, price, label}]`
- `_wc_tpp_packages` - Array of package objects `[{qty, price, label}]`
- `_wc_tpp_restrict_to_packages` - Per-product quantity restriction (yes/no)

#### 5. `WC_TPP_Frontend`

- **Location:** `includes/class-wc-tpp-frontend.php`
- **Responsibilities:**
  - Enqueues frontend CSS/JS on product pages
  - Displays pricing tables via Twig templates
  - Localizes currency settings to JavaScript
  - Hides quantity inputs for restricted products
  - Modifies catalog "Add to Cart" buttons to "View Options" for restricted products
  - Static methods for price lookups (`get_tier_price()`, `get_package_price()`)

#### 6. `WC_TPP_Cart`

- **Location:** `includes/class-wc-tpp-cart.php`
- **Responsibilities:**
  - **MOST CRITICAL CLASS** - Handles all cart price calculations
  - Applies tier/package pricing during cart totals calculation
  - Stores pricing metadata in cart items for display
  - Customizes cart item display (price labels, quantity indicators)
  - Validates package quantities on add-to-cart
  - Hides/disables quantity inputs for restricted products (classic cart + blocks)
  - **WooCommerce Blocks support** via `woocommerce_store_api_product_quantity_editable` filter

**Price Calculation Priority (in `apply_tier_package_pricing()`):**

1. Check for exact package match → Use package price if found
2. Check for tier match → Use tier price if found
3. Fall back to regular product price

#### 7. `WC_TPP_Template_Loader`

- **Location:** `includes/class-wc-tpp-template-loader.php`
- **Pattern:** Singleton
- **Responsibilities:**
  - Initializes Twig environment with proper paths
  - Renders Twig templates from both admin and frontend directories
  - Handles template caching and error handling

## Important Implementation Details

### Price Calculation Logic

**Package Pricing** (exact match):

```php
// In cart: if quantity == 10 and package exists for 10, use package price
if ($quantity == $package['qty']) {
    $unit_price = $package['price'] / $quantity;  // Total price divided by quantity
    $product->set_price($unit_price);  // WooCommerce expects unit price
}
```

**Tier Pricing** (range-based):

```php
// In cart: if quantity >= 10, use tier price for quantities 10+
foreach ($tiers as $tier) {
    if ($quantity >= $tier['min_qty']) {
        $applicable_price = $tier['price'];  // This is already unit price
    }
}
$product->set_price($applicable_price);
```

### Quantity Restriction Feature

Products can be configured to ONLY allow purchase in package quantities:

- **Global setting:** `wc_tpp_restrict_package_quantities`
- **Per-product setting:** `_wc_tpp_restrict_to_packages`
- **When enabled:**
  - Quantity inputs are hidden on product page, cart, and mini-cart
  - Customers must use package selection buttons
  - Validation prevents arbitrary quantities from being added
  - Catalog buttons change to "View Options" instead of "Add to Cart"

### WooCommerce Blocks Compatibility

**CRITICAL BUG FIXED in v1.1.20:**

- Filter `woocommerce_store_api_product_quantity_editable` passes `WC_Product` object, NOT cart item array
- Previous code tried to use product object as array → fatal error
- Fixed by accepting product object and using `$product->get_id()`

### Cart Item Metadata

The plugin stores additional data in cart items for display purposes:

```php
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_pricing_type'] = 'package' | 'tier';
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_total_price'] = 99.99;  // For packages
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_unit_price'] = 9.99;   // For tiers
```

This metadata is used by display filters to show "(Package price)" or "(Volume discount)" labels.

## Common Patterns & Conventions

### Class Instantiation Pattern

All classes auto-instantiate at the end of their file:

```php
if (!class_exists('WC_TPP_Frontend')) {
    class WC_TPP_Frontend {
        // class code
    }
}
new WC_TPP_Frontend();  // Auto-instantiate
```

**Exception:** Admin and Settings classes use singleton pattern to prevent duplicates.

### Security Best Practices

- All user inputs are sanitized (integers for quantities/prices)
- Nonce verification on form submissions
- Capability checks (`edit_products`) before saving
- Output escaping in templates (`esc_attr`, `esc_html`, `esc_js`)
- Direct file access prevention via `ABSPATH` check

### Translation Ready

All user-facing strings use:

```php
__('Text to translate', 'wc-tier-package-prices')
_e('Text to translate', 'wc-tier-package-prices')
```

Text domain: `wc-tier-package-prices`

**Available Translations (as of v1.1.22):**

- `en_US` - English (United States)
- `de_DE` - German (Germany, formal)
- `de_DE_informal` - German (Germany, informal "du")
- `de_CH` - German (Switzerland, formal "Sie")
- `de_CH_informal` - German (Switzerland, informal "du")
- `fr_CH` - French (Switzerland)
- `it_CH` - Italian (Switzerland)

Note: Swiss locales use CHF currency formatting in examples (e.g., "CHF 50.-")

## Known Issues & Historical Context

### Settings Page Duplication Saga (v1.1.15-1.1.19)

Multiple versions attempted to fix settings page appearing twice:

- **Root cause:** Settings file auto-instantiation + Composer autoloader
- **Solution:** Removed auto-instantiation from settings file, explicit instantiation in admin class
- **Prevention:** Singleton pattern + duplicate detection in array

### Class Redeclaration Issues (v1.1.8-1.1.14)

Plugin was completely non-functional:

- **Cause:** Incorrect initialization pattern without `class_exists()` guards
- **Solution:** Added guards and restored direct instantiation pattern
- **Lesson:** Always wrap class declarations in `class_exists()` checks

### WooCommerce Blocks Fatal Error (v1.1.19 → v1.1.20)

```txt
Fatal error: Cannot use object of type WC_Product_Simple as array
Location: includes/class-wc-tpp-cart.php:233
```

- **Cause:** Filter signature mismatch - expected array, received product object
- **Fix:** Changed method signature to accept `WC_Product $product` instead of `$cart_item` array
- **Status:** FIXED in v1.1.20

### CSS Specificity Issues (v1.2.3 → v1.2.4)

**Problem:** Admin table borders still visible despite `border: none` declarations in v1.2.3

```txt
Issue: WooCommerce's core admin CSS has higher specificity border rules
Location: assets/css/admin.css
Symptom: Tables still showing borders in product edit screens
```

- **Root Cause:** WooCommerce's default admin CSS uses highly specific selectors that override simple `border: none` declarations
- **Failed Approach (v1.2.3):** Adding `border: none` to table elements without `!important`
- **Successful Fix (v1.2.4):**
  - Added `!important` flags to ALL border removal rules
  - Added `border-collapse: collapse !important` to force borderless styling
  - Targeted all table structural elements: `table`, `th`, `td`, `thead`, `tbody`, `tr`
- **Lesson:** When overriding WooCommerce core CSS, `!important` is often necessary due to high specificity in core styles

**Problem:** Help icon positioned at right edge instead of next to label text

```txt
Issue: WooCommerce help-tip uses float: right positioning
Location: assets/css/admin.css (checkbox/label layout)
Symptom: Help icon appearing far from label text at container edge
```

- **Root Cause:** WooCommerce's default `.woocommerce-help-tip` styling uses `float: right`
- **Failed Approach (v1.2.3):** Simple margin adjustments without changing positioning model
- **Successful Fix (v1.2.4):**
  - Removed float positioning: `float: none`
  - Changed to `display: inline-block` with `vertical-align: middle`
  - Wrapped label and help-tip in flexbox container: `display: inline-flex; align-items: center`
  - Controlled precise spacing with margins (checkbox: 12px, help-tip: 6px)
- **Lesson:** Overriding float-based layouts often requires changing to flexbox for proper control

## Release Process

### Version Bumping

Update version in 3 places:

1. `wc-tier-and-package-prices.php` - Plugin header comment (line 7)
2. `wc-tier-and-package-prices.php` - `WC_TPP_VERSION` constant (line 26)
3. `composer.json` - version field (optional, not critical)

### Creating Release Package

**CRITICAL:** The zip command must be run from the **parent directory** of the plugin folder to create proper archive structure.

```bash
# From parent directory (/home/magdev/workspaces/node)
cd /home/magdev/workspaces/node

# Create zip excluding dev files - note the correct path structure
zip -r wc-tier-and-package-prices/releases/wc-tier-and-package-prices-X.X.X.zip wc-tier-and-package-prices/ \
  -x 'wc-tier-and-package-prices/.git*' \
  'wc-tier-and-package-prices/*.log' \
  'wc-tier-and-package-prices/.claude/*' \
  'wc-tier-and-package-prices/CLAUDE.md' \
  'wc-tier-and-package-prices/releases/*' \
  'wc-tier-and-package-prices/node_modules/*' \
  'wc-tier-and-package-prices/.DS_Store' \
  'wc-tier-and-package-prices/Thumbs.db' \
  'wc-tier-and-package-prices/.vscode/*' \
  'wc-tier-and-package-prices/.idea/*' \
  'wc-tier-and-package-prices/*.sublime-*' \
  'wc-tier-and-package-prices/notes.*' \
  'wc-tier-and-package-prices/logs/*' \
  'wc-tier-and-package-prices/templates/cache/*' \
  'wc-tier-and-package-prices/composer.lock'

# Return to project directory
cd wc-tier-and-package-prices

# Generate checksums
cd releases
md5sum wc-tier-and-package-prices-X.X.X.zip > wc-tier-and-package-prices-X.X.X.zip.md5
sha256sum wc-tier-and-package-prices-X.X.X.zip > wc-tier-and-package-prices-X.X.X.zip.sha256
cd ..
```

**IMPORTANT NOTES:**

- The `vendor/` directory MUST be included in releases (Twig dependency required for runtime)
- Running zip from wrong directory creates empty or malformed archives
- Exclusion patterns must match the relative path structure used in zip command
- Always verify the package with `unzip -l` and test extraction before committing

### Verification Steps

After creating the release package, always verify:

```bash
# Check package size (should be ~400-450KB, NOT 8MB+ or near 0)
ls -lh releases/wc-tier-and-package-prices-X.X.X.zip

# Verify exclusions worked
unzip -l releases/wc-tier-and-package-prices-X.X.X.zip | grep -E "CLAUDE\.md|\.claude/|\.git" && echo "ERROR: Excluded files found!" || echo "OK: No excluded files"

# Test extraction
cd /tmp && rm -rf test-extract && unzip -q /path/to/releases/wc-tier-and-package-prices-X.X.X.zip -d test-extract && ls -la test-extract/wc-tier-and-package-prices/

# Verify version in extracted package
head -30 /tmp/test-extract/wc-tier-and-package-prices/wc-tier-and-package-prices.php | grep -E "Version:|WC_TPP_VERSION"

# Verify template changes (if applicable)
grep 'class="regular"' /tmp/test-extract/wc-tier-and-package-prices/templates/admin/*.twig
```

### Git Workflow for Releases

**Standard workflow:** Work on `dev` branch → merge to `main` → tag → push

```bash
# 1. Ensure you're on dev branch with all changes committed
git checkout dev
git add [files]
git commit -m "Release version X.X.X - [description]

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"

# 2. Merge dev to main
git checkout main
git merge dev --no-edit  # Should be fast-forward

# 3. Create annotated tag
git tag -a vX.X.X -m "Release version X.X.X - [description]"

# 4. Push everything
git push origin main
git push origin vX.X.X

# 5. Update dev and push
git checkout dev
git rebase main  # Should be up-to-date already
git push origin dev

# 6. If you have uncommitted local changes (like .claude/settings.local.json)
git stash push -m "Local development settings"
# ... do git operations ...
git stash pop
```

**Important Git Notes:**

- Always commit from `dev` branch first
- Tags should use format `vX.X.X` (e.g., `v1.1.22`)
- Use annotated tags (`-a`) not lightweight tags
- Commit messages should follow the established format with Claude Code attribution
- `.claude/settings.local.json` changes are typically local-only (stash before rebasing)

### What Gets Released

- All plugin source files
- Compiled vendor dependencies
- Translation files (.mo compiled from .po)
- Assets (CSS, JS)
- Documentation (README, CHANGELOG, etc.)

### What's Excluded

- Git metadata (`.git/`)
- Development files (`.vscode/`, `.claude/`, `CLAUDE.md`)
- Logs and cache files
- Previous releases
- `composer.lock` (but `vendor/` is included)

## Testing Checklist

When making changes, test these critical paths:

### Admin

- [ ] Settings page appears once under WooCommerce > Tier & Package Prices
- [ ] Settings save correctly
- [ ] Product edit page shows tier/package meta boxes
- [ ] Adding/removing tiers works
- [ ] Adding/removing packages works
- [ ] Data saves when clicking "Update" on product

### Frontend (Product Page)

- [ ] Pricing tables display when configured
- [ ] Package buttons update quantity selector
- [ ] Price updates dynamically when quantity changes
- [ ] Restricted products hide quantity input
- [ ] "View Options" appears on catalog for restricted products

### Cart & Checkout

- [ ] Correct prices applied for tier pricing
- [ ] Correct prices applied for package pricing
- [ ] Cart displays pricing type labels
- [ ] Package quantities can't be edited if restricted
- [ ] Prices recalculate if quantity changed (non-restricted products)
- [ ] Checkout totals are correct

### WooCommerce Blocks (Critical!)

- [ ] Mini cart block doesn't throw fatal errors
- [ ] Cart block works correctly
- [ ] Checkout block processes orders
- [ ] Quantity editable flag works for blocks

## Development Tips for Future AI Assistants

### Common Pitfalls and Solutions

#### Release Package Creation

**Problem:** Empty or corrupted zip files (0 bytes or wrong structure)
**Cause:** Running zip command from wrong directory or incorrect path patterns
**Solution:** Always run from parent directory (`/home/magdev/workspaces/node`) and use full relative paths in exclusions

**Problem:** Development files included in release (CLAUDE.md, .claude/, .git)
**Cause:** Exclusion patterns don't match actual paths used in zip command
**Solution:** Test with `unzip -l | grep` to verify exclusions before committing

**Problem:** Package size is 8MB+ instead of ~400KB
**Cause:** Development files not excluded (especially .git directory)
**Solution:** Follow verification steps and check package size immediately after creation

#### UI Changes in Admin

**WooCommerce CSS Classes:**

- `short` - Small input fields (~60px width)
- `regular` - Medium input fields (~120px width)
- `long` - Large input fields (~200px+ width)

When modifying admin input fields in Twig templates, use WooCommerce's standard classes for consistency.

**Location:** `templates/admin/*.twig` for admin UI changes

#### CSS Specificity and WooCommerce Overrides

**CRITICAL LESSON from v1.2.4:** WooCommerce's core admin CSS uses high-specificity selectors that require `!important` to override.

**Problem Symptoms:**
- CSS rules not applying despite correct selectors
- Styles work in simple cases but fail with WooCommerce elements
- Browser DevTools shows rule crossed out or overridden

**Diagnostic Steps:**
1. Inspect element in browser DevTools
2. Check "Computed" tab to see which styles are actually applied
3. Look for crossed-out rules in "Styles" tab (indicates override)
4. Check selector specificity - WooCommerce often uses complex selectors

**Solution Patterns:**

**For Table Styling:**
```css
/* ❌ This will likely be overridden */
.wc-tpp-tiers-table {
    border: none;
}

/* ✅ Use !important for core WooCommerce overrides */
.wc-tpp-tiers-table,
.wc-tpp-tiers-table th,
.wc-tpp-tiers-table td,
.wc-tpp-tiers-table thead,
.wc-tpp-tiers-table tbody,
.wc-tpp-tiers-table tr {
    border: none !important;
}

/* ✅ Also use border-collapse to prevent cell borders */
.wc-tpp-tiers-table {
    border-collapse: collapse !important;
}
```

**For Float-Based Layouts:**
```css
/* ❌ Float positioning is hard to control precisely */
.woocommerce-help-tip {
    float: right;
    margin-left: 10px;
}

/* ✅ Use flexbox for precise control */
label[for="_wc_tpp_restrict_to_packages"] {
    display: inline-flex;
    align-items: center;
    gap: 0;
}

.woocommerce-help-tip {
    float: none;
    display: inline-block;
    vertical-align: middle;
    margin-left: 6px;
    margin-right: 0;
}
```

**General Rules:**
1. **Always test in actual WordPress admin** - browser preview may not show WooCommerce's CSS
2. **Target all related elements** - tables require styling on `table`, `thead`, `tbody`, `tr`, `th`, `td`
3. **Use `!important` sparingly but don't fear it** - sometimes it's the only way to override WooCommerce core
4. **Prefer flexbox over floats** - gives better control over alignment and spacing
5. **Check across browsers** - table rendering can vary between Chrome/Firefox/Safari

**When Styles Don't Apply:**
- First verify selector is correct (DevTools should show rule, even if crossed out)
- If selector is correct but crossed out, increase specificity or add `!important`
- If selector doesn't appear at all, check file is enqueued and cache is cleared
- Use browser's "Inspect" right-click to see exact element structure

#### Git Workflow Issues

**Problem:** Cannot rebase due to uncommitted changes
**Solution:** Stash local config files (`.claude/settings.local.json`) before git operations

**Problem:** Tag already exists
**Solution:** Delete with `git tag -d vX.X.X` locally and `git push --delete origin vX.X.X` remotely

**Problem:** Wrong branch for commits
**Solution:** Always start on `dev` branch, merge to `main`, never commit directly to `main`

### Working with Twig Templates

The plugin uses Twig 3.0 for templating. Key files:

- `templates/admin/tier-row.twig` - Single tier input row in product edit page
- `templates/admin/package-row.twig` - Single package input row in product edit page
- `templates/frontend/*.twig` - Customer-facing pricing displays

**Template rendering:** Done via `WC_TPP_Template_Loader` singleton class

**When modifying templates:**

1. Templates are cached - clear cache or test in development mode
2. Always escape output: use Twig's built-in filters or `|esc_attr`, `|esc_html`
3. Translation strings: `{{ 'Text'|__('wc-tier-package-prices') }}`
4. Keep consistent with WooCommerce admin UI patterns

### Complete Release Workflow Summary

Based on v1.1.22, v1.2.2, and v1.2.3 release experience, here's the complete workflow:

1. **Fix bugs/add features** on `dev` branch
2. **Update version numbers** (3 files: main plugin file header, constant, composer.json)
3. **Update CHANGELOG.md** with detailed changes
4. **Update CLAUDE.md** version number and roadmap
5. **Create release package** from parent directory with correct exclusions
6. **Verify package** (size, contents, exclusions, extraction test)
7. **Commit changes** to `dev` branch with proper message format
8. **Merge to main** (fast-forward merge)
9. **Create annotated tag** (`vX.X.X`)
10. **Push all** (main, tag, dev)
11. **Verify remote** (check repository web UI)

**Time estimate:** 15-20 minutes for full release cycle

**Files typically changed in a release:**

- `wc-tier-and-package-prices.php` - Version bumps
- `composer.json` - Version bump
- `CHANGELOG.md` - Release notes
- `CLAUDE.md` - Version and roadmap updates
- `releases/wc-tier-and-package-prices-X.X.X.zip*` - Package and checksums
- Feature-specific files (templates, PHP classes, etc.)

### Release Package Creation - Critical Notes

**IMPORTANT:** The zip command must be run from the **parent directory** to create proper archive structure.

**Correct command (from `/home/magdev/workspaces/php`):**

```bash
cd /home/magdev/workspaces/php
zip -r wc-tier-and-package-prices/releases/wc-tier-and-package-prices-X.X.X.zip wc-tier-and-package-prices/ \
  -x '*/\.git/*' '*/.git/*' 'wc-tier-and-package-prices/.git/*' \
  '*.gitignore' '*.log' '*/.claude/*' '*/CLAUDE.md' \
  '*/releases/*' '*/wordpress/*' '*/node_modules/*' \
  '*/.DS_Store' '*/Thumbs.db' '*/.vscode/*' '*/.idea/*' \
  '*.sublime-*' '*/notes.*' '*/logs/*' '*/templates/cache/*' \
  '*/composer.lock'
```

**Critical Exclusions:**

- `*/wordpress/*` and `*/core/*` - MUST be excluded! The project has a symlink to WordPress installation that zip will follow, creating 129MB+ packages instead of ~430KB
- `.git/*` - All git metadata (multiple patterns needed for reliability)
- `.claude/*` and `CLAUDE.md` - Development documentation
- `releases/*` - Prevents including previous releases in new ones
- `composer.lock` - Not needed in production (vendor/ is included)

**Expected Package Size:** ~430-431KB (383 files)

**Package Size Alert:** If >1MB, exclusions failed (likely wordpress symlink included)

**Verification Steps:**

```bash
# 1. Check size (should be ~430KB)
ls -lh releases/wc-tier-and-package-prices-X.X.X.zip

# 2. Verify file count (should be 383 files)
unzip -l releases/wc-tier-and-package-prices-X.X.X.zip | tail -1

# 3. Check for excluded files
unzip -l releases/wc-tier-and-package-prices-X.X.X.zip | grep -E "CLAUDE\.md|\.claude/|\.git/|wordpress/"
# Should return nothing (exit code 1)

# 4. Verify version in package
unzip -p releases/wc-tier-and-package-prices-X.X.X.zip wc-tier-and-package-prices/wc-tier-and-package-prices.php | head -30 | grep -E "Version:|WC_TPP_VERSION"
```

### Future Features and Roadmap

The is a hierarchical list for upcoming features and can be considered as a
Roadmap for the upcoming development.

#### Version 1.1.x (Completed)

1. ~~Add translations for `de_CH`, `de_DE_informal`, `fr_CH`, `it_CH`~~ ✅ **COMPLETED in v1.1.21**
2. ~~The double-install bug is back again. A new version of the plugin is installed as new plugin instead of updating the previous version.~~ ✅ **DOCUMENTED in v1.1.22** - Added workaround to CHANGELOG. Root cause: No automatic update mechanism (requires WordPress.org repository or custom update server).
3. ~~Make the label fields in the backend for tierprices and package-prices twice as long as it is.~~ ✅ **COMPLETED in v1.1.22**
4. ~~Make the plugin work with variable products~~ ✅ **COMPLETED in v1.2.0** - Full variation-level pricing support with independent configuration per variation, AJAX-based frontend display, and complete WooCommerce Blocks compatibility.

#### Version 1.2.x

##### Bugfixes (Completed in v1.2.1)

1. ~~The admin templates are not show right. The row templates didn't match the new table structure. The table-body columns didn't fit the table-head columns.~~ ✅ **FIXED in v1.2.1** - Updated admin.css to remove flexbox styling that was breaking the new `<table>/<tr>/<td>` structure introduced in v1.2.0. The CSS was still using flexbox layout from the old `<div>/<p>` structure.

2. ~~The tier and package prices are not shown on simple product pages~~ ✅ **FIXED in v1.2.1** - Removed global enable/disable checks from frontend template. Pricing tables now display if configured on a product AND the "Display Pricing Table" setting is enabled, regardless of "Enable Tier Pricing" or "Enable Package Pricing" global settings. Cart calculations still respect global enable settings.

##### Bugfixes (Completed in v1.2.2)

1. ~~Remove the table borders in admin on variable product to better fit the surrounding element styles.~~ ✅ **FIXED in v1.2.2** - Added CSS rules to remove table borders specifically for variation pricing tables (`.wc-tpp-variation-pricing`), matching WooCommerce's borderless variation UI style.

2. ~~Add missing translations in admin templates ("Price", "Tier & Package Pricing", "Min Quantity") for all languages used in this project.~~ ✅ **FIXED in v1.2.2** - Added missing translation entries for "Min Quantity", "Price", and "Label (optional)" to all .po files (de_DE, de_DE_informal, de_CH, de_CH_informal, fr_CH, it_CH, en_US) and recompiled .mo files.

3. ~~Check the template for wc_tpp_restrict_to_packages[] checkbox elements in admin on variable products and fix the rendering.~~ ✅ **FIXED in v1.2.2** - Fixed checkbox value parameter in variation pricing fields. Changed from ternary expression to direct value assignment, allowing WooCommerce's `woocommerce_wp_checkbox()` to properly handle the checked state.

##### Bugfixes (Completed in v1.2.3)

1. ~~Style the tier and packages tables in admin on simple products according to the styles on variable products.~~ ✅ **FIXED in v1.2.3** - Applied borderless table styling to all tier/package tables (both simple and variable products). Removed borders from table, th, and td elements to match WooCommerce's clean admin UI style.

2. ~~The checkbox styles from 1.2.2 bug 3 are still not looking correct. The helptext is written instead of hidden after the help icon and the margin between checkbox and label are to small.~~ ✅ **FIXED in v1.2.3** - Added `desc_tip => true` to variation checkbox to show tooltip instead of inline text. Added CSS rules to increase checkbox-label margin (8px) and hide inline description text when tooltip is used.

##### Bugfixes (Completed in v1.2.4)

1. ~~Bug 1 in v1.2.3 is not fixed. Now both table display have border again. they shouldn't have border.~~ ✅ **FIXED in v1.2.4** - Added `!important` flags and `border-collapse: collapse` to table CSS to override WooCommerce's default table styling. Added comprehensive border removal for all table elements (table, thead, tbody, tr, th, td) to ensure truly borderless tables across all browsers.

2. ~~Bug 2 in v1.2.3: Increase the margin between checkbox and label and put the help icon right next to the label, not at the right border~~ ✅ **FIXED in v1.2.4** - Increased checkbox right margin from 8px to 12px. Repositioned help tip icon to display inline right next to the label text using flexbox layout with `display: inline-flex`, removing float positioning that caused it to appear at the right edge.

##### Enhancements (Completed in v1.2.5)

1. ~~Hide the table-headers in admin area until a tier or respectivly a package price is defined.~~ ✅ **COMPLETED in v1.2.5** - Added CSS `:has()` pseudo-class selectors to automatically hide table headers when tbody is empty. Creates a cleaner interface showing only the helpful empty state message and "Add" button when no pricing rules are configured.

2. ~~Make it possible to define tier or package prices on variable products in the parent product as a default for that product and all variants of it unless a variant has its own tier or package prices.~~ ✅ **COMPLETED in v1.2.5** - Implemented parent product default pricing with automatic fallback. Variable products can define tier/package pricing once at parent level; variations inherit these defaults unless they have their own specific pricing. Added helper methods in cart class and updated all pricing/restriction checks to support parent fallback.

##### Bugfixes (Completed in v1.2.6 and v1.2.7)

1. ~~Table headers in admin are still visible when empty.~~ ✅ **FIXED in v1.2.7** - The CSS `:has()` pseudo-class approach from v1.2.5/v1.2.6 wasn't working reliably across all browsers. Implemented JavaScript-based solution that adds/removes `has-rows` class on tables. Headers now hide by default (CSS) and show only when table has rows (JavaScript toggles class). Function `updateTableHeaders()` is called on page load and after all add/remove row operations.

2. ~~Parent product pricing forms not visible in admin.~~ ✅ **FIXED in v1.2.6 and v1.2.7** - The backend fallback logic from v1.2.5 was implemented but the admin UI to configure it was missing. Added `add_variable_parent_pricing_fields()` method that displays pricing forms for variable product parents. Fixed hook issue in v1.2.7: changed from `woocommerce_product_options_pricing` (only fires for simple products) to `woocommerce_product_options_general_product_data` (fires for all product types). Variable product parents now have a "Default Tier & Package Pricing for All Variations" section where defaults can be configured.

##### Translation Updates (Completed in v1.2.7)

1. ✅ **COMPLETED** - Updated all translation files (.pot, .po, .mo) with new strings from v1.2.6 and v1.2.7 for variable product parent pricing features. All 7 language variants updated with translations for "Default Tier & Package Pricing for All Variations" and related strings.

##### Bugfixes (Completed in v1.2.8)

1. ~~Add a Suffix with the current configured default currency to the table-header and form placeholder. Use the common currency notation in placeholder~~ ✅ **FIXED in v1.2.8** - Updated all table headers in admin to display "Price (€)" format using `printf(__('Price (%s)'), get_woocommerce_currency_symbol())`. Modified all template render methods (tier_row, package_row, variation_tier_row, variation_package_row) to pass currency_symbol to Twig templates. Updated admin/tier-row.twig and admin/package-row.twig to concatenate currency symbol in price input placeholders (e.g., "e.g., 9.99 €"). Applied to simple products, variable parent products, and all variations.

2. ~~Already stored tier and package prices on the children of a variable product are still available after deletion. Looks like the storage mechanism has an error. This occurs only on the child product, not on the parent product.~~ ✅ **FIXED in v1.2.8** - Fixed save logic in both `save_tier_package_fields()` and `save_variation_pricing_fields()` methods. Root cause: Empty arrays were being saved via `update_post_meta()` instead of being deleted. Changed logic from "save on isset, delete otherwise" to "filter entries, then save if not empty, delete if empty". Added `if (!empty($tiers))` and `if (!empty($packages))` checks before calling `update_post_meta()`. Now properly calls `delete_post_meta()` when all pricing entries are removed, preventing empty arrays from persisting in database.

##### Planned Enhancements for v1.2.9+

1. Create different, selectable templates for tierprices and packages to use in the frontend. Make the new templates selectable globally on the settings-page, not per product.

### When Debugging Cart Issues

1. Check `includes/class-wc-tpp-cart.php` first
2. The `apply_tier_package_pricing()` method runs on `woocommerce_before_calculate_totals`
3. Always validate product objects with `is_a($product, 'WC_Product')`
4. Remember: WooCommerce expects UNIT prices, not total prices (except for internal calculations)

### When Working with WooCommerce Hooks

- WooCommerce has both classic and block-based systems
- Classic cart uses different hooks than Store API (blocks)
- Always check filter/action documentation for parameter types
- Don't assume cart item arrays everywhere - sometimes it's product objects!

#### CRITICAL: Product Type-Specific Hooks (Learned in v1.2.6/v1.2.7)

WooCommerce has different hooks for different product types in the admin product edit page:

- `woocommerce_product_options_pricing` - **ONLY fires for simple products**, NOT variable products
- `woocommerce_product_options_general_product_data` - Fires for ALL product types after the general tab
- `woocommerce_variation_options_pricing` - Fires for individual variations within variable products

**Lesson:** When adding admin UI for variable product parents, use `woocommerce_product_options_general_product_data` and check `$product->is_type('variable')` to conditionally display. Using `woocommerce_product_options_pricing` will cause forms to never appear for variable products (as discovered in v1.2.6 → v1.2.7 fix).

#### CRITICAL: Currency Symbol Display (Learned in v1.2.8)

When displaying currency symbols in admin interface table headers and input placeholders:

**Table Headers:**

```php
// ✅ Correct - Use printf with translation and WooCommerce currency function
<th><?php printf(__('Price (%s)', 'wc-tier-package-prices'), get_woocommerce_currency_symbol()); ?></th>

// ❌ Wrong - Hard-coded or missing currency
<th><?php _e('Price', 'wc-tier-package-prices'); ?></th>
<th><?php _e('Price (€)', 'wc-tier-package-prices'); ?></th>
```

**Twig Template Placeholders:**

```twig
{# ✅ Correct - Pass currency_symbol from PHP and concatenate in template #}
placeholder="{{ ('e.g., 9.99 ' ~ currency_symbol)|__('wc-tier-package-prices') }}"

{# ❌ Wrong - Hard-coded or missing currency #}
placeholder="{{ 'e.g., 9.99'|__('wc-tier-package-prices') }}"
placeholder="{{ 'e.g., 9.99 €'|__('wc-tier-package-prices') }}"
```

**Implementation Pattern:**

1. In PHP render methods, pass currency symbol to Twig: `'currency_symbol' => get_woocommerce_currency_symbol()`
2. In Twig templates, concatenate using `~` operator: `'text ' ~ currency_symbol`
3. Always use WooCommerce's `get_woocommerce_currency_symbol()` - never hard-code currency symbols

**Affected Methods:** All template render methods must pass `currency_symbol`:

- `render_tier_row()`
- `render_package_row()`
- `render_variation_tier_row()`
- `render_variation_package_row()`

#### CRITICAL: Post Meta Deletion vs. Empty Arrays (Learned in v1.2.8)

When saving product meta data, WordPress distinguishes between "no data" (deleted meta) and "empty data" (empty array saved as meta):

**Problem Pattern:**

```php
// ❌ WRONG - Saves empty array when all entries removed
if (isset($_POST['_wc_tpp_tiers'])) {
    $tiers = array();
    foreach ($_POST['_wc_tpp_tiers'] as $tier) {
        if (!empty($tier['min_qty']) && !empty($tier['price'])) {
            $tiers[] = array(...);
        }
    }
    update_post_meta($post_id, '_wc_tpp_tiers', $tiers);  // Saves [] if no valid entries
} else {
    delete_post_meta($post_id, '_wc_tpp_tiers');
}
```

**Correct Pattern:**

```php
// ✅ CORRECT - Deletes meta when no valid entries exist
if (isset($_POST['_wc_tpp_tiers'])) {
    $tiers = array();
    foreach ($_POST['_wc_tpp_tiers'] as $tier) {
        if (!empty($tier['min_qty']) && !empty($tier['price'])) {
            $tiers[] = array(...);
        }
    }
    // Only save if we have valid entries, otherwise delete
    if (!empty($tiers)) {
        update_post_meta($post_id, '_wc_tpp_tiers', $tiers);
    } else {
        delete_post_meta($post_id, '_wc_tpp_tiers');
    }
} else {
    delete_post_meta($post_id, '_wc_tpp_tiers');
}
```

**Why This Matters:**

- Empty arrays `[]` saved via `update_post_meta()` persist in database as serialized empty arrays
- Frontend/cart code checking `if ($tiers)` will evaluate `[]` as falsy, but meta still exists in database
- Database queries like `get_post_meta()` return `[]` instead of `false`, causing subtle bugs
- Properly deleting meta keeps database clean and prevents "ghost" configurations

**Affected Methods in v1.2.8:**

- `save_tier_package_fields()` - Simple and variable parent products
- `save_variation_pricing_fields()` - Individual variations

**Rule:** Always check `if (!empty($array))` before calling `update_post_meta()` for array data. If empty, call `delete_post_meta()` instead.

### When Adding Features

- Follow the existing pattern: add setting → add UI → add logic → add template
- Use Twig for all new templates (consistency)
- Add translations for all user-facing strings
- Test with both simple products and variable products (if applicable)
- Consider both classic and block-based cart/checkout

### When Fixing Bugs

1. Check `CHANGELOG.md` for historical context
2. Look for similar issues in past versions
3. Always add detailed changelog entry explaining root cause
4. Consider edge cases (guest checkout, logged-in users, AJAX add-to-cart, etc.)

## File Locations Quick Reference

| Task | File(s) |
| ------ | --------- |
| Change version | `wc-tier-and-package-prices.php` (2 places) |
| Add global setting | `includes/class-wc-tpp-settings.php` |
| Modify product meta box | `includes/class-wc-tpp-product-meta.php` + `templates/admin/*.twig` |
| Change product page display | `includes/class-wc-tpp-frontend.php` + `templates/frontend/*.twig` |
| Fix cart pricing | `includes/class-wc-tpp-cart.php` |
| Update styles | `assets/css/frontend.css` or `assets/css/admin.css` |
| Fix JavaScript bugs | `assets/js/frontend.js` or `assets/js/admin.js` |
| Add translations | `languages/*.po` then compile to `.mo` |
| Document changes | `CHANGELOG.md` |

## Compatibility Notes

### WordPress

- Minimum: 6.0
- Tested up to: 6.9.x
- Uses standard plugin API, no deprecated functions

### WooCommerce

- Minimum: 8.0
- Tested up to: 10.x
- HPOS compatible (declared via `FeaturesUtil::declare_compatibility`)
- Blocks compatible (with proper filter handling)

### PHP

- Minimum: 7.4
- Uses modern PHP features (type hints acceptable in new code)
- Composer autoloader handles namespacing

### Browsers

- Modern browsers (ES6+ JavaScript)
- Responsive CSS (mobile-friendly)
- jQuery dependency (WooCommerce provides)

## Support & Resources

- **Repository:** <https://src.bundespruefstelle.ch/magdev/wc-tier-package-prices>
- **Documentation:** See `README.md`, `QUICKSTART.md`, `USAGE_EXAMPLES.md`, `INSTALLATION.md`
- **Changelog:** `CHANGELOG.md` (detailed version history)
- **Issue Tracking:** Check fatal-errors-*.log files for production errors

## Final Notes

This is a production-quality plugin with real-world usage. Any changes should:

1. Maintain backward compatibility with existing tier/package configurations
2. Not break WooCommerce core functionality
3. Work with both classic and block-based themes
4. Be thoroughly tested before release
5. Include proper error handling and validation
6. Update CHANGELOG.md with detailed explanations

The plugin architecture is solid and well-tested. Most bugs arise from:

- WooCommerce API changes (especially blocks)
- Filter/action signature changes
- Edge cases in cart calculations
- Settings persistence issues

---

Always refer to this document when starting work on this project. Good luck!
