--- name: hyva-module-compatibility description: Identify and fix Magento 2 module compatibility issues with Hyvä Themes. Covers block plugin bypasses, RequireJS/Knockout replacements, ViewModels, and Alpine.js integration for modules that work in admin but fail on Hyvä frontend. --- # Hyvä Module Compatibility Skill ## Overview This skill helps identify and fix Magento 2 module compatibility issues with Hyvä Themes. Hyvä uses Alpine.js and TailwindCSS instead of Luma's Knockout.js and RequireJS, which often breaks modules designed for the default Luma theme. ## When to Use This Skill - A Magento 2 module works in admin but not on Hyvä frontend - Plugins targeting Magento blocks don't apply on frontend - JavaScript-based features are missing or broken - Custom rendering/UI components don't appear correctly ## Common Hyvä Compatibility Issues ### 1. **Block Plugins Don't Execute** **Symptom:** Plugins that modify block HTML output (e.g., `after*Html()` methods) don't apply on frontend. **Root Cause:** Hyvä often uses custom ViewModels and templates that bypass standard Magento blocks. **Example from this project:** ```php // ❌ This plugin doesn't work with Hyvä class SelectPlugin { public function afterGetValuesHtml(Select $subject, string $result): string { // Modifies HTML - but Hyvä doesn't call getValuesHtml() } } ``` **Solution:** Instead of plugins, use: - Template overrides in your theme - Custom ViewModels injected via `ViewModelRegistry` - Alpine.js for client-side rendering ### 2. **RequireJS/Knockout Dependencies** **Symptom:** JavaScript features don't load; console errors about missing modules. **Root Cause:** Hyvä doesn't include RequireJS or Knockout.js by default. **Solution:** - Replace RequireJS modules with vanilla JavaScript or Alpine.js - Use `
``` #### Pattern C: ViewModel for Data Preparation **File:** `app/code/Your/Module/ViewModel/YourViewModel.php` ```php yourModel = $yourModel; } /** * Get data for Alpine.js/frontend * * @param int $entityId * @return array */ public function getData(int $entityId): array { return [ 'key' => 'value', 'items' => $this->yourModel->getItems($entityId), ]; } /** * Get data as JSON for Alpine.js * * @param int $entityId * @return string */ public function getDataJson(int $entityId): string { return json_encode($this->getData($entityId), JSON_THROW_ON_ERROR); } } ``` **Usage in template:** ```php require(\Your\Module\ViewModel\YourViewModel::class); ?>
``` ### Step 4: Testing ```bash # Clear caches ddev exec bin/magento cache:flush # Check for errors in console # Browser DevTools → Console # Check for errors in logs tail -f var/log/system.log var/log/exception.log # Test all option types if applicable # - Select dropdowns # - Radio buttons # - Checkboxes # - Multi-select ``` ## Real-World Example: Custom Option Default Values ### Original (Luma-Compatible) Approach ```php // Plugin: Plugin/Catalog/Block/Product/View/Options/Type/SelectPlugin.php class SelectPlugin { public function afterGetValuesHtml(Select $subject, string $result): string { // Modify HTML to add selected="selected" attribute // ❌ Doesn't work with Hyvä - method never called } } ``` ### Hyvä-Compatible Approach **1. Created ViewModel:** ```php // ViewModel/CustomOptionImage.php class CustomOptionImage implements ArgumentInterface { public function getDefaultValuesForProduct(int $productId): array { return $this->defaultValueResource->getDefaultValuesForProduct($productId); } } ``` **2. Modified Template:** ```php // app/design/frontend/Uptactics/nto/Magento_Catalog/templates/product/view/options/options.phtml use Uptactics\CustomOptionImage\ViewModel\CustomOptionImage; /** @var CustomOptionImage $customOptionImageViewModel */ $customOptionImageViewModel = $viewModels->require(CustomOptionImage::class); $defaultValues = $customOptionImageViewModel->getDefaultValuesForProduct((int)$product->getId()); ``` **3. Added Alpine.js Logic:** ```javascript function initOptions() { return { defaultValues: , applyDefaultValues($dispatch) { Object.entries(this.defaultValues).forEach(([optionId, optionTypeId]) => { const selectElement = document.querySelector(`select[name="options[${optionId}]"]`); if (selectElement) { selectElement.value = optionTypeId; this.updateCustomOptionValue($dispatch, optionId, selectElement); } }); } } } ``` **4. Called on Initialization:** ```html
``` ## Hyvä Theme Patterns Reference ### Alpine.js Directives ```html