<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: André Flitsch</title>
    <description>The latest articles on DEV Community by André Flitsch (@pixelhed).</description>
    <link>https://dev.to/pixelhed</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3982251%2Fbedd96ee-3657-4f4b-90b7-5f2e8874e1e0.png</url>
      <title>DEV Community: André Flitsch</title>
      <link>https://dev.to/pixelhed</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pixelhed"/>
    <language>en</language>
    <item>
      <title>Make Magento 2 Discounts Readable: One Line per Cart Price Rule</title>
      <dc:creator>André Flitsch</dc:creator>
      <pubDate>Mon, 09 Nov 2026 17:05:09 +0000</pubDate>
      <link>https://dev.to/pixelhed/make-magento-2-discounts-readable-one-line-per-cart-price-rule-10c6</link>
      <guid>https://dev.to/pixelhed/make-magento-2-discounts-readable-one-line-per-cart-price-rule-10c6</guid>
      <description>&lt;p&gt;On a shop I look after, a note that no customer was meant to read appeared in the cart. The text came from a rule’s description field, which the admin presents as a place for the team’s own notes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Magento stores, and what it does not
&lt;/h2&gt;

&lt;p&gt;Magento merges every applied discount into one totals row: “Discount (…)”. The text in brackets is built from the rules’ labels. The chain is in &lt;a href="https://github.com/magento/magento2/blob/2.4.7/app/code/Magento/SalesRule/Model/RulesApplier.php" rel="noopener noreferrer"&gt;&lt;code&gt;RulesApplier::getRuleLabel()&lt;/code&gt;&lt;/a&gt;: the rule’s store label first, then, when a coupon applies, the rule description, then the coupon code.&lt;/p&gt;

&lt;p&gt;In the admin, description is a notes field. It holds whatever the team wrote down for itself: which campaign a rule belongs to, which ticket it came from. That is the text that reached the cart.&lt;/p&gt;

&lt;p&gt;A template fix cannot produce one line per rule, because the amounts are not stored. On the order, Magento keeps three fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;coupon_code&lt;/code&gt;, a single code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;discount_description&lt;/code&gt;, a text field&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;applied_rule_ids&lt;/code&gt;, a list of ids&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those three are the whole record. &lt;code&gt;sales_order&lt;/code&gt; in &lt;a href="https://github.com/magento/magento2/blob/2.4.7/app/code/Magento/Sales/etc/db_schema.xml" rel="noopener noreferrer"&gt;&lt;code&gt;Magento/Sales/etc/db_schema.xml&lt;/code&gt;&lt;/a&gt; carries &lt;code&gt;coupon_code&lt;/code&gt; and &lt;code&gt;discount_description&lt;/code&gt; as &lt;code&gt;varchar(255)&lt;/code&gt;, &lt;code&gt;applied_rule_ids&lt;/code&gt; as &lt;code&gt;varchar(128)&lt;/code&gt;, and no column for an amount per rule. Checked against Magento 2.4.7.&lt;/p&gt;

&lt;p&gt;No per-rule amounts. Once the order exists, the split of the discount across the rules cannot be reconstructed. Anything that wants one line per rule has to store the amounts at the moment the order is created.&lt;/p&gt;

&lt;p&gt;I have also seen the same text in the shipping row, “Shipping &amp;amp; Handling (…)”, on an order whose only rule was free shipping. A free-shipping rule runs through the same description code, in &lt;code&gt;Validator::processShippingAmount()&lt;/code&gt;. I could not find the core line that puts the text into that row, so take the shipping row as something I saw in the lab and the discount row as the path the source shows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4mcqwpi5ubau982mussz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4mcqwpi5ubau982mussz.webp" alt="An order confirmation whose discount row label reads: Autumn sale -5 %, INTERNAL description, must never show to customers." width="700" height="1700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The same mechanism in the lab, with core's merged row rendered inside the new email template. The rule description is in the customer's inbox. Test data from a clean Mage-OS.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The email itself is not Magento’s. That layout comes from a second module, described in &lt;a href="https://pixelperfect.at/en/blog/magento-order-confirmation-email/" rel="noopener noreferrer"&gt;the sister post&lt;/a&gt;, and neither module needs the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  The benchmark was Shopify
&lt;/h2&gt;

&lt;p&gt;Shopify does this better, and that is what started the module. Its checkout lists each applied discount as its own line in the order summary, including when several discounts combine (&lt;a href="https://shopify.dev/changelog/apply-multiple-discounts-to-an-order-with-discount-combinations" rel="noopener noreferrer"&gt;discount combinations changelog&lt;/a&gt;). The separate lines are my own reading of a Shopify checkout rather than a documented promise. I looked for a page in Shopify’s own documentation that states it, and did not find one.&lt;/p&gt;

&lt;p&gt;Shopify has its own weakness here: the line carries the code the customer typed, and a merchant cannot substitute a friendly name (&lt;a href="https://community.shopify.com/t/showing-discount-name-instead-of-unique-discount-code-at-checkout/411457" rel="noopener noreferrer"&gt;Shopify Community&lt;/a&gt;). What it shows for an automatic discount, which has no code to show, I have not been able to establish from a source worth citing.&lt;/p&gt;

&lt;p&gt;So the target was both: one line per rule, and a label that comes from a field written for customers, including for a rule the customer never typed anything to trigger.&lt;/p&gt;

&lt;h2&gt;
  
  
  The label rule
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kind&lt;/th&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Amount column&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;coupon&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the coupon code&lt;/td&gt;
&lt;td&gt;-amount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;automatic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the rule’s store label, else a translated “Discount”&lt;/td&gt;
&lt;td&gt;-amount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;free_shipping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the code or the store label, else “Free shipping”&lt;/td&gt;
&lt;td&gt;-shipping discount, or “Free”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gift&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the coupon code of the promotion&lt;/td&gt;
&lt;td&gt;”Gift” when it carries no money, otherwise the amount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gift_card&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;”Gift card ••••1234”, masked&lt;/td&gt;
&lt;td&gt;-amount&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rule description and the rule name are never used. Labels and amounts are snapshotted when the order is placed, so a rule that is relabelled or deleted later does not change existing orders.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frrwvq39m5s3kusyimf5q.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frrwvq39m5s3kusyimf5q.webp" alt="A cart summary with two separate discount rows: Autumn sale -5 % at -3.15 euro, and a coupon chip reading LAB-PERCENT-15 at -8.96 euro." width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The same order with one line per rule. The automatic rule renders its store label, the coupon renders the code, and each names its own amount.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgd87ik1q76jjnx6wzhbv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgd87ik1q76jjnx6wzhbv.webp" alt="A cart summary with three rows: Autumn sale -5 % at -3.15 euro, the coupon LAB-PERCENT-15 at -8.96 euro, and Gift card dot dot dot dot 1234 at -20.00 euro." width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Three kinds in one cart: an automatic rule, a coupon and a gift card. The card is masked to its last four digits, which is enough for a customer to tell which card they used.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the amounts survive checkout
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Storage on the quote address, at collect time.&lt;/strong&gt; After an address’s totals are collected, the per-rule lines are resolved and stored on the quote address (&lt;code&gt;quote_address.pp_discount_lines&lt;/code&gt;). Resolution is one generic rule resolver that reads core’s own per-rule discount breakdown of the address, so it covers every &lt;code&gt;simple_action&lt;/code&gt; including &lt;code&gt;by_percent&lt;/code&gt;, &lt;code&gt;by_fixed&lt;/code&gt;, &lt;code&gt;cart_fixed&lt;/code&gt; and &lt;code&gt;buy_x_get_y&lt;/code&gt;, plus a free shipping resolver. Both sit in a composite, and that composite is the extension point: an adapter adds a named resolver to it through &lt;code&gt;di.xml&lt;/code&gt;. A failed collect clears the stored lines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A totals segment extension attribute.&lt;/strong&gt; A plugin on &lt;code&gt;CartTotalRepositoryInterface&lt;/code&gt; adds the lines to the &lt;code&gt;discount&lt;/code&gt; total segment as &lt;code&gt;extension_attributes.discount_lines&lt;/code&gt; and replaces the segment title, which carries the rule description in core. A quote with only zero-value lines, a gift or free shipping, gets a zero &lt;code&gt;discount&lt;/code&gt; segment so renderers still receive the lines. Stored lines that do not add up to the segment value are not exposed at all, and the title stays the plain “Discount”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Persistence at order placement.&lt;/strong&gt; On &lt;code&gt;sales_model_service_quote_submit_success&lt;/code&gt; the lines are written to &lt;code&gt;pp_discount_line&lt;/code&gt;: order-level lines plus item-level per-rule amounts, which are what make the credit memo split possible. A discounted order with no attributable lines gets one generic &lt;code&gt;automatic&lt;/code&gt; “Discount” line for the full amount, so an order never falls back to the core label.&lt;/p&gt;

&lt;h2&gt;
  
  
  The contract, for anyone rendering the rows
&lt;/h2&gt;

&lt;p&gt;Each line is its own totals row in the standard Magento totals blocks, with the row code &lt;code&gt;pp_discount_line_&amp;lt;n&amp;gt;&lt;/code&gt;, replacing the core &lt;code&gt;discount&lt;/code&gt; row. The row carries the data key &lt;code&gt;discount_line&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;discount_line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;coupon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;automatic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gift&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;free_shipping&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gift_card&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;rule_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// null for gift_card&lt;/span&gt;
  &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// coupon or gift card code, masked for gift_card&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// resolved by the label rule, safe to show&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="nx"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// order currency, positive number = discount&lt;/span&gt;
  &lt;span class="na"&gt;base_amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;float&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Detect a discount line by that data key, not by the row code. The Mageplaza adapter keeps Mageplaza’s own row codes and still carries &lt;code&gt;discount_line&lt;/code&gt; with kind &lt;code&gt;gift_card&lt;/code&gt;. Row &lt;code&gt;label&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt; stay populated as core expects, so a template that ignores the data key still prints a correct plain row.&lt;/p&gt;

&lt;h2&gt;
  
  
  One base, small renderers
&lt;/h2&gt;

&lt;p&gt;The base package knows no frontend. It owns the data, the storage, the label rule and every sales document. Storefront rendering lives in separate packages, so a shop installs only what it runs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;What it adds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;magento2-module-discount-lines&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Data, storage, label rule, documents, configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-hyva-checkout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hyvä Checkout price summary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-hyva-theme&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hyvä default theme cart page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-luma&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Luma cart summary and checkout sidebar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-amasty-promo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Amasty Free Gift rules as gift lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-mageplaza-giftcard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mageplaza gift cards, masked&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All six were released as 1.0.0 on 2026-11-01, MIT licensed, on GitHub and Packagist, so the Hyvä and Luma renderers both exist today.&lt;/p&gt;

&lt;p&gt;The two Hyvä packages are not alternatives to each other. &lt;code&gt;-hyva-theme&lt;/code&gt; renders the cart page of the Hyvä default theme and &lt;code&gt;-hyva-checkout&lt;/code&gt; renders the price summary in Hyvä Checkout, so a shop running both needs both.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgo6tv5bdk2ncldw70djd.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgo6tv5bdk2ncldw70djd.webp" alt="The Hyvä Checkout order summary listing Autumn sale -5 % at -3.15 euro and the coupon LAB-PERCENT-15 at -8.96 euro above the grand total." width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hyvä Checkout reads the same data through the totals segment, so the checkout summary and the cart agree without either one knowing about the other.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgu6dhws8kytjfdlfzian.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgu6dhws8kytjfdlfzian.webp" alt="The Luma shopping cart with its summary panel showing the same two discount rows above an order total of 55.68 euro." width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Luma, the same lab order. The renderer is a separate package, so a Luma shop installs no Hyvä code and the other way round.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Loki Checkout is not built. Because the base package is frontend-agnostic, any further checkout can get a renderer, but today none exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Amasty and Mageplaza, and how to add your own
&lt;/h2&gt;

&lt;p&gt;Those two adapters exist because those two extensions are in the installation I work with, not because I surveyed the market. Mageplaza happens to be the gift card extension running there.&lt;/p&gt;

&lt;p&gt;For any other provider, write the adapter yourself. It is one class with one method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;DiscountLineResolverInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Resolve discount lines for a collected quote address.
     *
     * @return DiscountLineInterface[]
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Address&lt;/span&gt; &lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Quote&lt;/span&gt; &lt;span class="nv"&gt;$quote&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then hand it to the composite under a name of your own, in &lt;code&gt;di.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;type&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"PixelPerfect\DiscountLines\Service\Resolver\CompositeResolver"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;arguments&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"resolvers"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"array"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"your_provider"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"object"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                Your\Module\Service\Resolver\YourResolver
            &lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/arguments&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/type&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An entry that does not implement the interface is skipped and logged rather than thrown, so a broken adapter degrades to the core row instead of taking the cart down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four variants, chosen per store view
&lt;/h2&gt;

&lt;p&gt;Under Stores &amp;gt; Configuration &amp;gt; Sales &amp;gt; Discount Lines: A puts the coupon chip below the label, B makes the chip the label and is the default, C groups the lines with a “You save” sum, and D uses an icon and a monospace code with no chip.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F64qetrj8nm5oormrsk78.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F64qetrj8nm5oormrsk78.webp" alt="The same cart in variant C: the two discount rows inside one block, with a You save row below them showing -12.11 euro." width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Variant C groups the same two lines and adds their sum. B and C differ in presentation only, not in the data behind them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sales documents always use plain text, for example “Coupon CODE -2.25”, because Outlook and PDF renderers cannot draw a chip safely.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the lines add up to
&lt;/h2&gt;

&lt;p&gt;The lines always add up to the document’s discount total, to the cent.&lt;/p&gt;

&lt;p&gt;Rounding differences of up to one cent per line are absorbed by the largest line. A larger remainder that belongs to no rule becomes a generic “Discount” line rather than being assigned to one, because a wrong attribution is harder to spot than a vague one. An invoice scales the stored lines to the invoice discount; a credit memo splits each rule in proportion to the refunded quantities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Orders placed before installation keep the merged core row. The per-rule amounts cannot be rebuilt, because Magento never stored them.&lt;/li&gt;
&lt;li&gt;Multishipping is not supported in 1.0.&lt;/li&gt;
&lt;li&gt;Across several invoices of one order, per-rule amounts can drift by one cent per rule. Every document’s discount total is exact.&lt;/li&gt;
&lt;li&gt;With Amasty, a gift line gets too much of the amount when an automatic rule also discounts the gift item. The sum stays correct. The fix is the Amasty condition “is promo item = No” on automatic rules.&lt;/li&gt;
&lt;li&gt;Existing carts show the core row until the next totals collect.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require pixelperfectat/magento2-module-discount-lines
composer require pixelperfectat/magento2-module-discount-lines-hyva-checkout
bin/magento setup:upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second line is a renderer. Install one for every frontend you run, not one in total: &lt;code&gt;-hyva-checkout&lt;/code&gt; for Hyvä Checkout, &lt;code&gt;-hyva-theme&lt;/code&gt; for the cart page of the Hyvä default theme, &lt;code&gt;-luma&lt;/code&gt; for Luma. A shop that runs the Hyvä theme and Hyvä Checkout installs both. Add &lt;code&gt;-amasty-promo&lt;/code&gt; or &lt;code&gt;-mageplaza-giftcard&lt;/code&gt; if you run those extensions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quality, in numbers
&lt;/h2&gt;

&lt;p&gt;Built and tested in a clean Mage-OS 3.1.0 lab on PHP 8.5, with demo data only. Every package runs &lt;code&gt;composer check&lt;/code&gt; in CI: composer validate, PHP lint, PHP_CodeSniffer, PHPStan and PHPUnit. Four of the six add Infection mutation testing, which gates the build below an MSI of 85 or a covered-code MSI of 90.&lt;/p&gt;

&lt;p&gt;Mutation tests are deliberately broken copies of the code, and the score is the share the test suite catches. Every figure below was read out of the public CI log of the run linked beside it, not copied from a release note.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;MSI / covered-code MSI&lt;/th&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;magento2-module-discount-lines&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;93 / 93&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/pixelperfectat/magento2-module-discount-lines/actions/runs/42242224997" rel="noopener noreferrer"&gt;42242224997&lt;/a&gt;, 2026-11-06&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-amasty-promo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100 / 100&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/pixelperfectat/magento2-module-discount-lines-amasty-promo/actions/runs/42242248810" rel="noopener noreferrer"&gt;42242248810&lt;/a&gt;, 2026-11-06&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-mageplaza-giftcard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;99 / 99&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/pixelperfectat/magento2-module-discount-lines-mageplaza-giftcard/actions/runs/42242346501" rel="noopener noreferrer"&gt;42242346501&lt;/a&gt;, 2026-11-06&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-luma&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;93 / 93&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/pixelperfectat/magento2-module-discount-lines-luma/actions/runs/42242324118" rel="noopener noreferrer"&gt;42242324118&lt;/a&gt;, 2026-11-06&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-hyva-theme&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no Infection step&lt;/td&gt;
&lt;td&gt;runs validate, lint, cs, stan, test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-hyva-checkout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no Infection step&lt;/td&gt;
&lt;td&gt;runs validate, lint, cs, stan, test&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two Hyvä renderers have no Infection step, so they carry no score. I do not count a missing score as a passing one.&lt;/p&gt;

&lt;p&gt;Mutation testing found a real bug before release: an all-digit Mageplaza gift card code crashed the masking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Please try to break it
&lt;/h2&gt;

&lt;p&gt;These modules are new, and a lab is a narrow slice of what Magento shops throw at a cart: rule combinations, currencies, tax setups, other extensions that also touch totals.&lt;/p&gt;

&lt;p&gt;Install them in a test environment and stack things. Several coupons at once, free shipping with a gift, a partial refund across two invoices, a currency switch between order and base. If the numbers disagree, or a line looks wrong, that is worth an &lt;a href="https://github.com/pixelperfectat/magento2-module-discount-lines/issues" rel="noopener noreferrer"&gt;issue on GitHub&lt;/a&gt;, including when it is only ugly rather than wrong. I read every one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The emails are a separate question
&lt;/h2&gt;

&lt;p&gt;The lines appear in the order confirmation, but the email itself is still Magento’s. A second module replaces those templates, and &lt;a href="https://pixelperfect.at/en/blog/magento-order-confirmation-email/" rel="noopener noreferrer"&gt;this post describes it&lt;/a&gt;. Neither module requires the other.&lt;/p&gt;

</description>
      <category>magento</category>
      <category>hyva</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>Not Your Weights, Not Your Workflow</title>
      <dc:creator>André Flitsch</dc:creator>
      <pubDate>Sat, 13 Jun 2026 06:24:45 +0000</pubDate>
      <link>https://dev.to/pixelhed/not-your-weights-not-your-workflow-d4g</link>
      <guid>https://dev.to/pixelhed/not-your-weights-not-your-workflow-d4g</guid>
      <description>&lt;p&gt;&lt;em&gt;I left a multi-agent refactor running overnight. By morning the model was gone, pulled out from under me by a government I don't even vote for, on the other side of an ocean. This isn't really a story about Anthropic. It's a story about who's actually holding the off-switch, and right now it probably isn't you.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;So here's how my morning went.&lt;/p&gt;

&lt;p&gt;I had a job running. Not a toy, a proper codebase-wide refactor that had been grinding away continuously for the best part of two days. Multi-agent setup, left to run overnight, the kind of long, messy, long-horizon task that every model before this one just fell over on. &lt;a href="https://www.anthropic.com/news/claude-fable-5-mythos-5" rel="noopener noreferrer"&gt;Claude Fable 5&lt;/a&gt; was handling it like it was nothing. Anthropic's own launch notes talk about it compressing months of work into days, and honestly, on my own codebase, that wasn't marketing. It was just what was happening.&lt;/p&gt;

&lt;p&gt;Then I woke up. And the model was gone.&lt;/p&gt;

&lt;p&gt;Not rate-limited. Not having a wobble. Gone. The thing I'd built two days of momentum on simply did not exist any more.&lt;/p&gt;

&lt;p&gt;Turns out that on the 12th of June &lt;a href="https://www.anthropic.com/news/fable-mythos-access" rel="noopener noreferrer"&gt;the US government issued an export-control directive&lt;/a&gt; telling Anthropic to cut off all access to Fable 5 and Mythos 5 for any foreign national. And because you can't exactly sort a global user base by passport in real time, that meant pulling it for everyone. Including me, sat in Tyrol, watching my overnight run go cold.&lt;/p&gt;

&lt;p&gt;Anthropic did the right things, for what it's worth. They complied fast, they said out loud that they disagreed, and they're fighting to get it back. About as well as a vendor can behave in that situation.&lt;/p&gt;

&lt;p&gt;(As I write this it's still down. Anthropic reckon it's a misunderstanding and they're trying to get it restored, so maybe by the time you read this it's back up. Doesn't change a single thing about the point I'm making.)&lt;/p&gt;

&lt;p&gt;And it made absolutely no difference to me. That, right there, is the whole point of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  The offer was the trap
&lt;/h2&gt;

&lt;p&gt;Wind back a few days. Fable 5 dropped as the best model anyone had shipped, and the offer was lovely: &lt;a href="https://www.anthropic.com/news/claude-fable-5-mythos-5" rel="noopener noreferrer"&gt;free on Pro, Max, Team and Enterprise from launch right through to the 22nd of June&lt;/a&gt;, with paid credits only kicking in on the 23rd. Use the shiny new thing, get the rewards now, pay later. Simple. Who's saying no to that?&lt;/p&gt;

&lt;p&gt;So you don't say no. You wire it in. You let it run your big jobs overnight. And your investment in the ecosystem gets deep, fast, because that's exactly what a free window is designed to do. Then something completely outside your control happens, and the tool you've come to lean on just evaporates halfway through something that mattered. Boom. 💥 Now what?&lt;/p&gt;

&lt;p&gt;And here's the bit I want to be honest about, because the lazy version of this argument falls apart the second anyone clever pokes it.&lt;/p&gt;

&lt;p&gt;I could not have run this job on open source. There's no open-weights model out there today that touches Fable for this kind of long-horizon agentic work, and even if there was, I don't have a room full of NVIDIA cards humming away in my workshop. Self-hosting frontier capability, as a one-man shop, is financially mental. So no. "You should've just used Llama" is not the lesson here. Anyone telling you that hasn't tried it.&lt;/p&gt;

&lt;p&gt;The lesson is smaller and a lot harder to wriggle out of. The only thing nobody can switch off is the thing you already hold.&lt;/p&gt;

&lt;p&gt;Closed frontier models sell you capability, and the price is control. That's a fine trade right up until the day it isn't, and you don't get to pick the day. Weights sat on your own disk can't be recalled by a pricing committee, a quiet change of terms, or a letter from some agency. Everything else, you're renting. On terms that can change while you sleep.&lt;/p&gt;

&lt;h2&gt;
  
  
  None of this is new. It's just my turn now.
&lt;/h2&gt;

&lt;p&gt;If you've built on platforms for any length of time, you've watched this exact thing happen to other people. Now it's my turn. The shape never changes: you build on someone else's ground, and one day they move the ground.&lt;/p&gt;

&lt;p&gt;Twitter, January 2023. Third-party clients &lt;a href="https://techcrunch.com/2023/01/12/third-party-twitter-apps-tweetbot-twitterrific-echofon-down/" rel="noopener noreferrer"&gt;stopped working overnight&lt;/a&gt;, no warning, no announcement, the API just quietly cut off. Tweetbot and Twitterrific, apps with twelve-plus years behind them and millions of users, &lt;a href="https://techcrunch.com/2023/01/19/twitterrific-tweetbot-app-store-removal-twitter-api" rel="noopener noreferrer"&gt;got yanked from the App Store and shut down for good&lt;/a&gt;. Tapbots said it best in their goodbye: closing "due to circumstances beyond our control." Then a &lt;a href="https://www.socialmediatoday.com/news/twitters-new-api-access-charges-could-price-many-apps-and-researchers-out/644775/" rel="noopener noreferrer"&gt;pricing wall&lt;/a&gt; came along and finished off whatever was left standing.&lt;/p&gt;

&lt;p&gt;Reddit, June 2023. New API pricing meant Apollo, one of the best mobile apps anyone ever built, would've cost its solo developer &lt;a href="https://techcrunch.com/2023/06/08/popular-third-party-reddit-app-apollo-is-shutting-down-as-a-result-of-reddits-new-api-pricing/" rel="noopener noreferrer"&gt;somewhere around 20 million dollars a year&lt;/a&gt; to keep running. So it didn't. Gone on the 30th of June. Years of craft, ended by a line on a pricing page.&lt;/p&gt;

&lt;p&gt;Facebook. Anyone who built on the Graph API lived through the &lt;a href="https://github.com/facebookarchive/fbconsole/issues/33" rel="noopener noreferrer"&gt;v1.0 deprecation&lt;/a&gt; and then the &lt;a href="https://www.theregister.com/2018/04/06/facebook_shoots_itself_in_the_api/" rel="noopener noreferrer"&gt;2018 lockdown&lt;/a&gt; after the Cambridge Analytica mess, where "scheduled deprecation" suddenly became "your working app is broken as of right now." Refactor on their clock, or die on it.&lt;/p&gt;

&lt;p&gt;And Shopify, which is the one closest to home for me, given I spend my days in e-commerce. Merchants get terminated for some alleged terms violation &lt;a href="https://dilendorf.com/blog/shopify-suspended-account-key-tips-for-merchants.html" rel="noopener noreferrer"&gt;with zero warning&lt;/a&gt;. Storefront dark, business stopped, that morning. And if they were on Shopify Payments, &lt;a href="https://easysellapp.com/blogs/wiki/shopify-store-suspended-appeal-recover" rel="noopener noreferrer"&gt;the money gets frozen for anywhere from 120 to 365 days&lt;/a&gt;. Years of work and your own cash, held hostage to a decision you had no say in.&lt;/p&gt;

&lt;p&gt;Different platforms, same lesson every single time. You rented capability on someone else's terms, and the terms changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open still wins on the bit that matters
&lt;/h2&gt;

&lt;p&gt;I'm not going to sit here and tell you open source is more capable than the frontier labs right now. It isn't. Pretend otherwise and you lose every technical reader in the first paragraph. But capability isn't the only thing that matters, and for anything you genuinely depend on, it isn't even the main thing.&lt;/p&gt;

&lt;p&gt;Open source wins on control, flat out, because it's distributed. There's no central kill switch. No single outfit, vendor or government, can reach across the network and wipe it off every machine at once. Once those weights are on your disk, they're yours.&lt;/p&gt;

&lt;p&gt;(Caveat for the pedants, and they've got a point: GitHub has geo-blocked sanctioned regions before, and there's a live policy fight right now about restricting open model weights. So "impossible to block" is too strong. "Massively harder to block, and impossible to claw back once you've got it" is the honest version. It still wins.)&lt;/p&gt;

&lt;p&gt;There's a second thing closed systems can't give you either. When the source is open, the whole world finds and fixes the bugs. You're free to break it yourself, sure. But you're also free to fix it yourself, and so is everyone else. With a closed model you're not even allowed to lift the bonnet, never mind pick up a spanner.&lt;/p&gt;

&lt;h2&gt;
  
  
  So where does that leave people like me?
&lt;/h2&gt;

&lt;p&gt;You'd think a model that compresses months of engineering into days makes a developer like me pointless. This week argued the exact opposite, and loudly.&lt;/p&gt;

&lt;p&gt;Who sets up a pipeline so that when the main model disappears overnight, it limps along instead of dropping dead? Who keeps the orchestration, the prompts, the data, the eval setup under their own roof, so the model is a part you can swap and not a single point of failure? Who picks up a half-finished refactor when the tool that was running it becomes literally illegal somewhere between midnight and breakfast?&lt;/p&gt;

&lt;p&gt;Not the model. The person who planned for an off-switch they knew they didn't control.&lt;/p&gt;

&lt;p&gt;That's the actual job now. Not typing the code, because for big stretches the AI genuinely is better at that than I am, and I've made my peace with that. The job is judgement. Knowing what you're allowed to depend on, and building so that the rug-pull, when it comes, and it always comes, is an annoying Tuesday and not the end of your business.&lt;/p&gt;

&lt;p&gt;This AI moment is genuinely amazing and also, let's be honest, nowhere near as all-powerful as the hype keeps insisting. Will the robots take over the world? Maybe. But only for as long as the vendors are allowed to sell them to you. And this week showed exactly how fast that permission can be pulled, through no fault of the vendor, with you stuck halfway through something that mattered.&lt;/p&gt;

&lt;p&gt;Don't build on rented ground if you can help it. And where you can't help it, which for frontier AI today is basically all of us, at least build like you know the landlord can change the locks while you're asleep.&lt;/p&gt;

&lt;p&gt;Because this morning, mine did.&lt;/p&gt;




&lt;h3&gt;
  
  
  The receipts, all in one place
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Fable 5 launch, the capabilities and the free-until-22-June offer: &lt;a href="https://www.anthropic.com/news/claude-fable-5-mythos-5" rel="noopener noreferrer"&gt;anthropic.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The government suspension directive: &lt;a href="https://www.anthropic.com/news/fable-mythos-access" rel="noopener noreferrer"&gt;anthropic.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Twitter kills third-party clients: &lt;a href="https://techcrunch.com/2023/01/12/third-party-twitter-apps-tweetbot-twitterrific-echofon-down/" rel="noopener noreferrer"&gt;TechCrunch (the outage)&lt;/a&gt;, &lt;a href="https://techcrunch.com/2023/01/19/twitterrific-tweetbot-app-store-removal-twitter-api" rel="noopener noreferrer"&gt;TechCrunch (the shutdowns)&lt;/a&gt;, &lt;a href="https://www.socialmediatoday.com/news/twitters-new-api-access-charges-could-price-many-apps-and-researchers-out/644775/" rel="noopener noreferrer"&gt;Social Media Today (the pricing)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Reddit pricing kills Apollo: &lt;a href="https://techcrunch.com/2023/06/08/popular-third-party-reddit-app-apollo-is-shutting-down-as-a-result-of-reddits-new-api-pricing/" rel="noopener noreferrer"&gt;TechCrunch&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Facebook Graph API breaking changes: &lt;a href="https://github.com/facebookarchive/fbconsole/issues/33" rel="noopener noreferrer"&gt;v1.0 deprecation&lt;/a&gt;, &lt;a href="https://www.theregister.com/2018/04/06/facebook_shoots_itself_in_the_api/" rel="noopener noreferrer"&gt;2018 lockdown (The Register)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Shopify terminations and frozen payouts: &lt;a href="https://dilendorf.com/blog/shopify-suspended-account-key-tips-for-merchants.html" rel="noopener noreferrer"&gt;Dilendorf&lt;/a&gt;, &lt;a href="https://easysellapp.com/blogs/wiki/shopify-store-suspended-appeal-recover" rel="noopener noreferrer"&gt;EasySell&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>claude</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
