@tokenoftrust/storefront-runner 2.4.36 → 2.4.38
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/apps/storefront/src/components/islands/CheckoutComplianceGate.tsx +7 -3
- package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +22 -1
- package/apps/storefront/src/components/islands/MobileNav.tsx +3 -1
- package/package.json +1 -1
- package/packages/public-runtime/src/activity/catalog.ts +3 -3
|
@@ -375,17 +375,20 @@ export default function CheckoutComplianceGate({ preflightUrl, minAge }: Props)
|
|
|
375
375
|
|
|
376
376
|
return (
|
|
377
377
|
<div
|
|
378
|
-
class="fixed inset-0 z-[110]
|
|
378
|
+
class="fixed inset-0 z-[110] bg-[color-mix(in_srgb,var(--color-text)_75%,transparent)] backdrop-blur-sm"
|
|
379
379
|
role="dialog"
|
|
380
380
|
aria-modal="true"
|
|
381
381
|
aria-labelledby="cgate-title"
|
|
382
382
|
onClick={(e) => {
|
|
383
|
-
if (e.target === e.currentTarget) close();
|
|
383
|
+
if (e.target === e.currentTarget || (e.target as HTMLElement).hasAttribute("data-viewport-frame")) close();
|
|
384
384
|
}}
|
|
385
385
|
>
|
|
386
|
+
{/* The fixed box is the LAYOUT viewport, which content wider than a phone
|
|
387
|
+
stretches; place the dialog in a frame sized to what the visitor sees. */}
|
|
388
|
+
<div data-viewport-frame class="absolute left-0 top-0 flex h-[100svh] w-[100vw] overflow-y-auto p-5">
|
|
386
389
|
<div
|
|
387
390
|
ref={dialogRef}
|
|
388
|
-
class="w-[min(32rem,100%)] rounded-[var(--radius-lg)] bg-[var(--color-surface)] p-7 text-left shadow-[var(--shadow-lg)]"
|
|
391
|
+
class="m-auto w-[min(32rem,100%)] rounded-[var(--radius-lg)] bg-[var(--color-surface)] p-7 text-left shadow-[var(--shadow-lg)]"
|
|
389
392
|
>
|
|
390
393
|
<p class="eyebrow eyebrow--tick mb-3 w-fit">Compliance check</p>
|
|
391
394
|
<h2 id="cgate-title" class="display-sm">
|
|
@@ -606,6 +609,7 @@ export default function CheckoutComplianceGate({ preflightUrl, minAge }: Props)
|
|
|
606
609
|
</button>
|
|
607
610
|
</div>
|
|
608
611
|
</div>
|
|
612
|
+
</div>
|
|
609
613
|
</div>
|
|
610
614
|
);
|
|
611
615
|
}
|
|
@@ -118,6 +118,7 @@ export function isolateAgeGateBackground(host: HTMLElement): BackgroundIsolation
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
const HOST_MARKER = "data-tot-compliance";
|
|
121
|
+
const HOST_MOVES_PER_TASK = 20;
|
|
121
122
|
let mountedHost: HTMLElement | null = null;
|
|
122
123
|
const HOST_STYLE_FLOOR = {
|
|
123
124
|
all: "initial",
|
|
@@ -221,6 +222,25 @@ export function mountIsolatedAgeGate({ minAge, brand, tenantId, presentation }:
|
|
|
221
222
|
// writes exactly what this observer watches, so it runs disconnected: a repair
|
|
222
223
|
// that is not perfectly idempotent can never re-trigger itself and spin the
|
|
223
224
|
// main thread in an unbounded microtask loop.
|
|
225
|
+
//
|
|
226
|
+
// Another script that also keeps itself last in <body> would answer every move
|
|
227
|
+
// with one of its own, and two observers can ping-pong forever without either
|
|
228
|
+
// re-triggering itself. Moves are budgeted per task; past the budget the repair
|
|
229
|
+
// yields to the event loop and resumes on the next task.
|
|
230
|
+
let active = true;
|
|
231
|
+
let moves = 0;
|
|
232
|
+
let resumeScheduled = false;
|
|
233
|
+
const spendMove = () => {
|
|
234
|
+
if (!resumeScheduled) {
|
|
235
|
+
resumeScheduled = true;
|
|
236
|
+
setTimeout(() => {
|
|
237
|
+
resumeScheduled = false;
|
|
238
|
+
moves = 0;
|
|
239
|
+
if (active) ensure();
|
|
240
|
+
}, 0);
|
|
241
|
+
}
|
|
242
|
+
return ++moves <= HOST_MOVES_PER_TASK;
|
|
243
|
+
};
|
|
224
244
|
const observeHost = () => {
|
|
225
245
|
observer.observe(document.body, { childList: true });
|
|
226
246
|
observer.observe(host, {
|
|
@@ -231,7 +251,7 @@ export function mountIsolatedAgeGate({ minAge, brand, tenantId, presentation }:
|
|
|
231
251
|
const ensure = () => {
|
|
232
252
|
observer.disconnect();
|
|
233
253
|
try {
|
|
234
|
-
if (!host.isConnected || document.body.lastElementChild !== host) {
|
|
254
|
+
if ((!host.isConnected || document.body.lastElementChild !== host) && spendMove()) {
|
|
235
255
|
// Moving the host blurs whatever the gate had focused; give it back so a
|
|
236
256
|
// keyboard visitor is never stranded by a script appending to <body>.
|
|
237
257
|
const focused = shadow.activeElement as HTMLElement | null;
|
|
@@ -249,6 +269,7 @@ export function mountIsolatedAgeGate({ minAge, brand, tenantId, presentation }:
|
|
|
249
269
|
observeHost();
|
|
250
270
|
|
|
251
271
|
return () => {
|
|
272
|
+
active = false;
|
|
252
273
|
observer.disconnect();
|
|
253
274
|
stylesheetObserver.disconnect();
|
|
254
275
|
backgroundIsolation?.release();
|
|
@@ -167,7 +167,9 @@ export default function MobileNav({ nav, announcement, currentPath }: Props) {
|
|
|
167
167
|
|
|
168
168
|
{open && (
|
|
169
169
|
<div
|
|
170
|
-
|
|
170
|
+
// Sized to what the visitor sees, not `inset-0`: on a phone the fixed box is the
|
|
171
|
+
// layout viewport, which wide page content stretches past the screen's edge.
|
|
172
|
+
class="fixed left-0 top-0 z-50 h-[100dvh] w-[100vw]"
|
|
171
173
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
|
172
174
|
onKeyDown={onKeyDown}
|
|
173
175
|
>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.38",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
|
|
6
6
|
"packageManager": "pnpm@11.9.0",
|
|
@@ -150,7 +150,7 @@ export const ACTION_CATALOG = {
|
|
|
150
150
|
},
|
|
151
151
|
"preview.build.completed": {
|
|
152
152
|
actors: ["system"], source: "server", sinks: LIFECYCLE,
|
|
153
|
-
argsAllow: ["changeId", "result"], renderedAllow: [],
|
|
153
|
+
argsAllow: ["changeId", "result", "prNumber", "headSource", "forgeOpenVerified", "terminalSource"], renderedAllow: [],
|
|
154
154
|
desc: "A preview build finished (result: ok|failed).",
|
|
155
155
|
},
|
|
156
156
|
"preview.override": {
|
|
@@ -176,7 +176,7 @@ export const ACTION_CATALOG = {
|
|
|
176
176
|
// A reconcile reached a terminal state (reconciled|blocked|failed). Low
|
|
177
177
|
// volume, high value → timelined for the ops UI.
|
|
178
178
|
actors: ["system"], source: "server", sinks: LIFECYCLE,
|
|
179
|
-
argsAllow: ["changeId", "result", "evidenceStatus", "evidencePushError"], renderedAllow: [],
|
|
179
|
+
argsAllow: ["changeId", "result", "evidenceStatus", "evidencePushError", "prNumber", "forgeOpenVerified", "terminalSource"], renderedAllow: [],
|
|
180
180
|
desc: "A candidate reconcile reached a terminal state.",
|
|
181
181
|
},
|
|
182
182
|
"candidate.closed": {
|
|
@@ -185,7 +185,7 @@ export const ACTION_CATALOG = {
|
|
|
185
185
|
// unmerged), so the row leaves the operator's review queue. Low volume (one
|
|
186
186
|
// per PR close), high value — the end-of-life marker for a candidate's timeline.
|
|
187
187
|
actors: ["system"], source: "server", sinks: LIFECYCLE,
|
|
188
|
-
argsAllow: ["changeId", "merged", "result", "status"], renderedAllow: [],
|
|
188
|
+
argsAllow: ["changeId", "merged", "result", "status", "prNumber"], renderedAllow: [],
|
|
189
189
|
desc: "A merged/closed PR webhook transitioned a candidate to a terminal state.",
|
|
190
190
|
},
|
|
191
191
|
"prebuild.dispatched": {
|