Light API 0.4.0 is a public beta: a minor release such as 0.5.0 may still break. What that means for your module
Light
Developer guide

Changelog

What each Light API release added, changed and deprecated.

0.4.11 (beta)

No @api change, so composer update 'mrx/*' 'magerex/*' -W stays inside ^0.4. Run setup:upgrade right after it.

Added

  • Settings > Checkout > Customer accounts: "Log in as any customer". With it on, "Log in as customer" opens every account, also when the customer didn't turn on "Allow remote shopping assistance". Off by default, kept once for all channels, and shown only while Magento_LoginAsCustomerAssistance is on. The admin still needs a role that may log in as a customer, and Magento still logs every login. The config path is mrx_settings/checkout/assist_without_consent; a plugin on Magento's IsLoginAsCustomerAllowedResolver clears its refusal while the switch is on. The customer page's "You can't log in as this customer" dialog names the switch.

0.4.10 (beta)

Fixes only; no @api change, so composer update 'mrx/*' 'magerex/*' -W stays inside ^0.4.

Fixed

  • Magento 2.4.8: the nav counters' cache clean (BadgeInvalidator) and the test-payment count in Orders named Magento\Framework\Cache\CacheConstants, a class Magento 2.4.9 added. On 2.4.8 the first order, invoice or other change that refreshes a counter stopped with a 500 "Class "Magento\Framework\Cache\CacheConstants" not found". Both pass the mode's value now, which works on 2.4.8, 2.4.9 and Mage-OS 3.x.

0.4.9 (beta)

No @api change, so composer update 'mrx/*' 'magerex/*' -W stays inside ^0.4. Run setup:upgrade right after it.

Changed

  • magerex/distribution-nl and magerex/distribution-nl-hyva no longer install magerex/light-branding: Light from a metapackage is white-label and shows the platform's own logo and default theme. The MageRex brand is an add-on that magerex/distribution-nl suggests. A shop that keeps the brand requires magerex/light-branding ^0.4 itself, in the same command as the update (From 0.4.8 to 0.4.9).

Added

  • The guide: Packages and compatibility, with every package, which ones install on their own, what a shop may leave off and the bridges to other modules. The published guide at https://light.magerex.nl/ gives assistants full links: llms.txt, llms-full.txt and the Markdown of each page point at the full address of every page they link to.

0.4.8 (beta)

One package, magerex/module-flex-bridge; the other packages stay at 0.4.7. No @api change.

Fixed

  • magerex/module-flex-bridge: bin/magento mrx:light:doctor reported private_api because the page editor template named Mrx_Content's internal ContentFormInterface in a docblock; it now names no internal type.

0.4.7 (beta)

Fixes and small screen changes; no @api change, so composer update 'mrx/*' 'magerex/*' -W stays inside ^0.4. Run setup:upgrade right after it.

Added

  • magerex/module-flex-bridge: a Flex Editor card in the category editor and in the product editor, with "Edit in Flex Editor" in a new tab. A category opens on a store view whose menu holds it, a product on a store view of a channel that sells it and where it is enabled, each at its own storefront URL. The product card says "This product has its own Flex blocks." when it has them; the category card says "Your category pages have Flex blocks." when the blocks every category shares exist, because Flex keeps one set for all categories. No card for a root or hidden category, a category no store view's menu holds, a disabled product, one that isn't visible on its own or that no store view sells, without Flex, or without the editor permission. The package now requires mrx/module-catalog ^0.4.
  • magerex/module-flex-bridge: light/flexbridge/open takes type category or product with id and an optional store; cms, the default, works as before. A category or product that is gone, or that no store view shows, goes back to its list with a message, and an unknown type is a 404.

Fixed

0.4.6 (beta)

Fixes and small screen changes; no @api change, so composer update 'mrx/*' 'magerex/*' -W stays inside ^0.4. Run setup:upgrade right after it.

Fixed

  • Users and permissions (security): every role Light makes or completes, and a role saved in Magento's own role editor with Light ticked, gets the user's own second factor (Magento_TwoFactorAuth::tfa) without its parents Magento_Backend::system and Magento_User::acl. Magento allows the second factor under a denied parent, so sign-in and the two-factor set-up work as before, while Magento_Backend::system no longer opens admin/system/*, the Varnish VCL export and the media storage sync for staff. Magento's role tree posts the parents of every ticked resource, so a save in Magento's own role editor drops Magento_Backend::system and Magento_User::acl again unless another ticked resource sits below them (Settings' cache screen sits below System). A role saved before 0.4.6 keeps the parents it holds until it is saved in Magento's own role editor with Light ticked, or until you untick System in the advanced view (Users and permissions).

Changed

0.4.5 (beta)

Fixes and small screen changes; no @api change, so composer update 'mrx/*' 'magerex/*' -W stays inside ^0.4. Run setup:upgrade right after it.

Fixed

  • Settings: on Light's own settings pages a save writes only the switches the merchant turned (Shipping's "Ship from the business address" always stores 1 or 0, as a stored 0 means an own ship-from). A switch posted back off where nothing is stored (no row and no config.xml default, such as Magento's sales/minimum_order/active) no longer writes a 0 at All channels, and in a channel no longer becomes the channel's own value. ScopedConfigWriterInterface::save() compares a PHP bool the same way; pass the string '0' when the row itself must exist (Saving per scope).
  • Users and permissions: an admin whose role lacks Magento_Config::config who types the stock configuration URL without a section (admin/system_config/index or .../edit) gets Magento's "Sorry, you need permissions to view this content." page (403) instead of a 500 "Undefined array key 0" from Magento_Paypal's structure plugin. A URL with a section, and a role that may open the first section, keep Magento's behaviour.

Changed

  • ScopedConfigWriterInterface::save() treats a PHP bool as a switch: false equals '0', '' and a path with no value, so it writes no row for a switch left off. Pass the string '0' when the row itself must exist. No signature change.

0.4.4 (beta)

Fixes and small screen changes; no @api change, so composer update 'mrx/*' 'magerex/*' -W stays inside ^0.4. Run setup:upgrade right after it.

Fixed

  • Users and permissions (security): every Light role holds Magento_TwoFactorAuth::tfa, which in Magento also opens the web API routes that read or change another admin's second factor (GET and PUT /V1/tfa/user-providers/:userId, GET /V1/tfa/providers-to-activate/:userId, GET and PUT /V1/tfa/default-provider-code/:userId, also through /async and SOAP). With an admin token those routes now work on another user only when the role also holds user management (Magento_User::acl_users), else they answer 401 "The consumer isn't authorized to access Magento_User::acl_users."; the token's own user, full access and integrations keep Magento's behaviour (Users and permissions).
  • Users and permissions (security): that guard refuses a guarded two-factor method that has no userId parameter instead of letting the call through, so a rename in Magento cannot switch it off silently. An integration that holds Magento_TwoFactorAuth::tfa can still read and change any admin's second factor, as in stock Magento; give it that resource only when it should.

Changed

  • Home: for developers, ?mrx_kit_prelaunch=1 shows Home's before-the-first-sale layout and Recent orders' empty state on a shop that has orders, while the kit fixtures are on (developer mode and dev/mrx_light/kit_fixtures); it never changes an order and does nothing in production mode.

0.4.3 (beta)

Fixes and small screen changes; no @api change, so composer update 'mrx/*' 'magerex/*' -W stays inside ^0.4. Run setup:upgrade right after it.

Fixed

  • Flex bridge: the Flex Editor banner on Home loads its stylesheet only when it shows, so a shop without Flex, or an admin without the editor permission, loads no MageRex_FlexBridge file on Home.
  • Users and permissions: a role saved in Magento's own role editor (the advanced view) with Light (Mrx_Light::light) ticked now gets the user's own second factor (Magento_TwoFactorAuth::tfa) with its parents Magento_Backend::system and Magento_User::acl, as roles of Light's role editor do, so its users can finish signing in on a shop with two-factor on. Nothing else is added: "All resources", roles without Light and roles that have it already are saved as ticked, and integrations are never touched (Users and permissions).
  • Upgrade note for 0.1.2: the first setup:upgrade on 0.1.2 or later saves each role that holds Mrx_Light::light and lacks the second factor once more through Magento's Rules::saveRel() (the Staff and Shipping staff presets also when an area misses screens). That save writes the role again from the ACL tree of that moment, so a resource of a module that was disabled then is gone from the role and doesn't come back when the module is enabled again; dump the database first, and check the roles named in var/log/system.log ("Light: roles ... got the screens") (Upgrading).

Changed

  • magerex/module-flex-bridge: the Flex Editor banner on Home stands out like FlexCore's own launcher: a gradient from the theme's inverse surface into its primary colour with a soft glow, a "Content" eyebrow and a light pill button, so it follows every theme.

0.4.2 (beta)

  • Rich-text editor: text is 16px on touch screens and narrow windows, so iOS no longer zooms in when you tap into it.
  • Analytics: a chart without sales in the period keeps its frame like Home's "Sales over time" (gridlines, a labelled zero, date ticks, the previous period's dashed line when it sold) with a quiet "No sales in this period yet", and "Create an order" for admins who may.
  • Categories: a language's own "Visible" or "Show in menu" is now only a store-view value that differs from the default. The editor no longer lists a language whose store-view value equals the default, and a change of the switch doesn't ask about it: that row takes the new value and stays (multi-scope audit D9, decision F7). Changing a switch therefore never asks any more; "Show in menu" and "Hide from menu" in the list still ask when they would replace a language's own value.
  • Orders: the order list no longer builds every payment method on each load to decide on "Incomplete payments" and "Capture payments"; the answers are cached until a payment setting changes, so the list stops reopening the admin session (Mollie and PayPal start it) and answers sooner.
  • Settings: in a channel, a value the channel's default language has of its own under a text with Translations inputs (payment names and instructions, the pickup name, opening hours, the welcome text, the documents footer) is named under the field with "Remove the Nederlands value", as on schema pages (Store-view values from the advanced view). "Changed for English in its translation" is now only said when that language has a translation input under All channels; otherwise the note says "in the advanced view".

0.4.1 (beta)

Fixes and small screen changes; no @api change, so composer update 'mrx/*' 'magerex/*' -W stays inside ^0.4. Run setup:upgrade right after it.

Changed

  • Orders: "Print picklist" drops canceled, shipped and other-location orders first and then prints the first 250 of the rest, so "Only the first 250 of N orders are printed" counts orders that can be printed. The check reads at most 2000 selected orders and says "of more than 2000" beyond that. Packing slips and order confirmations still cut at 250 before they filter.
  • Apps: a config section mageplaza is no longer dropped as an upsell entry, so Mageplaza_Core shows as "Mageplaza (general settings)". Magefan_AdminUserGuide folds into the developer's "(general settings)" row, like the *_Core, *_Base and *_Community modules (What detection does).
  • Settings > Legal pages: the dialog says when it refills the draft.
  • Home: the dashboard shows from the first day. Before the first sale the setup guide sits on top and the toolbar, KPIs, chart, to-dos, mrx.home.widgets and the lists follow it, where they used to be hidden; mrx.home.top stays right under the guide, above the toolbar. The channel filter applies before the first sale too, and hiding the guide no longer reloads the page.
  • Home: designed zero states. A KPI card without sales shows the formatted zero (€0.00, 0, 0%) with a short helper in place of the comparison and a muted flat sparkline; Analytics' series cards show the same. "Sales over time" keeps its gridlines, €0 baseline, date ticks and the previous period's dashed line, with a centred "No sales in this period yet" and "View store" and "Create an order". Recent orders and Top products show an icon, one line and "Create an order" or "View products" ("Add a product" while the catalog is empty). Each link shows only when its page is installed and the admin may open it (Home to-dos, setup steps and widgets).
  • magerex/module-flex-bridge: on a page built with the Flex Editor, the page editor's Content field shows a Flex banner with "Edit in Flex Editor" instead of Light's Page Builder notice and the old content; the side card shows only on pages that aren't built. Every other page keeps Light's editor.

Fixed

  • Settings > Channels: Magento's own "Default Category" is never removed with an empty, unused menu.
  • Support: the local copy of tickets moves to another shop id in a safer order, and a sync that overlaps a connect no longer leaves the new shop with the old shop's resume point.
  • Cards: a card that is only a header (a title, a line and an action, such as "Keep stock in more places?" on Settings > Locations) keeps the same space below as above (.mrx-card__header:last-child).
  • Settings > Locations: the pickup line says "Customers can pick up here." or "Customers can't pick up here yet." with "Change in Shipping and pickup", instead of "Customers can pick up here: Off" next to a loose link.
  • Sign-in: the reCAPTCHA v2 checkbox stays inside the card down to a 344px wide screen, and etc/csp_whitelist.xml adds connect-src https://www.google.com/recaptcha/, which the stock reCAPTCHA modules leave out.

0.4.0 (beta)

One new layout container on Home, mrx.home.top. A new container is an addition, but a 0.x minor never matches the range of the one before it, so every range moves to ^0.4 (Upgrading from 0.3.x to 0.4.0). Move the ranges with composer require (a composer update stays inside ^0.3) and run setup:upgrade right after it.

Added

  • Home: the container mrx.home.top (handle light_home_index, ACL Magento_Backend::dashboard) for full-width banners right under the setup guide, above the date range and the figures, and in the same place before the first sale. Its blocks are .mrx-card blocks that render nothing when they have nothing to say; Home adds no wrapper around them, so with nothing in it Home looks as before. ?mrx_slots=1 outlines it in developer mode, like mrx.home.widgets (Home to-dos, setup steps and widgets).

Changed

  • magerex/module-flex-bridge: the Flex Editor card on Home is a banner under the setup guide (in mrx.home.top, no longer in mrx.home.widgets), with "Open Flex Editor" and "Choose a page" on the right.
  • Every range moves to ^0.4: extra.mrx-light-api, the range guard in registration.php and the requires on mrx/module-* and magerex/*. ExtensionApi::VERSION and the surface snapshot say 0.4.0.

0.3.2 (beta)

Fixes and small screen changes; no @api change, so composer update 'mrx/*' 'magerex/*' -W stays inside ^0.3. Run setup:upgrade right after it.

Changed

  • Documents and Orders: the filenames of PDFs with several documents (order-confirmations-*, invoices-*, creditmemos-*, packing-slips-*, picklist-*) use the shop's local time; before, all but the picklist used UTC.
  • Orders: "Print picklist" on more than 250 orders prints the first 250 and says "Only the first 250 of N orders are printed", like packing slips and order confirmations.
  • The notification bell: the inbox section is called "Updates and news", so the panel no longer says "Notifications" twice.
  • Settings: the legal pages section is called "Legal pages"; Mrx_Returns names it "Returns and legal pages" while it is enabled.
  • Orders > Create order: the customer search results push "Create a new customer" down instead of covering it.
  • The two-factor screen: "Logout" sits below the card instead of above its heading.
  • Dutch pack, Settings > Taxes: the Dutch VAT card names the first four countries without a rate and "and N more", with the full list behind "Show all countries". When the ship-from address is outside the Netherlands it says which channel's address that is, links to the ship-from field on Shipping (in that channel) and names OSS or the regular VAT settings as the way to go.

Fixed

  • Documents: the order, invoice and credit memo PDFs left the fixed product tax (FPT, such as a disposal fee) in the subtotal and printed it again as its own row. The subtotal now leaves it out, as the order page does; the document's own amounts are unchanged.
  • Documents: "View email" on the order timeline looks the stored copy up within its order, and a copy that can't be unpacked answers "This email copy can't be opened." (404) instead of a server error.
  • Demo data (MageRex_DemoData) no longer fails on a shop without Magento_Bundle or Magento_ConfigurableProduct.
  • Support: the ticket detail, refresh and reply routes declare their ACL resource (MageRex_Support::tickets) themselves.
  • White-label: core READMEs, composer descriptions, CSS, PHP comments and table comments no longer say "MageRex Light". A setup:upgrade updates the table comments.

0.3.1 (beta)

One new package, magerex/module-flex-bridge; the other packages stay at 0.3.0. No @api change.

Added

  • magerex/module-flex-bridge (MageRex_FlexBridge), new and optional. On a shop with Disrex Flex (Disrex_FlexCore) it adds a Flex Editor card on Home, "Edit in Flex Editor" on Content > Pages (row action and More actions) and in the page editor, a "Flex" column that says "Built with Flex", a palette action and an Apps row. It opens the editor in a new tab on the store view the page shows in, and the Content area of Settings > Users and permissions includes it. It shows nothing without FlexCore. It is in neither metapackage, and it changes no @api: it uses the target key and the row-only bulk action of 0.3.0. Checked against FlexCore 2.0.26. Custom roles that held Content before the install need one save in Settings > Users and permissions (Flex shops).

0.3.0 (beta)

Two new routes, notification dismissal and legal page visibility, plus smaller additions to lists, the page header, the palette and Settings > Payments. A new route is an addition, but a 0.x minor never matches the range of the one before it, so every range moves to ^0.3 (Upgrading from 0.2.x to 0.3.0). Move the ranges with composer require (a composer update stays inside ^0.2) and run setup:upgrade right after it.

Added

  • Page header and palette: a target key on header_actions and palette_actions items. _blank opens the link in a new tab: a pool action in More actions renders target="_blank" rel="noopener", and a palette action opens its page in a new tab by click and by Enter and hands the focus back. Any other value is ignored. A palette search group's result may carry target _blank too (GroupInterface::search()); a command never gets one (The advanced view, the route map and header actions, The command palette).
  • Lists: a bulk action with row_only true is left out of the bulk bar and runs only through a row action that names it, on that one row. The list config carries it under rowBulkActions; a list whose bulk actions are all row-only gets no checkboxes (Lists).
  • Mrx\Settings\Api\ChannelPaymentProviderStateInterface (@api), which extends PaymentProviderStateInterface with isOfferedIn(?int $channelId): bool. Settings > Payments asks it when it saves one channel, so a provider connected or switched on for that channel only counts there, and one switched off there doesn't (multi-scope audit 4.3). null means every channel, as isOffered(). A state with only the old interface is asked isOffered() as before. The Mollie and Pay. packs implement it (A settings page from your system.xml).
  • Settings > Legal pages: a page a legal page links has a "Make visible" button (and "Hide" once it is visible) next to its badge, for admins who may save pages (Magento_Cms::save). It asks "Have you had the text checked? Customers can read it once it's visible.", then switches the page's visible state through the CMS repository (light/settings/legalPageVisibility, posts page_id and visible) and reloads the screen; a page no legal page links is refused. A visible page also says that customers find it in the footer. Each card has an anchor (#privacy, #terms, ...).
  • The notification bell: a finished bulk task (Magento_AsynchronousOperations, such as "Update attributes" or "Rule processing") has a Dismiss button, and the System messages section has "Dismiss all finished" while it lists a task, as the stock message bar's Dismiss and "Dismiss All Completed Tasks" do. Both post to light/notifications/dismissTasks (uuid[] or all=1, ACL Magento_Logging::system_magento_logging_bulk_operations), which acknowledges only the signed-in admin's own finished tasks through Magento's BulkNotificationManagement::acknowledgeBulks(); another admin's task or one still waiting is refused. A task that hasn't run yet says it waits for the store's background jobs (cron) instead.

Changed

  • Every range moves to ^0.3: extra.mrx-light-api, the range guard in registration.php and the requires on mrx/module-* and magerex/*. ExtensionApi::VERSION and the surface snapshot say 0.3.0.
  • Phones: Safari no longer zooms into a field when it gets the focus. Under @media (pointer: coarse), (max-width: 640px) every text field (inputs of a text type, selects, textareas; Light's and the stock ones, in pages, modals, the bulk bar and the palette) has 16px text, one !important floor in light.css; login.css does the same on the sign-in, two-factor and password-reset cards. A field that sets a smaller size for phones loses it there. Desktop sizes are unchanged (Design system).
  • Phones: the sign-in, two-factor and password-reset card starts near the top of the screen (24px) instead of in the middle, and the page scrolls; up to 640px wide.
  • Phones and touch tablets: the search palette is a full-screen panel anchored to the top instead of a bottom sheet. It follows window.visualViewport, so the search row stays put when the on-screen keyboard opens or closes and only the results list, which scrolls inside the panel, changes height. It has no grab handle and no slide; Close, Escape and a result close it at once. The palette no longer carries .mrx-sheet-handle, .is-closing or the --mrx-palette-viewport property; Mrx_Light/js/sheet is the modals' alone (Design system).
  • Apps: an open row leaves room between its header and the description, settings and screens below it.
  • Settings > Shipping: the country picker has a "Clear all" button next to "Add EU countries" (disabled while nothing is selected). The 0% tax warning under it names the first four countries and "and N more", with a "Show all" / "Show fewer" toggle, so a shop shipping everywhere no longer gets a page-long list.
  • Home: while a privacy policy draft exists that no one has made visible, the setup step "Review your legal pages" says the draft is ready and needs a check and "Make visible", its button reads "Open legal pages" and opens Settings > Legal pages at #privacy. The step is still done only when the privacy policy is visible.
  • Analytics: cards in one row are as tall as the row, so a short card next to a tall one leaves no empty gap, and an empty state sits in the middle of its card. The reading order and the phone layout are unchanged.

Fixed

  • The Mollie and Pay. pages' own switch-off and disconnect guards ask whether another way to pay is on in the channel being saved (PaymentMethods::hasOtherCheckoutMethod() passes the channel to the provider state), so a provider connected for one channel only no longer makes them refuse in another.

0.2.3 (beta)

A patch: multi-scope round 2 (a second store group is a menu of its channel, category visibility asks before it resets languages, legal pages reach every store view of their language, a channel's default language gets a translation field) and the order-number fix of 0.2.2 for every package. No @api change. Run setup:upgrade after composer update 'mrx/*' 'magerex/*' -w.

Added

  • Lists: a bulk action's route may answer 409 with confirm (title, message, label, param) to ask the admin first; on yes the list posts the same ids[] again with param (default confirmed) set to 1 (Lists).
  • mrx:light:doctor has a new rule, legal_page_hidden: a warning per legal page link that a store view of its language can't open (the page isn't visible there, or no longer exists), naming the page, the language and each store view. Such a store view's footer leaves the page out while Settings > Legal pages shows it as linked (multi-scope audit D7).

Changed

  • A second store group in a website is a second menu of that website's channel, not a channel (multi-scope audit D5). ChannelProvider::getChannelsByRootCategory() also returns the channel for the root category of its other store groups; the channel's rootCategoryId and default language stay its default group's. Products > Categories lists such a root under its channel and channel filter, the product editor's category picker groups it as "Studio Noord · Kids", and a category below it offers only that group's languages (the channel's own menu leaves them out). Content > Menu has a tab per menu (light/menu/index/channel/<id>/root/<root id>, saves post root). Settings > Channels lists each menu's languages under the channel and offers only the channel's own menu's languages as its default language. Two store groups of one website on the same root category are one menu, named after the channel once (Channels and store views).
  • Category visibility still applies to every language, but when a language has its own value (set in the advanced view) that differs from the new one, the category editor, the list's "Show in menu" and "Hide from menu" and the menu editor name those languages and ask first; nothing is written before the answer (409 with confirm, then the same request with confirmed=1). An own value equal to the new one stays. The menu editor asks before its first step, so a "no" leaves no new category behind. The category editor lists the languages with an own value under each switch ("Hidden for Deutsch (Outlet), set in the advanced view"), and light/categories/save answers with ownValueNotes, so the list follows a confirmed change. Before, any save that changed a switch removed every language's own value of both switches (multi-scope audit D9).
  • Category editor: a category whose main-language texts differ from its default values (written in the advanced view) shows the main language as a language of its own next to "Default (all languages)", as the product editor does, and a save reaches those texts. Main-language rows equal to the default values keep it merged (multi-scope audit D9).
  • Settings > Legal pages keeps one page per language for every channel, and a save now makes each linked page visible in every store view of its language: a Dutch page shown in the main channel's Dutch store view is added to the outlet's Dutch one too. This runs for every link on the screen, changed or not, so a store view added later gets the page with the next save. A page of another language is still refused, and a page's own visible or hidden switch is never changed. Only an admin who may save pages (Magento_Cms::save) makes this change; for others the link saves as before. When a store view can't be added (another page uses the address there), a changed link is refused with the reason (Channels and store views).
  • The translations under a settings field (i18n[<store id>][...]) have an input under All channels for a channel's default language when it isn't the shop's main language, such as an English channel on a Dutch shop. Before, that channel showed the main-language text unless every text got a channel value. In the channel itself the main input is its own language and shows the channel's value, while that translation wins on the storefront: on a schema page the field there names it as "Changed for English in its translation" with "Remove the English value", like a value from the advanced view. Under All channels and for the channel's other languages a translation is never named as a change. The texts Light keeps per language itself (payment and carrier titles, opening hours, the welcome text, the documents footer) show no such note yet (multi-scope audit D8).

0.2.2 (beta)

A hotfix for mrx/module-settings only; the other packages stay at 0.2.1. No @api change. Run setup:upgrade after composer update 'mrx/module-settings' -w.

Fixed

  • setup:upgrade no longer hangs in Mrx_Settings' data patch UseChannelOrderNumbers on a shop whose channel has a second store view with its own order numbers. The patch dropped that store view's sequence table inside its own transaction, and the DROP waited for the transaction's metadata lock. Order numbers of a channel still share one sequence; the store view's old table now stays until Magento removes the store view. A run that was killed left the store view's order numbering pointing at a dropped table, so orders in that store view failed: the next setup:upgrade with 0.2.2 repairs it.

0.2.1 (beta)

A patch: Locations and pickup follow-ups, the single-channel fold of multi-scope D1, search and returns fixes, the install guide for 0.2.0. No @api change. Run setup:upgrade after composer update 'mrx/*' 'magerex/*' -w.

Docs

  • Install Light is current for 0.2: magerex/distribution-nl installs 18 Light modules and -hyva 21, with Mrx_Locations coming in through Catalog, Orders and Home; the rate table pack is in neither metapackage and no metapackage suggests it; the first install names the modules in a module:enable line before setup:upgrade; new sections on stock and locations (simple mode with one source, locations mode from the second) and on two-factor sign-in; where the doctor fits.
  • A shop that required Light at ^0.1 doesn't reach 0.2.0 with composer update 'mrx/*' 'magerex/*' -w, which the 0.2.0 notes below suggest: a caret on 0.x stops at the next minor. Move the range with composer require 'magerex/distribution-nl:^0.2' -w (and Returns, the rate table pack or -hyva where the shop has them), then setup:upgrade (Updating).

Changed

  • A shop with one channel (one website) and settings kept at that website, as many existing stores have them: "All channels" now shows the website's value, which is what the storefront uses, and a save writes default scope and removes the website's row for each path it saves (multi-scope audit D1). This holds for ScopedConfigWriterInterface::save() with $websiteId = null too, so a module page on such a shop writes one scope; with two or more channels nothing changes (Channels and store views).

Fixed

  • Discounts: the product and category picker stays closed after Escape. A search still waiting for the typing pause, or on its way back, opened the list again over the fields below it.
  • On a shop with one channel, Settings > Payments shows that channel's own IBAN and Settings > Taxes its own "I don't charge tax" value, as the save now writes them (multi-scope D1). Before, the page showed the all-channels value, and a save could have replaced the channel's own one with it.
  • On a shop with one channel, saving Settings > Shipping with zones also removes that channel's own carriers/flatrate/active row, so the zones' flat rate is on at checkout as the page says.
  • Palette: the Categories group and the ranking of exact results fold the query like "Go to" and "Actions" (StaticMatcher::normalize()), so "e-mail" and "email" find the same categories, and "E-mail templates" ranks a result named "Email templates" first.
  • Apps: a composer package's hyphens split its parts into words, so "email" no longer finds an app through vendor/module-mailer ("modulemailer"). "module mailer" or the package pasted whole still finds it; the page and the palette search alike.
  • Settings > Returns: a new reason named like one renamed in the same save is added as a reason of its own; before, it was merged into the renamed one.
  • Dutch VAT card: the basis the button sets ("VAT of the customer's EU country") counts a Dutch rule only when OSS can copy it, as Settings > Taxes does: OSS's own rules, which may still charge a Dutch rate after the business moved to the Netherlands, no longer count.
  • Settings > Checkout: the one terms agreement is active while any channel asks for terms. A channel switching its terms off, or following All channels that has them off, now switches the agreement off when no other channel asks; All channels switching off keeps it for a channel that has terms on of its own.
  • Taxes: a home move (Business details, or the advanced view) updates the OSS rates on that save: the new home's rate leaves the OSS rules at once, where it waited for the next shipping-country or OSS save.
  • Doctor: store_override on a setting of a payment provider's own section (Mollie, Pay.) names that provider's settings on Settings > Payments and the advanced view, where it said that no Light settings page shows the setting.
  • Locations: the first "Add location" carries Local pickup over at All channels. It writes carriers/instore/active for all channels and a channel row only where that channel's pickup differed, and removes the channels' carriers/mrxpickup/active rows. Before, it wrote a row for every channel, so switching pickup for All channels on Settings > Shipping later changed nothing at checkout. The channel rows the conversion writes are Light's (multi-scope D11): an All channels save that makes one equal removes it, so that channel follows All channels again, and a save at the channel itself makes it the merchant's own. A shop converted with 0.2.0 keeps its row per channel: choose "Use the all-channels value" under the pickup switch in each channel, or remove the rows in the advanced view.
  • Settings > Shipping in locations mode: an empty translation of the pickup name is filled with Light's pickup name in that language ("Abholung im Geschäft"), as it already was for Local pickup at the business address. Magento's own names for pickup at a location count as Light's English name.

0.2.0 (beta)

Locations and pickup at a location, plus the fixes found on the 0.1.5 shop re-checks. Run setup:upgrade after composer update 'mrx/*' 'magerex/*' -w: it creates Light's location and pickup tables. A new core module, new @api types and new routes are additions, so the minor goes up and every range moves to ^0.2 (Upgrading from 0.1.x to 0.2.0). Run setup:upgrade after composer update: it creates mrx_location, mrx_location_stock and mrx_order_pickup.

Added

  • Mrx_Locations (mrx/module-locations), the 14th core module: Settings > Locations (light/settings/locations, the settings_pages item locations), shown only while Magento's inventory modules are on, to a role with Settings and Magento's Magento_InventoryApi::source (the palette leaves it out for a role without Settings). A shop with one location sees the business address and "Add location"; the first add makes the business address the first location, copies its stock there and keeps every channel selling the same quantities, in steps that resume after a stop (in the cron job mrx_locations_conversion above 3,000 products). Each location has a page (light/locations/edit) with its details, "Sell online from this location", "Sells to" with more than one channel, and "Customers can pick up here" with the name at checkout, opening hours, pickup instructions and "Usually ready in". Drag the list to set which location ships first; turn a location off once its stock has moved ("Move stock to another location"). Routes light/locations/{edit,save,add,order,disable,movestock,moveoldstock,progress,resume} (Locations and pickup).
  • Mrx\Locations\Api\LocationsInterface and Mrx\Locations\Api\Data\LocationInterface (@api, read only): whether the inventory modules are on, whether the shop is in locations mode, the locations in shipping order, the main location and the locations a channel sells from. In simple mode they return the business address as the one location, code ''.
  • Stock per location: the product's Stock card shows a row per location with "Stocked here" and "On hand", and "Available to sell" per channel; the variants table, the Stock page and "Adjust stock" get a location select ("All locations" shows read-only sums, "Not stocked here" offers "Stock here"); a new product or variant is stocked at 0 at every location that sells online; Duplicate copies the quantities per location. Changing them needs Magento_InventoryApi::stock_source_item_assign.
  • Ship from a location: "Ship items" says where it ships from, preset to Magento's priority suggestion, and caps each line at what that location holds; shipments and the timeline say "Shipped from" and the location, the packing slip prints it, the picklist takes a location, and the refund dialogs say where restocked items go back to. Bulk "Mark as shipped" ships each order from one location that holds all of it and skips an order that needs two.
  • Pickup at a location, on Magento's In-Store Pickup: Settings > Shipping's "Local pickup" card switches it per channel, names it per language and lists the pickup locations. Pickup orders show "Pickup ·" and the location and get "Ready for pickup" (routes light/orders/readyforpickup: ships from the pickup location and sends the email mrx_orders_ready_for_pickup, with the location, opening hours and instructions from the layout handle mrx_orders_email_pickup) and "Picked up" (light/orders/pickedup, with "Also mark as paid" for an unpaid offline payment). Local pickup at the business address gets the same two steps. The order list filters on pickup orders.
  • Home to-dos: pickup orders waiting, stock left at the old location, and "Your checkout doesn't offer pickup at locations yet." while a location offers pickup that the checkout can't show (Mrx_SettingsHyva names Hyvä Checkout for it).
  • The pool checkouts_without_pickup (Mrx\Home\Model\Todo\Counter\PickupCheckoutMissing::checkoutsWithoutPickup, adminhtml): module names of storefront checkouts without a step to pick up at a location; while one of them is on and a location offers pickup, Home warns. Mrx_SettingsHyva adds Hyva_Checkout.
  • Three plugins on Magento: a new channel sells from the Light stock, the checkout offers a pickup location only when it holds every product in the cart, and without a Google key a typed postcode or city lists the pickup locations.

Changed

  • The several-locations guards are gone. With MSI and more than one enabled source, the product's Stock card, the variants, the Stock page and bulk "Adjust stock" edit stock per location instead of leaving it to the advanced view, and "Ship items" and "Mark as shipped" ship from a location instead of answering 422. Mrx\Catalog\Model\Inventory\StockLocations, BulkResult::MULTI_SOURCE, Mrx\Orders\Model\Service\MsiSourceMode and ShipItems::shipsFromSeveralLocations() went with them (Upgrading from 0.1.x to 0.2.0).
  • The products list's quantity on hand, its CSV export and the draft order's product picker sum what the enabled locations hold; "Sold out" and "Low stock" still follow what the channel can sell.
  • The picklist's storage column is headed "Bin": a location is now a place that holds stock.
  • "Location" and Magento's checkout label "Select Store" show as "Locatie" / "Standort" and "Kies een afhaallocatie" / "Abholort wählen" in Dutch and German: Mrx_Locations lists them in prefer_phrases, where the language packs said "Plaats" / "Ort" and "Selecteer winkel" / "Store wählen".
  • Every range moves to ^0.2: extra.mrx-light-api, the range guard in registration.php and the requires on mrx/module-*.

Settings

  • Business details' branding card names a store view's own logo, browser icon or email logo, set under Content > Design in the advanced view, with "Changed for Deutsch in the advanced view" and "Remove the Deutsch value" under that file, like any field with a path. Customers of that language saw the store view's file while the card showed the channel's.
  • mrx:light:doctor's store_override names where to remove the value: the settings page that shows the setting ("under the field on Settings > Checkout"), or, for a setting no Light page shows (a theme's setting, the compare link, the wishlist), only the advanced view (Stores > Configuration at that store view; Content > Design > Configuration for a logo). Before, every finding pointed to a settings page that most of these settings don't have. The message still starts with the path and the store view. Schema pages count with their fields; a module in app/code/Mrx declares the paths its own page notes in the pages argument of Mrx\Settings\Model\Config\ShownPaths (no @api type, so other modules' rows point to the advanced view).
  • Magento's own default VAT basis, the delivery address, no longer counts as "OSS chosen". While no row stores tax/calculation/based_on for a channel or for all channels and no rule charges a rate of another EU country, the shop is on home VAT for Light: Settings > Taxes selects the home choice, OssInterface::isUsed() is false and addRates() adds nothing. A stored delivery or billing address, or rates of other EU countries (Light's OSS rules or the merchant's own), still mean OSS. Saving the Taxes page while no all-channels row exists stores the basis All channels shows, also when it equals Magento's default: under All channels the chosen basis, in a channel the one All channels showed before. OSS chosen for one channel no longer moves All channels to OSS through the shared rates.

Dutch pack (mrx/module-country-nl)

  • The Dutch VAT card reads the basis the same way: on a shop that never chose a basis its checklist shows "Dutch VAT on every order" as missing, and "Check Dutch VAT setup" writes origin and adds no OSS rates.
  • The card's checklist names only what differs. While the settings match, one line names the prices and the VAT the button keeps (with OSS now "Prices and shipping include VAT, VAT of the customer's EU country"). While they don't, each setting that differs is a line of its own (data-mrx-tax-check="<name>"), so a shop whose prices are fine no longer reads "Prices and shipping include VAT: Missing". The separate "Not set yet" list is gone; its lines are the checklist's.

Shell and palette

  • The palette's "Go to" and "Actions" groups fold punctuation the way the Apps group and the Apps page do since 0.1.5, so "e-mail" finds Customer emails, Sender addresses and Emails & documents, as "email" does. The fold is Mrx\Light\Model\Search\Group\StaticMatcher::normalize() (lower case, diacritics stripped, hyphens and apostrophes joined, other punctuation a space); Mrx\Apps\Model\Search\AppMatcher::normalize() now calls it. Groups that search the database (orders, products, customers, pages, blocks and the rest) still match the text as typed.
  • Opening a Light page ends the advanced view also when Magento refuses the address's secret key (one copied earlier in the session, after a cache flush). Magento then sends the admin to the startup page before any controller runs, and that page opened as Magento's dashboard in the advanced view; it now opens Light Home. A before plugin on Magento\Framework\App\Request\CompositeValidator (Plugin\Request\LeaveAdvancedViewForLightPage) ends the advanced view for a light_* page request (GET, not a script call) before the key checks.

Hyvä storefront

  • No "Broken reference" lines for mrx.settings.legal_page.links and mrx.content.footer.menu any more. Both sat in Luma's footer_links, which Hyvä doesn't have. Mrx_ContentHyva and Mrx_SettingsHyva declare them again on hyva_default in Hyvä's footer-content, which moves them before Magento builds the page. The footer menu block is mrx.content.footer.menu on Hyvä too (it was mrx.content.footer.menu.hyva), and without Mrx_Content the legal page links now show in Hyvä's footer (mrx.settings.legal_page.links.hyva, template Mrx_SettingsHyva::footer/legal-links.phtml, through the new Mrx\Settings\Block\LegalPageLinks::getLinks()). Luma is unchanged (Hyvä storefront parts).

0.1.5 (beta)

A patch. Run setup:upgrade after composer update 'mrx/*' 'magerex/*' -w: a data patch takes from preset roles what their areas exclude. No @api type, pool key, route or token changed, so the API surface is the same as in 0.1.4. Two settings behaviours that modules build on changed, and preset roles lose what their areas exclude (Upgrading).

Settings

  • A channel keeps a value of its own also when it equals the all-channels value. ScopedConfigWriterInterface::save() for a website, and every channel-scoped settings page, no longer remove the channel's value when it matches all channels: a channel that switched guest checkout on while all channels had it on keeps it on after all channels switch it off. Only null removes the channel's own value, which is what "Use the all-channels value" posts (mrx_use_default[]). A value equal to what the channel reads now is still not written, so a field the merchant left alone keeps following all channels (Saving per scope). Values Light works out for a channel itself (its invoice address, its ship-from taken from its business address, a sender name that follows a rename) still follow all channels when they match it.
  • A save posted for a channel that no longer exists answers 422 "This channel no longer exists. Reload the page." on every page that saves through light/settings/save/page/<code>, the packs' pages and schema pages included, before the page's save() runs. Before, most pages read the removed channel as "All channels" and wrote the tab's values for every channel. It also applies on a shop that has one channel left.
  • Settings > Payments' "Keep at least one payment method on" looks at the channel being saved. In a channel, switching every manual method off is refused unless a payment provider is on for that channel; a provider on for all channels but off for the outlet no longer lets the outlet lose every way to pay, and one on for the outlet only no longer blocks it. Under all channels, every channel needs a provider on for it or a manual method it keeps on with its own value.
  • A value a store view has of its own for a channel setting, set in the advanced view, is no longer invisible. It wins on the storefront for that language, so a field with a path (Fields and core's pages) now shows "Changed for Deutsch in the advanced view" under it, with "Remove the Deutsch value": the save posts mrx_store_reset[]=<store id>:<path> and light/settings/save removes that store-view row after the page saved. In a channel the note lists that channel's languages, under All channels every language with the channel's name, and it shows on a shop with one channel too. Light still has no store-view level in its settings and never writes such a value. Texts Light keeps per language itself (titles, names, instructions, opening hours, the locale, email templates, legal page links, system pages, the documents footer) don't count, nor do the fields a schema page translates, nor a language's own web address. Removing one needs the ACL resource of that setting's section (for a logo Magento_Config::config_design), and a secret such as an API key is never removed this way (Store-view values from the advanced view).
  • Taxes' "Prices include tax" writes every tax display setting, so its note names a store view's own value of any of them (a German store view that shows prices without tax while the switch says they include it). One note per language; "Remove the Deutsch value" removes all of that language's rows. Such a button lists its values space-separated in data-mrx-store-reset.
  • mrx:light:doctor has a new rule, store_override: one warning per such store-view value, naming the path and the store view (code, id, language and channel). A warning, so the doctor still exits 0.
  • Switching OSS on (EuVat::ensureRates(), also run by the AddOssRates observer) unlinks the home country's whole-country rate from an OSS rule when a rule of the home already charges that class. A shop that had OSS on, moved from the Netherlands to Belgium and then added a Belgian rule charged Belgian customers 42%: the Belgian rule and the old OSS rate "BE 21%". The rate itself stays. An OSS rule whose last rate goes that way (the merchant's own rules already tax every other country) is removed instead of saved empty, which Magento refuses.
  • Checkout in a channel: "Use the all-channels value" on "Set a minimum order amount", while all channels has no minimum, also removes the channel's own sales/minimum_order/amount and sales/minimum_order/tax_including. The amount field hides with the switch, so it had no reset to press and the channel kept its amount rows.

Returns

  • Settings > Returns treats a new reason named like a reason the shop has (the reason's default label, ignoring case) as that reason. The same "Add reason" row posted twice before the page reloaded, by a double click or a retry, made a second reason with the code <code>_2; now the second post changes nothing. A merchant who adds a reason by the name of a switched-off one switches that one on again with the new position and labels.

Discounts

  • Saving a discount writes the titles of store 0 and of the languages the editor shows, and leaves every other store view's title alone. A title set in the advanced view for the default store view or for an inactive store view was emptied on every save in Light.

Home

  • The payments step of the setup guide no longer counts a token method as a way to pay. PayPal's billing agreement, which Magento ships switched on and which saving the PayPal section stores as on, only charges an agreement a customer made before, so a shop with it and "free" as its only active methods had the step marked done. Mrx\Settings\Model\Payments\OfferedMethodList, the method list Home's check reads when Settings is installed, now also leaves out what ProviderDetector::takesPayment() rejects: admin-only methods and saved cards (Magento_Vault) as well.

Users and roles

  • The first setup:upgrade on this release takes from the Staff and Shipping staff roles Light made what their areas exclude: "Clean balanced reservations" and "Mass Delete Reservations", which 0.1.4 took out of the Products area while a role made before kept them. The data patch RemoveExcludedFromPresetRoles runs once, touches only roles named like a preset that hold Mrx_Light::light and lack full access, and logs each role with what it took. Saving such a role in Light's role editor takes the excluded resources of the areas it keeps away as well; any other role keeps what it has outside its areas (Upgrading).

Rate table pack (mrx/module-shipping-matrixrate)

  • After "Rate table saved" the Advanced block shows "Numeric postcode ranges: Yes" and the warning that they are off goes, without a reload. A save always turns them on; the block kept saying "No" until the page was reloaded.

Dutch pack (mrx/module-country-nl)

  • The Dutch VAT card counts a price display of "Including and excluding tax" as including VAT, and its button keeps it. A shop that shows prices including VAT and the subtotal both ways on invoices (tax/sales_display/subtotal = 3) no longer sees "Prices and shipping include VAT" as missing, and "Check Dutch VAT setup" no longer sets that display back to including only.
  • The card names the VAT its button sets: "Dutch VAT on every order", or "VAT of the customer's EU country" when OSS stays. A fresh shop stores no VAT basis, so Magento's delivery address read as OSS and the card said "VAT of the customer's EU country", while the button, finding no Dutch rule for OSS to add rates to, charged Dutch VAT on every order. The OSS radio and the lines about countries without a rate still follow the shop's current mode.

Apps

  • Every tab of the Apps page says when it lists no app: "No apps in the menu", "No apps with screens", "No apps with only settings" and "No hidden apps", each with a line about what would show there. Before, the tab showed an empty panel under the toolbar. All keeps "All apps are hidden", and a search without hits still says "No apps found".
  • Search on the Apps page and in the palette folds punctuation: hyphens and apostrophes join and any other punctuation counts as a space, so "e-mail" finds "Email", "email" finds "E-mail templates" and "mage-os" finds what "mageos" finds. The page and the palette fold alike (Mrx\Apps\Model\Search\AppMatcher and the page's finder, with the shared cases in Test/Unit/_files/matcher-vectors.json). Because terms join too, a package such as vendor/module-mailer also holds "email"; such an app ranks last, found by its terms only.

Logs

  • In developer mode the sign-in page and every two-factor screen no longer log about 27 INFO "Broken reference" lines per render. The default handle fills containers the admin-login page layout lacks (header, page.menu, main.top, before.body.end and the rest); Light's admin_login.xml now declares them inside a block nothing renders, so their blocks stay off the page as before and nothing else on the sign-in card changes.
  • Light pages no longer log "Broken reference: the 'notification.messages' tries to reorder itself towards 'user'" on Magento 2.4.9, whose module order schedules Magento_AdminNotification's toolbar before the user menu. mrx_base.xml removes that toolbar, and now also moves it without ordering it, which replaces the backend theme's "after user" move.

0.1.4 (beta)

A patch for the modules real shops run. No @api type, pool key, route or token changed, so the API surface is the same as in 0.1.3. Light now works with Multi-Source Inventory on with one source, PayPal, fixed product tax (Weee), gift messages, Meta robots tag and the PCI DSS 4 rules; Apps finds every app at scale; the rate table pack (mrx/module-shipping-matrixrate) is new and optional. Run setup:upgrade after composer update (Upgrading from 0.1.3 to 0.1.4).

Shell

  • Simple mode hides Magento's header container (<header class="page-header">). Light removes its logo, search and user menu there, so it rendered nothing, until a module put a block in it: Magento_AdminAnalytics' tracking scripts made an empty 40px band above every page. Scripts in that container still run. A module that wants something visible in simple mode's top bar uses mrx.topbar.user.menu, as before; advanced mode keeps the stock header.
  • In simple mode the critical-message banner leaves Magento's "One or more of the Cache Types are invalidated" message to the top bar's cache menu, which already says the cache is outdated and refreshes it. The bell still lists the message and counts it, and advanced mode, which has no cache menu, still shows it in the banner.

Apps

  • The app_vendors pool also takes a module name (Acme_Rates), which wins over its vendor prefix, and an empty name for a module shows no maker for that app: no line under its name, no "App · Acme" in the palette, and Group by Developer puts it under "Other developers". A pack that presents another vendor's module as part of the shop uses it; the rate table pack does, so the Apps page no longer shows "WebShopApps" under "Rate table" (What detection does). No pool, key or @api type changed.
  • The Apps page looks like Light's other lists: full width, one card with the tabs, a Group by select ("Group by category", "Group by developer", "No groups (A–Z)") and the search on top, then the groups as subdued header strips with their rows. The segmented Group by buttons and the separate toolbar card are gone; on a phone the search and the select each take a line under the tabs. Settings > Apps puts its search in the same toolbar. In an open row the screen and setting pins line up with the row's pin.
  • The search field on Apps no longer takes focus when the page opens, like the search on Orders or Products. Focused on load, the theme's focus colour around it read as an error. ?q= and #app-<key> work as before.
  • Settings > Apps leads back to where the merchant came from. Opened with "View app settings" on the Apps page, its back arrow returns to Apps with the search, tab and grouping it had; opened from Settings or directly, to Settings, as before. The Apps page keeps its tab in the address next to the search (?tab=) and takes a grouping for one visit (?group=); values it doesn't offer are dropped, and the way back is always built from the Apps route, never from a URL in the request (Links). No route, pool or @api type changed.
  • Settings > Apps uses Light's list search as Orders and Apps have it, at the same width and with the same focus ring, instead of a full-width field of its own; it takes no focus on load. A search without hits on Apps or Settings > Apps shows only its empty state: "0 apps found" stays for screen readers. On a phone a row's developer line no longer ends in a lone "·" when its counts wrap to the next line.
  • Two requests that find no cached app scan no longer both scan: the second waits (at most 5 seconds) for the first and reads its result. A cold scan of 100 modules took about 376 ms.
  • The Apps page lists one compact row per app instead of a card, with a search field, the tabs All, In menu, With screens, Settings only and Hidden with their counts, and Group by Category, Developer or A–Z, kept per admin. A row opens in place on what the card showed; with 4 apps or fewer every row starts open. The hidden apps moved from the list under the page to the Hidden tab (An app, its category and pins).
  • The search on the page and the palette follow the same rules: every word must match, case and accents don't count, and it looks at the name, developer, module, package, composer keywords, description, screen and setting titles in the admin's language and in English, the category and job words such as cart, payment and order. A row found through one of its screens or settings opens on just those. Settings > Apps has the same search.
  • The palette gives an app its own result ("App · Acme") that opens the app's row, keeps the matching screens and settings in view, finds settings with "settings" or "configure" and the app's name, lists hidden apps last, and ends with "Show all N results in Apps" when more matched than it shows.
  • light/apps/index?q=<words> opens the page with a search, and #app-<key> opens and focuses one app's row. Back to Apps in the advanced-view banner opens the app's row, also after a click on that row; from another Light page it leads back to that page.
  • Detection names apps after their settings or menu instead of a generic label ("Information", "Configuration"), reads the developer from the app_vendors pool, the config tab or the vendor prefix, and places more apps in a real category through whole words of the module, its sections and its composer keywords. Composer descriptions are shortened to one line. A vendor's *_Core, *_Base or *_Community module with only settings becomes one "Acme (general settings)" row, and a module that only adds groups to another app's section folds into that app (What detection does). A descriptor still wins over every rule.
  • A module that only adds to a stock page gets a row that says where ("Adds to the order page in the advanced view").
  • A pin whose name another visible app shares reads with its developer: "Blog (Acme)".
  • The palette destination Apps (apps_keywords) also answers to "plugins", "add-ons", "integrations" and "apps", and to the Dutch "uitbreidingen", "extensies" and "koppelingen".
  • Settings that a module's system.xml pulls in with <include> are found; before, their app had no settings or was missing.
  • Vendor upsell and info entries (a marketplace, user guides, "More Acme Extensions", support and feature-request links) and menu links to the stock configuration are no longer listed as screens or settings.
  • A menu screen that its own config switches off no longer names the app.
  • Two settings lines that open the same page show once, and a pin on the dropped one stays visible on the kept one.
  • A config section shown only at website or store level opens at that scope from its row instead of on an empty default page.
  • The advanced-view banner prefers a back-link provider's link into the page the admin came from (the same Light route, controller and action) over the bare page, so it lands on the spot the provider names.
  • A screen that two menu items of one app open shows once in the palette, and "Show all N results in Apps" counts it once.
  • A vendor menu item that opens a stock screen simple mode replaces (Magefan Blog's link to the widget grid, which went to Pages) opens that screen in the advanced view, as its row says.
  • Under 768 px the Group by buttons give way to the select; they showed next to it.
  • A closed app row builds its panel and its ⋯ menu when it opens, so 100 apps stay under 4,000 DOM nodes (they were near 7,600). Without JavaScript the rows still open.

Sign-in

  • Signing in with an Adobe ID (Magento_AdminAdobeIms, Magento only) locked Light: Magento_AdminAdobeImsTwoFactorAuth skips the second factor without granting the two-factor session, so every Light page went to the two-factor screen. Light now counts an Adobe ID sign-in as complete, exactly when Magento skips its own check (Magento_AdminAdobeImsTwoFactorAuth on and adobe_ims/integration/admin_enabled on). The doctor notes it with the new rule tfa_adobe_ims, listed with the findings it doesn't count (Troubleshooting).
  • Under the PCI DSS 4 rules (Aligent_Pci4Compatibility on, as Mage-OS ships it) Settings > Users offers 5, 10 or 15 minutes and refuses a longer session or more than 10 failed attempts on save; a longer session or a higher lockout count stored before shows as 15 minutes or 10 attempts with a note, so the next save fixes it. Magento's own admin/security/minimum_password_length alone doesn't switch these rules on; Your account still honours it. Aligent's module checks those limits only in the stock config form, so a shop could leave PCI DSS 4 through Light before. Your account refuses a new password shorter than 12 characters with Light's own message and says so beside the field. Light pages, which leave out Aligent's warning modal, warn a minute before an idle session ends and offer "Stay signed in". Nothing changes for your module: a request through Mrx_Light/js/api or jQuery counts as activity.

Notifications

  • With Magento_AsynchronousOperations on, the bell also lists the admin's bulk tasks that the stock bar shows, such as "Update attributes" from the product grid. A task stays until it is dismissed, which the bell can't do yet. The bell reads the system messages the way the stock bar does, through toArray() of Magento_AdminNotification's Synchronized collection, where that module adds them.

Stock (Multi-Source Inventory)

  • With Multi-Source Inventory on, the Stock page has a read-only column salable ("Available to sell") after Quantity, the products list's stock badge reads "5 in stock, 3 available", and every product and variant in the create-order search (light/orders/productsearch) has stock.salable. It is MSI's salable quantity on the stock of the list's channel (the default channel when the list shows all): on hand plus the open orders' reservations, minus the out-of-stock threshold. On a shop with one source a disabled product shows 0, as in the advanced view. Without MSI the column, the badge text and the field are absent, as before. A table extension on the inventory list that adds a column after Quantity now lands after Available to sell.
  • With Multi-Source Inventory on, "Sold out" and "Low stock" go by what is available to sell on that stock: the sold_out and low_stock tabs of the products and inventory lists, the products list's stock badge ("Sold out, 5 in stock" when everything on hand is held by open orders) and Home's products_sold_out and products_low_stock to-dos. Those two to-dos now count the products list's tabs through its provider (getRows() with page size 1), the way the orders to-dos count per channel, so a table extension that filters a products tab changes the to-do count with it. A Draft or a switched-off variant, which Magento's stock index leaves out on the default stock, goes by its stock item in those tabs, as without MSI. Without MSI nothing changes.
  • With Multi-Source Inventory on, Light leaves a quantity alone when its stock is kept at several locations: a product stocked at a source other than the default one, or any product once the shop has a second enabled source (not in MSI's single-source mode). The product editor shows its Stock card read-only with a link to the advanced view, and a variant's quantity reads "Several locations"; saving leaves the stock item and every source as they were. The Stock page and bulk "Adjust stock" refuse such products with the existing "kept at several locations" message, now also every product of a shop with several sources. Duplicate keeps a simple product's locations (MSI copies them) and gives a copied variant kept at several locations quantity 0. In the create-order search such a product has stock.locations: true, and its stock.inStock follows what the channel's stock can sell instead of its stock item.
  • With Multi-Source Inventory on and more than one enabled source, Ship (light/orders/ship) and the orders list's Mark as shipped (light/orders/massShip) ship nothing and answer 422 with a message that sends the merchant to the advanced view, where the location is chosen; the Ship dialog links there. With one source, a shipment MSI refuses names the items short of stock, or else says in plain words that their stock couldn't be taken from the shipping location, instead of Magento's "see error log". Without MSI nothing changes.

Orders

  • Fixed product tax (Magento_Weee), such as a disposal fee: the order page and the create-order summary showed the fee inside the subtotal, so the subtotal no longer matched the lines. The fee now has a row of its own right after the subtotal, code fpt, labelled with the fee's own name. Orders registers it in the order_totals pool under the key fpt while Magento_Weee is on, so a totals provider of yours should take another code.
  • A refund of an order with a fixed product tax gives the fee back: the refund dialog on the order page and on a return counts it in each unit's price, and a full refund no longer leaves the fee on the credit memo as an adjustment. A refund with an amount also no longer comes out short: working out the adjustment made Magento book the fee as refunded before the real credit memo was made.
  • Gift messages (Magento_GiftMessage): the order page's Notes card and the packing slip show the gift message of the order and of each item; a parcel's slip shows only the messages of its own items. The packing slip's document data gains the key gift_messages (a list of item_id, item, sender, recipient, message, title) and the labels gift_from and gift_to, which a document_sections entry for packing_slip can read. mrx/module-documents no longer requires magento/module-gift-message, it suggests it: the order email's item template reads the message without that module's classes, so Light installs and mails on a shop without it.

Products

  • mrx/module-catalog no longer requires magento/module-bundle and magento/module-grouped-product, it suggests them: Catalog only checks a product's type against their classes, so Light installs and compiles on a shop without bundles or product sets. A module of yours that uses their classes now requires them itself instead of getting them through mrx/module-catalog.

Content

  • With MageOS_MetaRobotsTag on (Mage-OS ships it enabled), the page editor's Visibility card offers "Hide from search engines": it sets the page's noindex and nofollow flags, and clearing it removes both. A noarchive or a nofollow set in the advanced editor stays as it is. Duplicating a page, or copying the home page for a channel, now keeps all three meta robots flags; before, the copy was listed by search engines again. mrx/module-content suggests mage-os/module-meta-robots-tag and installs without it.

Users and roles

  • The Products area no longer brings "Clean balanced reservations" and "Mass Delete Reservations" (Mage-OS's reservations grid): they change what every product can sell, so they stay with the owner, like sign-in security in the Settings area. Viewing reservations still comes with Products. The area's exclude list in Mrx\Settings\Model\Users\RoleAreas names MageOS_InventoryReservationsGrid::clean; a role that already holds them keeps them until the owner removes them. No pool key or @api type changed.

Rate table pack (mrx/module-shipping-matrixrate)

  • mrx/module-shipping-matrixrate (Mrx_ShippingMatrixrate), new and optional: rate tables by destination, weight, order amount or number of items, on top of webshopapps/module-matrixrate 20.5. Settings > Shipping and pickup asks "How do you charge shipping?" per channel, Shipping zones or Rate table, through the new rates_from path above; zones and rates both stay stored through every switch. The Rate table page (light/ratetable/index) edits the table with steps, postcodes, "Free from" per method and labels per language, imports your zones, and imports and exports CSV in MatrixRate's format, all or nothing. At checkout, postcodes with letters ("8881 AB") find their numeric range, method codes follow the method name so a coupon that moves the order into another step keeps the chosen method, "Order amount" is the zones' amount (incl. VAT, after discount), and MatrixRate's debug log stays quiet. It is in neither metapackage: magerex/distribution-nl suggests it (Install, rate tables, Settings pages). No @api type, pool, route or token in core changed for it.
  • A CSV import turns on numeric postcode ranges for the channel, as a save on the Rate table page does. Before, imported ranges ("1000,1999") matched nothing at checkout until the page was saved once. It also drops "Free from" amounts and labels of methods the import removed.
  • Because the import turns numeric postcode ranges on, it now stores postcodes the way the Rate table page does: an exact number without a "to" ("8881") becomes a range of one (8881 to 8881), which matches at checkout, and what MatrixRate then never matches is refused with its line: a prefix of digits ("88%", or "%" alone; "Line 4: use a range like 1000-1999 in the two postcode columns instead of "88%"."), digits with letters ("1012AB", "1012 AB" or "1012-AB", which the checkout reduces to 1012) and a prefix of digits with letters ("1012A%"). Like the checkout, it drops spaces and hyphens and upper-cases letters first, so "sw1a 1aa" is stored as "SW1A1AA". Postcodes and prefixes that start with letters ("SW1%") are kept.
  • The first switch to the rate table gives it the zones' title at checkout, at the scope switched and at each channel and store view with a zones title of its own, while the rate table still shows the pack's default "Shipping". A Dutch checkout keeps "Verzending" (and an English store view "Shipping"); a title you gave the rate table stays.
  • The table reads text that differs only in accents ("Express", "Exprèss") as the same, as it already did for case. The editor and the CSV import now refuse two such method names for one destination and step with a message that names both spellings, and cities or postcodes that differ only in accents as overlapping steps or a repeated line. Before, the save failed with "Something went wrong".

Settings

  • Payments: a new IBAN after a cleared one replaces the IBAN the bank transfer instructions still name. Clearing the IBAN keeps the text (customers keep the last account until you enter a new one); the next IBAN had no old one to swap, so the text kept naming the old account and the save was refused because the text didn't name the new one. Light now takes the first valid IBAN the text names as the one to replace, and writes its own generated text again with the new IBAN and holder. No pool, config path or @api type changed.
  • Taxes: a save without prices_include_tax leaves the price display alone, as a save without no_tax already left "I don't charge tax" alone. Before, a request without the key (a crafted POST, or a form whose switch wasn't rendered) set prices to excluding tax and changed what every stored price means. The page's own form always posts the switch.
  • Business details: changing the currency keeps the other display currencies. currency/options/allow becomes the new base plus the previous list without the old base, so a shop that also shows pounds keeps them, and changing back gives the list it started with. Before, the list was replaced by the new currency alone.
  • Legal pages: "Create from template" pressed again renders the linked page again in place while it is still the untouched draft (unpublished, content equal to the freshly rendered template): same page and URL key, toast "Page updated". Before, every press made a new page with a "-2" URL key and left the draft unlinked. A page you published or edited stays as it is, unlinked, and a new draft takes the link, as before.
  • Legal pages: setting a language's page back to "None" removes that language's store view link, so its customers get the default language's page again where that page is published. Before, a stored 0 hid the default page for that language.
  • Channels: a menu (root category) a channel leaves on its page is removed when it is empty and no channel uses it, as removing a channel already did. Before, "new menu -> shared menu -> new menu" left an empty root category behind each time.
  • Shipping: switching "Same as my business address" back on for all channels gives every channel its own business address as ship-from again; a channel whose business address is the all-channels one loses its ship-from rows. Before, a channel that saved a ship-from address of its own while the switch was off kept shipping (and, with VAT by ship-from, charging VAT) from that address, while the page said it shipped from the business address. No pool, config path or @api type changed.
  • Payments with Magento_Paypal on: a method that only charges a saved card (Magento_Vault) or a PayPal billing agreement is no longer a way to pay. Magento ships PayPal's billing agreement switched on without a PayPal account, so Settings > Payments listed PayPal as active ("1 of 8 methods on"), never said "Keep at least one payment method on", the orders list offered "Capture payments", and the Mollie and Pay. guards let the merchant disconnect the last way to pay. PaymentMethodsInterface::hasOtherCheckoutMethod() now leaves these methods out; its signature is the same. A provider under "Other online payments" no longer lists them and shows "Not set up" instead of "Inactive" until a method customers can pay with is on.
  • New config path mrx_settings/shipping/rates_from (default zones, all channels or per website): a pack whose carrier charges shipping its own way writes its carrier code there. While that carrier is on and installed, Settings > Shipping and pickup folds the zones to a note, keeps them stored, and a save for all channels no longer switches the flat rate back on. Without such a pack nothing changes (Settings pages).

Dutch pack (mrx/module-country-nl)

  • The Dutch VAT button keeps a price display the merchant chose. When tax/calculation/price_includes_tax has a stored all-channels row of 0 (Light's "Prices include VAT" switch writes one), the button writes the price display for prices excluding VAT, its answer adds "Prices stay excluding VAT, as you set them. Consumer shops in the Netherlands show prices including VAT.", and the card names the choice instead of "Prices and shipping include VAT". A shop without that row still gets prices including VAT. Before, the button switched prices to including VAT, and after the merchant switched back the card reported the prices as not set up and every press changed them again.
  • The Dutch VAT button never replaces a ship-from address of the merchant's own (Shipping's "Same as my business address" saved off). A Dutch one keeps its address, as before. With VAT of the customer's EU country (OSS) the button leaves the address alone, because that VAT doesn't read it. With Dutch VAT on all orders, VAT reads the ship-from country, so a ship-from address outside the Netherlands refuses the press with a 422 and writes no rate, class, rule or setting: "Your ship-from address is in Germany. Dutch VAT on all orders charges VAT from a Dutch ship-from address: change it on Shipping first." Before, the button emptied the address and set the country to the Netherlands. A shop that never saved the switch (a fresh install with Magento's US default) gets the business address, as before.

Mollie pack (mrx/module-payments-mollie)

  • Disconnect leaves nothing that can offer Mollie. It removes both API keys at every scope (before: the all-channels value only), deletes the channel and store view rows of payment/mollie_general/enabled and of every Mollie method's active, and writes 0 for each at all channels, because Mollie's config.xml has every method on. A channel that switched Mollie on for itself no longer keeps it after a disconnect; after a reconnect the methods stay off until you switch them on. ConnectionWriter gets disconnect() and the constructor arguments configWriter, resource and methodCodes; Disconnect gets channelProvider. Both are internal classes.
  • Disconnect is refused while any channel, not only all channels, offers Mollie and has no other way to pay, as Pay.'s Disconnect already did.
  • A Mollie save for a channel that no longer exists (a tab left open on a removed channel) is refused with "This channel no longer exists. Reload the page.", as Taxes refuses it. Before, it was saved for all channels.

Pay. pack (mrx/module-payments-paynl)

  • A Pay. save for a channel that no longer exists is refused with "This channel no longer exists. Reload the page.". Before, it switched the methods for all channels.

0.1.3 (beta)

A security patch. No @api type, pool, route or token changed, so the API surface is the same as in 0.1.2. Run setup:upgrade after composer update: it repairs the preset roles (Upgrading from 0.1.2 to 0.1.3).

Security: preset roles

  • A Staff or Shipping staff role that Light made when someone first got it through Settings > Users > Add user held every resource of the admin, not only its areas: users and roles, all configuration, backups, integrations, import and export, Discounts and Analytics. A Staff user could add users and make themselves Owner. Since 0.1.0, RolePresets expanded every parent in the preset's list (Magento_Backend::admin among them) with everything below it. A preset role now gets exactly its areas and the resources every role gets, each with what is below it, plus their parents, which no longer bring what is below them. Roles made or changed in Light's role editor were not affected.
  • The first setup:upgrade on 0.1.3 resets a role that carries the leak to exactly what its preset gives, and logs each role it reset with what it removed. A role carries the leak when it is named Staff or Shipping staff, holds Mrx_Light::light and holds both user and role management (Magento_User::acl_users, Magento_User::acl_roles), which no Light area gives; a role without Mrx_Light::light is never reset, whatever its name. The 0.1.0 to 0.1.2 role completion gave Mrx_Light::light to any role named Staff or Shipping staff, so on a shop that ran those versions a role of the merchant's own with that name and with user and role management on purpose is reset too. Resources a merchant gave a leaked role on purpose in Magento's own role editor go too, as do areas added to it in Light's role editor: check the logged roles after the update. It runs once, as the data patch RepairPresetRoles, so a role you extend afterwards keeps what you give it (Users and permissions).
  • Light's role completion (setup:upgrade, every time) treated any role named Staff or Shipping staff as its preset, also a role Light never made: it gave such a role every screen of the areas it touched and the resources every Light role gets. A stock "Shipping staff" role that could only view orders gained invoicing, shipping, refunds, cancelling and order editing. PresetRoleCompleter now completes a role with a preset's name only when it holds Mrx_Light::light; any other role keeps exactly what it has. What 0.1.0 to 0.1.2 already gave such a role stays (Upgrading from 0.1.2 to 0.1.3).

Sign-in

  • With Magento_TwoFactorAuth on, a script's light/* request while the second factor is open now gets the 401 {error: true, ajaxExpired: 1, ajaxRedirect, message} that 0.1.2 promised. In 0.1.2 the two-factor module answered it first with a redirect to its screen, before Light's controller plugin ran; no data left the shop either way. Light now answers it from an after plugin on Magento's request validator (CompositeValidator), which runs before that redirect and only once Magento's own CSRF, form-key, secret-key and HTTP-method checks passed, so a request without a valid key gets Magento's answer. Pages still go to the two-factor screen. A module that adds its own admin request validator keeps working: Light doesn't redefine the validator list.

Dutch pack (mrx/module-country-nl)

  • The Dutch VAT card takes the retail customer class from the customer groups: the class of NOT LOGGED IN, else of the default group (General), as checkout charges them. "Default Tax Class for Customer" counts only when no group's class exists. A shop whose setting still named a deleted class (its groups and rules on a class of their own) showed "Not set up", and its button would have added a second set of rates and classes. TaxRecords::customerClassId() reads the groups for the Taxes page, Home's tax step and the EU VAT card too.

0.1.2 (beta)

A patch. No @api type, pool, route or token changed, so the API surface is the same as in 0.1.1. Four new database indexes come with it: run setup:upgrade after composer update (Install, updating).

Sign-in and two-factor

  • Light shows nothing of the admin before a complete sign-in. Magento signs the admin in at the password step, before the second factor, and a two-factor screen drew the whole shell around the code form: the navigation and its counts, the store name and the admin's name and email in #mrx-config. The shell, #mrx-config, the counts, the bell and the store name in the tab title now wait until Magento_TwoFactorAuth grants the session (where that module is on), and the two-factor screens show the Light sign-in card.
  • Every light/* request, of every Light module, refuses a session that isn't fully signed in: a script call gets a 401 {error: true, ajaxExpired: 1, ajaxRedirect, message} without data, a page goes to the two-factor screen (or the start page when the session ended). A Light endpoint never answers before the second factor, so there is nothing to change in your controllers.
  • Light reads and writes nothing of the admin before that point either: Mrx_Themes leaves the admin's own theme and the theme cookie alone, and Mrx_Apps the admin's pins, until the sign-in is complete (How Light fits together).
  • Mrx_Light/js/api: a request that Magento answered with the sign-in page reloads the page instead of rejecting with "Something went wrong", so the sign-in page never lands in a modal or a list.
  • The sign-in page and the two-factor screens print no platform footer line: neither Magento's copyright nor Mage-OS's "Thank you for choosing Mage-OS". The card shows the active brand; a brand package that wants a footer line adds a block to login.footer in its own admin_login.xml.
  • Staff could not sign in on a shop with two-factor on. Every role Light makes (the Staff and Shipping staff presets and roles of the role editor) now gets Magento_TwoFactorAuth::tfa, the user's own second factor, as part of what every role gets; without it Magento showed "Sorry, you need permissions to view this content" after the password. setup:upgrade gives existing roles the second factor: the presets with the rest of what every role gets, other roles that hold Mrx_Light::light without full access (also one made in Magento's own role editor with Light ticked) only the second factor, so a resource a merchant removed in the advanced view stays removed. Roles without Mrx_Light::light stay as they are (Users and permissions).
  • A module that switches the second factor off without granting the two-factor session (MarkShust_DisableTwoFactorAuth, WolfSellers_EnableDisableTfa) keeps every Light page on the two-factor screen while stock pages open. The doctor warns about it with the new rule tfa_bypass (a warning, so it still exits 0), and Troubleshooting has the fix.

Notifications

  • Magento_AdminNotification's system messages and unread notifications moved into a notification bell: in the top bar in simple mode, beside the user menu in advanced mode. Mark as read and "See all notifications" use the module's own actions; a critical item also shows one compact banner. The yellow "System Messages" bar, its pop-ups and the stock toolbar are gone from Light pages in both modes. Without Magento_AdminNotification there is no bell.
  • In simple mode the messages a stock screen shows after a redirect and the global notices use the Light banner look, follow the theme (dark included) and fit a phone. No token was added; the bell's classes (mrx-notifications*) are tier 2.

Home's setup guide

The install tests on Mage-OS 3.5, Magento 2.4.9 and a copy of a live Hyvä shop found steps that never completed, or that disagreed with the settings page they link to. Home and the settings pages now read the same checks.

  • Shipping: the step counts a carrier that someone saved as on, or a channel's own flat-rate zones with at least one rate (carriers/flatrate/zones). Settings > Shipping and pickup stores flat rate as on with every save for all channels, so a merchant who adds a zone and a rate completes the step. Magento's untouched flat rate and the zones a country pack seeds during setup:upgrade still don't count. HasShippingMethod takes the rate paths per carrier as the constructor argument ratePaths.
  • Payments: Mrx_Settings gives the step's check the method list that Settings > Payments shows, so a method of a provider that isn't connected no longer counts. Mrx_Settings sets the constructor argument paymentMethodList of Mrx\Home\Model\Setup\Check\HasPaymentMethod, not the step's check key, because Mrx_Home loads after it.
  • Taxes: mrx/module-country-nl replaces the step's check with Mrx\CountryNl\Model\Setup\HasDutchVat, the status of its Dutch VAT card, so the step and Settings > Taxes agree. A country pack of yours can do the same through the setup_steps pool (Home to-dos, setup steps and widgets).
  • Business details: while a sender address is still an example such as owner@example.com, the step's description (a description_provider) names that sender and where to fix it, and the Business details page shows a note beside it.

Settings

  • A settings save refreshes the stored config instead of cleaning the whole config cache, and Light's own pages clean the page cache only when the change shows on the storefront; a payment, shipping or checkout save no longer empties Varnish. A page that returns config from getCacheTypes() still gets the whole config cache cleaned; a page that relied on the old promise ("besides config") and caches config-derived data under that type now returns config itself (Settings pages).
  • When a save fails on one field, the message by the save bar names that field ("Postal code: Enter a Dutch postcode like 1013 AB."), so the merchant knows what to fix while the page is still scrolling to it. Your settings page gets this from the field's label (or its aria-label), so give every field one.

Dutch pack (mrx/module-country-nl)

  • The Dutch VAT card recognises a shop's own Dutch VAT by its structure, under any names: a 21% and a 9% Dutch rate charged to retail customers through tax rules. It shows "Set up" and names those rates. Its button then changes nothing it didn't create: no rate, rule or product class of the shop's own, no Magento sample rate, no default product class that a Dutch rule already charges (a food shop keeps its 9% default), and it adds no 0% tier. Before, it looked only for its own names, showed an existing setup as missing and merged its rates into same-named rules.
  • The card names each tax setting that differs instead of one "Prices and shipping include VAT" line.
  • "Use Dutch VAT" fills the ship-from address from the business address while it isn't a Dutch address yet. Before, it set only the country, so Magento's US postcode 90034 stayed next to NL and the next save of the shipping page failed on it.
  • On a first install with setup:upgrade --keep-generated, the data patch SetUpShippingZones used to fail on Mrx_Settings' ZonesWriterInterface, after which Magento left every new module disabled. It now leaves the zones for the next setup:upgrade (Install).

Apps

  • A menu.xml action written with the front name (admin/email_template/index) opens from its app card, its pin, the palette and the advanced-view banner. Apps maps the front name to the route id the way the stock menu does; before, the URL got a secret key for the wrong route and Magento sent the admin to Home. Write your actions with the route id anyway (An app, its category and pins).
  • Menu items without a controller are no longer listed. The scan version is part of the cache key, so a shop rescans its apps after the update.
  • Every screen and settings line on an app card can be pinned on its own, with the pin beside it. Pins stay per admin, follow the admin's role and disappear with their module.

Brand and themes

  • MageRex_Branding ships the real MageRex logo and icon, cropped from the brand files. Its file paths are unchanged.
  • An own logo always wins: once the owner uploads any logo or icon on Settings > Appearance > Your own theme, BrandInterface::get() returns the owner's files only, and a slot without an upload shows the store name instead of the brand's file (A brand package).
  • The phone menu shows the lockup drawn for its colour: login.lockup while the menu is light, logo.lockup_on_dark (or login.lockup_on_dark) while it is dark, and the mark with the name when there is none for that colour. --mrx-nav-mark-on-light and --mrx-nav-mark-on-dark now pick the lockup too (Admin themes).
  • The top bar shows a lockup 34 pixels high and never stretches it; the SVG's viewBox sets the width.
  • An own-theme upload over 1 MB is refused the moment it is chosen, with a message beside the slot; the server keeps the same check on save.

Lists and speed

  • Lists, the nav counters and the palette search release the admin session lock before calling providers, so a page's requests run in parallel instead of one after another. A provider, extension or search group must not write to the session.
  • The IndexTable block takes first_rows. With it on, the page computes the first page of rows itself and the list draws it without a light/table/data request; Light's orders, products and customers lists have it on. Other lists, yours included, still load their rows in a request of their own. A list with first rows fires mrx:table:loaded in the microtask after it drew them: a script that attaches later checks loadedOnce (Light lists).
  • Orders: the list's "Test payment" column no longer counts the open test payments on every list request. The count is cached for up to an hour and dropped after an order save that changes it, so a payment module saves the order after it stores PaymentMode::INFO_KEY, as before (Order and customer pages).
  • The loader shows its skeleton after 200 ms instead of 150 ms and removes it as soon as the work is done instead of keeping it 300 ms; list search waits 150 ms after the last key instead of 300 ms, the palette 100 ms instead of 200 ms.
  • New indexes, added by setup:upgrade: Mrx_Orders on sales_order (state, grand total, total paid, store) for the Not paid count; Mrx_Customers on customer_entity.created_at for the customers list, which now reads its page before it sums; Mrx_Home on sales_order (created at, state, store) and a covering index on sales_order_item for the top products on Home and Analytics. On a large shop the first setup:upgrade takes longer while MySQL builds them.
  • The inventory search skips a count it didn't need.

Doctor, logs and install

  • mrx:light:doctor judges Light modules and modules that extend or name Light. Findings about other modules, such as an agency's own mail templates, are one uncounted summary line and never make it exit 1; --all lists them (Doctor rules).
  • Log and console text of Light core says "Light", not "MageRex": AI, Apps and Catalog lines start with "Light AI:", "Light Apps:" and "Light Catalog:", and the compatibility gate logs Light hides a module: %s. Run bin/magento mrx:light:doctor. A list that fails to load logs Light table "<provider>" failed:. A log filter of yours that matched the old text needs the new one (Compatibility gate messages).
  • A walk through 40 Light pages logs one "Broken reference" line instead of several hundred: the UI kit's fixture cards load only on their own editor, and the advanced-mode bell no longer names the user block.
  • Returns: the return dialogs say when return mails are off, since MageOS RMA sends none while "Accept return requests" is off, and link to Settings > Returns for roles that may open it.
  • Documents: Settings > Emails & documents says that Light's PDFs replace Magento's, and names the enabled modules that plug Magento's PDF classes, whose changes don't show while Light makes the PDFs.
  • Mrx_SettingsHyva: the welcome bar above a Hyvä header stays hidden while the text is empty or Magento's default "Default welcome msg!".

0.1.1 (beta)

Returns is optional. No @api type, pool, route or token changed, so the API surface is the same as in 0.1.0.

  • magerex/distribution-nl no longer requires mrx/module-returns; it suggests it. A shop gets Orders > Returns and mage-os/module-rma only when it installs mrx/module-returns itself (Upgrading).
  • magerex/light-demo-data no longer requires mrx/module-returns either. Its examples seeder adds the example return only when Mrx_Returns is enabled.
  • Light core runs without Mrx_Returns and MageOS_RMA: the Returns nav item, its settings card, its order actions and its role area are simply absent. Mrx_Returns stays a core module of Surface::CORE_MODULES and ships with the same VERSION.

0.1.0 (beta, 2026-10-03)

The first published Light API, released as a public beta for a testing phase; 1.0.0 follows after that testing. During 0.x a minor release may break and a patch release never does (Versioning). Everything below is tier 1 unless it says otherwise (Versioning). Extension targets and API reference list every pool, container, handle, form and @api type with the version that added it.

Contract and tooling

  • Mrx\Light\Api\ExtensionApi with VERSION and satisfies(), @api markers on every public type, and the surface snapshot with its version rules.
  • The snapshot records only Light's own members: a method that overrides or implements a Magento one (execute(), _toHtml(), _isAllowed(), dispatch()) and a property a Magento parent declares ($_template) stay Magento's contract, so a Mage-OS signature change never forces a Light release.
  • The catalogue of pools, containers, handles, forms, JS modules and events, and the doctor bin/magento mrx:light:doctor with --module, --format=text|json|md, --catalogue and --api.
  • The compatibility gate: extra.mrx-light-api, the range guard in registration.php, the developer-mode banner, the log line and the setup:upgrade report (Versioning).
  • bin/magento mrx:light:app scaffolds an app, a pattern-A bridge (--for) or a pattern-B module (--pattern=builtin).
  • Mrx\Light\Model\Redirect\Condition\HasParam is @api: a redirect whose condition needs a request parameter, as the scaffold's record redirect of a bridge list does.
  • Doctor rules: unknown_key, wrong_area, two_areas, light_collision, require_range, unknown_resource, unknown_container, unknown_reference, private_api, deprecated_use, api_constraint, api_undeclared, api_range_form, mixed_layout, hyva_in_core, nav_sort, nav_shortcut, missing_module, override_order, override_gate, unknown_icon, app_category, schema_field, validate_class, acl_unreachable, pattern_b, inline_script, parse_error and rule_failed (Troubleshooting).
  • A null item removes an item another module declares, in every pool.

Modes

  • Mrx\Light\Api\ModeInterface and Mrx\Light\Api\AdvancedUrlInterface; #mrx-config keys stockView and apiVersion.
  • The AdvancedHint block ("Only available in the advanced view").
  • The advanced-view banner has a title, a line of explanation and a "Back to …" button. A back-link provider's text replaces the explanation line; the title stays.
  • The advanced key on nav and palette items.
  • Header actions on any Light page through the pool header_actions, always in More actions.

Apps

  • Apps categories as the DI argument categories (pool app_categories).
  • Declared apps with entry_route and settings_page, without a menu.xml or system.xml.
  • Default pins are offered once to every admin, including admins who already pinned something.

Settings

  • Settings pages generated from a module's system.xml (Page\Pool array items), with a simple allowlist, labels, notes, a validator and store-view inputs.
  • The Advanced section at the end of a settings page (Mrx_Settings::advanced-section.phtml, view model Mrx\Settings\ViewModel\AdvancedSection): the page item key advanced lists up to six fields shown as read-only rows, with the count of the other settings and the button to the stock section. A page without the key gets the section with the count and the button; a page with nothing hidden gets none. It replaces the loose advanced-view banner under schema pages. The banner of the advanced view leads back to the page by name ("Back to Returns"), and a secret (obscure, password or an Encrypted backend model) shows as ******.
  • The contracts in Mrx\Settings\Api: PageInterface, ChannelScopedInterface, ScopedConfigWriterInterface, SettingsValidatorInterface and ValidationException.
  • Role areas in the catalogue, and preset roles that pick up new resources on every setup:upgrade.
  • Settings > Payments has two bands, "Online payments" and "Manual payment methods". The pool payment_providers (item keys label, prefix, own_section, state, card, logo, description, dashboard_url, config_section, advanced_section, resource, sort_order, module) registers a payment provider module. With own_section and a card (Mrx\Settings\Api\PaymentProviderCardInterface), Light draws the provider's card, and the module's forms go into the block mrx.settings.payments.online as the children <code>_methods and <code>_connection (Payment providers).
  • Mrx\Settings\Api\PaymentProviderStateInterface with isOffered().
  • The container mrx.settings.payments.providers sits in the Online payments band, after the provider cards and outside the Payments form.
  • Mrx\Settings\ViewModel\Fields: field(), select(), multiselect(), toggle(), icon() and idFor() draw the controls of core's settings pages in your own template, with the channel note and lock state of a path.

Countries

Core works for a shop in any country. What is Dutch lives in the pack Mrx_CountryNl (Packs).

  • The pool country_rules: items keyed by country code, with the groups business_id, tax_id and postcode (label, example, check and message). Core's own labels, "Tax ID" and "Business registration number", come from code and translate through the CSVs (Country rules).
  • Mrx\Settings\Api\OssInterface, LegalPageVariablesInterface and ZonesWriterInterface; ChannelScopedInterface::PARAM; the @api classes Mrx\Settings\Model\Tax\TaxRecords and PriceDisplay.
  • The pools carriers (item keys label, patterns, tracking_url, sort_order, select_order; Order and customer pages), legal_page_templates and legal_page_types.
  • The containers mrx.settings.taxes.preset, mrx.settings.taxes.cross_border, mrx.settings.business.registration and mrx.settings.payments.providers, and the handle light_settings_{code}. They hold display cards; a card that needs input posts to its own controller.
  • The data hook data-mrx-tax-action: a button in a Taxes card that posts to its URL and reloads the page.
  • Settings > Taxes has "Add rate" (a rate, a product tax class and a rule of the same name), "I don't charge tax" (the config path mrx_settings/taxes/no_tax), a line that sends the merchant to the tax authority, and a link to the advanced tax rules.
  • The Tax rates table on Settings > Taxes follows the EU VAT choice of the scope on screen. With home VAT it lists the home country's rates and rates no OSS rule charges, and one line (data-mrx-tax-oss-ready) counts the countries whose OSS rates are kept for later. With OSS (under All channels: while any channel uses it) it lists every rate once, grouped by country. The OSS card's custom-rate notes follow the same choice.
  • EuVat::OSS_THRESHOLD: the One Stop Shop threshold, 10,000 euros for every EU home country.
  • The pool page_templates (Mrx\Content\Model\Page\Template\TemplatePool): the page drafts of Content > Pages, Add page. An item's file is a name in Mrx_Content's view/adminhtml/page_templates folder, or Module_Name::name for a draft in that module's view/adminhtml/page_templates/<locale> folder (Changing Light for one shop).
  • The page interfaces Mrx\Settings\Api\NormalizesInputInterface (a page hands back the values it cleaned up while saving) and ReloadsAfterSaveInterface (a page whose save changes more than its own fields reloads the form).
  • The doctor rule country_pack warns when the shop's home country has a pack that isn't enabled (Troubleshooting).

Changed:

  • mrx_settings/store/kvk is now mrx_settings/store/business_id, and the Business details field kvk is business_id.
  • OSS works for any EU home country, not only the Netherlands. EuVat::MODE_DUTCH is EuVat::MODE_HOME; the stored value origin stays.
  • The carrier code constants (Carriers::POSTNL and the rest) are gone, and the codes are the keys of the carriers pool; only Carriers::OTHER stays.
  • The checkout's postcode-before-city order and the house-number lines come from their own plugin, DutchAddressOrder, which Mrx_CountryNl carries.
  • Countries::EU is now Countries::getEuCountries(), and Normalizer is gone: country_rules checks identifiers.
  • The two Dutch repair patches for older internal builds, ChargeDutchVatOnEveryOrder and UseEnglishTaxClassNames, are gone.

Form of address

  • Mrx\Light\Api\FormOfAddressInterface and the pool locales: languages with two forms of address (Dutch "je" and "u", German "du" and "Sie"). A module ships i18n/<locale>.informal.csv or <locale>.formal.csv next to its CSV, and the merchant picks the form per language on Settings > Channels, stored in mrx_light/locale/form_of_address per store view (Channels and store views).
  • The variant files i18n/<locale>.informal.csv and <locale>.formal.csv hold the rows that change with the form. One plugin lays the file for the store's form over the language pack in a customer area and in store emulation, which covers the storefront, customer emails, PDFs and the REST and GraphQL messages. The JS dictionary keeps the base rows. A theme's CSV and the merchant's inline edits still win.
  • The pool prefer_phrases (Mrx\Light\Plugin\Translation\PreferModulePhrases::phrases, global, item keys phrase and module, keyed by locale): for the phrases it lists, a module's own i18n/<locale>.csv row beats the installed language package. It replaces Returns' private PreferReturnsPhrases, and core lists Dutch "Status" and German "Returns" and "Product" (Troubleshooting).
  • Changed: customer text follows the store's form. The Dutch base rows are informal and the German ones formal, and Light ships the other form of every customer row, so Dutch mails that said "u" in some modules and "je" in others now use one form. Admin text stays informal. Dutch greetings follow the form ("Hoi Anna," and "Beste Anna,"), German keeps "Hallo Anna," in both.

Payments

  • Mrx\Orders\Api\PaymentMode: a payment module stores PaymentMode::INFO_KEY (mrx_payment_mode) with TEST or LIVE in the payment's additional information, and Orders warns on an open order paid in test mode, for any provider. The orders list has a "Test payment" column (Order and customer pages).
  • Mrx\Orders\Api\RefundReasonInterface: a payment module that refuses a refund sets one sentence, and the refund fails with it.
  • Mrx\Settings\Api\PaymentMethodsInterface: asks whether customers can still pay another way before a provider is switched off.
  • The pool accountant_fee_columns (argument feeColumns of Mrx\Home\Model\Accountant\DocumentSource): a payment fee column of invoices and credit memos, with the column that holds its VAT, goes into "Export for your accountant" (Home).

Packs

Light core names no country and no payment provider. They ship as packs, modules outside core in packages/ of this repository. Each pack declares extra.mrx-light-api and carries the range guard ^0.1 in its registration.php (Ship it as a pack).

  • mrx/module-country-nl (Mrx_CountryNl, "Light for the Netherlands"): the Dutch VAT setup (the card on Taxes, route light/dutchvat/apply), the KvK number and Dutch VAT number checks, the Dutch postcode, the address order, PostNL and the Dutch tracking links, the Dutch shipping zones, the legal page drafts under Dutch law with the German set for Dutch sellers, and the page drafts.
  • mrx/module-payments-mollie (Mrx_PaymentsMollie): Mollie's card, connection and payment methods on Settings > Payments, the test-mode record, refunds with a reason, the payment fee in the accountant export and the Apps entry.
  • mrx/module-payments-paynl (Mrx_PaymentsPaynl): the same for Pay.
  • magerex/distribution-nl, a metapackage: the core modules, the pack for the Netherlands, the Pay. and Mollie packs, and the MageRex brand, which is what Disrex Flex installs for a Dutch shop. From 0.1.1 it leaves out mrx/module-returns, which a shop adds itself.

Forms

  • The form extension pipeline on seven Light editors (product, category, cms_page, cms_block, discount, customer_new, customer): FormExtensionInterface, DeclaresFieldsInterface, AppliesToEntityInterface, FormContextInterface and CurrentFormInterface, the pool form_extensions, the ExtensionCard block and the JS module Mrx_Light/js/form-hooks with the event mrx:form-saved.
  • New containers on the category, CMS page, CMS block, discount, new-customer and customer contact editors, and mrx.product.form.main.after_pricing.

Lists, Home and AI

  • Row actions on Light lists (RowActionsInterface and meta).
  • A list row with 'tone' => 'subdued' greys its text out (mrx-index-table__row--subdued), as Orders does for a paid and shipped order.
  • A badge's progress_tone (success) colours its progress icon green on a grey badge (mrx-badge__progress--success), in table cells, layout header badges and the fourth argument of PageHeader::addBadge().
  • The Home widgets container mrx.home.widgets.
  • A setup step's description_provider (Mrx\Home\Api\SetupDescriptionInterface) gives a description that depends on the store; the item's description stays the text for when it returns ''. Core's payments step names the providers of payment_providers this way, in their sort_order.
  • Settings > AI limits for every caller of LlmClientInterface, and "Translate from" the default language in the Light editors.
  • Settings > AI as one simple page.
  • Mrx\Light\Api\Navigation\Badge, BadgeSourceInterface, BadgeReaderInterface and BadgeCacheInterface; the BadgePool argument providers (pool nav_badge_providers) with resource, rollup and ttl; the cache type mrx_light_badges; the event mrx:badges.
  • The same count on pins, in the palette and on Home (a to-do with badge).
  • Mrx\Light\Attribute\ProvidesNavItems on a nav item provider names the keys it returns, so unknown_reference accepts a counter, a to-do or a child item that points at them (A nav item with a counter).

Brand and themes

  • The brand pool (brands), Mrx\Light\Api\Branding\BrandInterface and Brand. Without a brand package, Light shows the platform's own logo.
  • The Mage-OS admin themes mage-os and mage-os-dark.
  • New core tokens --mrx-color-primary-text, --mrx-color-control-checked, --mrx-color-border-focus-inverse, --mrx-color-magic, --mrx-color-text-magic, --mrx-color-surface-field, --mrx-color-surface-field-critical, --mrx-font-display, --mrx-color-nav-badge-bg and --mrx-color-nav-badge-text.
  • The brand key logo.lockup_on_dark and Brand::$logoLockupOnDark: a brand shows its full logo in the top bar. The neutral Mage-OS brand ships Mage-OS's own logo files, with the word mark in white on dark.
  • Tokens for a dark menu (--mrx-color-nav-text, --mrx-color-nav-text-secondary, --mrx-color-nav-icon, --mrx-color-nav-focus), for flat buttons (--mrx-gradient-button, --mrx-shadow-button, --mrx-shadow-button-pressed, --mrx-shadow-button-primary, --mrx-shadow-button-primary-pressed, --mrx-shadow-button-critical), for the frame's corner (--mrx-radius-frame) and for the sign-in card (--mrx-login-brand-align).
  • Owners switch themes off on Settings > Appearance. The Mage-OS themes follow the Mage-OS media kit, with flat buttons.
  • The owner's own theme on Settings > Appearance > Your own theme: the theme code custom (now reserved), a base and a few colours in mrx_themes/custom/*, and the routes light/themes/custom, saveCustom, previewCustom and deleteCustom. The store logos uploaded there replace the active brand's lockups through a plugin on BrandInterface::get(), and the brand's label becomes the store name. Two brand icons uploaded beside them (mrx_themes/custom/icon_on_frame and icon_on_light) replace the brand's favicon and both marks, the one for white backgrounds being the favicon; with one icon, it serves for both. Without an icon, an owner's logo empties the marks and phones show the lockups.
  • Tokens for the phone menu's mark (--mrx-nav-mark-on-light, --mrx-nav-mark-on-dark): a theme with a dark menu shows the brand's mark for dark surfaces there.
  • POST light/themes/saveDefault takes the owner's switches (switches=1, enabled[]) and answers reload.
  • The sign-in page shows the theme the browser last used signed in, so its buttons match the store logo: a cookie mrx_theme (fixed:<theme> or auto:<light>:<dark>, HttpOnly, on the admin path) written on signed-in pages and when a theme is saved. A missing, unknown or disabled theme falls back to the store default.

Emails and documents

  • The core module Mrx_Documents: one brand kit (logo, accent colour, footer text, company details) and a format give every customer email one look, set on Settings > Emails & documents (light/settings/documents, with light/documents/preview and light/documents/testsend) under the ACL resource Mrx_Documents::documents (Your module's email and document).
  • The pool formats with Light's formats clean, bold and letter; an agency format adds its own email_inline_css file.
  • The email tokens __MRX_ACCENT__ and __MRX_ACCENT_TEXT__, and the email classes the shell styles (the email-class lines of the surface snapshot).
  • Doctor rules email_shell, email_inline_style, email_shell_config and email_css (Troubleshooting).
  • Light's own bodies for the order confirmation, "Payment received" and the shipping confirmation, each with a guest version: mrx_documents_order_template, mrx_documents_order_guest_template, mrx_documents_invoice_template, mrx_documents_invoice_guest_template, mrx_documents_shipment_template and mrx_documents_shipment_guest_template, set on sales_email/{order,invoice,shipment}/template and guest_template. They have a "View your order" or "Track your parcel" button, product pictures, headings in German and English and dates in the store's language, and a shipping mail without a tracking number has no tracking block. The sales email events get the variables mrx_order_url, mrx_created_at, mrx_invoice_date, mrx_has_tracking, mrx_track_url, mrx_track_number and mrx_carrier (The variables of the sales bodies).
  • Settings > Customer emails stores the subject, the opening text and the button text as config rows (mrx_settings/email_texts/<code>/*) and puts them into the template each time a mail renders, instead of copying the template. An edited mail keeps getting your module's fixes.
  • The emails pool is global, and its items take button (the editor offers the text between <!--mrx:button--> and <!--/mrx:button-->) and group (the email's group in the preview picker). A module that registered emails in etc/adminhtml/di.xml should move them to etc/di.xml, or its saved texts apply only to mails sent from the admin; the doctor reports wrong_area.
  • The preview picker on Emails & documents lists every customer email by group, other modules' mails included, with preview and test email; invoice and credit memo mails show an unsaved example when the store has none.
  • Every mail goes out with a plain-text part next to its HTML.
  • Light keeps an exact copy of every customer mail about an order (vars order, order_id or rma, sent to the order's customer), and the order timeline links the mail's line to it with View email, through the route light/documents/sentemail (Magento_Sales::actions_view). A mail whose vars carry mrx_no_copy is not kept (Copies of sent mails). On new orders the "…email was sent to…" lines show when the mail went out, not when the document was made, which differs when sending is asynchronous. Resends show on the timeline, each with its link. Orders placed before this keep their lines without a link.
  • Settings > Users mails a new user a "You now have access" invite with a link to choose a password (mrx_settings_user_invite_template), instead of showing a password. "Send invite again" (light/settings/userinvite) sends a new link while the user has never signed in.
  • Invoice, credit memo and packing slip PDFs in the brand kit and format, rendered with dompdf for every stock getPdf() caller, unless Magento classic is on; the stock shipment PDF and its "Print All" print the packing slip (Your module's document); plugins on the stock drawing code stop running, which the doctor reports as pdf_inner_plugin (Troubleshooting). The paper is A4, and the accountant ZIP holds PDFs up to 300 documents, at most 100 per file, grouped by store view (Upgrading).
  • Order confirmation PDF (order): the letterhead, the order number and date, both addresses, the shipping and payment method, the items through item_lines, the totals and the footer. Magento has no order PDF, so it prints only through DocumentRendererInterface and light/documents/download, under Magento_Sales::actions_view (Light's own document types).
  • The packing slip as PDF (packing_slip for orders, packing_slip_shipment for one parcel), supplied by Mrx_Orders, and one setting, "Show prices" (mrx_documents/packing_slip/show_prices, off). The customer's note prints whenever there is one. "Print packing slip" in the More actions of an order that isn't canceled, and on a shipment, downloads the PDF. The bulk action "Print packing slips" asks light/orders/packingslips first, as "Print order confirmations" does: it skips canceled orders, says how many, and says so when a selection holds more than 250 orders. The packing slip's browser page is gone: light/orders/packingslips answers that check in JSON instead of printing a page, and Mrx\Orders\ViewModel\PackingSlips and the template Mrx_Orders::order/packing-slips.phtml are gone with it.
  • The picklist (picklist, from Mrx_Orders): one table over up to 250 orders with the SKU, name, options, total quantity and order numbers, sorted by bin location when a module supplies one, else by SKU. The orders list's bulk action "Print picklist" (light/orders/picklist) skips canceled orders and orders with nothing left to ship, and says how many.
  • "Print order" in an order's More actions, and the bulk action "Print order confirmations" (light/orders/confirmations), which says so when a selection holds more than the 250 orders one PDF prints. "Print credit memo" in the More actions of an order with a credit memo, for the latest one.
  • Mrx\Documents\Api\DocumentSectionInterface and the pool document_sections (keys section, document, position, sort_order, module): a block after the parties, items or totals of any document (Add a block to a document).
  • Mrx\Documents\Api\PickLocationInterface and the pool pick_locations: bin locations for the picklist's Location column and sort (Bin locations on the picklist).
  • Mrx\Documents\Api\PdfRendererInterface, DocumentRendererInterface, DocumentTypeInterface, ItemLineInterface and Data\Document.
  • The pool documents with Light's types order, invoice, creditmemo, packing_slip, packing_slip_shipment and picklist, and the pool item_lines with the lines default, bundle and downloadable; a format's pdf_css file styles its PDFs.
  • The routes light/documents/download (any type of the documents pool, under the type's own ACL resource) and light/documents/samplepdf (a sample of a type of the pool in the preview, under the type's own ACL resource; the picker's Documents group lists the types that build a sample, which only Light's own do: order confirmations, invoices, credit memos and packing slips; an example built from an order also needs Magento_Sales::actions_view).
  • PDFs on customer emails (PDFs on customer emails). "Send the invoice PDF with" (mrx_documents/invoice/send_with: shipment, the default, payment or none) puts the invoice PDF on the shipping confirmation or on "Payment received", once per invoice, recorded in mrx_documents_invoice_mailed. The refund email carries the credit memo PDF unless the setting is none. "Attach the order confirmation PDF to the order email" (mrx_documents/order/attach, off) does the same for the order email. Light's order, "Payment received" and shipping bodies get the variable mrx_has_attachment. The seven sales senders get Light's senderBuilderFactory; the attachment provider interfaces are internal. "Email invoice" in an order's More actions (light/orders/emailinvoice, Magento_Sales::email) sends "Payment received" with the invoice PDF; "Mark as paid" and "Capture" still send no email. "Print invoice" and "Print refund" in My Account and the guest order view open Light's PDF instead of the browser print page.
  • The return slip (return_slip, from Mrx_Returns, under MageOS_RMA::rma_manage): the return number with a Code 128 barcode, where to send the parcel, the customer, the order number and the items with their reason and condition. The customer's approval mail carries it: "Return status changed" when the new status is approved, "Return request received" when the return is approved on arrival. That mail prints its review promise under {{depend mrx_awaits_review}} and "We have approved your return." under {{depend mrx_approved}}. Both Returns bodies get mrx_has_attachment. "Print return slip" in a return's More actions prints it from approval onward. Mrx_Returns now requires mrx/module-documents (The return slip on the approval mail).
  • "Magento classic" (Stores > Configuration > Sales > Emails and documents (Light) > Advanced, mrx_documents/advanced/classic) switches the Light look off: the stock header, footer, styles, bodies and PDFs come back, and no PDF or return slip goes on a mail.

UI kit

  • The payment provider card on Settings > Payments: the classes mrx-provider-card*, mrx-method-strip* and mrx-payments-methods*, and the hooks data-mrx-payments-band, data-mrx-provider, data-mrx-provider-panel and data-mrx-provider-test-mode (Design system).

  • On phones every modal from Mrx_Light/js/modal is a bottom sheet like the search palette: it slides up and back down with the backdrop fading, has a grab handle (.mrx-sheet-handle), and closes when dragged down by its handle or header. The root keeps .is-closing and is inert until the slide-out ends, about 280 ms, so a browser test waits for the modal to go (toBeHidden(), toHaveCount(0)) instead of checking at once (Design system).

  • Mrx_Light/js/loader with during, button, skeleton and spinner: a region that waits shows a skeleton shaped like what is coming (list, table, card, text or media) after 150 ms and keeps it at least 300 ms, sets aria-busy and says "Loading" to screen readers only; a busy button keeps its width and is disabled. Light lists use it: the first-load skeleton has the list's own columns, and a reload slower than 150 ms swaps the rows for skeleton rows. .mrx-skeleton no longer pulses with reduced motion, and .mrx-skeleton--media is new (Design system).

  • On phones the nav drawer opens a section in place. A top-level item with children renders a button (.mrx-nav__toggle, aria-expanded, aria-controls) next to its link; a tap expands the section's list (.mrx-nav__sub-wrap > .mrx-nav__sub) and neither navigates nor closes the drawer. The list starts with the section's own page (.mrx-nav__sub-item--own), then its children, and the drawer opens with the current page's section expanded and the rest collapsed, one section at a time. Every section now renders its children, and each child carries its own count. The sidebar (768px and up) and a page without script look as before. A theme that styles the menu should name .mrx-nav__toggle wherever it names .mrx-nav__link, and give the own entry the same current-page mark as an active child (aria-current="page", see themes.md); the built-in themes do.

Moved out of core

Each line is a line of the API surface snapshot that left core, and where it lives now:

  • Mrx_Payments is Mrx_PaymentsMollie (mrx/module-payments-mollie). Its page mollie in the pool settings_pages and its route light/mollie/disconnect come from that module now.
  • The carrier postnl of the pool carriers, the country NL of country_rules and the legal page types withdrawal and imprint are items of Mrx_CountryNl. Core's footer and legal page links still know the names withdrawal and imprint.
  • The route light/settings/taxpreset is light/dutchvat/apply in Mrx_CountryNl.
  • The Dutch classes DutchVat, DutchZones and TaxPreset and the legal page drafts (now under Mrx_CountryNl::legal_page_templates) moved with them. The Dutch page drafts are copied, not moved: core's drafts in Mrx_Content stay, and the pack's copies replace them through same-key file items (Upgrading).

Words and names

Light now uses its chosen words in identifiers too. Each line is old ⇒ new:

  • Orders tab key unpaid ⇒ not_paid, so the URL is ?tab=not_paid, and a Home to-do that links to it sets tab to not_paid.
  • Cancel reason unpaid ⇒ not_paid.
  • Mail variable reason_unpaid ⇒ reason_not_paid in the order_canceled template. A customised copy of that template needs the new name.
  • Routes light/orders/fulfil ⇒ light/orders/ship and light/orders/massfulfil ⇒ light/orders/massship.
  • Mrx\Orders\Model\Service\Fulfilment ⇒ Mrx\Orders\Model\Service\ShipItems, and its method fulfil() ⇒ ship().
  • Mrx\Home\Model\Todo\Counter\OrdersToFulfil ⇒ Mrx\Home\Model\Todo\Counter\OrdersToShip.
  • Orders tab key unfulfilled ⇒ not_shipped, so the URL is ?tab=not_shipped.
  • Orders column key fulfilment ⇒ shipping_status. A column you add with 'after' => 'fulfilment' now says 'after' => 'shipping_status'.
  • Shipping status values unfulfilled, partially_fulfilled and fulfilled ⇒ not_shipped, partially_shipped and shipped.
  • Home to-do key orders_to_fulfil ⇒ orders_to_ship, and the nav badge provider orders_unfulfilled ⇒ orders_not_shipped. A module that replaces or removes either entry by key uses the new key.
  • Role preset key fulfilment ⇒ shipping, and its name "Fulfilment" ⇒ "Shipping staff".
  • Mrx\Catalog\Model\Product\UrlHandle ⇒ Mrx\Catalog\Model\Product\UrlKey, and Mrx\Catalog\Model\Category\UrlHandleTakenException ⇒ UrlKeyTakenException.
  • Products tab key out_of_stock ⇒ sold_out, so the URL is ?tab=sold_out. The Inventory tab, TAB_OUT_OF_STOCK ⇒ TAB_SOLD_OUT in both lists, and a Home to-do that links to it sets tab to sold_out.
  • Home to-do key products_out_of_stock ⇒ products_sold_out, and Mrx\Home\Model\Todo\Counter\OutOfStockProducts ⇒ SoldOutProducts. A module that replaces or removes the entry by key uses the new key.
  • Product form field track_quantity ⇒ track_stock. A module that posts to the product save endpoint, or reads the field in a form extension, uses the new name.
  • Palette destination key store_emails ⇒ sender_addresses in both di.xml files that merge into it, with the label "Sender addresses". A module that replaces or removes the entry by key uses the new key.
  • A palette destination without a label shows no result of its own: when its URL is a nav item's, its keywords go to that item; otherwise it shows nothing. Core's "Reports" entry analytics_reports is now analytics_keywords, so "reports" finds the Analytics item itself.
  • Settings group team ⇒ users, labelled "Users and permissions". A card that sets section to team now sets it to users.
  • Mrx\Settings\Model\Users\StaffInvite ⇒ Mrx\Settings\Model\Users\InviteMail, its config path mrx_settings/staff_invite/template ⇒ mrx_settings/user_invite/template, and its mail template mrx_settings_staff_invite_template ⇒ mrx_settings_user_invite_template (file user_invite.html). A customised copy of the invite needs the new template id.
  • Recipe recipes/staff-access ⇒ recipes/users-and-permissions, and the specs staff-access.spec.ts and staff-invite.spec.ts ⇒ users-and-permissions.spec.ts and user-invite.spec.ts, with the helper helpers/staff.ts ⇒ helpers/users.ts. mrx:light:app writes "Role access:" where it wrote "Staff access:".
  • Settings > Notifications is now Settings > Customer emails. Namespace Mrx\Settings\Model\Notifications\* ⇒ Mrx\Settings\Model\CustomerEmails\*, so the emails and email_previews pools are arguments of Mrx\Settings\Model\CustomerEmails\Emails and Mrx\Settings\Model\CustomerEmails\EmailPreview. A module that registers an email changes its <type name> in di.xml.
  • Settings page code notifications ⇒ customeremails, with the route light/settings/notifications ⇒ light/settings/customeremails, the layout handle light_settings_notifications ⇒ light_settings_customeremails and the Settings card key notifications ⇒ customeremails.
  • Config paths mrx_settings/notifications/welcome_email and mrx_settings/notifications/contact_reply ⇒ mrx_settings/customer_emails/welcome_email and mrx_settings/customer_emails/contact_reply.
  • AdvancedView section key notifications ⇒ customer_emails.
  • Guide anchor recipes/settings-page#6-notification-emails ⇒ #6-customer-emails.
  • Settings > Store details is now Settings > Business details. Page code store ⇒ business, with the route light/settings/store ⇒ light/settings/business, the container mrx.settings.store.registration ⇒ mrx.settings.business.registration and its handle light_settings_store ⇒ light_settings_business. The group store and the config paths mrx_settings/store/* keep their names. A card that sets page to store now sets it to business.
  • Mrx\Settings\Model\Page\StoreDetails ⇒ BusinessDetails, Mrx\Settings\ViewModel\StoreDetails ⇒ BusinessDetails and Mrx\Home\Model\Setup\Check\HasStoreDetails ⇒ HasBusinessDetails. Mrx\Documents\ViewModel\DocumentsPage::getStoreDetailsUrl() is now getBusinessDetailsUrl().
  • Palette key store_details ⇒ business_details, the AdvancedView match key store_details ⇒ business_details and the Home setup step store_details ⇒ business_details. A module that replaces or removes one of them by key uses the new key. The palette entry keeps "store name" as a keyword, so a search for the old word still finds the page.
  • Settings > Policies is now Settings > Legal pages. Page code policies ⇒ legalpages, with the routes light/settings/policies ⇒ light/settings/legalpages and light/settings/policycreate ⇒ light/settings/legalpagecreate, the layout handle light_settings_policies ⇒ light_settings_legalpages and the Settings card key policies ⇒ legalpages. A card that sets page to policies now sets it to legalpages.
  • Mrx\Settings\Api\PolicyVariablesInterface ⇒ Mrx\Settings\Api\LegalPageVariablesInterface, with the same method.
  • Pools policy_types ⇒ legal_page_types and policy_templates ⇒ legal_page_templates. The type codes (privacy, terms, returns, shipping, withdrawal, imprint) keep their names.
  • Namespace Mrx\Settings\Model\Policies\* ⇒ Mrx\Settings\Model\LegalPages\*, with PolicyManager ⇒ LegalPageManager and PublishedPolicies ⇒ PublishedLegalPages, so the pools are arguments of Mrx\Settings\Model\LegalPages\LegalPageManager and Mrx\Settings\Model\LegalPages\Templates. A module that adds a type or a draft changes its <type name> in di.xml. In the Dutch pack, Mrx\CountryNl\Model\Policies\GermanVariables ⇒ Mrx\CountryNl\Model\LegalPages\GermanVariables.
  • Config paths mrx_settings/policies/* ⇒ mrx_settings/legal_pages/*.
  • Draft folder view/adminhtml/policy_templates ⇒ view/adminhtml/legal_page_templates, so a file reads Mrx_CountryNl::legal_page_templates/<language>/<type>.html.
  • Storefront block mrx.settings.policy.links ⇒ mrx.settings.legal_page.links. A layout that removes the footer links uses the new name.
  • Palette key settings_policies ⇒ settings_legal_pages and the Home setup step policies ⇒ legal_pages. The palette entry keeps "policies" as a keyword.
  • Guide anchor recipes/settings-page#policy-drafts ⇒ #legal-page-drafts.
  • Menu link type policy ⇒ legal_page (Mrx\Content\Model\Menu\MenuItems::TYPE_POLICY ⇒ TYPE_LEGAL_PAGE), with the column mrx_content_menu_item.policy ⇒ legal_page and the policy field of a menu item ⇒ legal_page. EditorData::getPolicies() and getPolicyTypes() are now getLegalPages() and getLegalPageTypes(). Code that writes menu items with the old type or field uses the new names.
  • The product form has Price, Sale price and Sale period, as Magento stores them (price, special_price, special_from_date/special_to_date); compare-at and the scheduled-sale lock are gone. Request field compare_at_price ⇒ sale_price, with the new optional fields sale_from and sale_to (YYYY-MM-DD). The inventory save, the variant rows and the category product list name the field compare_at ⇒ sale_price, and light/products/massPrice takes target (price or sale_price) where it took compare_at=1.
  • Label "Cost per item" on the product form ⇒ "Cost price" (Inkoopprijs, Einkaufspreis).
  • The card "Search engine listing" on the product, category and page editors and in the UI kit ⇒ "SEO", the field "Page title" ⇒ "SEO title" (SEO-titel, SEO-Titel) and the AI purpose "Search engine listings" ⇒ "SEO texts" (SEO-teksten, SEO-Texte). The preview sentences still say "search engine listing".
  • Settings > Shipping and delivery ⇒ Shipping and pickup (Verzenden en afhalen, Versand und Abholung): the Settings tile and page title, the channel link, the note on the zones page and the Apps category. The page code shipping stays.
  • The Apps entry "Admin activity" ⇒ "Activity log" (Activiteitenlog, Aktivitätsprotokoll) and the Apps category "Reports" ⇒ "Analytics" (Analyse, Statistiken). The category code reports stays. The Apps texts say "apps" where they said "extensions".
  • The navigation item "Menu" and the titles of the two menu pages ⇒ "Menus" (Menu's, Menüs). The channel's "Menu", the category tree, keeps its name.
  • The user menu entry "Theme: …" and the store-default select on Settings > Appearance ⇒ "Appearance: …" and "Appearance" (Uiterlijk, Aussehen). Theme codes and routes stay.
  • The category name placeholder "e.g. Summer collection, Sofas, Gifts" ⇒ "e.g. Sofas, Lamps, Gifts".
  • The title and file name of the credit memo PDF come from the key Credit memo (PDF title): "Credit memo" in English (US), "Credit note" in British English, Creditfactuur in Dutch and Rechnungskorrektur in German. A store in another language falls back to "Credit memo". The admin keeps "Credit memo" (Creditfactuur, Gutschrift). Mrx_Documents now has the British rows "Credit memo" ⇒ "Credit note" and "Credit memos" ⇒ "Credit notes", as Mrx_Home has.
  • The first tile of the Settings group "Users and permissions", its page title and its palette entry read "Users" (Gebruikers, Benutzer). The group keeps its name, and the catalogue mode text of role_areas and role_presets says "simple: Settings > Users".

Where a Dutch or German language pack translated a bare English word differently, Light now calls its own key, so the pack no longer wins. Your module can reuse these keys with the same meaning, and gets the Dutch and German text with them:

  • Payment authorized (Gereserveerd, Reserviert): the payment badge that said "Authorized".
  • Refund items (Terugbetalen, Erstatten): the refund button. Refund (noun) prints "Refund" in English (Terugbetaling, Erstattung) and titles the refund dialog and card. The Returns list column uses Refund amount.
  • Refunded (payment status) prints "Refunded" in English (Terugbetaald, Erstattet).
  • Shipment #%1 (order page) prints "Shipment #%1" in English (Zending #%1, Sendung #%1).
  • Shipping costs (Verzendkosten, Versandkosten) for an amount, Shipping details (Verzending, Versand) for a card title, and Delivery (Verzending, Versand) for the name of a custom rate.
  • Internal note (Interne notitie, Interne Notiz), Guest customer (Gast), Downloadable product (Downloadproduct, Download-Artikel), Newsletter subscribers (Nieuwsbriefabonnees, Newsletter-Abonnenten) and Choose a block (Kies een blok, Block auswählen).
  • Size (option name) prints "Size" in English (Maat, Größe).
  • Export (list button) and Import (list button) print "Export" and "Import" in English (Exporteren, Importeren; Exportieren, Importieren).

Copy and languages

  • The English source is US English with neutral tax words. Phrases say "tax", not "VAT", and use "color", "postal code" and "recognize". i18n/en_GB.csv in Catalog, Content, Discounts, Documents, Home, Orders and Settings gives British admins "VAT rate", "Colour" and "Postcode" back.
  • A country module words its country through CSV rows. Mrx_CountryNl ships i18n/en_US.csv and i18n/nl_NL.csv with the sentences core lost (the KvK number, iDEAL, the GDPR, "Dutch customers"), so a Dutch shop reads what it read before and no other shop sees them. The pack sequences the core modules whose rows it overrides, so its rows win (Country wording).
  • Installed language packages win over module CSVs. community-engineering/language-nl_nl translates "Tax" as "BTW", so a new phrase of yours avoids the English phrases such a package translates (Troubleshooting).
  • Core's search keywords lost kvk, ideal, mollie, aangifte, impressum and widerrufsbelehrung. Mrx_CountryNl sets the full strings again for the palette entries business_details, payment_methods, settings_legal_pages and accountant_export, so search on a Dutch shop finds what it found (Palette).
  • The page drafts of Content are neutral in core, with bracketed placeholders to fill in. The pack keeps the Dutch drafts.
  • Settings > Users offers English (United Kingdom), English (United States) and Nederlands. The preferred argument of Mrx\Settings\Model\Languages\Locales is gone: the language pickers list the languages the shop can show first, each group sorted by name.
  • The order timeline writes ordinals with ICU (NumberFormatter::ORDINAL) in the admin's language.
  • The price and stock fields accept any currency sign, not a list of three.
  • The glossary lists Light's words with Magento's names (Glossary).
  • The page where an admin chooses a look is called Appearance ("Uiterlijk", "Aussehen"), on the Settings tile, the page heading and the palette; the palette action is "Change appearance". The built-in theme Klassiek is called Classic. The theme code klassiek, the routes, the ACL ids and the config paths stay, so a theme package changes nothing. The Themes phrases follow the US spelling, with i18n/en_GB.csv rows for the British words.

Changed wording, in short. The Dutch is unchanged:

  • Spelling: "Colour" ⇒ "Color", "Greyed-out" ⇒ "Grayed-out", "Postcode" ⇒ "Postal code" and "recognised" ⇒ "recognized".
  • Tax words: "VAT rate" ⇒ "Tax rate", "Set VAT rate" ⇒ "Set tax rate", "No VAT" ⇒ "No tax", "Prices include VAT" ⇒ "Prices include tax", "VAT number" ⇒ "Tax ID" and the export column "VAT" ⇒ "Tax amount". A module that reuses one of these phrases in its own CSV renames the key the same way.
  • Country sentences: "Printed on invoices and refunds, together with your VAT and KvK numbers." became "…together with your tax ID and business registration number.", "Turn on a way to get paid: connect %1 for iDEAL and cards, or offer bank transfer." became "…connect %1 for cards and wallets, or offer bank transfer.", and "Dutch customers expect a separate line for the house number." became "In some countries customers expect a separate line for the house number."

Changed defaults

These may affect tier-2 changes (Upgrading):

  • RolePresets is registered in the global area only; an admin-area registration used to replace the global one.
  • Default pins are offered once per admin, so an admin who already pinned apps now gets them too.
  • Every header action from the header_actions pool goes in More actions; there is no slot key.
  • The store default theme comes from the brand when no default was saved.
  • The nav_badges pool accepts any provider; Orders sets badges/orders to null and counts through providers.
  • Home to-dos can count through a nav counter (badge) instead of their own counter.
  • Static labels in XML (the ACL root title, email template labels, the Themes config group) read "Light", not "MageRex".
  • The -hyva modules and MageRex_DemoData guard their registration on the Light API range, like every module outside core, so they load only with the Light release they ship with.
  • The page redirect marker is "Light: page URL changed". Redirects written with the old wording are still recognised.
  • On Mage-OS, automatic mode suggests mage-os-dark before Noordzee when no dark theme is saved.
  • Baken's private variables are --baken-*, and Signaal's --mrx-shadow-lift is now --signaal-shadow-lift.
  • Settings > AI has two levels: "Fast" stores balanced and "Best quality" powerful. Usage and limits count per calendar month, in the shop currency.
  • The Dutch wording on core Taxes and Payments cards is block arguments, which are tier 2, and core sets neutral values. Mrx_CountryNl sets the Dutch ones with a referenceBlock: country, tax_name, oss_url, oss_label and oss_authority of mrx.settings.taxes.oss; iban_placeholder of mrx.settings.payments; title and text of mrx.settings.payments.providers.none.
  • Mrx_CountryNl changes two core items by key, which is tier 2 too: the tracking_url of the carriers dhl, dhl_express, gls and dpd (core has an international tracking page for gls only; dhl, dhl_express and dpd keep Dutch URLs in core until a working international page turns up, and the pack sets the Dutch URLs for all four), and the file of the page_templates items about-us, contact, faq, shipping-returns and privacy-policy. The pack ships in the same release as core.

For theme authors. Set the nav badge tokens --mrx-color-nav-badge-bg and --mrx-color-nav-badge-text at your theme's root, and the new tokens above, instead of restyling components (An admin theme module).

Before 0.1.0

These were internal builds of the extension API, numbered 1.0.0 to 1.3.0 in ui-kit.md before the version rules existed. No module can declare those numbers; read them as pre-release notes of 0.1.

Last updated on

On this page