Recent Activities
This page shows what are we working on.
-
1.18.4
-
Version 1.18.4
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> aa4b80 -
Merge pull request #102 from swissup/fix/requirejs-bundle-pool-order-27428
fix(defer): keep the bundle pool and our RequireJS patches in place 7aeb97 -
test: repair the typo'd DOCTYPE in the offset fixture
`<!` followed by a space is not a doctype, so libxml parsed it as a bogus
comment. With LIBXML_HTML_NOIMPLIED the parser then had no document element to
attach the real <html> to, emitting "misplaced <html> tag"; Magento's unit
bootstrap promotes that warning to an exception, so the test errored on every
run instead of asserting anything.
Also close the space inside the require.js src in the same fixture. That one did
not fail the test — the assertions only count //script[@src] and compare their
offsets — but the path was meaningless as written.
The three other fixtures in this file already spell the doctype correctly.
Fixes #103
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> cdbf37 -
fix(defer): narrow the RequireJS patch guard to override.js
The bootstrap guard matched the whole Swissup_Pagespeed/js/lib/requirejs/
directory, which pinned preload-all-scripts.js in <head> as well. That file is
not bootstrap: addPreloadAllScripts() add()s it at the end of the asset
collection rather than inserting it after the min resolver, and its entire body
runs inside a window load handler, so it cannot race require.config(). Guarding
it only costs a render-blocking head script on stores with Force RequireJS
Loading enabled.
Match 'Swissup_Pagespeed/js/lib/requirejs/override' instead, and correct the
comment: it described insert()-after-min-resolver placement that applies to
override.js alone.
testPreloadAllScriptsStaysOptimizable pins the reverse direction - the file must
still move to body end and take async past the counter threshold. It fails with
the directory-wide prefix and passes with the narrowed one.
Also assert asset presence while collecting positions in
testLiveHeadKeepsBootstrapChainInOrder: strpos returns false for a dropped
asset, which asort() sorts first, reporting a bogus ordering violation instead
of the missing script.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 5e0e10 -
fix(defer): keep the bundle pool and our RequireJS patches in place
The 1.18.2 bootstrap guard was meant to cover the bundled module pool, but
'js/bundle/bundle' only matches Magento's own bundle0.js. Advanced JS Bundling
names its pool after the layout handle -- default-bundle0.js,
cms-page-view-bundle0.js -- so none of our bundles ever matched, and
Swissup_Pagespeed/js/lib/requirejs/override.js was not listed at all.
Combined with the 1.18.2 counter change, where skipped-but-executable scripts
consume a slot, these scripts crossed the jsCounter > 5 threshold and started
getting async -- something that did not happen before 1.18.2. Both call
require.config(): the bundle pool registers the jsbuild sources, and override.js
wraps require.config() and must be installed before requirejs-config calls it.
So on a store running Advanced JS Bundling or Interactive Defer, 1.18.3
introduced a fresh instance of the very failure class it fixed.
Match the whole js/bundle/ directory instead of a filename prefix (anything
living there is a module pool by construction) and add our own RequireJS patch
directory.
Regression coverage uses the head sequence dev2.dlastore.com actually serves,
with both features on: six ignored third-party inline blocks, the global require
config, then Magento's bootstrap interleaved with the bundle pool. The fixture
asserts the whole chain stays ahead of the body content, carries no async/defer,
and keeps its document order. It fails in seven places without the fix.
Refs TTF-27428
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 1b477c
-
-
1.18.3
-
Version 1.18.3
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 82a806 -
Merge pull request #101 from swissup/fix/requirejs-bootstrap-coverage
fix(defer): cover every RequireJS bootstrap asset and stop counter drift 5a4389 -
fix(defer): count only executable scripts and cover global require config
Three follow-ups on the previous commit in this branch.
Counting every skipped script restored bootstrap coverage but also counted
inert blocks. text/x-magento-template, text/html and JSON payloads are never
executed by the browser, and Magento emits a handful of them on most pages
(minicart, checkout, swatches). Counting them inflated jsCounter and pushed
later scripts past the async (> 5) and third-party defer (> 1) thresholds, so
scripts that used to stay synchronous started getting async and losing their
execution order. jsCounter now advances only for script tags the browser
actually runs: an empty type, a JS MIME type, or the configured delayed type
(those are executed by the delayed-script runtime, so they keep their slot).
The inline bootstrap matcher only recognized the require.config() call. The
other legitimate form, a global `var require = {...}` / `window.require =
{...}` object emitted before require.js loads, carries the same
baseUrl/paths/map data and is under the same ordering constraint, so it was
still relocated behind the content and reproduced TTF-27428 on themes that
use it. Added REQUIREJS_INLINE_GLOBAL_PATTERN.
Inline matching is textual, not parsed, so a script mentioning the config
statement inside a string or a comment is opted out too. That direction is
deliberate - a missed optimization instead of a broken RequireJS bootstrap -
but it was documented as if only src matching had that property. Corrected the
docblock and pinned the behaviour with a test. Also gated both patterns behind
a stripos() check, since the matcher runs on every inline body including the
multi-hundred-kilobyte checkoutConfig payload.
Tests: 35 -> 42. Verified against the pre-fix code: 6 failures, one per gap
(inert-counter case plus five global-config spellings). With this change
705/705 green, phpcs 0 errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> ee4c79 -
fix(defer): cover every RequireJS bootstrap asset and stop counter drift
PR #99 protected only requirejs/require.js, mage/requirejs/mixins.js and
requirejs-config.js. Magento renders up to eight bootstrap assets in <head>
(Magento\RequireJs\Block\Html\Head\Config::_prepareLayout), and the rest were
still relocated to the end of <body>:
- requirejs-min-resolver.js patches ctx.nameToUrl to append the .min sign and
is emitted whenever JS minification is on, i.e. in production
- mage/requirejs/baseUrlResolver.js + requirejs-map.js install the URL map
- mage/requirejs/static.js and js/bundle/bundle*.js define the module pool
Moving any of them behind the content reproduces the same failure class as
TTF-27428: module ids resolve against the bare baseUrl and static URLs are wrong.
Replace the five ignore signatures with isRequireJsBootstrap(), which matches
external assets on src only (an unrelated script merely mentioning a bootstrap
path in its code is no longer opted out of deferring) and inline blocks on the
require.config()/requirejs.config() call itself, tolerant to any spacing.
Also increment jsCounter for skipped scripts. Without it every ignore signature
shifted the unpack/async/defer thresholds of later scripts: with PR #99 only
three of six eligible inline scripts were still delayed on a page whose <head>
carries the bootstrap.
Extend RequireJsBootstrapOrderRegressionTest with a provider over all eight
minified asset names, seven inline-config spellings, a guard that a script only
mentioning a bootstrap path stays optimized, and a counter-stability assertion.
Against the previous code the new cases produce 9 failures; with this change the
suite is 698/698 green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 8ae870 -
Merge pull request #100 from swissup/fix/category-image-swap-xss-14036
fix(image): keep the processed marker out of the DOM (#xss-14036) 374f33 -
fix(dom): rank class last in attribute matching (#xss-14036)
Follow-up on review of the processed-marker fix.
AttributeBasedStrategy tried `class` before `src`, which is what let the
production patch land on a foreign product image in the first place. The
ambiguity guard alone does not cover a class that matches exactly one -
but wrong - element, so order the attributes from most to least
identifying and keep `class` as the last resort. Testing for a second
match from the first match's offset replaces the full-document
preg_match_all, and a skipped match is now logged instead of silently
dropping the optimization.
ProcessedNodeRegistryTrait no longer requires the consuming class to call
an initializer from its constructor: the storage is created on first use,
so a reader can never hit an uninitialized typed property. Added __clone
so a cloned document gets its own registry, and documented that entries
are keyed by PHP object identity and do not survive a node being rebuilt
from HTML.
PipelineCategoryImageSwapTest claimed to reproduce the image swap. It
does not: in a fixture that small OffsetBasedStrategy still resolves the
orphaned patch group, so AttributeBasedStrategy is never reached and no
image is swapped. Its src assertions are non-regression guards; what is
actually red before the fix is the dropped Lcp patch and the leaked
marker. Docblock now says so, and the swap stays pinned at unit level.
Tests: 673 tests, 2030 assertions, OK.
phpcs --standard=Magento2 on the touched files: 0 errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 5e9d02 -
fix(image): keep the processed marker out of the DOM (#xss-14036)
On a category page the first two product images were rendered with each
other's <img> tag. Reproduced on mcprod.boutinot.com/wines.html: item 1
links to CHV722 but shows CHV726, item 2 the reverse. Only the two images
that LCP marks with fetchpriority="high" were affected, and the wrong
output was then stored in the full page cache.
markImageAsProcessed() set data-pagespeed-processed through the raw
\DOMElement::setAttribute(), so the mutation was never recorded as a patch.
That made the oldHtml of every later patch of the same node stale, so
PatchGrouper could not chain it and the patch fell through to the fuzzy
replacement strategies. AttributeBasedStrategy then matched by
class="product-image-photo" - shared by every product image - and rewrote
the first product image with the second product's HTML.
Keep the marker in an SplObjectStorage registry on TrackedDomDocument
instead of in the DOM, and stop AttributeBasedStrategy from matching by a
class that several elements share. A patch that fails to apply only loses
an optimization; a patch applied to the wrong element corrupts the page.
Side effect: data-pagespeed-processed no longer appears in page output.
Nothing in JS, templates or tests reads it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> c5b7a3
-
-
head
-
Fixed layout shift when result list is long 8715cc
-
Fixed overflowed content in Firefox (Apollo theme) de660b
-
Merge pull request #57 from swissup/phpunit12-test-api-56
Migrate Test/Unit off removed PHPUnit APIs (PHPUnit 12 / PHP 8.5) 095093 -
test: migrate removed test APIs for PHPUnit 12 / PHP 8.5
The CI runner targets PHP 8.5, which requires PHPUnit 12.x. Migrate the
unit tests off APIs removed in PHPUnit 10-12:
- setMethods() (removed v10) -> onlyMethods(); all mocked methods verified
to exist on the target class via reflection. Dropped dead duplicate mock
builders (Magento CollectionFactory immediately overwritten by the
Swissup one).
- getMockForAbstractClass() (removed v12) -> createMock().
- will($this->returnValue()/returnSelf()/onConsecutiveCalls()) (removed
v12) -> willReturn()/willReturnSelf()/willReturnOnConsecutiveCalls().
- Non-static data providers (hard error v12) -> public static function.
Also fixed two removed APIs not listed in the issue but caught while
running the suite:
- $this->at() (removed v10) in DataTest and ProductTest -> willReturnMap()
/ atLeastOnce() / consecutive-call stubs.
InitTest was stale independently of PHPUnit: it exercised a removed
getClassNames() method and the old constructor/getSettings signature.
Realigned it to the current Init block and dropped the redundant
getClassNames test (already covered by Helper\DataTest).
Verified: 41 tests, 99 assertions, 0 errors, 0 deprecations on
PHPUnit 10.5 / PHP 8.3 (`bin/test`).
Refs swissup/module-ajaxsearch#56
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> 20854c -
test: add module-local PHPUnit runner
Add bin/test and phpunit.xml.dist so the unit suite can be run in
isolation via `bin/test` (runs vendor/bin/phpunit inside the container),
mirroring the setup already present in module-askit. .gitignore keeps the
PHPUnit result cache out of the repo.
Refs swissup/module-ajaxsearch#56
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> a4e899
-
-
1.4.9
-
1.4.1
-
Version 1.4.1 7fe48d
-
-
1.4.1
-
1.3.2
-
1.3.2 bdb384
-
-
1.3.1
-
Merge pull request #7 from vinai-drive-by-commits/ai-friendly-error-page
Add copy buttons for the stack trace and message to the error page f73b13 -
Add copy buttons for the stack trace and message to the error page
Two buttons in the bottom-right of the error page copy either a
PHP-style stack trace (exception class, message and frames) or just
the exception message, so an error can be quickly searched for or
handed to an AI assistant. Cmd/Ctrl+C copies the full stack trace as
well, unless the user has selected some text themselves.
The same text is exposed for browser-automation agents without going
through the clipboard: a `window.swissupIgnition` object
({ report, message, stackTrace, copyStackTrace(), copyMessage() }) and
hidden DOM nodes `#swissup-ignition-stacktrace` /
`#swissup-ignition-message` that can be read via textContent.
The widget is injected through Spatie's addCustomHtmlToBody() and built
with the DOM API (no injected <style>, no inline event handlers) to stay
compatible with the script-nonce CSP the module applies to the page. 163a07 -
Fixed missing error modal on failed ajax request, when `global:false` 915a01
-
-
head
-
Merge pull request #5 from swissup/fix/arrow-room-lost-after-mount
fix(arrows): keep the arrow room after the switcher mounts 1a9480 -
fix(arrows): keep the arrow room after the switcher mounts
`.control:has(> .input-text.qty)` only matches the server-rendered markup.
`product.js` wraps the input into `.qty-wrapper` on init, after which the input
is no longer a direct child of `.control`, the rule stops matching and the 20px
reserved on each side for the arrows disappears — the arrows overflow the field
and the layout shifts once JS runs.
Match the input as a descendant instead, which holds both before and after the
wrap. Verified in the browser: `.control` padding went `0px 20px` -> `0px` the
moment the wrapper was inserted, and stays `0px 20px` with this selector.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 082597
-
-
1.2.3
-
head
-
Merge pull request #21 from swissup/fix/quickview-qty-clipping
fix(qty): stop quick view clipping quantities of 10 or more 541862 -
fix(qty): stop quick view clipping quantities of 10 or more
The qty switcher styles were nested inside `.catalog-product-view`, but the
Ajaxpro quick view renders the same product markup on category and search
pages, where that body class is absent. In the popup the input therefore fell
back to breeze's `max-width: 4rem` (64px) while keeping the legacy
`padding: 5px 34px 5px 15px` — leaving an 11px content box. At 18px font that
is one digit, so every value >= 10 rendered as its first digit only.
Scope both the input sizing and the switcher arrows to
`.product-info-wrapper` instead, and drop the 34px right padding: it reserved
room for an in-input arrow design that no longer exists — the arrows are
absolutely positioned outside the field. Verified on the live dev store:
content box goes 11px -> 64px, `scrollWidth` no longer exceeds `clientWidth`
for 10, 20 or 100.
The padding rule is now specific enough (0,4,0) to win over the stale
`.product-info-main .input-text.qty { padding: 5px 34px 5px 15px }` still
present in the theme editor CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 439222
-
-
v1.8.0
-
Version v1.8.0 dcc43a
-
feat(faq): card-style two-column FAQ accordion like reference design
Turn the RichSnippets FAQ accordion into white rounded cards laid out
in a two-column grid on desktop (single column on mobile), matching
the stigefabrikken.dk reference more closely.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> 0cf16e -
feat(faq): style KB listing and RichSnippets FAQ widgets via CSS vars
Override CSS custom properties of Swissup_KnowledgeBase listing widget
(--kb-faq-*, module >= 1.1.44) and Swissup_RichSnippets FAQ accordion
(--faqs-*) to match the stigefabrikken.dk reference design: bottom
borders between items, semibold questions, accent-color hover.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> c0f2c4
-
-
head
-
Installer. Assign one banner to multiple placeholders 721496
-
Revert "update Marketplace installer commad to assign existing banner to the placeholder."
This reverts commit 054e032d04fd358089648872fb47cccbdf1da3f4. c9476e -
Revert "update Marketplace installer commad to assign existing banner to the placeholder."
This reverts commit 054e032d04fd358089648872fb47cccbdf1da3f4. a464ee -
update Marketplace installer commad to assign existing banner to the placeholder. 054e03
-
-
1.0.80
-
1.0.79