@blamejs/blamejs-shop 0.3.14 → 0.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/lib/asset-manifest.json +3 -3
- package/lib/storefront.js +41 -26
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.3.x
|
|
10
10
|
|
|
11
|
+
- v0.3.15 (2026-05-30) — **Product image galleries are interactive — click a thumbnail to view that image full size.** A product page's image gallery now lets a shopper switch between the product's images: clicking a thumbnail swaps the main image, with no JavaScript required. Previously the thumbnail strip was decorative — the additional images were unreachable, a single-image product showed empty placeholder slots, and only the first four images were shown. Now the main image stacks the product's images and the thumbnails are real, keyboard-accessible controls that select which one is shown; a product with one image shows just that image (no empty slots), and the four-image cap is raised to twelve so far more images are reachable. The picker is pure HTML and CSS, so it works without scripting and degrades cleanly. **Fixed:** *The product image gallery actually switches images now* — Thumbnails on the product page are now interactive: selecting one shows that image as the main image, using a no-JavaScript radio/label control. The empty placeholder slots a single-image product used to show are gone, the four-image cap is raised to twelve so far more images are reachable, and the strip is keyboard-navigable with accessible labels. The edge and container renderers emit identical markup.
|
|
12
|
+
|
|
11
13
|
- v0.3.14 (2026-05-30) — **Drop the inert Document-Policy header that only produced browser warnings.** Container-rendered pages were sending a Document-Policy response header whose feature tokens (document-write, unsized-media, oversized-images) are no longer recognized by current browsers — so it enforced nothing while making the browser log a warning for each token on every page. The header is now omitted; every other security header (Content-Security-Policy, Permissions-Policy, HSTS, X-Frame-Options, the COOP/CORP set, and the rest) is unchanged. The edge and container paths now agree — neither sends Document-Policy — closing a header-parity gap. **Fixed:** *No more Document-Policy console warnings* — The Document-Policy header is no longer emitted on container-rendered pages. Its tokens were rejected by current browsers (the policy applied nothing and logged a warning per token on every page), so the header is dropped rather than shipped inert; the full set of other security headers is retained unchanged. Edge and container responses are now consistent.
|
|
12
14
|
|
|
13
15
|
- v0.3.13 (2026-05-30) — **Updated runtime hardens the storage and audit layers; admin error pages no longer show raw database text.** The vendored blamejs runtime is updated to v0.14.7, which tightens the data and audit layers the shop is built on: database queries are checked against each table's declared columns (a reference to an undeclared column fails closed), the database encryption key is bound to its deployment so a copied key can't be unsealed elsewhere, raw SQL fragments refuse embedded string literals so values bind through placeholders, audit-chain purges can require two authorizers, and breach-notification deadlines raise a running clock. Separately, when an admin form action fails the console now shows a short generic message with the correct status instead of rendering a raw database or parser string into its error banner. No operator action is required. **Changed:** *Vendored runtime updated to v0.14.7 (storage + audit hardening)* — The underlying runtime now checks every database query against the table's declared columns and fails closed on an undeclared reference, binds the database encryption key to its data directory and key path so a relocated key won't unseal, refuses embedded string literals in raw SQL fragments, supports two-authorizer dual control on audit-chain purges, can compute sealed-column lookup hashes as a keyed MAC, and ships a running clock for breach-notification deadlines. The shop inherits these protections; no configuration change is needed. **Fixed:** *Console error banners show a safe message, not internal text* — Every admin HTML form handler now routes a thrown error through one shared classifier: a duplicate key becomes a 409 "That value is already in use.", a missing referenced record a generic not-found, malformed input a generic "Invalid input.", and an unexpected failure a generic message whose detail is recorded server-side rather than shown. Operator-facing validation messages are unchanged. This closes the path where a create form could render a raw database constraint string into its error banner, matching the API behavior the bearer path already had.
|
package/lib/asset-manifest.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.3.
|
|
2
|
+
"version": "0.3.15",
|
|
3
3
|
"assets": {
|
|
4
4
|
"css/admin.css": {
|
|
5
5
|
"integrity": "sha384-1SIn6oAf1DjECbRfKENZasdKHJiywGdXR58wn0hsFGcdVHzUmvfgMkEz5ANIAZJ3",
|
|
6
6
|
"fingerprinted": "css/admin.72c808274ebcefd9.css"
|
|
7
7
|
},
|
|
8
8
|
"css/main.css": {
|
|
9
|
-
"integrity": "sha384-
|
|
10
|
-
"fingerprinted": "css/main.
|
|
9
|
+
"integrity": "sha384-kbtcIsmD7X/DDsTJIYQ49JexBKXkz/GU1aQ8430XEyHHW/hcOPMWqbgZ7wRqvsbT",
|
|
10
|
+
"fingerprinted": "css/main.37860144b12a94fe.css"
|
|
11
11
|
},
|
|
12
12
|
"js/announcement.js": {
|
|
13
13
|
"integrity": "sha384-z4zcEMn+tScoVnYRE4nEf8N/oyvpxdpaxTNrT4QO/jURChid4+qjAvWkzatCaAPq",
|
package/lib/storefront.js
CHANGED
|
@@ -1709,42 +1709,57 @@ var PRODUCT_PAGE =
|
|
|
1709
1709
|
"RAW_RELATED_PLACEHOLDER";
|
|
1710
1710
|
|
|
1711
1711
|
// PDP gallery markup — composed once per render call from the
|
|
1712
|
-
// product's media rows.
|
|
1713
|
-
//
|
|
1714
|
-
//
|
|
1715
|
-
//
|
|
1712
|
+
// product's media rows. A no-JS, CSS-`:checked` picker: every media row
|
|
1713
|
+
// is rendered both as a hidden radio + a stacked main `<img>` (only the
|
|
1714
|
+
// `:checked` radio's image is visible) and as a `<label for>` thumbnail
|
|
1715
|
+
// (clicking/keyboard-activating it checks the radio → CSS swaps the
|
|
1716
|
+
// visible image). The first image is selected on load. A single-image
|
|
1717
|
+
// product renders just that image with no thumbnail strip; when media is
|
|
1718
|
+
// absent the gallery falls back to the letter-mark placeholder so a
|
|
1716
1719
|
// freshly-seeded product never renders an empty square.
|
|
1720
|
+
//
|
|
1721
|
+
// Byte-identical to the edge renderer (`worker/render/product.js
|
|
1722
|
+
// #_buildPdpGallery`) — keep the two in sync on every change.
|
|
1717
1723
|
function _buildPdpGallery(product, media, assetPrefix) {
|
|
1718
1724
|
var prefix = assetPrefix || "/assets/";
|
|
1719
1725
|
function _escAttr(s) { return b.template.escapeHtml(s); }
|
|
1720
1726
|
if (!media || media.length === 0) {
|
|
1721
1727
|
return "<figure class=\"pdp__media pdp__media--placeholder\" aria-hidden=\"true\">" +
|
|
1722
1728
|
"<svg class=\"media-ph__svg\" viewBox=\"0 0 240 240\" aria-hidden=\"true\"><rect width=\"240\" height=\"240\" fill=\"none\"/><g stroke=\"currentColor\" stroke-opacity=\"0.10\" stroke-width=\"1\"><path d=\"M0 40 H240 M0 80 H240 M0 120 H240 M0 160 H240 M0 200 H240 M40 0 V240 M80 0 V240 M120 0 V240 M160 0 V240 M200 0 V240\"/></g><g fill=\"none\" stroke=\"#AD38DB\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M120 64 L162 80 L162 122 C162 152 144 168 120 178 C96 168 78 152 78 122 L78 80 Z\" stroke=\"#732A8D\" stroke-width=\"2.4\"/><path d=\"M120 92 L146 105 L146 134 L120 147 L94 134 L94 105 Z\"/><path d=\"M94 105 L120 118 L146 105 M120 118 V147\" stroke=\"#732A8D\" stroke-width=\"2.4\"/><path d=\"M107 101 L112 105 L107 109\" stroke=\"currentColor\" stroke-width=\"2.4\"/><path d=\"M116 110 H128\" stroke=\"currentColor\" stroke-width=\"2.4\"/></g><text x=\"120\" y=\"208\" text-anchor=\"middle\" font-family=\"ui-monospace,Menlo,Consolas,monospace\" font-size=\"12\" letter-spacing=\"2\" fill=\"#6b6b78\">no image yet</text></svg>" +
|
|
1723
|
-
"</figure>"
|
|
1724
|
-
"<ul class=\"pdp__thumbs\" aria-hidden=\"true\">" +
|
|
1725
|
-
"<li class=\"is-active\"></li><li></li><li></li><li></li>" +
|
|
1726
|
-
"</ul>";
|
|
1729
|
+
"</figure>";
|
|
1727
1730
|
}
|
|
1728
1731
|
var hero = media[0];
|
|
1729
|
-
var heroUrl = prefix + hero.r2_key;
|
|
1730
1732
|
var heroAlt = hero.alt_text || product.title || "Product image";
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
//
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
var thumbs =
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
var
|
|
1743
|
-
var
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1733
|
+
// The CSS picker (main.css) maps :checked radios through nth-of-type(12),
|
|
1734
|
+
// so render at most that many — a thumbnail that checked a radio with no
|
|
1735
|
+
// matching visibility rule would blank the gallery. Twelve covers any
|
|
1736
|
+
// realistic product; keep this in lockstep with the rule count in main.css.
|
|
1737
|
+
var shown = media.length < 12 ? media.length : 12;
|
|
1738
|
+
var radios = "";
|
|
1739
|
+
var imgs = "";
|
|
1740
|
+
var thumbs = "";
|
|
1741
|
+
for (var i = 0; i < shown; i += 1) {
|
|
1742
|
+
var m = media[i];
|
|
1743
|
+
var url = prefix + m.r2_key;
|
|
1744
|
+
var id = "pdp-img-" + i;
|
|
1745
|
+
var checked = i === 0 ? " checked" : "";
|
|
1746
|
+
var alt = i === 0 ? heroAlt : (m.alt_text || product.title || "Product image");
|
|
1747
|
+
var loading = i === 0 ? "eager" : "lazy";
|
|
1748
|
+
radios += "<input class=\"pdp__radio\" type=\"radio\" name=\"pdp-img\" id=\"" + _escAttr(id) + "\"" + checked + ">";
|
|
1749
|
+
imgs += "<img class=\"pdp__img\" src=\"" + _escAttr(url) + "\" alt=\"" + _escAttr(alt) + "\" loading=\"" + loading + "\">";
|
|
1750
|
+
thumbs += "<li>" +
|
|
1751
|
+
"<label class=\"pdp__thumb\" for=\"" + _escAttr(id) + "\">" +
|
|
1752
|
+
"<img src=\"" + _escAttr(url) + "\" alt=\"\">" +
|
|
1753
|
+
"<span class=\"sr-only\">Show image " + (i + 1) + "</span>" +
|
|
1754
|
+
"</label>" +
|
|
1755
|
+
"</li>";
|
|
1756
|
+
}
|
|
1757
|
+
// Radios are siblings of the figure + thumbnail list (not nested), so a
|
|
1758
|
+
// checked radio can reach both the stacked images and the matching
|
|
1759
|
+
// thumbnail through the general-sibling combinator in CSS.
|
|
1760
|
+
var figure = "<figure class=\"pdp__media pdp__media--image pdp__media--gallery\">" + imgs + "</figure>";
|
|
1761
|
+
if (shown === 1) return radios + figure;
|
|
1762
|
+
return radios + figure + "<ul class=\"pdp__thumbs\">" + thumbs + "</ul>";
|
|
1748
1763
|
}
|
|
1749
1764
|
|
|
1750
1765
|
// Accessible star glyph row — the precise figure rides in a visually-
|
package/package.json
CHANGED