xiaohongshu
xiaohongshu
hyva-module-compatibility
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.
Full skill instructions
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.
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:
// ❌ 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:
ViewModelRegistrySymptom: JavaScript features don't load; console errors about missing modules.
Root Cause: Hyvä doesn't include RequireJS or Knockout.js by default.
Solution:
<script> tags with modern ES6+ JavaScriptSymptom: Blocks defined in layout XML don't appear on frontend.
Root Cause: Hyvä templates may not include the block reference points.
Solution:
$block->getChildHtml() in templates# Test in admin first (should work)
# Then test on frontend with Hyvä theme
# Check browser console for errors
# Check var/log/system.log for PHP errors
# Verify theme is actually Hyvä
bin/magento theme:list
Check for these patterns:
// ❌ Block HTML modification plugins
public function afterGetHtml(...) {}
public function afterToHtml(...) {}
// ❌ RequireJS dependencies
<script type="text/x-magento-init">
require(['jquery', 'mage/...'], function($) {});
// ❌ Knockout.js data-bind
<div data-bind="scope: 'component'">
Find Hyvä equivalents:
# Search Hyvä theme templates
find vendor/hyva-themes -name "*.phtml" | xargs grep -l "pattern"
# Check if Hyvä has a compatibility module
ls app/code/Hyva/*/
File: app/design/frontend/YourVendor/your-theme/Module_Name/templates/your-template.phtml
<?php
use Hyva\Theme\Model\ViewModelRegistry;
use Your\Module\ViewModel\YourViewModel;
/** @var ViewModelRegistry $viewModels */
$viewModels = $viewModels ?? \Magento\Framework\App\ObjectManager::getInstance()
->get(ViewModelRegistry::class);
/** @var YourViewModel $customViewModel */
$customViewModel = $viewModels->require(YourViewModel::class);
// Get data from ViewModel
$data = $customViewModel->getData();
?>
<!-- Use Alpine.js for interactivity -->
<div x-data="{
myData: <?= $escaper->escapeHtmlAttr(json_encode($data)) ?>,
myMethod() {
// Alpine.js method
}
}" x-init="myMethod()">
<span x-text="myData.someValue"></span>
</div>
When: You need client-side reactivity (dropdowns, filters, dynamic updates)
// In your template .phtml file
<script>
function initYourFeature() {
return {
// Data properties
selectedValue: null,
options: <?= /* @noEscape */ json_encode($options) ?>,
// Initialization
init() {
this.$nextTick(() => {
this.applyDefaults();
});
},
// Methods
applyDefaults() {
// Set default values
if (this.defaultValue) {
this.selectedValue = this.defaultValue;
// Update DOM
const element = document.querySelector('#my-select');
if (element) {
element.value = this.defaultValue;
}
}
},
// Event handlers
handleChange($dispatch, value) {
this.selectedValue = value;
$dispatch('custom-event', { value });
}
}
}
</script>
<div x-data="initYourFeature()" x-init="init()">
<select x-on:change="handleChange($dispatch, $event.target.value)">
<template x-for="option in options">
<option :value="option.value" x-text="option.label"></option>
</template>
</select>
</div>
File: app/code/Your/Module/ViewModel/YourViewModel.php
<?php
declare(strict_types=1);
namespace Your\Module\ViewModel;
use Magento\Framework\View\Element\Block\ArgumentInterface;
class YourViewModel implements ArgumentInterface
{
private $yourModel;
public function __construct(
\Your\Module\Model\YourModel $yourModel
) {
$this->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
/** @var Your\Module\ViewModel\YourViewModel $viewModel */
$viewModel = $viewModels->require(\Your\Module\ViewModel\YourViewModel::class);
?>
<div x-data='<?= $viewModel->getDataJson($product->getId()) ?>'>
<!-- Your Alpine.js component -->
</div>
# 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
// 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
}
}
1. Created ViewModel:
// ViewModel/CustomOptionImage.php
class CustomOptionImage implements ArgumentInterface
{
public function getDefaultValuesForProduct(int $productId): array
{
return $this->defaultValueResource->getDefaultValuesForProduct($productId);
}
}
2. Modified Template:
// 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:
function initOptions() {
return {
defaultValues: <?= /* @noEscape */ json_encode($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:
<div x-data="initOptions()"
x-init="$nextTick(() => { applyDefaultValues($dispatch); })">
<!-- Data binding -->
<div x-data="{ count: 0 }"></div>
<!-- Conditionals -->
<div x-show="isVisible"></div>
<div x-if="shouldRender"></div>
<!-- Loops -->
<template x-for="item in items" :key="item.id">
<div x-text="item.name"></div>
</template>
<!-- Events -->
<button x-on:click="handleClick()">Click</button>
<select x-on:change="handleChange($event)">
<!-- References -->
<div x-ref="myElement"></div>
<!-- Access via this.$refs.myElement -->
<!-- Initialization -->
<div x-init="init()"></div>
<!-- Lifecycle -->
<div x-init="$nextTick(() => { /* code */ })"></div>
// In templates
/** @var ViewModelRegistry $viewModels */
// Require a ViewModel
$myViewModel = $viewModels->require(MyViewModel::class);
// Check if ViewModel exists
if ($viewModels->has(MyViewModel::class)) {
$myViewModel = $viewModels->require(MyViewModel::class);
}
// Simple data (use escapeHtmlAttr for attributes)
<div x-data='{ value: "<?= $escaper->escapeHtmlAttr($value) ?>" }'></div>
// Complex data (use json_encode)
<div x-data='<?= $escaper->escapeHtmlAttr(json_encode($data)) ?>'></div>
// Don't escape JSON in JavaScript context
<script>
const data = <?= /* @noEscape */ json_encode($data) ?>;
</script>
// ✅ Correct
class MyViewModel implements ArgumentInterface { }
// ❌ Wrong - won't work
class MyViewModel { }
// ✅ Correct - no escaping in JavaScript context
defaultValues: <?= /* @noEscape */ json_encode($defaults) ?>,
// ❌ Wrong - breaks JSON
defaultValues: <?= $escaper->escapeHtml(json_encode($defaults)) ?>,
// ✅ Correct - use arrow function to preserve 'this'
x-init="$nextTick(() => { this.myMethod() })"
// ❌ Wrong - 'this' refers to wrong context
x-init="$nextTick(function() { this.myMethod() })"
app/design/frontend/
└── YourVendor/
└── your-theme/
└── Magento_Catalog/ ← Module name
└── templates/
└── product/
└── view/
└── options/
└── options.phtml
vendor/hyva-themes/magento2-default-theme/vendor/hyva-themes/magento2-theme-module/app/design/frontend/YourVendor/your-theme/Hyva\Theme\ViewModel\CustomOption - Custom options renderingHyva\Theme\ViewModel\ProductPage - Product page utilitiesHyva\Theme\ViewModel\ProductPrice - Price formattingHyva\Theme\ViewModel\SvgIcons - Icon rendering# Find Hyvä template
find vendor/hyva-themes -name "select.phtml"
# Check if ViewModel exists
grep -r "class CustomOption" vendor/hyva-themes
# Clear all caches
ddev exec bin/magento cache:flush
# Check for Alpine.js errors in browser
# Open DevTools → Console → Filter for "Alpine"
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience