Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
348 changes: 348 additions & 0 deletions skills/hyva-commerce-dashboard-widgets/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
---
name: hyva-widgets

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The frontmatter name doesn't match the directory hyva-commerce-dashboard-widgets. All 12
existing skills in this repo have name: matching their directory, and
install-hyva-skill.sh resolves skills by directory name while agents register the skill
under the frontmatter name — so users would install hyva-commerce-dashboard-widgets but
invoke /hyva-widgets. The name hyva-widgets is also misleadingly generic: Magento has a
core storefront "widgets" concept, so this name invites exactly the mis-triggering the body
warns against (lines 12–14). Please rename to hyva-commerce-dashboard-widgets.

description: Use when creating or modifying a Hyva Commerce Admin Dashboard widget for Magento Admin using the newer Hyva AdminDashboardApi V1 composition contract, including etc/adminhtml/hyva_dashboard_widget.xml registration, WidgetTypeInterface methods with WidgetContextInterface, configurable/display properties, display data, permissions, save hooks, built-in display types, and optional custom templates/scripts. Examples must be generic for app/code/Vendor/Module and not company-specific.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Three issues:

  1. "Examples must be generic for app/code/Vendor/Module and not company-specific." is an
    instruction to the skill author, already duplicated in the body (lines 16–23). It doesn't
    help trigger matching and wastes space in the description.
  2. The description is 485 of the 500 characters Codex allows — any future edit tips it over.
    Removing the sentence above solves this too.
  3. Unlike every sibling skill, there is no explicit trigger-phrase list. Suggest following
    the repo pattern: "This skill should be used when… Trigger phrases include 'dashboard
    widget', 'admin dashboard widget', 'hyva commerce widget', …".

---

# Hyva Admin Dashboard Widgets

This skill is for **Hyva Commerce Admin Dashboard widgets**: pluggable cards
merchants add to the Magento Admin Dashboard, such as KPIs, charts, tables,
links, and custom template widgets.
Comment on lines +8 to +10

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Disambiguation] Since Magento Admin Dashboard and Hyvä Commerce Admin Dashboard are two distinct things, I would avoid mentioning the Magento Admin Dashboard; see the suggested change.

Suggested change
This skill is for **Hyva Commerce Admin Dashboard widgets**: pluggable cards
merchants add to the Magento Admin Dashboard, such as KPIs, charts, tables,
links, and custom template widgets.
This skill is for creating customised widgets for **Hyvä Commerce Admin Dashboard**, such as KPIs, charts, tables, links, and custom template widgets.


It is not for generic Hyva storefront Alpine.js components. If the user asks
for an ordinary storefront `.phtml` interaction, skip this skill and follow the
nearest theme pattern instead.

Use vendor-neutral examples by default:

- PHP namespace: `Vendor\Module`
- Magento module name: `Vendor_Module`
- Module path: `app/code/Vendor/Module`

Replace those placeholders with the real module only when implementing in an
existing codebase.

## Source Of Truth

Only support the newer composition API:

- Implement `Hyva\AdminDashboardApi\Api\V1\WidgetTypeInterface`.
- Every widget method receives `WidgetContextInterface $ctx` as its first
argument.
- Do not extend `AbstractWidgetType` and do not implement older widget
interfaces.

This keeps custom widgets dependent on
`hyva-themes/commerce-module-admin-dashboard-api` rather than the full
dashboard runtime.
Comment on lines +27 to +37

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The skill never tells the agent to actually declare the dependency: require
hyva-themes/commerce-module-admin-dashboard-api in composer.json and add
Hyva_AdminDashboardApi to etc/module.xml <sequence>. The package and module names are
[verified in source] (composer.json, src/etc/module.xml); the interface docblock
itself states that only the api package is needed for setup:di:compile.
An agent
scaffolding a fresh module will otherwise produce one that doesn't load the XSD or
interfaces. Please add a short module-setup step.


Useful official docs:

- PHP implementation: https://docs.hyva.io/hyva-commerce/features/admin-dashboard/devdocs/widget-types/php.html
- XML configuration: https://docs.hyva.io/hyva-commerce/features/admin-dashboard/devdocs/widget-types/xml.html
- Configurable inputs: https://docs.hyva.io/hyva-commerce/features/admin-dashboard/devdocs/widget-types/configurable-inputs.html
- Available widget types: https://docs.hyva.io/hyva-commerce/features/admin-dashboard/devdocs/widget-types/available-types.html
Comment on lines +39 to +44

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All four URLs are dead redirects. Each …/devdocs/widget-types/*.html page returns only a
redirect stub ("You're being redirected…") that many agent fetch tools won't follow. This
matters doubly because the skill defers detail to these links (chart data shapes, input
types). Canonical URLs (verified live):

Current Replace with
…/devdocs/widget-types/php.html …/devdocs/widget-php.html
…/devdocs/widget-types/xml.html …/devdocs/widget-xml.html
…/devdocs/widget-types/configurable-inputs.html …/devdocs/configurable-inputs.html
…/devdocs/widget-types/available-types.html …/devdocs/available-widget-types.html

Additionally, the API package bundles an offline reference the skill could point agents at:
vendor/hyva-themes/commerce-module-admin-dashboard-api/docs/implementing-a-widget.md
— it covers the same contract and works without network access.


## Widget Type Vs Instance

- A **widget type** is the reusable definition: one XML `<widget>` entry plus
one PHP class implementing the relevant widget type interface.
- A **widget instance** is what an admin places on a dashboard: a widget type
plus saved configuration/display values.

Creating a widget is two steps: register it in XML, then implement the PHP
behavior.

## XML Registration

Create `etc/adminhtml/hyva_dashboard_widget.xml` and use the API-package
schema:

```xml
xsi:noNamespaceSchemaLocation="urn:magento:module:Hyva_AdminDashboardApi:etc/adminhtml/hyva_dashboard_widget.xsd"
```

Minimal widget:

```xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Hyva_AdminDashboardApi:etc/adminhtml/hyva_dashboard_widget.xsd">
<widget id="my_widget">
<class>Vendor\Module\Model\Widget\MyWidget</class>
<display_type>text</display_type>
</widget>
</config>
Comment on lines +71 to +75

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The minimal example (line 73) and the display_type table row (line 106) use text, but
text is not a mapped display type: the
displayTypeTemplateMap argument in the dashboard runtime's framework/etc/di.xml maps
exactly bar_chart, line_chart, number, pie_chart, table, and
date-interval-table (plus template, which is handled via the widget's own <template>).
No first-party widget registers display_type=text either — the built-in "text" widget
uses display_type=template. Following the minimal example verbatim therefore produces a
widget with no template mapping. Also note the skill's own built-ins list (lines 118–121)
omits number, which IS a built-in. Please use a real built-in type in the minimal example
and state the definitive list once: table, bar_chart, line_chart, pie_chart,
number, date-interval-table, template.

```

Custom template widget:

```xml
<widget id="my_widget">
<title>My Widget</title>
<class>Vendor\Module\Model\Widget\MyWidget</class>
<category>operations</category>
<tags>orders,operations</tags>
<display_type>template</display_type>
<template>Vendor_Module::widget/my-widget.phtml</template>
<icon>chart-spline</icon>
<min_height>2</min_height>
<min_width>2</min_width>
<trailing_action>
<label>View all</label>
<route>sales/order/index</route>
<target>_self</target>
</trailing_action>
</widget>
```

XML keys:

| Key | Required | Notes |
|---|---|---|
| `id` attribute | yes | Unique non-empty alphanumeric value; hyphen, underscore, and period are allowed. |
| `disabled` attribute | no | Defaults to `false`; disabled widgets cannot be created, edited, deleted, or rendered. |
| `class` | yes | PHP class implementing `Hyva\AdminDashboardApi\Api\V1\WidgetTypeInterface`. |
| `display_type` | yes | Template key such as `text`, `table`, `bar_chart`, `line_chart`, `pie_chart`, `date-interval-table`, or `template`. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

text display type does not exits (see comment on lines 71-75)

| `template` | required for `display_type=template` | Magento template notation, e.g. `Vendor_Module::widget/my-widget.phtml`. |
| `acl` | no | Magento ACL resource; defaults to `Magento_Backend::admin`. |
| `cache_lifetime` | no | Seconds; default is `86400`. |
| `category` | no | Groups widgets in the Add Widget modal; uncategorized widgets appear under `Other`. |
| `full_screen` | no | Enables a full-screen widget instance menu action. |
| `icon` | no | Lucide icon name available to `Hyva\Theme\ViewModel\LucideIcons`. |
| `min_height` / `min_width` | no | Dashboard grid minimum rows/columns; default is `1`. |
| `tags` | no | Comma-separated search keywords. |
| `title` | no | Defaults to the formatted widget `id`. |
| `trailing_action` | no | Footer link data: `label`, `route`, optional `target` (`_self` or `_blank`). |

For non-template display types, the display type must be mapped by the
dashboard runtime converter configuration. Official built-ins include
`table`, `bar_chart`, `line_chart`, `pie_chart`, and several first-party
template widgets.
Comment on lines +118 to +121

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The skill's own built-ins list
omits number, which IS a built-in. Please use a real built-in type in the minimal example
and state the definitive list once: table, bar_chart, line_chart, pie_chart,
number, date-interval-table, template.


## PHP Implementation

Implement `Hyva\AdminDashboardApi\Api\V1\WidgetTypeInterface`.

```php
<?php
declare(strict_types=1);

namespace Vendor\Module\Model\Widget;

use Hyva\AdminDashboardApi\Api\V1\WidgetContextInterface;
use Hyva\AdminDashboardApi\Api\V1\WidgetInstanceInterface;
use Hyva\AdminDashboardApi\Api\V1\WidgetTypeInterface;
use Magento\Framework\Phrase;

class MyWidget implements WidgetTypeInterface
{
public function getDisplayData(WidgetContextInterface $ctx, WidgetInstanceInterface $widgetInstance): mixed
{
return [];
}

public function getTitle(WidgetContextInterface $ctx, ?WidgetInstanceInterface $widgetInstance): Phrase
{
return $ctx->getTitle();
}

public function getConfigurableProperties(WidgetContextInterface $ctx): array
{
return $ctx->getConfigurableProperties();
}

public function getDisplayProperties(WidgetContextInterface $ctx): array
{
return $ctx->getDisplayProperties();
}

public function getTrailingAction(WidgetContextInterface $ctx, ?WidgetInstanceInterface $widgetInstance): array
{
return $ctx->getTrailingAction();
}

public function isAllowed(WidgetContextInterface $ctx, ?WidgetInstanceInterface $widgetInstance): bool
{
return $ctx->isAllowed($widgetInstance);
}

public function beforeSave(WidgetContextInterface $ctx, WidgetInstanceInterface $widgetInstance): WidgetInstanceInterface
{
return $widgetInstance;
}

public function afterSave(WidgetContextInterface $ctx, WidgetInstanceInterface $widgetInstance): WidgetInstanceInterface
{
return $widgetInstance;
}
}
```

With this API, read defaults from `$ctx` and merge only your own additions.
`getTitle()` receives `null` before the widget has been placed, so fall back to
`$ctx->getTitle()` when deriving a title from instance configuration.
Comment on lines +183 to +184

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"getTitle() receives null before the widget has been placed" is garbled — it's the
$widgetInstance parameter that is null. The interface docblock
says: "$widgetInstance is null when no instance exists yet, and is set once the widget
has been placed. Implementations that vary the title by configuration should fall back to
$ctx->getTitle() for the null case." Suggest wording along those lines.


## Properties

`getConfigurableProperties()` returns inputs that affect data or behavior,
such as filters, URLs, statuses, or date ranges.

`getDisplayProperties()` returns inputs that affect rendering, such as chart
scale, interval, or display options.

Shape:

```php
[
'limit' => [
'label' => __('Number of Items'),
'input' => [
'type' => 'text',
'subtype' => 'number',
'attributes' => ['value' => 5, 'min' => 1, 'max' => 20, 'required' => true],
],
],
]
```

Common input types include `text`, `date`, `select`, `toggle`, and `scope`.
Conditionally show a field with `depends`, keyed by the other field's form
name:

```php
'target_url' => [
'label' => __('URL'),
'input' => [
'type' => 'text',
'subtype' => 'url',
'attributes' => ['required' => true],
'depends' => ['configurable_properties[url_type]' => 1],
],
],
```
Comment on lines +209 to +223

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The input-type list omits documented/shipped types — the framework
registers input blocks for date, dynamic-rows, select, scope, website,
root-category, template, text, textarea, toggle (plus note). At minimum add
textarea and dynamic-rows. Also worth one sentence on depends semantics: multiple
dependencies are AND-ed with equality matching only, and the key format
'depends' => ['display_properties[show_path]' => 1] is confirmed in first-party widget
cod.


Read saved values from the widget instance. Use constants from
`Hyva\AdminDashboardApi\Api\ConfigurationKeys`.
Comment on lines +225 to +226

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"Read saved values from the widget instance. Use constants from
Hyva\AdminDashboardApi\Api\ConfigurationKeys." names a class but no method. The API is:

$widgetInstance->getPropertyValue(ConfigurationKeys::CONFIGURABLE_PROPERTIES, 'limit');
$widgetInstance->getPropertyValues(ConfigurationKeys::DISPLAY_PROPERTIES);
$widgetInstance->getConfiguration();

ConfigurationKeys exists with exactly CONFIGURABLE_PROPERTIES = 'configurable_properties'
and DISPLAY_PROPERTIES = 'display_properties'. As written, the agent must guess the single
most common operation a widget needs — please show a one-line example.


## Display Data

`getDisplayData()` returns the computed value rendered by the widget. The
required shape depends on `display_type` or the custom template.

For the built-in `table` display type:

```php
return [
'headings' => ['Name', 'Value'],
'rows' => [
['href' => 'https://example.test/', 'values' => ['Foo', 'Bar']],
['values' => ['Baz', 'Qux']],
],
'footer' => ['Total', '2'],
'caption' => 'Example Table',
];
```

Top-level table keys are optional. A row `href` makes the row clickable.

For chart display types, inspect the installed templates for the exact shape
expected by `bar-chart.phtml`, `line-chart.phtml`, or `pie-chart.phtml`.
Marker interfaces live under `Hyva\AdminDashboardApi\Api\V1\ChartType\*`.
Comment on lines +249 to +251

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"Marker interfaces live under Hyva\AdminDashboardApi\Api\V1\ChartType\*" undersells them.
Five exist: BarChartWidgetTypeInterface,
LineChartWidgetTypeInterface, PieChartWidgetTypeInterface, NumberWidgetTypeInterface,
DateIntervalWidgetTypeInterface. Per the API package's bundled docs, implementing a
chart-type interface "tells the framework which display type the widget uses and wires the
matching defaults into $ctx" — i.e. they are the intended way to build chart widgets, not
just markers to be aware of. The number display type is missing from the skill entirely.
Also, "inspect the installed templates" only works when Hyvä Commerce is installed — with
the doc links fixed (lines 41–44) that's acceptable, otherwise it's a dead end.


## Permissions And Save Hooks

Use XML `acl` for normal access control. Add `isAllowed()` only for extra
per-instance rules.

Use `$ctx->isAllowed($widgetInstance)` as the default implementation, then add
extra checks only when the widget needs per-instance rules.
Comment on lines +255 to +259

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"Add isAllowed() only for extra per-instance rules" — the interface makes all eight
methods mandatory (and the skill forbids the abstract base on line
32), so it can't be "added" optionally. The next sentence (lines 258–259) then gives the
correct guidance and largely repeats the first. Suggest merging into one statement:
"Implement isAllowed() as a delegation to $ctx->isAllowed($widgetInstance); add extra
checks only when the widget needs per-instance rules. Use XML acl for normal access
control."


Use `beforeSave()` and `afterSave()` only for widget-instance save behavior.
Both must return a `WidgetInstanceInterface`, normally the same instance.
Prefer `afterSave()` for side effects such as fetching/caching external data;
catch/log failures and avoid breaking dashboard saves for recoverable errors.

## Custom Templates

For `display_type=template`, create the template under
`view/adminhtml/templates/widget/my-widget.phtml`.

The widget instance is passed on the block:

```php
<?php
use Hyva\AdminDashboardApi\Api\V1\WidgetInstanceInterface;

/** @var WidgetInstanceInterface|null $widgetInstance */
$widgetInstance = $block->getData('widget_instance');

if (!$widgetInstance || !($data = $widgetInstance->getDisplayData())) {
return;
}
?>
Comment on lines +274 to +283

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The template example imports Hyva\AdminDashboardApi\Api\V1\WidgetInstanceInterface and
then calls $widgetInstance->getDisplayData() — but the API interface does not declare
getDisplayData(). That method lives on the runtime's extended
interface: Hyva\AdminDashboardFramework\Api\V1\WidgetInstance\WidgetInstanceInterface,
signature getDisplayData(bool $loadFromCache = true): mixed. The framework's own
widget/table.phtml docblock uses exactly that framework interface. Static analysis would
flag the skill's example, and it type-hints a contract the call doesn't exist on. Fix: use
the framework interface in the template docblock (templates only render when the dashboard
runtime is installed, so the API-only dependency rule isn't violated).

```

Escape output with `$escaper`.

If the template needs formatting helpers, create a view model implementing
`Magento\Framework\View\Element\Block\ArgumentInterface` and load it with
`$viewModels->require(Vendor\Module\ViewModel\MyWidget::class)`.

## Companion JavaScript

For custom interactivity, prefer a separate adminhtml template registered via
layout XML instead of inline script in the content template.

`view/adminhtml/layout/hyva_dashboard_widget.xml`:

```xml
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="widget-container.after">
<block name="widget-type.my_widget.js" template="Vendor_Module::js/widget/my-widget.phtml"/>
</referenceBlock>
</body>
</page>
```

`view/adminhtml/templates/js/widget/my-widget.phtml`:

```php
<script>
function myWidget()
{
return {
init() {},
};
}

window.addEventListener('alpine:init', () => Alpine.data('myWidget', myWidget), {once: true});
</script>
<?php isset($hyvaCsp) && $hyvaCsp->registerInlineScript(); ?>
```

In the widget content template:

```html
<div x-data="myWidget"></div>
```

## Verification Checklist

- The module path, namespace, and template aliases use the real
`Vendor/Module`, `Vendor\Module`, and `Vendor_Module` values.
- XML validates against the API-package schema.
- `class` implements `Hyva\AdminDashboardApi\Api\V1\WidgetTypeInterface`.
- `display_type=template` has a valid `<template>` value.
- Built-in `display_type` values are mapped by the dashboard runtime converter.
- Configurable/display properties render, persist, and conditional `depends`
rules work.
- `getDisplayData()` returns the shape expected by the selected display type.
- ACL and any `isAllowed()` instance rules are checked with an admin user that
should not have access.
- Save hooks always return the widget instance and handle recoverable failures
without throwing.
- Run Magento cache flush/setup steps required by the project, then confirm

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The checklist asks to "Run Magento cache flush/setup steps required by the project" — this
is exactly what the hyva-exec-shell-cmd skill handles. Consider adding
requires: hyva-exec-shell-cmd to the frontmatter, matching sibling skills.

the widget appears in the Admin Dashboard Add Widget modal.