Skip to content

Conversation

@samueljseay
Copy link
Contributor

@samueljseay samueljseay commented Oct 31, 2025

Changes proposed in this Pull Request:

TBD

Closes # .

(For Bug Fixes) Bug introduced in PR # .

How to test the changes in this Pull Request:

TBD

Testing that has already taken place:

TBD

Changelog entry

  • Automatically create a changelog entry from the details below.
  • This Pull Request does not require a changelog entry. (Comment required below)
Changelog Entry Details

Significance

  • Patch
  • Minor
  • Major

Type

  • Fix - Fixes an existing bug
  • Add - Adds functionality
  • Update - Update existing functionality
  • Dev - Development related task
  • Tweak - A minor adjustment to the codebase
  • Performance - Address performance issues
  • Enhancement - Improvement to existing functionality

Message

[Experimental] iAPI Add To Cart with Options Block - refactor quantity selection to avoid imperative DOM manipulation.

Changelog Entry Comment

Comment

@github-actions github-actions bot added the plugin: woocommerce Issues related to the WooCommerce Core plugin. label Oct 31, 2025
@claude
Copy link

claude bot commented Nov 4, 2025

Claude finished @samueljseay's task —— View job


Backwards Compatibility Review

Summary: MEDIUM Risk - Breaking changes to private/locked JavaScript store API

This refactoring introduces breaking changes to JavaScript Interactivity API stores that are currently marked as private/locked (lock: true). While these stores are technically private, the changes could affect extensions that have bypassed the lock to access internal APIs. The PHP changes are non-breaking.

Rationale: The PR renames/restructures internal WordPress Interactivity API stores (woocommerce/product-datawoocommerce/product-context) and modifies the signature of locked store actions. Extensions accessing these private stores will break. However, since these stores are locked with the message "I acknowledge that using a private store means my plugin will inevitably break on the next store release", this is technically within acceptable breakage boundaries for private APIs.

Breaking Changes Detected

1. JavaScript Store Renamed - woocommerce/product-datawoocommerce/product-context

Impact: HIGH for extensions using private stores
Files:

  • plugins/woocommerce/client/blocks/assets/js/base/stores/woocommerce/product-data.ts (DELETED)
  • plugins/woocommerce/client/blocks/assets/js/base/stores/woocommerce/product-context.ts (ADDED)
  • plugins/woocommerce/client/blocks/bin/webpack-config-interactive-blocks.js:46

Details:
The WordPress Interactivity API store woocommerce/product-data has been completely removed and replaced with woocommerce/product-context. The new store has a different structure:

Old structure:

store('woocommerce/product-data', {
  state: {
    productId: number,
    variationId: number | null
  },
  actions: {
    setVariationId: (variationId: number | null) => void
  }
})

New structure:

store('woocommerce/product-context', {
  state: {
    currentProductId: number,
    childProductId?: number,
    // No variationId at root level
  }
  // No actions exposed
})

Breaking changes:

  • state.productIdstate.currentProductId
  • state.variationId removed from store (moved to woocommerce/add-to-cart-with-options store as state.selectedVariationId)
  • actions.setVariationId() removed entirely

Affected code locations:

  • plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/frontend.ts:68
  • plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/frontend.ts:160
  • plugins/woocommerce/client/blocks/assets/js/atomic/blocks/product-elements/frontend.ts:23
  • plugins/woocommerce/client/blocks/assets/js/atomic/blocks/product-elements/button/frontend.ts:58
  • plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/frontend.ts:168

Extensions at risk:

  • Any extension accessing store('woocommerce/product-data') directly
  • Extensions reading productDataState.productId or productDataState.variationId
  • Extensions calling productDataActions.setVariationId()

Mitigation: Extensions must update to use the new store name and structure. However, since these stores are locked, this is expected breakage per the lock message.


2. Modified Action Signature - setQuantity() now requires third parameter

Impact: MEDIUM for extensions using private stores
File: plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/frontend.ts:157-160

Details:
The setQuantity action signature changed:

Old signature:

setQuantity(productId: number, value: number)

New signature:

setQuantity(productId: number, value: number, refToDispatchChangeEvent: HTMLInputElement | null)

Affected locations:

  • plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/frontend.ts:307
  • plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/quantity-selector/frontend.ts:115-120
  • plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/frontend.ts:361-365

Why changed: To programmatically dispatch 'change' events for compatibility with extensions monitoring quantity changes via DOM events (see issue #53031).

Backward compatibility: The third parameter is optional (refToDispatchChangeEvent?: HTMLInputElement), so existing calls with 2 parameters will still work functionally, but TypeScript consumers will get type errors if they haven't updated their code.

Extensions at risk:

  • TypeScript extensions directly calling actions.setQuantity() from the locked store
  • Extensions that bypass the lock and access the private API

Mitigation: Update calls to include the third parameter (or pass null). The parameter is optional at runtime, so existing calls will function but may not dispatch change events as expected.


3. PHP Data Attribute Changes - Interactivity API bindings renamed

Impact: LOW to MEDIUM for themes/extensions manipulating DOM
Files:

  • plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/AddToCartWithOptions.php:581
  • plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/QuantitySelector.php:162-168
  • plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/Utils.php:95-99
  • plugins/woocommerce/src/Blocks/Templates/SingleProductTemplate.php:91-98

Details:

AddToCartWithOptions.php:581 - Changed binding namespace:

// Old
// New

QuantitySelector.php:162-168 - Changed binding sources:

// Old

// New  // NEW

Utils.php:95-99 - Context attribute renamed:

// Old
wp_interactivity_data_wp_context([
    'productId' => $child_product_id || ...
])

// New
wp_interactivity_data_wp_context([
    'childProductId' => $child_product_id || ...  // Renamed key
], 'woocommerce/product-context')  // Now namespaced

SingleProductTemplate.php:91-98 - Server state structure changed:

// Old
wp_interactivity_state('woocommerce/product-data', [
    'templateState' => [
        'productId' => $product->get_id(),
        'variationId' => null,
    ]
])

// New
wp_interactivity_state('woocommerce/product-context', [
    'templateState' => [
        'currentProductId' => $product->get_id(),
        // variationId removed
    ]
])

Extensions at risk:

  • JavaScript code that queries or manipulates data-wp-* attributes on quantity inputs
  • Code reading context.productId from woocommerce/single-product context (should use woocommerce/product-context now)
  • Server-side code that filters or modifies the interactivity state before output

Mitigation: Extensions should avoid directly manipulating Interactivity API attributes. Use the public block attributes and WP hooks instead.


Non-Breaking Changes (Safe)

PHP Hooks Preserved: All do_action() and apply_filters() calls remain unchanged (verified in AddToCartWithOptions.php and related files)

Block Names/Attributes: No changes to block.json files detected

Public PHP APIs: No changes to public method signatures in AbstractBlock descendants

REST Endpoints: No REST API changes detected

Templates: No .html template file changes

CSS/Script Handles: Webpack entry points updated but handle names unchanged in production output


Ecosystem Impact Assessment

Payment Gateways: ⚠️ LOW RISK

Most payment gateways hook into checkout, not product pages. However, gateways that customize variable product forms via JS could be affected if they:

  • Monitor quantity changes via the private store API
  • Read productId/variationId from the old store

Testing recommended: PayPal with product-specific buttons, Stripe with variable subscriptions

WooCommerce Subscriptions: ⚠️ MEDIUM RISK

If Subscriptions customizes variable subscription product selection via the Interactivity API stores, it will break. Likely uses traditional WC JS patterns instead.

Testing recommended: Variable subscription product add-to-cart

WooCommerce Bookings/Memberships: ⚠️ LOW RISK

These typically extend product types and use their own forms. Risk is low unless they integrate with the block-based add-to-cart.

Themes: ⚠️ MEDIUM RISK

Block themes customizing product templates via FSE could be affected if they:

  • Override single product template and reference the old woocommerce/product-data store
  • Use custom JavaScript that reads product context via the old API
  • Filter the wp_interactivity_state output

Mitigation: Theme authors should update custom templates/JS to use new store names.

Custom Quantity Selectors: ⚠️ HIGH RISK

Extensions adding custom quantity controls that access the locked stores will break immediately.

Examples:

  • Min/Max Quantity extensions reading from private stores
  • Tiered pricing plugins monitoring quantity state changes
  • Bulk order forms that query product context

Testing Focus

Prioritize regression testing in these areas:

1. Variable Products
  • Select variations via dropdowns - verify state.selectedVariationId updates correctly
  • Change variation selection multiple times - ensure no stale state
  • Verify variation price updates display correctly
  • Check that invalid variation selections show proper errors
  • Test with extensions that modify variation forms (e.g., variation swatches)
2. Grouped Products
  • Add multiple grouped products with different quantities
  • Verify quantity can be set to 0 for grouped product children
  • Test min/max quantity constraints on grouped products
  • Ensure quantity validation works for grouped products
3. Quantity Changes
4. Add to Cart
  • Add simple product to cart
  • Add variable product to cart after selecting variation
  • Add grouped products to cart
  • Verify cart contains correct product IDs and quantities
  • Test with quantity > 1
5. Product Gallery
  • Change variation and verify gallery images update
  • Check that gallery thumbnails respond to variation changes
6. Extension Compatibility ⚠️ CRITICAL

Test with popular extensions that might use private APIs:

  • Variable product extensions (swatches, quick view)
  • Quantity customization plugins (min/max, wholesale)
  • Product bundles/composites
  • Subscription products with variations
  • Dynamic pricing plugins
  • Quick order forms
7. Theme Compatibility
  • Test with block themes that customize product templates
  • Verify FSE single product template renders correctly
  • Check themes with custom product page JavaScript

Deprecation Requirements

Since this PR makes breaking changes to locked APIs, the following deprecations are recommended:

1. Deprecation Notice for woocommerce/product-data Store

Recommendation: Add a deprecation shim that:

  1. Logs a console warning when the old store is accessed
  2. Proxies reads/writes to the new woocommerce/product-context store
  3. Maps productIdcurrentProductId and variationIdselectedVariationId (from add-to-cart store)

Example shim:

// Deprecation shim for backward compatibility
store('woocommerce/product-data', {
  state: {
    get productId() {
      console.warn('woocommerce/product-data::productId is deprecated. Use woocommerce/product-context::currentProductId');
      return store('woocommerce/product-context').state.currentProductId;
    },
    get variationId() {
      console.warn('woocommerce/product-data::variationId is deprecated. Use woocommerce/add-to-cart-with-options::selectedVariationId');
      return store('woocommerce/add-to-cart-with-options').state.selectedVariationId;
    }
  },
  actions: {
    setVariationId(id) {
      console.warn('woocommerce/product-data::setVariationId is deprecated.');
      // Map to new location if possible
    }
  }
});

Rationale: Even though the store is locked, providing a deprecation path reduces immediate breakage and gives extension authors time to migrate.


2. TypeScript Type Export

Current: Types are exported from the new location only
Recommendation: Re-export types from old location with @deprecated JSDoc

// In product-data.ts (restore as deprecated shim)
/**
 * @deprecated Use @woocommerce/stores/woocommerce/product-context instead
 */
export * from './product-context';

3. PHP Context Backward Compatibility

Current: productId context key renamed to childProductId
Recommendation: Include both keys during a deprecation period

// In Utils.php:95
wp_interactivity_data_wp_context([
    'childProductId' => $child_product_id || ...,
    'productId' => $child_product_id || ..., // @deprecated
], 'woocommerce/product-context')

Required Documentation Updates

If this PR merges without deprecation shims:

  1. Developer Blog Post: Announce breaking changes to Interactivity API stores
  2. Migration Guide: Document how to update code using old stores
  3. Changelog: Must clearly mark as breaking change with migration instructions
  4. Type: Must be "Dev" or "Breaking" in changelog

Suggested Changelog:

**Breaking**: Refactored WordPress Interactivity API stores for product context. The private `woocommerce/product-data` store has been replaced with `woocommerce/product-context`. Extensions using the locked product data stores must update to use the new store names and structure. See [migration guide](link) for details.

Recommendations

  1. Add Deprecation Shims: Implement backward compatibility layer for at least one major version
  2. Update Store Lock Message: Consider unlocking/stabilizing the product context API if many extensions depend on it
  3. Test Extension Compatibility: Reach out to popular extension authors for testing
  4. Document Breaking Changes: Create comprehensive migration guide before merging
  5. Consider Feature Flag: Gate changes behind a flag to allow opt-in testing period

Confidence: 85%

Justification: High confidence in BC risk assessment based on thorough code review of all 15 changed files. The breaking changes are clearly identifiable in the diff. Confidence is not 100% because:

  • Cannot verify all extensions that might use locked stores
  • Cannot test runtime behavior without running the code
  • May be extensions using creative workarounds to access private APIs that aren't immediately obvious

Final Assessment

No breaking changes detected ❌ - This statement would be FALSE

This PR introduces intentional breaking changes to private/locked JavaScript Interactivity API stores. While technically acceptable (stores are locked with an explicit warning), the changes will break extensions that have bypassed the lock.

Recommendation:

  • If this is acceptable breakage for a major version: ✅ Merge with comprehensive docs
  • If targeting a minor version: ⚠️ Add deprecation shims first
  • If extensions are known to use these stores: 🛑 Coordinate with extension authors

The PHP changes are safe and well-architected. The refactoring achieves its goal of avoiding imperative DOM manipulation. The breaking changes are a necessary consequence of improving the internal architecture.



const { state: productDataState } = store< ProductDataStore >(
'woocommerce/product-data',
const { state: productContextState } = store< ProductDataStore >(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BC Risk: Store name changed from woocommerce/product-data to woocommerce/product-context. Extensions accessing the old locked store will break.

Old: store('woocommerce/product-data')
New: store('woocommerce/product-context')

The state structure also changed:

  • productDataState.productIdproductContextState.currentProductId
  • productDataState.variationId removed (moved to this store as state.selectedVariationId)

Consider adding a deprecation shim for the old store name to ease migration.

selectedAttributes: SelectedAttributes[];
productData: NormalizedProductData | NormalizedVariationData | null;
childProductData: NormalizedProductData | NormalizedVariationData | null;
activeProduct: NormalizedProductData | NormalizedVariationData | null;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BC Risk: setQuantity() signature changed - added third parameter refToDispatchChangeEvent.

Old signature: setQuantity(productId: number, value: number)
New signature: setQuantity(productId: number, value: number, refToDispatchChangeEvent: HTMLInputElement | null)

The parameter is optional at runtime, so existing calls will work but may not dispatch change events. TypeScript consumers will get type errors.

Impact: Extensions calling this locked action will need to update their TypeScript types or pass null as the third parameter.

if ( $product ) {
wp_interactivity_state(
'woocommerce/product-data',
'woocommerce/product-context',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BC Risk: Server-side interactivity state structure changed.

Changes:

  1. Store name: woocommerce/product-datawoocommerce/product-context
  2. State key: productIdcurrentProductId
  3. Removed: variationId property

Impact: JavaScript code expecting the old state structure from server-rendered data will break. Extensions filtering this state before output will need updates.

Example of code that will break:

const { productId, variationId } = wp.interactivity.getContext('woocommerce/product-data');

Should now be:

const { currentProductId } = wp.interactivity.getContext('woocommerce/product-context');
const { selectedVariationId } = wp.interactivity.getContext('woocommerce/add-to-cart-with-options');

$context_attribute = wp_interactivity_data_wp_context(
array(
'productId' => $child_product_id || ! $product instanceof \WC_Product ?
'childProductId' => $child_product_id || ! $product instanceof \WC_Product ?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BC Risk: Context attribute key renamed from productId to childProductId.

Impact: JavaScript code reading context.productId from this context will get undefined. Extensions expecting the old key structure will break.

Mitigation: Consider including both keys during a deprecation period:

'childProductId' => $child_product_id || ...,
'productId' => $child_product_id || ..., // @deprecated for BC

$input_attributes['data-wp-bind--step'] = 'woocommerce/add-to-cart-with-options::state.productData.step';
$input_attributes['data-wp-watch'] = 'woocommerce/add-to-cart-with-options::callbacks.watchQuantityConstraints';
$input_attributes['data-wp-watch---dispatch-change-event'] = 'woocommerce/add-to-cart-with-options::callbacks.maybeDispatchChangeEvent';
$input_attributes['data-wp-bind--value'] = 'woocommerce/add-to-cart-with-options::state.inputQuantity';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BC Risk: Data binding attributes changed - namespace moved from woocommerce/product-elements to woocommerce/add-to-cart-with-options.

Changes:

  • data-wp-bind--min/max/step now points to woocommerce/add-to-cart-with-options::state.productData.* instead of woocommerce/product-elements::state.productData.*
  • Added new data-wp-bind--value binding to state.inputQuantity
  • Added new watch callback for change event dispatching

Impact: JavaScript code or browser extensions manipulating these DOM attributes will need to account for the new binding sources. Low risk since these are implementation details, but could affect advanced customizations.

@claude
Copy link

claude bot commented Nov 4, 2025

Claude finished @samueljseay's task —— View job


Backwards Compatibility Review

Summary: MEDIUM Risk - Breaking changes to private/locked JavaScript Interactivity API stores

This refactoring introduces breaking changes to JavaScript stores marked as private/locked (lock: true). The store woocommerce/product-data has been completely removed and replaced with woocommerce/product-context, with significant structural changes. While these stores are technically private with an explicit lock message ("I acknowledge that using a private store means my plugin will inevitably break..."), extensions that have bypassed the lock to access these APIs will break immediately. The PHP hooks and public APIs remain unchanged, so the risk is limited to JavaScript/TypeScript code accessing the locked Interactivity API stores.


Breaking Changes Detected

1. JavaScript Store Renamed and Restructured (HIGH impact for private API users)

Files affected:

  • product-data.ts (DELETED) → product-context.ts (ADDED)
  • frontend.ts:68 (store reference updated)
  • SingleProductTemplate.php:91-94 (server state updated)

Changes:

// OLD (trunk)
store('woocommerce/product-data', {
  state: {
    productId: number,
    variationId: number | null
  },
  actions: {
    setVariationId: (variationId: number | null) => void
  }
})

// NEW (this PR)  
store('woocommerce/product-context', {
  state: {
    currentProductId: number,
    childProductId?: number
    // variationId moved to woocommerce/add-to-cart-with-options::selectedVariationId
  }
  // No actions exposed
})

Breaking changes:

  • Store name: woocommerce/product-datawoocommerce/product-context
  • State key: productIdcurrentProductId
  • State: variationId removed (moved to woocommerce/add-to-cart-with-options::selectedVariationId)
  • Action: setVariationId() completely removed

Code that will break:

// Extensions doing this will break:
const { state } = store('woocommerce/product-data', {}, { lock: '...' });
const productId = state.productId; // ❌ undefined
const variationId = state.variationId; // ❌ undefined

2. Action Signature Changed (MEDIUM impact for private API users)

File: frontend.ts:170-173

Change:

// OLD
setQuantity(productId: number, value: number)

// NEW
setQuantity(productId: number, value: number, refToDispatchChangeEvent: HTMLInputElement | null)

The third parameter is optional at runtime (refToDispatchChangeEvent?: HTMLInputElement), so existing calls will work functionally but TypeScript consumers will get type errors. The parameter is used to dispatch synthetic change events for extension compatibility (issue #53031).

3. PHP Interactivity Bindings Updated (LOW impact)

Files:

  • QuantitySelector.php:162-168: Binding namespace changed from woocommerce/product-elements to woocommerce/add-to-cart-with-options
  • Utils.php:95: Context key renamed from productId to childProductId
  • SingleProductTemplate.php:94: State key renamed from productId to currentProductId

Impact: JavaScript code querying these data-wp-* attributes or reading context properties by name will break. Low risk as this is an internal implementation detail.


Non-Breaking Changes (Safe)

PHP Hooks: No changes to do_action() or apply_filters() calls
Block Attributes: No changes to block.json schemas
Public APIs: No changes to public PHP class methods
REST Endpoints: No REST API modifications
Templates: No .html template file changes


Testing Focus

Prioritize regression testing in these critical areas:

1. Variable Products (CRITICAL)

  • Select variations via dropdowns - verify selection state updates correctly
  • Change variation selection multiple times - ensure no stale state
  • Verify variation price updates display after selection changes
  • Test invalid variation selections show proper validation errors
  • Check product gallery updates when variation changes
  • Test with extensions that modify variation forms (e.g., WooCommerce Additional Variation Images, variation swatches)

2. Grouped Products (HIGH)

  • Add multiple grouped child products with different quantities
  • Set quantity to 0 for some grouped product children
  • Test min/max quantity constraints on grouped products
  • Verify quantity validation works correctly for grouped products
  • Ensure correct product IDs are sent to cart for grouped products

3. Quantity Changes (HIGH - issue #53031 compatibility)

  • Use stepper buttons (+/-) to change quantity
  • Type quantity directly into input field
  • Tab away from input with invalid quantity (letters, 0, exceeds max)
  • CRITICAL: Verify change events are dispatched for extension compatibility
  • Test with extensions monitoring quantity changes via DOM events (e.g., Min/Max Quantities)

4. Add to Cart (CRITICAL)

  • Add simple product to cart
  • Add variable product to cart after selecting variation
  • Add grouped products to cart (multiple children)
  • Verify cart contains correct product IDs and quantities
  • Test with quantity > 1

5. Extension Compatibility (HIGH RISK - private API users)

Test with popular extensions that might use the locked stores:

High Risk Extensions:

  • WooCommerce Subscriptions (if uses Interactivity API for variable subscriptions)
  • Variable product extensions (Product Add-Ons, variation swatches)
  • Quantity customization plugins (Min/Max Quantities, Wholesale Suite)
  • Product Bundles/Composite Products
  • Dynamic pricing plugins that monitor product state

Testing approach: Install extension, test variable/grouped product add-to-cart flow, check browser console for errors like:

Cannot read property 'productId' of undefined
store 'woocommerce/product-data' is not defined

6. Theme Compatibility (MEDIUM)

  • Test with block themes customizing single product templates via FSE
  • Verify custom product page JavaScript doesn't break
  • Check themes with custom add-to-cart forms

Ecosystem Impact Assessment

Payment Gateways: ⚠️ LOW RISK - Most gateways don't hook into product pages, but test PayPal/Stripe if they customize product forms

WooCommerce Subscriptions: ⚠️ MEDIUM RISK - If Subscriptions customizes variable subscription selection via the old store, it will break

WooCommerce Bookings/Memberships: ⚠️ LOW RISK - These use their own forms, minimal interaction with block stores

Themes: ⚠️ MEDIUM RISK - Themes with custom product template JS accessing the old store will break

Custom Quantity Selectors: ⚠️ HIGH RISK - Extensions accessing locked stores for quantity state will break immediately


Recommendations

  1. Add Deprecation Shim (optional but recommended): Create a compatibility layer for woocommerce/product-data that:

    • Logs console warnings when accessed
    • Proxies reads to woocommerce/product-context
    • Provides one major version of backwards compatibility
  2. Document Breaking Changes: Create migration guide for extension developers:

    // Migration example
    // OLD
    const { state } = store('woocommerce/product-data', {}, { lock: '...' });
    const id = state.productId;
    const variation = state.variationId;
    
    // NEW
    const { state: contextState } = store('woocommerce/product-context', {}, { lock: '...' });
    const { state: cartState } = store('woocommerce/add-to-cart-with-options', {}, { lock: '...' });
    const id = contextState.currentProductId;
    const variation = cartState.selectedVariationId;
  3. Changelog Entry: Mark as breaking change:

    Type: Dev
    Significance: Minor
    
    Breaking: Refactored WordPress Interactivity API stores for product context. 
    The private woocommerce/product-data store has been replaced with 
    woocommerce/product-context. Extensions using the locked product data 
    stores must update to use the new store names and structure.
    
  4. Extension Outreach: If known extensions use these private stores, notify authors before release


Confidence: 90%

Justification: High confidence based on thorough analysis of all 15 changed files and verification against trunk. The breaking changes are clearly identifiable in the diff. Confidence is not 100% because:

  • Cannot test runtime behavior or verify all extensions that might bypass the lock
  • May be extensions using creative workarounds to access private APIs
  • The stores are private/locked, so this breakage is technically "expected" per the lock message

Final Assessment

Breaking changes detected:YES - This PR introduces intentional breaking changes to private/locked JavaScript Interactivity API stores.

Is this acceptable?

  • ✅ For major version releases
  • ⚠️ For minor releases if the private API lock is considered sufficient warning
  • 🛑 For patch releases

The refactoring is architecturally sound and achieves its goal of avoiding imperative DOM manipulation. The breaking changes are a necessary consequence of improving the internal architecture. Since the stores are locked with an explicit warning message, the breakage is within acceptable boundaries for private APIs.

PHP changes:Non-breaking - All PHP hooks, public APIs, and templates remain unchanged.


Review complete. I've also added inline comments on the specific files/lines where breaking changes occur for easier reference during code review.


const { state: productDataState } = store< ProductDataStore >(
'woocommerce/product-data',
const { state: productContextState } = store< ProductDataStore >(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BC Risk (HIGH): Store name changed from woocommerce/product-data to woocommerce/product-context.

Extensions accessing the old locked store will break:

// This will break:
const { state } = store('woocommerce/product-data', {}, { lock: '...' });

Mitigation: Consider adding a deprecation shim that proxies the old store name to the new one with console warnings, providing one major version of backwards compatibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to do this. We explicitly made stores private so they can be subject to change right now.

actions: {
validateQuantity: ( productId: number, value?: number ) => void;
setQuantity: ( productId: number, value: number ) => void;
setQuantity: (
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BC Risk (MEDIUM): setQuantity() signature changed - added third parameter.

Old: setQuantity(productId: number, value: number)
New: setQuantity(productId: number, value: number, refToDispatchChangeEvent: HTMLInputElement | null)

The parameter is optional at runtime, so existing calls will function but TypeScript consumers will get type errors. The new parameter enables dispatching synthetic change events for extension compatibility (#53031).

if ( $product ) {
wp_interactivity_state(
'woocommerce/product-data',
'woocommerce/product-context',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BC Risk (MEDIUM): Server-side interactivity state structure changed.

Changes:

  1. Store name: woocommerce/product-datawoocommerce/product-context
  2. State key: productIdcurrentProductId (line 94)
  3. Removed: variationId property (moved to woocommerce/add-to-cart-with-options::selectedVariationId)

Impact: JavaScript reading server-rendered state from the old store name or expecting the old keys will break.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again this is all private right now. We must be able to adjust the API as we see fit before releasing these stores to be public.

$context_attribute = wp_interactivity_data_wp_context(
array(
'productId' => $child_product_id || ! $product instanceof \WC_Product ?
'childProductId' => $child_product_id || ! $product instanceof \WC_Product ?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BC Risk (LOW): Context attribute key renamed from productId to childProductId.

JavaScript code reading context.productId from this context will receive undefined. Consider including both keys during a deprecation period:

'childProductId' => $child_product_id || ...,
'productId' => $child_product_id || ..., // @deprecated for BC

$input_attributes['data-wp-bind--max'] = 'woocommerce/product-elements::state.productData.max';
$input_attributes['data-wp-bind--step'] = 'woocommerce/product-elements::state.productData.step';
$input_attributes['data-wp-watch'] = 'woocommerce/add-to-cart-with-options::callbacks.watchQuantityConstraints';
$wrapper_attributes['data-wp-bind--hidden'] = 'woocommerce/add-to-cart-with-options-quantity-selector::!state.allowsQuantityChange';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BC Risk (LOW): Data binding namespace changed from woocommerce/product-elements to woocommerce/add-to-cart-with-options.

Changes (lines 162-168):

  • data-wp-bind--min/max/step now points to woocommerce/add-to-cart-with-options::state.productData.*
  • Added data-wp-bind--value binding to state.inputQuantity
  • Added watch callback for change event dispatching

Impact: Extensions manipulating these DOM attributes directly will need updates. Low risk as these are implementation details.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extensions know this is a private store that can change. We provide no shim or back compat for this. esp note that this new add to cart with options block is still in experimental state.

@claude
Copy link

claude bot commented Nov 4, 2025

Claude finished @samueljseay's task —— View job


Backwards Compatibility Review

Summary: LOW Risk - Intentional refactoring of private/experimental APIs with acceptable breakage boundaries

This PR refactors WordPress Interactivity API stores (woocommerce/product-datawoocommerce/product-context) that are explicitly locked and marked as private with the warning: "I acknowledge that using a private store means my plugin will inevitably break on the next store release." Additionally, the author has confirmed that the Add to Cart with Options block is still in experimental state. Therefore, breaking changes to these internal APIs are within acceptable boundaries and expected behavior for locked/experimental features.

Key Context from Author:

  • These stores are intentionally private and subject to change
  • The Add to Cart with Options block is experimental
  • No backwards compatibility shims or deprecation paths are required for private APIs
  • Extensions using these locked stores accepted the risk of breakage

Breaking Changes (Acceptable for Private/Experimental APIs)

1. JavaScript Store Renamed (Expected breakage for locked API)

Change: woocommerce/product-datawoocommerce/product-context

Files:

  • product-data.ts (DELETED) → product-context.ts:20 (ADDED)
  • frontend.ts:68 (store reference updated)
  • SingleProductTemplate.php:91-94 (server state updated)

State structure changes:

// OLD
state: {
  productId: number,
  variationId: number | null
}

// NEW
state: {
  currentProductId: number,
  childProductId?: number
  // variationId moved to woocommerce/add-to-cart-with-options::selectedVariationId
}

Impact: Extensions bypassing the lock will break. This is expected and acceptable per the lock message.


2. Action Signature Updated (Extension compatibility improvement)

Change: setQuantity() signature at frontend.ts:306-310

// OLD
setQuantity(productId: number, value: number)

// NEW  
setQuantity(productId: number, value: number, refToDispatchChangeEvent?: HTMLInputElement)

Rationale: Third parameter enables dispatching synthetic change events for extension compatibility (issue #53031). The parameter is optional, so existing calls remain functional.

Impact: TypeScript consumers may see type warnings, but runtime behavior is backwards compatible.


3. PHP Interactivity Bindings Updated (Internal implementation detail)

Changes:

  • SingleProductTemplate.php:91-94: Store name and state keys updated
  • Utils.php:95: Context key renamed productIdchildProductId
  • QuantitySelector.php:162-168: Binding namespace updated

Impact: Minimal - these are internal WordPress Interactivity API implementation details. Extensions should not be directly manipulating these attributes.


Non-Breaking Changes ✅

  • PHP Hooks: No changes to do_action() or apply_filters() calls
  • Public PHP APIs: No changes to public class method signatures
  • REST Endpoints: No REST API modifications
  • Block Attributes: No changes to block.json schemas (verified via grep)
  • Templates: No .html template file changes
  • CSS/Script Handles: Webpack config updated but public handles unchanged

Testing Focus

Since the block is experimental and stores are private, testing should focus on functional regression rather than API stability:

1. Variable Products (CRITICAL)

  • Select variations via dropdowns
  • Change variation selection multiple times - verify no stale state
  • Verify variation price and image updates
  • Test invalid variation selections show validation errors
  • Check add-to-cart with selected variation

2. Grouped Products (HIGH)

  • Add multiple grouped child products with different quantities
  • Set quantity to 0 for grouped product children
  • Verify quantity validation for grouped products
  • Test add-to-cart with multiple grouped products

3. Quantity Management (HIGH - issue #53031)

  • Use stepper buttons (+/-) to change quantity
  • Type quantity directly into input
  • Test invalid quantities (0, exceeds max, non-numeric)
  • CRITICAL: Verify change events are dispatched (extensions monitoring quantity changes)

4. Simple Products (MEDIUM)

  • Basic add-to-cart flow
  • Quantity validation
  • Out of stock handling

5. Product Gallery (MEDIUM)

  • Gallery updates when variation selection changes
  • Thumbnail navigation works correctly

Ecosystem Impact Assessment

Risk Level: LOW - Experimental block with locked APIs

Extensions at Risk:

  • Only extensions that have explicitly bypassed the lock on private stores
  • Extensions accessing woocommerce/product-data store directly
  • Custom implementations of the experimental Add to Cart with Options block

Mainstream Extensions: ⚠️ LOW RISK

  • Payment gateways: No impact (checkout-focused)
  • WooCommerce Subscriptions: Low risk unless using experimental blocks
  • Product Add-Ons: Low risk unless interfacing with locked stores
  • Dynamic Pricing: Low risk unless reading private store state

Themes: ⚠️ LOW RISK

  • Only themes customizing experimental blocks or accessing locked stores
  • Standard block themes remain unaffected

Recommendations

  1. No Deprecation Required: Since stores are locked and block is experimental, breaking changes are acceptable without deprecation paths.

  2. Documentation: Update developer docs to reflect:

    • Store name change from woocommerce/product-data to woocommerce/product-context
    • State structure changes (once API is stabilized)
    • Timeline for API stabilization
  3. Changelog Entry:

    Type: Dev
    Significance: Patch
    
    Dev: Refactored WordPress Interactivity API stores for Add to Cart with Options block. 
    Renamed woocommerce/product-data to woocommerce/product-context and centralized 
    product state management. Note: These stores remain private/experimental and subject to change.
    
  4. Testing: Focus on functional regression testing (see Testing Focus section above)

  5. API Stabilization: Consider publishing a stability roadmap for when these APIs will be public


Confidence: 95%

Justification: Very high confidence in the BC assessment based on:

  • Thorough code review of all 15 changed files
  • Explicit author confirmation that stores are private and experimental
  • Clear lock message on stores warning of breaking changes
  • No changes to public PHP APIs, hooks, or templates
  • Changes are limited to experimental block functionality

The 5% uncertainty accounts for:

  • Potential extensions that bypassed locks despite warnings (unknown risk)
  • Future API stabilization requirements not yet defined

Final Assessment

Breaking changes detected:YES - But acceptable within private/experimental API boundaries

Risk Level:LOW - Breaking changes are:

  1. Limited to locked stores with explicit warning messages
  2. Within an experimental block
  3. Not affecting public PHP APIs or hooks
  4. Expected and acceptable per the author's design intent

Recommendation:SAFE TO MERGE with functional testing

The refactoring achieves its architectural goal of avoiding imperative DOM manipulation while maintaining backwards compatibility for all public APIs. The breaking changes to private/locked stores are within acceptable boundaries for pre-stable experimental features.

PHP APIs:Fully backwards compatible - All hooks, public methods, and templates unchanged


@claude
Copy link

claude bot commented Nov 4, 2025

Claude finished @samueljseay's task —— View job


Backwards Compatibility Review

Summary: LOW Risk - Private/experimental API refactoring with acceptable breakage boundaries

This PR refactors WordPress Interactivity API stores that are explicitly locked and marked as private with the warning: "I acknowledge that using a private store means my plugin will inevitably break on the next store release." The Add to Cart with Options block is experimental. Breaking changes to these internal APIs are within acceptable boundaries and represent expected behavior for locked/experimental features.

Key findings:

  • JavaScript stores: Breaking changes limited to private/locked stores (woocommerce/product-datawoocommerce/product-context)
  • PHP APIs: No breaking changes - all hooks, public methods, and templates remain unchanged
  • Block attributes: No changes to block.json schemas
  • Impact: Only affects extensions that have explicitly bypassed the lock on private stores

Breaking Changes (Acceptable for Private/Experimental APIs)

1. JavaScript Store Renamed and Restructured

Change: woocommerce/product-data (DELETED) → woocommerce/product-context (ADDED)

Affected files:

  • product-data.ts:20 (deleted) → product-context.ts:20 (added)
  • frontend.ts:65-68 (store reference updated)
  • product-gallery/frontend.ts:168
  • product-elements/button/frontend.ts:58
  • SingleProductTemplate.php:90-97 (server state updated)

State structure changes:

// OLD (trunk)
store('woocommerce/product-data', {
  state: {
    productId: number,
    variationId: number | null
  },
  actions: {
    setVariationId: (variationId: number | null) => void
  }
})

// NEW (this PR)
store('woocommerce/product-context', {
  state: {
    currentProductId: number,
    childProductId?: number
    // variationId moved to woocommerce/add-to-cart-with-options::selectedVariationId
  }
  // No actions exposed
})

Breaking aspects:

  • Store name changed
  • state.productIdstate.currentProductId
  • state.variationId removed (moved to woocommerce/add-to-cart-with-options::state.selectedVariationId)
  • actions.setVariationId() removed entirely

Impact: Extensions bypassing the lock will break immediately. This is expected and acceptable per the lock message at frontend.ts:56-57.


2. Action Signature Updated for Extension Compatibility

Change: setQuantity() signature at frontend.ts:303-310

// OLD
setQuantity(productId: number, value: number)

// NEW
setQuantity(productId: number, value: number, refToDispatchChangeEvent?: HTMLInputElement)

Purpose: The third parameter enables dispatching synthetic change events for extension compatibility (addresses issue #53031, see frontend.ts:39-53).

Impact:

  • Runtime: Backwards compatible (parameter is optional)
  • TypeScript: Consumers may see type warnings but code will function
  • Benefit: Extensions monitoring quantity changes via DOM events will continue working

3. PHP Interactivity Bindings Updated (Internal Implementation)

Changes:

  • SingleProductTemplate.php:90-97: Store name and state key productIdcurrentProductId
  • Utils.php:95: Context key productIdchildProductId
  • QuantitySelector.php:162-168: Binding namespace updated to woocommerce/add-to-cart-with-options

Impact: Minimal - these are internal WordPress Interactivity API implementation details. Extensions should not directly manipulate data-wp-* attributes.


Non-Breaking Changes ✅

Verified no changes to:

  • PHP Hooks: No changes to do_action() or apply_filters() calls (verified in all changed PHP files)
  • Public PHP APIs: No changes to public class method signatures
  • REST Endpoints: No REST API modifications detected
  • Block Attributes: No block.json files changed (verified via git diff)
  • Templates: No .html template file changes
  • Script Handles: Webpack config updated but public handles remain unchanged

Testing Focus

Since the block is experimental and stores are private, testing should prioritize functional regression:

1. Variable Products (CRITICAL)

  • Select variations via dropdowns - verify state updates correctly
  • Change variation selection multiple times - ensure no stale state
  • Verify variation price and gallery image updates
  • Test invalid variation selections show proper validation errors
  • Add selected variation to cart - verify correct product ID

Why critical: Core functionality most affected by state refactoring (selectedVariationId moved to new location)

2. Grouped Products (HIGH)

  • Add multiple grouped child products with different quantities
  • Set quantity to 0 for some grouped products (should be allowed per frontend.ts:253-259)
  • Verify quantity validation for grouped products
  • Test add-to-cart with multiple grouped products

Why high: New childProductId concept introduced to handle grouped products (product-context.ts:8)

3. Quantity Management (HIGH - Extension Compatibility)

Why high: New dispatchChangeEvent mechanism added specifically for extension compatibility (frontend.ts:50-53)

4. Simple Products (MEDIUM)

  • Basic add-to-cart flow with quantity > 1
  • Quantity validation (min/max/step constraints)
  • Out of stock product handling

5. Product Gallery (MEDIUM)

  • Gallery updates when variation changes (tests new store reference at product-gallery/frontend.ts:168)
  • Thumbnail navigation remains functional
  • Verify correct images display for selected variation

Ecosystem Impact Assessment

Overall Risk Level: 🟢 LOW - Changes are isolated to experimental/locked APIs

Extensions at Risk: (Only if bypassing locks)

  • Custom implementations accessing woocommerce/product-data store directly
  • Extensions reading productDataState.productId or productDataState.variationId
  • Extensions calling productDataActions.setVariationId()
  • Custom quantity selectors interfacing with locked stores

Mainstream Extensions: 🟢 LOW RISK

  • Payment Gateways: No impact (checkout-focused, not product page stores)
  • WooCommerce Subscriptions: Low risk unless using experimental blocks with locked stores
  • Product Add-Ons: Low risk unless interfacing with renamed stores
  • Dynamic Pricing: Low risk unless reading private store state
  • Variation Swatches: Low risk unless accessing locked product data store

Themes: 🟢 LOW RISK

  • Only themes customizing experimental blocks or accessing locked stores
  • Standard block themes and FSE remain unaffected
  • Custom product templates using public APIs remain functional

Why low risk:

  1. Stores are explicitly locked with warning message (frontend.ts:55-57)
  2. Block is marked as experimental (per author comment on review_comments)
  3. No public API surface area changed (hooks, templates, REST endpoints)
  4. Extensions accessing these stores accepted breakage risk via lock message

Recommendations

1. No Deprecation Required

Since stores are locked and the block is experimental, breaking changes are acceptable without deprecation paths. The lock message explicitly warns of breakage.

2. Documentation Updates (When API stabilizes)

  • Document store name change (woocommerce/product-datawoocommerce/product-context)
  • Document state structure changes once API is public
  • Publish roadmap for API stabilization

3. Changelog Entry

Type: Dev
Significance: Patch

Dev: Refactored WordPress Interactivity API stores for Add to Cart with Options block.
Renamed internal woocommerce/product-data store to woocommerce/product-context and 
centralized product state management to avoid imperative DOM manipulation.
Note: These stores remain private/experimental and subject to change.

4. Testing Priority

Focus on functional regression testing (see Testing Focus section) rather than API stability testing, since the APIs are private/experimental.


Confidence: 95%

Justification: Very high confidence based on:

  • ✅ Thorough code review of all 15 changed files
  • ✅ Explicit author confirmation that stores are private/experimental (review comments)
  • ✅ Clear lock message warning of breaking changes (frontend.ts:55-57)
  • ✅ Verified no changes to public PHP APIs, hooks, or templates
  • ✅ Verified no block.json attribute changes
  • ✅ Changes limited to experimental block functionality

The 5% uncertainty accounts for potential extensions that bypassed locks despite warnings (unknown risk).


Final Assessment

Breaking changes detected:YES - But acceptable within private/experimental API boundaries

Risk Level: 🟢 LOW - Breaking changes are:

  1. Limited to locked stores with explicit warning messages
  2. Within an experimental block
  3. Not affecting public PHP APIs, hooks, or templates
  4. Expected and acceptable per the lock message and author's design intent

Recommendation:SAFE TO MERGE with functional regression testing

The refactoring achieves its architectural goal of eliminating imperative DOM manipulation (as stated in PR title) while maintaining full backwards compatibility for all public APIs. Breaking changes to private/locked stores are within acceptable boundaries for pre-stable experimental features.


PHP APIs:Fully backwards compatible
JavaScript Public APIs:No public APIs affected
Private/Locked APIs: ⚠️ Breaking changes as expected/documented


@claude
Copy link

claude bot commented Nov 5, 2025

Claude finished @samueljseay's task —— View job


Backwards Compatibility Review

Summary: LOW Risk - Refactoring of private/experimental APIs with acceptable breakage boundaries

This PR refactors WordPress Interactivity API stores (woocommerce/product-datawoocommerce/product-context) that are explicitly locked and marked as private/experimental with the warning at frontend.ts:55-57: "I acknowledge that using a private store means my plugin will inevitably break on the next store release." The Add to Cart with Options block remains experimental. Breaking changes to these internal APIs are within acceptable boundaries and represent expected behavior for locked/experimental features.

Key Context:

  • Private/locked stores are subject to change without deprecation (per lock message)
  • The Add to Cart with Options block is experimental (confirmed by author in review comments)
  • All PHP APIs remain backwards compatible - no changes to hooks, public methods, or templates
  • No block.json changes - block attributes and public APIs unchanged
  • Breaking changes limited to private JavaScript store APIs only

Breaking Changes (Acceptable for Private/Experimental APIs)

1. JavaScript Store Renamed and Restructured

Change: woocommerce/product-data (DELETED) → woocommerce/product-context (ADDED)

Affected files:

  • product-data.ts (deleted) → product-context.ts:20 (added)
  • frontend.ts:65-68 (store reference updated)
  • product-gallery/frontend.ts:168
  • product-elements/button/frontend.ts:58
  • SingleProductTemplate.php:91-94 (server state updated)

State structure changes:

// OLD (trunk)
store('woocommerce/product-data', {
  state: {
    productId: number,
    variationId: number | null
  },
  actions: {
    setVariationId: (variationId: number | null) => void
  }
})

// NEW (this PR)
store('woocommerce/product-context', {
  state: {
    currentProductId: number,
    childProductId?: number
    // variationId moved to woocommerce/add-to-cart-with-options::selectedVariationId
  }
  // No actions exposed
})

Breaking aspects:

  • Store name: woocommerce/product-datawoocommerce/product-context
  • State key: productIdcurrentProductId (product-context.ts:23-31)
  • State: variationId removed (moved to woocommerce/add-to-cart-with-options::state.selectedVariationId at frontend.ts:151)
  • Action: setVariationId() removed entirely
  • New concept: childProductId added for grouped products (product-context.ts:8)

Impact: Extensions bypassing the lock will break immediately. This is expected and acceptable per the explicit lock message at frontend.ts:55-57.


2. Action Signature Updated for Extension Compatibility

Change: setQuantity() signature at frontend.ts:303-310

// OLD
setQuantity(productId: number, value: number)

// NEW
setQuantity(
  productId: number, 
  value: number, 
  refToDispatchChangeEvent?: HTMLInputElement
)

Purpose: The third parameter enables dispatching synthetic change events for extension compatibility (addresses issue #53031, documented at frontend.ts:39-53).

Impact:

  • Runtime: Backwards compatible (parameter is optional with ?)
  • TypeScript: Consumers may see type warnings but code will function
  • Benefit: Extensions monitoring quantity changes via DOM events will continue working (see dispatchChangeEvent at frontend.ts:50-53)

3. PHP Interactivity Bindings Updated (Internal Implementation)

Changes:

a) SingleProductTemplate.php:88-97 - Store name and state keys:

// OLD
wp_interactivity_state('woocommerce/product-data', [
    'templateState' => [
        'productId' => $product->get_id(),
        'variationId' => null,
    ]
])

// NEW
wp_interactivity_state('woocommerce/product-context', [
    'templateState' => [
        'currentProductId' => $product->get_id(),
    ]
])

b) Utils.php:92-99 - Context key renamed and namespaced:

// OLD
'productId' => $child_product_id || ...

// NEW  
'childProductId' => $child_product_id || ...
// Now explicitly namespaced to 'woocommerce/product-context'

c) QuantitySelector.php:162-170 - Binding namespace updated:

// OLD

// NEW
// New watch callback for change event dispatching

d) AddToCartWithOptions.php:581 - Binding simplified (no longer namespaced):

// OLD

// NEW

Impact: Minimal - these are internal WordPress Interactivity API implementation details. Extensions should not directly manipulate data-wp-* attributes. Low risk as these bindings are not part of the public API surface.


Non-Breaking Changes ✅

Verified no changes to:

  • PHP Hooks: No do_action() or apply_filters() calls modified (verified via diff search)
  • Public PHP APIs: No changes to public class method signatures
  • REST Endpoints: No REST API modifications detected
  • Block Attributes: No block.json files changed (verified - no block.json in changed files)
  • Templates: No .html template file changes
  • Script Handles: Webpack config updated (webpack-config-interactive-blocks.js:46) but public handles remain unchanged - only internal store name reference updated

Testing Focus

Since the block is experimental and stores are private, testing should prioritize functional regression:

1. Variable Products (CRITICAL)

  • Select variations via dropdowns - verify state.selectedVariationId updates correctly
  • Change variation selection multiple times - ensure no stale state
  • Verify variation price updates after selection changes
  • Test invalid variation selections show proper validation errors
  • Add selected variation to cart - verify correct product ID in cart
  • Check product gallery images update when variation changes

Why critical: Core functionality most affected by state refactoring (selectedVariationId moved from product-data store to add-to-cart-with-options store at frontend.ts:151)

2. Grouped Products (HIGH)

  • Add multiple grouped child products with different quantities
  • Set quantity to 0 for some grouped products (should be allowed per frontend.ts:253-259)
  • Verify quantity validation for grouped products
  • Test add-to-cart with multiple grouped products
  • Confirm correct product IDs sent to cart for grouped items

Why high: New childProductId concept introduced to handle grouped products (product-context.ts:8). New derived state activeProduct at frontend.ts:241-248 determines which product data to use.

3. Quantity Management (HIGH - Extension Compatibility)

  • Use stepper buttons (+/-) to change quantity
  • Type quantity directly into input field
  • Test invalid quantities (0 for non-grouped, exceeds max, non-numeric)
  • CRITICAL: Verify change events are dispatched for extensions monitoring quantity changes (issue Using qty input stepper in 9.5.0-beta1 does not trigger a qty field change #53031)
  • Blur input with valid/invalid quantity
  • Test inputQuantity derived state updates correctly (frontend.ts:211-219)

Why high: New dispatchChangeEvent mechanism added specifically for extension compatibility (frontend.ts:50-53). New inputQuantity binding at QuantitySelector.php:170 and Utils.php:75.

4. Simple Products (MEDIUM)

  • Basic add-to-cart flow with quantity > 1
  • Quantity validation (min/max/step constraints)
  • Out of stock product handling
  • Test with default quantity values

5. Product Gallery (MEDIUM)

  • Gallery updates when variation changes (tests new store reference at product-gallery/frontend.ts:168)
  • Thumbnail navigation remains functional
  • Verify correct images display for selected variation

6. Product Context Propagation (MEDIUM)

  • Verify currentProductId is correctly derived from context or template state (product-context.ts:23-31)
  • Test context inheritance in nested block structures
  • Confirm childProductId overrides work correctly for grouped products

Ecosystem Impact Assessment

Overall Risk Level: 🟢 LOW - Changes isolated to experimental/locked APIs

Extensions at Risk (Only if bypassing locks):

  • Custom implementations accessing woocommerce/product-data store directly
  • Extensions reading productDataState.productId or productDataState.variationId
  • Extensions calling productDataActions.setVariationId()
  • Custom quantity selectors interfacing with locked stores
  • Code expecting productId context key (now childProductId at Utils.php:95)

Mainstream Extensions: 🟢 LOW RISK

  • Payment Gateways: No impact (checkout-focused, not product page stores)
  • WooCommerce Subscriptions: Low risk unless using experimental blocks with locked stores
  • Product Add-Ons: Low risk unless interfacing with renamed stores
  • Dynamic Pricing: Low risk unless reading private store state
  • Variation Swatches: Low risk unless accessing locked product data store
  • Min/Max Quantities: Low risk unless monitoring private stores (should use DOM events which are now supported)

Themes: 🟢 LOW RISK

  • Only themes customizing experimental blocks or accessing locked stores affected
  • Standard block themes and FSE remain unaffected
  • Custom product templates using public APIs remain functional
  • Themes filtering interactivity state would need updates (rare)

Why low risk:

  1. Stores explicitly locked with warning message (frontend.ts:55-57)
  2. Block marked as experimental (per author confirmation)
  3. No public API surface area changed (hooks, templates, REST endpoints)
  4. Extensions accessing these stores accepted breakage risk via lock message
  5. Extension compatibility actually improved via dispatchChangeEvent mechanism (Using qty input stepper in 9.5.0-beta1 does not trigger a qty field change #53031)

Recommendations

1. No Deprecation Required

Since stores are locked and the block is experimental, breaking changes are acceptable without deprecation paths. The lock message explicitly warns of inevitable breakage.

2. Documentation Updates (When API stabilizes)

  • Document store name change (woocommerce/product-datawoocommerce/product-context)
  • Document state structure changes once API is public
  • Publish roadmap for API stabilization
  • Explain childProductId vs currentProductId distinction

3. Changelog Entry

Type: Dev
Significance: Patch

Dev: Refactored WordPress Interactivity API stores for Add to Cart with Options block.
Renamed internal woocommerce/product-data store to woocommerce/product-context and 
centralized product state management to avoid imperative DOM manipulation. Added support 
for dispatching change events to improve extension compatibility (#53031).
Note: These stores remain private/experimental and subject to change.

4. Testing Priority

Focus on functional regression testing (see Testing Focus section) rather than API stability testing, since the APIs are private/experimental.


Confidence: 95%

Justification: Very high confidence based on:

  • ✅ Thorough code review of all 15 changed files
  • ✅ Explicit author confirmation that stores are private/experimental (review comments)
  • ✅ Clear lock message warning of breaking changes (frontend.ts:55-57)
  • ✅ Verified no changes to public PHP APIs, hooks, or templates
  • ✅ Verified no block.json attribute changes
  • ✅ Changes limited to experimental block functionality
  • ✅ Analyzed server-side state initialization and client-side consumption

The 5% uncertainty accounts for potential extensions that bypassed locks despite warnings (unknown risk).


Final Assessment

Breaking changes detected:YES - But acceptable within private/experimental API boundaries

Risk Level: 🟢 LOW - Breaking changes are:

  1. Limited to locked stores with explicit warning messages
  2. Within an experimental block
  3. Not affecting public PHP APIs, hooks, or templates
  4. Expected and acceptable per the lock message and author's design intent
  5. Actually improve extension compatibility via change event dispatching

Recommendation:SAFE TO MERGE with functional regression testing

Architectural improvements:

The refactoring achieves its architectural goal of eliminating imperative DOM manipulation (as stated in PR title) while maintaining full backwards compatibility for all public APIs. Breaking changes to private/locked stores are within acceptable boundaries for pre-stable experimental features.


PHP APIs:Fully backwards compatible
JavaScript Public APIs:No public APIs affected
Private/Locked APIs: ⚠️ Breaking changes as expected/documented


@claude
Copy link

claude bot commented Nov 5, 2025

Claude finished @samueljseay's task —— View job


Backwards Compatibility Review

Summary: LOW Risk - Private/experimental API refactoring with acceptable breakage boundaries

This PR refactors WordPress Interactivity API stores (woocommerce/product-datawoocommerce/product-context) that are explicitly locked and marked as private/experimental with the warning at frontend.ts:55-57: "I acknowledge that using a private store means my plugin will inevitably break on the next store release." As confirmed by the author in review comments, these stores are intentionally private and the Add to Cart with Options block is still experimental. Breaking changes to these internal APIs are within acceptable boundaries for pre-stable features. All PHP public APIs, hooks, and templates remain unchanged.


Breaking Changes (Acceptable for Private/Experimental APIs)

1. JavaScript Store Renamed and Restructured

Change: woocommerce/product-data (DELETED) → woocommerce/product-context (ADDED)

State structure changes:

// OLD (trunk)
store('woocommerce/product-data', {
  state: {
    productId: number,
    variationId: number | null
  },
  actions: {
    setVariationId: (variationId: number | null) => void
  }
})

// NEW (this PR)
store('woocommerce/product-context', {
  state: {
    currentProductId: number,
    childProductId?: number
    // variationId moved to woocommerce/add-to-cart-with-options::selectedVariationId
  }
  // No actions exposed
})

Breaking aspects:

  • Store name changed
  • state.productIdstate.currentProductId (product-context.ts:23-31)
  • state.variationId removed (moved to woocommerce/add-to-cart-with-options::state.selectedVariationId at frontend.ts:151)
  • actions.setVariationId() removed entirely
  • New concept: childProductId added for grouped products (product-context.ts:8)

Impact: Extensions bypassing the lock will break. This is expected and acceptable per the explicit lock message.


2. Action Signature Updated for Extension Compatibility

Change: setQuantity() signature at frontend.ts:167-171

// OLD
setQuantity(productId: number, value: number)

// NEW
setQuantity(productId: number, value: number, refToDispatchChangeEvent?: HTMLInputElement)

Purpose: Third parameter enables dispatching synthetic change events for extension compatibility (issue #53031, documented at frontend.ts:39-53).

Impact:

  • Runtime: Backwards compatible (parameter is optional)
  • TypeScript: May show type warnings but code will function
  • Benefit: Extensions monitoring quantity changes via DOM events continue working

3. PHP Interactivity Bindings Updated (Internal Implementation)

SingleProductTemplate.php:88-97 - Store name and state keys:

// OLD: 'woocommerce/product-data' with 'productId' and 'variationId'
// NEW: 'woocommerce/product-context' with 'currentProductId' only

Utils.php:92-99 - Context key renamed:

// OLD: 'productId' => ...
// NEW: 'childProductId' => ... (now explicitly namespaced)

QuantitySelector.php:162-170 - Binding namespace updated:

// OLD: woocommerce/product-elements::state.productData.*
// NEW: woocommerce/add-to-cart-with-options::state.productData.*
// ADDED: data-wp-bind--value for inputQuantity
// ADDED: watch callback for change event dispatching

Impact: Minimal - these are internal implementation details not part of the public API.


Non-Breaking Changes ✅

Verified no changes to:

  • PHP Hooks: No do_action() or apply_filters() calls modified
  • Public PHP APIs: No changes to public class method signatures
  • REST Endpoints: No REST API modifications
  • Block Attributes: No block.json files changed
  • Templates: No .php template files in templates/ changed
  • Script Handles: Internal references updated but public handles unchanged

Testing Focus

Prioritize functional regression testing in these areas:

1. Variable Products (CRITICAL)

  • Select variations via dropdowns - verify selectedVariationId updates correctly
  • Change variation multiple times - ensure no stale state
  • Verify variation price and gallery image updates
  • Test invalid variation selections show validation errors
  • Add selected variation to cart with correct product ID

Rationale: Core functionality affected by state refactoring (selectedVariationId moved to new store location).

2. Grouped Products (HIGH)

  • Add multiple grouped products with different quantities
  • Set quantity to 0 for grouped products (should be allowed per frontend.ts:253-259)
  • Verify quantity validation for grouped products
  • Confirm correct product IDs sent to cart

Rationale: New childProductId concept and activeProduct derived state (frontend.ts:241-248) specifically handle grouped products.

3. Quantity Management (HIGH - Extension Compatibility)

Rationale: New dispatchChangeEvent mechanism (frontend.ts:50-53) and inputQuantity binding (QuantitySelector.php:170) improve extension compatibility.

4. Simple Products (MEDIUM)

  • Basic add-to-cart flow with quantity > 1
  • Quantity validation (min/max/step)
  • Out of stock handling

5. Product Gallery (MEDIUM)

  • Gallery updates when variation changes (new store reference at product-gallery/frontend.ts:168)
  • Thumbnail navigation functional
  • Correct images for selected variation

Ecosystem Impact Assessment

Overall Risk: 🟢 LOW - Changes isolated to experimental/locked APIs

Extensions at Risk (only if bypassing locks):

  • Custom code accessing woocommerce/product-data store
  • Extensions reading productDataState.productId or productDataState.variationId
  • Extensions calling productDataActions.setVariationId()
  • Custom quantity selectors interfacing with locked stores

Mainstream Extensions: 🟢 LOW RISK

  • Payment Gateways: No impact (checkout-focused)
  • WooCommerce Subscriptions: Low risk unless using experimental blocks
  • Product Add-Ons: Low risk unless interfacing with renamed stores
  • Dynamic Pricing: Low risk unless reading private store state
  • Variation Swatches: Low risk unless accessing locked stores

Themes: 🟢 LOW RISK

  • Only themes customizing experimental blocks or accessing locked stores affected
  • Standard block themes and FSE unaffected

Why low risk:

  1. Stores explicitly locked with warning (frontend.ts:55-57)
  2. Block marked as experimental (author confirmation)
  3. No public API changes (hooks, templates, REST)
  4. Extensions accepted breakage risk via lock message
  5. Extension compatibility actually improved via dispatchChangeEvent (Using qty input stepper in 9.5.0-beta1 does not trigger a qty field change #53031)

Recommendations

1. No Deprecation Required
Breaking changes to locked/experimental APIs are acceptable without deprecation. The lock message explicitly warns of inevitable breakage.

2. Documentation (when API stabilizes)

  • Document store rename and state structure changes
  • Explain childProductId vs currentProductId distinction
  • Publish API stabilization roadmap

3. Changelog Entry

Type: Dev
Significance: Patch

Dev: Refactored WordPress Interactivity API stores for Add to Cart with Options block.
Renamed internal woocommerce/product-data store to woocommerce/product-context and 
centralized product state management to eliminate imperative DOM manipulation. Added 
support for dispatching change events to improve extension compatibility (#53031).
Note: These stores remain private/experimental and subject to change.

Confidence: 95%

Justification: Very high confidence based on:

  • ✅ Thorough code review of all 15 changed files
  • ✅ Verified against trunk using git diff
  • ✅ Explicit author confirmation about private/experimental status
  • ✅ Clear lock message at frontend.ts:55-57
  • ✅ Verified no public PHP API, hook, or template changes
  • ✅ No block.json attribute changes
  • ✅ Changes limited to experimental block functionality

The 5% uncertainty accounts for unknown extensions that may have bypassed locks despite warnings.


Final Assessment

Breaking changes detected:YES - Acceptable within private/experimental API boundaries

Risk Level: 🟢 LOW

Breaking changes are:

  1. Limited to locked stores with explicit warnings
  2. Within an experimental block
  3. Not affecting public PHP APIs, hooks, or templates
  4. Expected per lock message and author intent
  5. Actually improve extension compatibility via change event dispatching

Recommendation:SAFE TO MERGE with functional regression testing

Architectural improvements:


PHP APIs: ✅ Fully backwards compatible
JavaScript Public APIs: ✅ No public APIs affected
Private/Locked APIs: ⚠️ Breaking changes as expected/documented


@claude
Copy link

claude bot commented Nov 5, 2025

Claude finished @samueljseay's task —— View job


Backwards Compatibility Review

  • Check for new commits since last review
  • Perform incremental BC analysis
  • Update findings

New Commit Analysis

Commit: fe5206cd68 - "Fix another instance of not using the right quantity context"

Summary: NO NEW BC RISKS - Internal bug fix with no API changes

This commit is a bug fix that corrects an internal consistency issue in the quantity selector's decrement button handler. It changes how the productId value is retrieved internally but does not introduce any new backwards compatibility concerns beyond those already identified in previous reviews.


Change Details

File: quantity-selector/frontend.ts:140-157

What changed:

// OLD (bug - was reading from wrong context)
const { productId } = getContext< Context >();

// NEW (fixed - now reads from correct source)
const { currentProductId } = addToCartWithOptionsStore.state.quantityContext;

Why this is NOT a BC risk:

  1. Internal implementation only - This is within the locked/private store implementation
  2. Bug fix - The decrement button was reading productId from the wrong context source, while other handlers (blur, increment) were already using quantityContext.currentProductId
  3. No API changes - No changes to:
    • Store names or public APIs
    • Action signatures
    • PHP hooks or templates
    • Block attributes
    • Public state structure
  4. Consistency improvement - Aligns the decrement handler with the rest of the codebase

Context: The previous reviews identified that productId was renamed to currentProductId as part of the store refactoring. This commit simply fixes a spot that was missed in the initial refactoring, ensuring all handlers read from the same source (quantityContext.currentProductId at line 157).


Previous Review Status

The comprehensive BC review from 2025-11-05T09:19:21Z remains valid:

Overall Risk: 🟢 LOW - Breaking changes acceptable within private/experimental API boundaries

Key Findings (unchanged):

  • Store renamed: woocommerce/product-datawoocommerce/product-context
  • State structure changed (private/locked APIs only)
  • All PHP public APIs, hooks, and templates remain backwards compatible
  • Block is experimental, stores are locked with explicit warning
  • No new BC concerns from this bug fix commit

Testing Focus (unchanged):
Priority areas remain: Variable products (CRITICAL), Grouped products (HIGH), Quantity management (HIGH)


Final Assessment

New BC risks from commit fe5206cd68: ✅ NONE

This is a straightforward bug fix that improves internal consistency without affecting any public APIs or introducing new breaking changes. The backwards compatibility assessment from the previous review remains accurate and complete.

Recommendation: ✅ Safe to merge (pending functional testing as outlined in previous review)


@claude
Copy link

claude bot commented Nov 5, 2025

Claude finished @samueljseay's task —— View job


Backwards Compatibility Review

  • Check for new commits since last review (2025-11-05T10:08:17Z)
  • Analyze new commits for BC risks
  • Finalize assessment

Status: NO NEW BC CONCERNS

Recent commits analyzed:

  1. fc976e5532 - Merge from trunk (unrelated Product Filters changes)
  2. d0eb25fd53 - Automated changefile generation
  3. fe5206cd68 - Bug fix already reviewed on 2025-11-05T09:40:27Z

Conclusion: No new commits affecting backwards compatibility have been added since the last comprehensive BC review. The most recent manual code change (fe5206cd68 - "Fix another instance of not using the right quantity context") was already analyzed and determined to introduce no new BC risks.


Comprehensive BC Assessment (Current as of 2025-11-05)

Summary: LOW Risk - Refactoring of private/experimental APIs with acceptable breakage boundaries

This PR refactors WordPress Interactivity API stores (woocommerce/product-datawoocommerce/product-context) that are explicitly locked and marked as private/experimental with the warning at frontend.ts:55-57: "I acknowledge that using a private store means my plugin will inevitably break on the next store release." As confirmed by the PR author in review comments, these stores are intentionally private and the Add to Cart with Options block is still experimental. All PHP public APIs, hooks, and templates remain unchanged.


Breaking Changes (Acceptable for Private/Experimental APIs)

1. JavaScript Store Renamed and Restructured

Store name: woocommerce/product-data (DELETED) → woocommerce/product-context (ADDED)

State structure changes:

// OLD (trunk)
store('woocommerce/product-data', {
  state: {
    productId: number,
    variationId: number | null
  },
  actions: {
    setVariationId: (variationId: number | null) => void
  }
})

// NEW (this PR)
store('woocommerce/product-context', {
  state: {
    currentProductId: number,
    childProductId?: number
    // variationId moved to woocommerce/add-to-cart-with-options::selectedVariationId
  }
  // No actions exposed
})

Impact: Extensions bypassing the lock will break. This is expected and acceptable per the explicit lock message.


2. Action Signature Updated

setQuantity() at frontend.ts:167-171 added optional third parameter for extension compatibility (issue #53031):

setQuantity(productId: number, value: number, refToDispatchChangeEvent?: HTMLInputElement)

Impact: Runtime backwards compatible. TypeScript consumers may see type warnings.


3. PHP Interactivity Bindings Updated

Internal WordPress Interactivity API implementation details changed:

  • SingleProductTemplate.php:88-97 - Store name and state keys updated
  • Utils.php:92-99 - Context key renamed productIdchildProductId
  • QuantitySelector.php:162-170 - Binding namespace updated

Impact: Minimal - these are internal implementation details not part of public API.


Non-Breaking Changes ✅

Verified no changes to:

  • ✅ PHP Hooks (do_action, apply_filters)
  • ✅ Public PHP method signatures
  • ✅ REST endpoints
  • ✅ Block attributes (block.json)
  • ✅ Templates
  • ✅ Public script handles

Testing Focus

1. Variable Products (CRITICAL)

  • Select variations via dropdowns - verify selectedVariationId updates
  • Change variation multiple times - ensure no stale state
  • Verify price and gallery image updates
  • Test invalid variation selections show validation errors
  • Add selected variation to cart with correct ID

2. Grouped Products (HIGH)

  • Add multiple grouped products with different quantities
  • Set quantity to 0 for grouped products
  • Verify quantity validation
  • Confirm correct product IDs in cart

3. Quantity Management (HIGH)

4. Simple Products (MEDIUM)

  • Basic add-to-cart flow
  • Quantity validation
  • Out of stock handling

5. Product Gallery (MEDIUM)

  • Gallery updates when variation changes
  • Thumbnail navigation functional

Ecosystem Impact: 🟢 LOW RISK

Why low risk:

  1. Stores explicitly locked with warning message
  2. Block marked as experimental (author confirmation)
  3. No public API changes (hooks, templates, REST)
  4. Extensions accepted breakage risk via lock message
  5. Extension compatibility actually improved via change event dispatching (Using qty input stepper in 9.5.0-beta1 does not trigger a qty field change #53031)

Mainstream extensions: Payment gateways, Subscriptions, Product Add-Ons, Dynamic Pricing, Variation Swatches - all LOW RISK unless bypassing locks.


Recommendations

1. No Deprecation Required
Breaking changes to locked/experimental APIs are acceptable per the lock message.

2. Changelog Entry

Type: Dev
Significance: Patch

Dev: Refactored WordPress Interactivity API stores for Add to Cart with Options block.
Renamed internal woocommerce/product-data store to woocommerce/product-context and 
centralized product state management to eliminate imperative DOM manipulation. Added 
support for dispatching change events to improve extension compatibility (#53031).
Note: These stores remain private/experimental and subject to change.

Confidence: 95%

Very high confidence based on:

  • ✅ Thorough code review of all 16 changed files
  • ✅ Verified against trunk using git diff
  • ✅ Author confirmation about private/experimental status
  • ✅ Clear lock message at frontend.ts:55-57
  • ✅ No public PHP API, hook, or template changes
  • ✅ Changes limited to experimental block functionality

Final Assessment

Breaking changes: ✅ YES - Acceptable within private/experimental API boundaries

Risk Level: 🟢 LOW

Recommendation:SAFE TO MERGE with functional regression testing

Architectural improvements:


PHP APIs: ✅ Fully backwards compatible
JavaScript Public APIs: ✅ No public APIs affected
Private/Locked APIs: ⚠️ Breaking changes as expected/documented


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plugin: woocommerce Issues related to the WooCommerce Core plugin.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants