@syntrologie/adapt-reviews 2.55.1 → 2.56.0
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/dist/cdn.js +2 -1
- package/dist/cdn.js.map +1 -1
- package/dist/chunk-IGIU6S7P.js +4051 -0
- package/dist/chunk-IGIU6S7P.js.map +7 -0
- package/dist/{chunk-ATGQNO24.js → chunk-M64YCKZP.js} +614 -15
- package/dist/chunk-M64YCKZP.js.map +7 -0
- package/dist/reviewsFlip.d.ts +10 -0
- package/dist/reviewsFlip.d.ts.map +1 -1
- package/dist/runtime.d.ts +2 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +2 -1
- package/dist/schema.js +13 -10
- package/dist/schema.js.map +1 -1
- package/package.json +2 -3
- package/dist/chunk-ATGQNO24.js.map +0 -7
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
external_exports
|
|
3
|
+
} from "./chunk-IGIU6S7P.js";
|
|
4
|
+
|
|
1
5
|
// src/reviewDisplay.ts
|
|
2
6
|
import { html, LitElement, nothing } from "lit";
|
|
3
7
|
import { styleMap } from "lit/directives/style-map.js";
|
|
@@ -167,7 +171,7 @@ var SyntroReviews = class extends LitElement {
|
|
|
167
171
|
const rootStyles = {
|
|
168
172
|
display: "block",
|
|
169
173
|
width: "100%",
|
|
170
|
-
color: "var(--sc-tile-text, var(--sc-overlay-text-color, #111827))",
|
|
174
|
+
color: "var(--sc-tile-text-color, var(--sc-overlay-text-color, #111827))",
|
|
171
175
|
fontFamily: "var(--sc-font-family, inherit)"
|
|
172
176
|
};
|
|
173
177
|
return html`
|
|
@@ -229,7 +233,7 @@ var SyntroReviews = class extends LitElement {
|
|
|
229
233
|
_panelStyles() {
|
|
230
234
|
return {
|
|
231
235
|
boxSizing: "border-box",
|
|
232
|
-
border: "1px solid var(--sc-
|
|
236
|
+
border: "1px solid var(--sc-content-divider-color, rgba(148, 163, 184, 0.24))",
|
|
233
237
|
borderRadius: "8px",
|
|
234
238
|
background: "var(--sc-tile-background, #ffffff)",
|
|
235
239
|
padding: "14px",
|
|
@@ -258,13 +262,13 @@ var SyntroReviews = class extends LitElement {
|
|
|
258
262
|
display: "inline-flex",
|
|
259
263
|
alignItems: "center",
|
|
260
264
|
gap: "4px",
|
|
261
|
-
color: "var(--sc-accent,
|
|
265
|
+
color: "hsl(var(--sc-accent-color, 221 83% 53%))",
|
|
262
266
|
whiteSpace: "nowrap"
|
|
263
267
|
};
|
|
264
268
|
}
|
|
265
269
|
_reviewStyles() {
|
|
266
270
|
return {
|
|
267
|
-
borderTop: "1px solid var(--sc-
|
|
271
|
+
borderTop: "1px solid var(--sc-content-divider-color, rgba(148, 163, 184, 0.2))",
|
|
268
272
|
paddingTop: "10px"
|
|
269
273
|
};
|
|
270
274
|
}
|
|
@@ -280,7 +284,7 @@ var SyntroReviews = class extends LitElement {
|
|
|
280
284
|
}
|
|
281
285
|
_starsStyles() {
|
|
282
286
|
return {
|
|
283
|
-
color: "var(--sc-accent,
|
|
287
|
+
color: "hsl(var(--sc-accent-color, 221 83% 53%))",
|
|
284
288
|
fontSize: "13px",
|
|
285
289
|
lineHeight: "18px",
|
|
286
290
|
whiteSpace: "nowrap"
|
|
@@ -295,7 +299,7 @@ var SyntroReviews = class extends LitElement {
|
|
|
295
299
|
}
|
|
296
300
|
_metaStyles() {
|
|
297
301
|
return {
|
|
298
|
-
color: "var(--sc-
|
|
302
|
+
color: "var(--sc-content-text-secondary-color, #64748b)",
|
|
299
303
|
fontSize: "12px",
|
|
300
304
|
lineHeight: "18px"
|
|
301
305
|
};
|
|
@@ -303,7 +307,7 @@ var SyntroReviews = class extends LitElement {
|
|
|
303
307
|
_stateStyles() {
|
|
304
308
|
return {
|
|
305
309
|
marginTop: "8px",
|
|
306
|
-
color: "var(--sc-
|
|
310
|
+
color: "var(--sc-content-text-secondary-color, #64748b)",
|
|
307
311
|
fontSize: "14px",
|
|
308
312
|
lineHeight: "20px"
|
|
309
313
|
};
|
|
@@ -403,8 +407,8 @@ var FlipController = class {
|
|
|
403
407
|
this.animating = false;
|
|
404
408
|
this.dir = "in";
|
|
405
409
|
this.settleTimer = null;
|
|
406
|
-
this.onAnimationEnd = (
|
|
407
|
-
if (
|
|
410
|
+
this.onAnimationEnd = (e2) => {
|
|
411
|
+
if (e2.animationName === `${this.keyframePrefix}-in` || e2.animationName === `${this.keyframePrefix}-out`) {
|
|
408
412
|
this.settle();
|
|
409
413
|
}
|
|
410
414
|
};
|
|
@@ -506,6 +510,542 @@ var FlipController = class {
|
|
|
506
510
|
}
|
|
507
511
|
};
|
|
508
512
|
|
|
513
|
+
// ../../../node_modules/@lit/context/lib/create-context.js
|
|
514
|
+
function n(n2) {
|
|
515
|
+
return n2;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// ../../sdk-contracts/dist/canvas-context.js
|
|
519
|
+
var canvasRuntimeContext = n("syntrologie:canvas-runtime");
|
|
520
|
+
|
|
521
|
+
// ../../sdk-contracts/dist/telemetry-events.js
|
|
522
|
+
var entry = (e2) => e2;
|
|
523
|
+
var TELEMETRY_EVENTS = {
|
|
524
|
+
// --- intervention metrics (April 2026) — wire names unchanged, all plan-scope
|
|
525
|
+
syntro_config_served: entry({
|
|
526
|
+
subject: "config",
|
|
527
|
+
verb: "served",
|
|
528
|
+
scope: "plan",
|
|
529
|
+
props: ["tiles", "actions"]
|
|
530
|
+
}),
|
|
531
|
+
syntro_intervention_seen: entry({
|
|
532
|
+
subject: "intervention",
|
|
533
|
+
verb: "seen",
|
|
534
|
+
scope: "plan",
|
|
535
|
+
props: ["intervention_id", "intervention_kind"]
|
|
536
|
+
}),
|
|
537
|
+
syntro_intervention_triggered: entry({
|
|
538
|
+
subject: "intervention",
|
|
539
|
+
verb: "triggered",
|
|
540
|
+
scope: "plan",
|
|
541
|
+
props: ["intervention_id", "intervention_kind"]
|
|
542
|
+
}),
|
|
543
|
+
syntro_intervention_interacted: entry({
|
|
544
|
+
subject: "intervention",
|
|
545
|
+
verb: "interacted",
|
|
546
|
+
scope: "plan",
|
|
547
|
+
props: ["intervention_id", "intervention_kind", "interaction_type"]
|
|
548
|
+
}),
|
|
549
|
+
// --- concierge (FEAT-1789150184) ---------------------------------------
|
|
550
|
+
syntro_concierge_opened: entry({
|
|
551
|
+
subject: "concierge",
|
|
552
|
+
verb: "opened",
|
|
553
|
+
scope: "open",
|
|
554
|
+
props: ["trigger", "page_path"]
|
|
555
|
+
// trigger: ConciergeOpenTrigger
|
|
556
|
+
}),
|
|
557
|
+
syntro_concierge_closed: entry({
|
|
558
|
+
subject: "concierge",
|
|
559
|
+
verb: "closed",
|
|
560
|
+
scope: "open",
|
|
561
|
+
props: ["duration_ms", "reason", "messages_sent", "tiles_dismissed", "pages_viewed"]
|
|
562
|
+
// reason: ConciergeCloseReason
|
|
563
|
+
}),
|
|
564
|
+
/**
|
|
565
|
+
* The shopper asked to leave the canvas by a link. Emitted on intent, before
|
|
566
|
+
* the host answers, so refused or denied opens are counted too (parity with
|
|
567
|
+
* syntro_concierge_closed reason navigation).
|
|
568
|
+
*
|
|
569
|
+
* On a merchant's own page a link tap closes the canvas, and
|
|
570
|
+
* `syntro_concierge_closed { reason: 'navigation' }` is the row that
|
|
571
|
+
* records it. Inside a chat app's card the frame is sandboxed: the canvas
|
|
572
|
+
* cannot navigate, does not close, and asks the host to open the link
|
|
573
|
+
* instead, so that close row never fires. This is its counterpart, and it
|
|
574
|
+
* carries the same `reason` so one query covers both surfaces.
|
|
575
|
+
*/
|
|
576
|
+
syntro_link_opened: entry({
|
|
577
|
+
subject: "link",
|
|
578
|
+
verb: "opened",
|
|
579
|
+
scope: "open",
|
|
580
|
+
props: ["kind", "reason"]
|
|
581
|
+
// kind: LinkOpenKind, reason: ConciergeCloseReason
|
|
582
|
+
}),
|
|
583
|
+
/**
|
|
584
|
+
* A store's product page opened inside a chat-app card instead of leaving
|
|
585
|
+
* through the host (the card's product view). `product` is the store's
|
|
586
|
+
* handle, never the display name; a rail switch is a new open. Its
|
|
587
|
+
* counterpart when the shopper goes back to the conversation's picks is
|
|
588
|
+
* syntro_product_view_closed.
|
|
589
|
+
*
|
|
590
|
+
* Plan scope, like the card's own syntro_card_lifecycle_seen: an inline
|
|
591
|
+
* card holds an open scope only while the host has it fullscreen, and a
|
|
592
|
+
* view opened from a tile fires before the host answers that request (or
|
|
593
|
+
* after it refused), so an open-scope row here would be dropped. The core
|
|
594
|
+
* still stamps surface and open_id whenever an open is live.
|
|
595
|
+
*/
|
|
596
|
+
syntro_product_view_opened: entry({
|
|
597
|
+
subject: "product_view",
|
|
598
|
+
verb: "opened",
|
|
599
|
+
scope: "plan",
|
|
600
|
+
props: ["product", "from"]
|
|
601
|
+
// from: ProductViewOpenSource
|
|
602
|
+
}),
|
|
603
|
+
syntro_product_view_closed: entry({
|
|
604
|
+
subject: "product_view",
|
|
605
|
+
verb: "closed",
|
|
606
|
+
scope: "plan",
|
|
607
|
+
props: ["product"]
|
|
608
|
+
}),
|
|
609
|
+
syntro_deck_paged: entry({
|
|
610
|
+
subject: "deck",
|
|
611
|
+
verb: "paged",
|
|
612
|
+
scope: "open",
|
|
613
|
+
props: ["from_index", "to_index", "method"]
|
|
614
|
+
// method: DeckPageMethod
|
|
615
|
+
}),
|
|
616
|
+
syntro_tile_dismissed: entry({
|
|
617
|
+
subject: "tile",
|
|
618
|
+
verb: "dismissed",
|
|
619
|
+
scope: "open",
|
|
620
|
+
props: ["tile_id", "tile_kind", "visible_ms"]
|
|
621
|
+
}),
|
|
622
|
+
// A tile's own data load finished: the network wait between the concierge
|
|
623
|
+
// mounting a tile and the shopper seeing its content (BUG-1784767817 was a
|
|
624
|
+
// 30 s reviews hang here). `outcome` is 'ok' | 'error'; `requested` is how
|
|
625
|
+
// many items the tile asked for, `received` how many came back.
|
|
626
|
+
syntro_tile_data_received: entry({
|
|
627
|
+
subject: "tile_data",
|
|
628
|
+
verb: "received",
|
|
629
|
+
scope: "open",
|
|
630
|
+
props: ["tile_id", "tile_kind", "total_ms", "outcome", "requested", "received"]
|
|
631
|
+
}),
|
|
632
|
+
// The three chat atoms are OPEN-scope: they carry `surface` / `open_id` and
|
|
633
|
+
// only make sense inside a concierge open. An INLINE chat bar (no concierge)
|
|
634
|
+
// still dispatches them on every turn, and the core drops each one and
|
|
635
|
+
// increments the `telemetry_dropped` health counter. That is EXPECTED on an
|
|
636
|
+
// inline-chat host page, not a bug — `telemetry_dropped` there mixes "real
|
|
637
|
+
// defect" with "inline chat has no open scope", so do not alert on it alone.
|
|
638
|
+
syntro_chat_message_sent: entry({
|
|
639
|
+
subject: "chat_message",
|
|
640
|
+
verb: "sent",
|
|
641
|
+
scope: "open",
|
|
642
|
+
props: ["turn", "chars", "via"]
|
|
643
|
+
// via: ChatSendVia
|
|
644
|
+
}),
|
|
645
|
+
syntro_chat_reply_received: entry({
|
|
646
|
+
subject: "chat_reply",
|
|
647
|
+
verb: "received",
|
|
648
|
+
scope: "open",
|
|
649
|
+
// mount_tools: the mount_* tool name of every mount this reply fired, in order (two product cards appear twice).
|
|
650
|
+
props: ["turn", "ttft_ms", "total_ms", "mounts", "cards", "mount_tools"]
|
|
651
|
+
}),
|
|
652
|
+
syntro_chat_interrupted: entry({
|
|
653
|
+
subject: "chat",
|
|
654
|
+
verb: "interrupted",
|
|
655
|
+
scope: "open",
|
|
656
|
+
props: ["turn", "after_ms"]
|
|
657
|
+
}),
|
|
658
|
+
syntro_chat_maximize_toggled: entry({
|
|
659
|
+
subject: "chat_maximize",
|
|
660
|
+
verb: "toggled",
|
|
661
|
+
scope: "open",
|
|
662
|
+
props: ["maximized"]
|
|
663
|
+
}),
|
|
664
|
+
syntro_chat_takeover_triggered: entry({
|
|
665
|
+
subject: "chat_takeover",
|
|
666
|
+
verb: "triggered",
|
|
667
|
+
scope: "open",
|
|
668
|
+
props: ["turn"]
|
|
669
|
+
}),
|
|
670
|
+
// --- search takeover (2026-09-17) ---------------------------------------
|
|
671
|
+
// All plan-scope: the search interceptor fires before any concierge open
|
|
672
|
+
// exists. No prop here ever carries query text (SEE the contract test's
|
|
673
|
+
// no-text-prop rule) — classification/signal_hits are the interceptor's
|
|
674
|
+
// own derived signals, never the string itself.
|
|
675
|
+
syntro_search_sent: entry({
|
|
676
|
+
subject: "search",
|
|
677
|
+
verb: "sent",
|
|
678
|
+
scope: "plan",
|
|
679
|
+
props: ["classification", "word_count", "signal_hits", "entry_point", "platform"]
|
|
680
|
+
// classification: SearchClassification, entry_point: SearchEntryPoint
|
|
681
|
+
}),
|
|
682
|
+
// `ms_to_skeleton` is intentionally the only timing prop: it is always
|
|
683
|
+
// knowable the instant the surface mounts. `ms_to_first_content` is NOT
|
|
684
|
+
// declared here (revision 1 defect) because a surface that never painted
|
|
685
|
+
// content still fires this event, and a required prop it cannot supply
|
|
686
|
+
// would make TelemetryCore.emit drop exactly the row worth measuring.
|
|
687
|
+
syntro_search_surface_seen: entry({
|
|
688
|
+
subject: "search_surface",
|
|
689
|
+
verb: "seen",
|
|
690
|
+
scope: "plan",
|
|
691
|
+
props: ["variant", "classification", "result_count", "ms_to_skeleton"]
|
|
692
|
+
// variant: SearchVariant, classification: SearchClassification
|
|
693
|
+
}),
|
|
694
|
+
syntro_search_result_tapped: entry({
|
|
695
|
+
subject: "search_result",
|
|
696
|
+
verb: "tapped",
|
|
697
|
+
scope: "plan",
|
|
698
|
+
props: ["variant", "region_id", "module", "position"]
|
|
699
|
+
// variant: SearchVariant
|
|
700
|
+
}),
|
|
701
|
+
syntro_search_followup_sent: entry({
|
|
702
|
+
subject: "search_followup",
|
|
703
|
+
verb: "sent",
|
|
704
|
+
scope: "plan",
|
|
705
|
+
props: ["delta_kind", "variant"]
|
|
706
|
+
// delta_kind: SearchDeltaKind, variant: SearchVariant
|
|
707
|
+
}),
|
|
708
|
+
// Declares `ms_since_boot` (not a "since surface mounted" timing prop) so a
|
|
709
|
+
// not-ready fallback the takeover never rendered is still measurable —
|
|
710
|
+
// revision 1 emitted nothing on this path.
|
|
711
|
+
syntro_search_native_served: entry({
|
|
712
|
+
subject: "search_native",
|
|
713
|
+
verb: "served",
|
|
714
|
+
scope: "plan",
|
|
715
|
+
props: ["reason", "platform", "ms_since_boot"]
|
|
716
|
+
// reason: SearchFallbackReason
|
|
717
|
+
}),
|
|
718
|
+
syntro_search_takeover_dismissed: entry({
|
|
719
|
+
subject: "search_takeover",
|
|
720
|
+
verb: "dismissed",
|
|
721
|
+
scope: "plan",
|
|
722
|
+
props: ["variant", "reason"]
|
|
723
|
+
// variant: SearchVariant, reason: SearchDismissReason
|
|
724
|
+
}),
|
|
725
|
+
// --- chat-app card lifecycle (2026-09-24) --------------------------------
|
|
726
|
+
// One row per stage a card reached, from `reportCardStage` in
|
|
727
|
+
// card-lifecycle.ts. Plan scope: most stages happen before a config is
|
|
728
|
+
// served and none happen inside a concierge open. `stage` is a
|
|
729
|
+
// CardLifecycleStage; `since_shell_ms` is the card shell's own clock, so a
|
|
730
|
+
// card's rows order themselves from the first byte of the page. The same
|
|
731
|
+
// stages reach the connector through the shell's beacon, which is the only
|
|
732
|
+
// record of a card whose SDK never booted.
|
|
733
|
+
syntro_card_lifecycle_seen: entry({
|
|
734
|
+
subject: "card_lifecycle",
|
|
735
|
+
verb: "seen",
|
|
736
|
+
scope: "plan",
|
|
737
|
+
props: ["stage", "since_shell_ms"]
|
|
738
|
+
// stage: CardLifecycleStage
|
|
739
|
+
})
|
|
740
|
+
};
|
|
741
|
+
var TELEMETRY_EVENT_NAMES = Object.keys(TELEMETRY_EVENTS);
|
|
742
|
+
var TELEMETRY_DOM_EVENT = "syntro:telemetry";
|
|
743
|
+
|
|
744
|
+
// ../../sdk-contracts/dist/card-lifecycle.js
|
|
745
|
+
var MCP_APP_SURFACE_ATTRIBUTES = Object.freeze({
|
|
746
|
+
surface_type: "mcp-app",
|
|
747
|
+
surface: "personal_agent"
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
// ../../sdk-contracts/dist/detector-events.js
|
|
751
|
+
var DETECTOR_EVENT_NAMES = [
|
|
752
|
+
"ui.hover",
|
|
753
|
+
"ui.idle",
|
|
754
|
+
"ui.hesitate",
|
|
755
|
+
"ui.rage_click",
|
|
756
|
+
"ui.scroll_thrash",
|
|
757
|
+
"ui.focus_bounce"
|
|
758
|
+
];
|
|
759
|
+
var CANONICAL_BUS_EVENT_NAMES = [
|
|
760
|
+
...DETECTOR_EVENT_NAMES,
|
|
761
|
+
"nav.section_viewed",
|
|
762
|
+
"nav.scroll_depth"
|
|
763
|
+
];
|
|
764
|
+
|
|
765
|
+
// ../../sdk-contracts/dist/routes.js
|
|
766
|
+
var RESERVED_BYTES = /* @__PURE__ */ new Set([
|
|
767
|
+
33,
|
|
768
|
+
// !
|
|
769
|
+
35,
|
|
770
|
+
// #
|
|
771
|
+
36,
|
|
772
|
+
// $
|
|
773
|
+
38,
|
|
774
|
+
// &
|
|
775
|
+
39,
|
|
776
|
+
// '
|
|
777
|
+
40,
|
|
778
|
+
// (
|
|
779
|
+
41,
|
|
780
|
+
// )
|
|
781
|
+
42,
|
|
782
|
+
// *
|
|
783
|
+
43,
|
|
784
|
+
// +
|
|
785
|
+
44,
|
|
786
|
+
// ,
|
|
787
|
+
47,
|
|
788
|
+
// /
|
|
789
|
+
58,
|
|
790
|
+
// :
|
|
791
|
+
59,
|
|
792
|
+
// ;
|
|
793
|
+
61,
|
|
794
|
+
// =
|
|
795
|
+
63,
|
|
796
|
+
// ?
|
|
797
|
+
64,
|
|
798
|
+
// @
|
|
799
|
+
91,
|
|
800
|
+
// [
|
|
801
|
+
93
|
|
802
|
+
// ]
|
|
803
|
+
]);
|
|
804
|
+
var utf8Decoder = new TextDecoder("utf-8", { fatal: false });
|
|
805
|
+
function decodeUnreservedOnly(input) {
|
|
806
|
+
let out = "";
|
|
807
|
+
let pending = [];
|
|
808
|
+
const flushPending = () => {
|
|
809
|
+
if (pending.length === 0)
|
|
810
|
+
return;
|
|
811
|
+
const bytes = new Uint8Array(pending);
|
|
812
|
+
out += utf8Decoder.decode(bytes);
|
|
813
|
+
pending = [];
|
|
814
|
+
};
|
|
815
|
+
let i2 = 0;
|
|
816
|
+
while (i2 < input.length) {
|
|
817
|
+
const ch = input[i2];
|
|
818
|
+
if (ch === "%" && i2 + 2 < input.length && isHex(input[i2 + 1]) && isHex(input[i2 + 2])) {
|
|
819
|
+
const byte = parseInt(input.slice(i2 + 1, i2 + 3), 16);
|
|
820
|
+
if (RESERVED_BYTES.has(byte)) {
|
|
821
|
+
flushPending();
|
|
822
|
+
out += `%${input.slice(i2 + 1, i2 + 3).toUpperCase()}`;
|
|
823
|
+
i2 += 3;
|
|
824
|
+
} else {
|
|
825
|
+
pending.push(byte);
|
|
826
|
+
i2 += 3;
|
|
827
|
+
}
|
|
828
|
+
} else {
|
|
829
|
+
flushPending();
|
|
830
|
+
out += ch;
|
|
831
|
+
i2 += 1;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
flushPending();
|
|
835
|
+
return out;
|
|
836
|
+
}
|
|
837
|
+
function isHex(c2) {
|
|
838
|
+
return c2 >= "0" && c2 <= "9" || c2 >= "a" && c2 <= "f" || c2 >= "A" && c2 <= "F";
|
|
839
|
+
}
|
|
840
|
+
function stripQueryAndHash(s4) {
|
|
841
|
+
const q = s4.indexOf("?");
|
|
842
|
+
if (q !== -1)
|
|
843
|
+
s4 = s4.slice(0, q);
|
|
844
|
+
const h = s4.indexOf("#");
|
|
845
|
+
if (h !== -1)
|
|
846
|
+
s4 = s4.slice(0, h);
|
|
847
|
+
return s4;
|
|
848
|
+
}
|
|
849
|
+
function normalizeRoute(path) {
|
|
850
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
851
|
+
throw new TypeError("normalizeRoute: input must be a non-empty string");
|
|
852
|
+
}
|
|
853
|
+
if (!path.startsWith("/")) {
|
|
854
|
+
throw new TypeError(`normalizeRoute: input must be absolute (start with '/'); got ${JSON.stringify(path)}`);
|
|
855
|
+
}
|
|
856
|
+
let s4 = stripQueryAndHash(path);
|
|
857
|
+
s4 = decodeUnreservedOnly(s4);
|
|
858
|
+
s4 = s4.replace(/\/+/g, "/");
|
|
859
|
+
if (s4.length > 1 && s4.endsWith("/"))
|
|
860
|
+
s4 = s4.slice(0, -1);
|
|
861
|
+
return s4;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// ../../sdk-contracts/dist/schemas.js
|
|
865
|
+
var NO_CSS_BREAKOUT_PATTERN = /^[^{}]*$/;
|
|
866
|
+
var AnchorIdZ = external_exports.object({
|
|
867
|
+
selector: external_exports.string().regex(NO_CSS_BREAKOUT_PATTERN, {
|
|
868
|
+
message: 'selector must not contain "{" or "}" \u2014 not valid CSS selector syntax, and content:hideBySelector injects this value directly into a <style> element where these characters would break out of the generated rule.'
|
|
869
|
+
}).describe("CSS selector for the target element"),
|
|
870
|
+
route: external_exports.union([external_exports.string(), external_exports.array(external_exports.string())]).superRefine((value, ctx) => {
|
|
871
|
+
for (const route of Array.isArray(value) ? value : [value]) {
|
|
872
|
+
let canonical;
|
|
873
|
+
try {
|
|
874
|
+
canonical = normalizeRoute(route);
|
|
875
|
+
} catch (err) {
|
|
876
|
+
ctx.addIssue({
|
|
877
|
+
code: external_exports.ZodIssueCode.custom,
|
|
878
|
+
message: `route must be an absolute path starting with "/" (got ${JSON.stringify(route)}): ${err instanceof Error ? err.message : String(err)}`
|
|
879
|
+
});
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
if (canonical !== route) {
|
|
883
|
+
ctx.addIssue({
|
|
884
|
+
code: external_exports.ZodIssueCode.custom,
|
|
885
|
+
message: `route ${JSON.stringify(route)} is not canonical \u2014 use ${JSON.stringify(canonical)} (this must match what the backend's RouteCanonicalityCheck accepts)`
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}).describe("URL path(s) where this element exists")
|
|
890
|
+
}).strict().describe("DOM element target. selector = CSS selector, route = URL path(s) where the element exists.");
|
|
891
|
+
var AuthoringFieldsZ = {
|
|
892
|
+
id: external_exports.string().optional().describe('Stable action identifier (e.g. "act_3db6a14d2ab0").'),
|
|
893
|
+
title: external_exports.string().max(200).optional().describe("Authoring-only: short label shown on the action plan dashboard. Stripped before serving to the runtime SDK."),
|
|
894
|
+
description: external_exports.string().max(1e3).optional().describe("Authoring-only: one-sentence explanation of what this action does and why. Stripped before serving to the runtime SDK."),
|
|
895
|
+
validation: external_exports.array(external_exports.string().max(500)).max(10).optional().describe("Authoring-only: ordered steps a reviewer can follow to trigger this action and visually confirm it works. Each entry is one step. Stripped before serving to the runtime SDK.")
|
|
896
|
+
};
|
|
897
|
+
var COUNTABLE_EVENTS = [
|
|
898
|
+
// User interactions (from PostHog autocapture normalization)
|
|
899
|
+
"ui.click",
|
|
900
|
+
"ui.scroll",
|
|
901
|
+
"ui.input",
|
|
902
|
+
"ui.change",
|
|
903
|
+
"ui.submit",
|
|
904
|
+
// Behavioral detectors (from event-processor)
|
|
905
|
+
"ui.hover",
|
|
906
|
+
"ui.idle",
|
|
907
|
+
"ui.scroll_thrash",
|
|
908
|
+
"ui.focus_bounce",
|
|
909
|
+
"ui.hesitate",
|
|
910
|
+
"ui.rage_click",
|
|
911
|
+
// Navigation
|
|
912
|
+
"nav.page_view",
|
|
913
|
+
"nav.page_leave"
|
|
914
|
+
];
|
|
915
|
+
var CountableEventZ = external_exports.enum(COUNTABLE_EVENTS).describe("Event name to count. ui.* = user interactions and behavioral detectors (hesitate, rage_click, scroll_thrash, focus_bounce, idle, hover); nav.* = page navigation.");
|
|
916
|
+
var SESSION_METRIC_KEYS = ["time_on_page", "page_views", "scroll_depth"];
|
|
917
|
+
var SessionMetricKeyZ = external_exports.enum(SESSION_METRIC_KEYS).describe("Session metric key. time_on_page = seconds on current page, page_views = pages visited this session, scroll_depth = 0-100 percentage.");
|
|
918
|
+
var PageUrlConditionZ = external_exports.object({
|
|
919
|
+
type: external_exports.literal("page_url"),
|
|
920
|
+
url: external_exports.string().describe('URL path to match (e.g. "/pricing", "/dashboard")')
|
|
921
|
+
}).describe('Fires when the current page URL matches. Use for page-specific actions. Example: {"type": "page_url", "url": "/pricing"}');
|
|
922
|
+
var RouteConditionZ = external_exports.object({
|
|
923
|
+
type: external_exports.literal("route"),
|
|
924
|
+
routeId: external_exports.string().describe("Named route ID from the route filter")
|
|
925
|
+
}).describe("Fires when the current route matches a named route ID.");
|
|
926
|
+
var AnchorVisibleConditionZ = external_exports.object({
|
|
927
|
+
type: external_exports.literal("anchor_visible"),
|
|
928
|
+
anchorId: external_exports.string().describe("CSS selector of the anchor element"),
|
|
929
|
+
state: external_exports.enum(["visible", "present", "absent"]).describe('"visible" = in viewport, "present" = in DOM, "absent" = not in DOM')
|
|
930
|
+
}).describe(`Fires based on a DOM element's visibility state. Example: {"type": "anchor_visible", "anchorId": "#cta-button", "state": "visible"}`);
|
|
931
|
+
var EventOccurredConditionZ = external_exports.object({
|
|
932
|
+
type: external_exports.literal("event_occurred"),
|
|
933
|
+
eventName: external_exports.string().describe('Event name (e.g. "ui.click", "$pageview")'),
|
|
934
|
+
withinMs: external_exports.number().optional().describe("Time window in ms. Omit = any time this session.")
|
|
935
|
+
}).describe('Fires when a specific event has occurred during this session. Example: {"type": "event_occurred", "eventName": "ui.click", "withinMs": 5000}');
|
|
936
|
+
var StateEqualsConditionZ = external_exports.object({
|
|
937
|
+
type: external_exports.literal("state_equals"),
|
|
938
|
+
key: external_exports.string().describe("Key in the SDK persistent state store (localStorage). Only valid for keys the host app explicitly sets via syntro.state.set()."),
|
|
939
|
+
value: external_exports.unknown().describe("Expected value to match against")
|
|
940
|
+
}).describe("Checks the SDK persistent state store (localStorage). ONLY for host-app state set via syntro.state.set() \u2014 NOT for user attributes like region, device, or UTM params (those are handled by segment targeting). Do NOT use this for targeting. If you do not know the valid state keys, do not use this condition type.");
|
|
941
|
+
var ViewportConditionZ = external_exports.object({
|
|
942
|
+
type: external_exports.literal("viewport"),
|
|
943
|
+
minWidth: external_exports.number().optional().describe("Minimum viewport width in pixels"),
|
|
944
|
+
maxWidth: external_exports.number().optional().describe("Maximum viewport width in pixels"),
|
|
945
|
+
minHeight: external_exports.number().optional().describe("Minimum viewport height in pixels"),
|
|
946
|
+
maxHeight: external_exports.number().optional().describe("Maximum viewport height in pixels")
|
|
947
|
+
}).describe('Fires based on viewport (screen) size. Use for responsive behavior. Example: {"type": "viewport", "minWidth": 768} \u2014 fires on tablet and larger.');
|
|
948
|
+
var SessionMetricConditionZ = external_exports.object({
|
|
949
|
+
type: external_exports.literal("session_metric"),
|
|
950
|
+
key: SessionMetricKeyZ,
|
|
951
|
+
operator: external_exports.enum(["gte", "lte", "eq", "gt", "lt"]),
|
|
952
|
+
threshold: external_exports.number().describe("Numeric threshold to compare against")
|
|
953
|
+
}).describe('Fires when a session metric crosses a threshold. Valid keys: "time_on_page" (seconds), "page_views" (count), "scroll_depth" (0-100). Example: {"type": "session_metric", "key": "time_on_page", "operator": "gte", "threshold": 30}');
|
|
954
|
+
var DismissedConditionZ = external_exports.object({
|
|
955
|
+
type: external_exports.literal("dismissed"),
|
|
956
|
+
key: external_exports.string().describe("Dismissal key (usually a tile or action ID)"),
|
|
957
|
+
inverted: external_exports.boolean().optional().describe("When true, fires if NOT dismissed (default behavior)")
|
|
958
|
+
}).describe("Checks if an item has been dismissed by the user. Use with inverted: true to show only if not dismissed.");
|
|
959
|
+
var CooldownActiveConditionZ = external_exports.object({
|
|
960
|
+
type: external_exports.literal("cooldown_active"),
|
|
961
|
+
key: external_exports.string().describe("Cooldown key"),
|
|
962
|
+
inverted: external_exports.boolean().optional().describe("When true, fires if cooldown is NOT active")
|
|
963
|
+
}).describe("Checks if a cooldown timer is currently active. Use to prevent showing the same intervention too frequently.");
|
|
964
|
+
var FrequencyLimitConditionZ = external_exports.object({
|
|
965
|
+
type: external_exports.literal("frequency_limit"),
|
|
966
|
+
key: external_exports.string().describe("Frequency counter key"),
|
|
967
|
+
limit: external_exports.number().describe("Maximum allowed count"),
|
|
968
|
+
inverted: external_exports.boolean().optional().describe("When true, fires if limit NOT reached")
|
|
969
|
+
}).describe("Checks if a frequency limit has been reached. Use to cap how many times an action fires per session.");
|
|
970
|
+
var MatchOpZ = external_exports.object({
|
|
971
|
+
equals: external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()]).optional(),
|
|
972
|
+
contains: external_exports.string().optional()
|
|
973
|
+
}).refine((operator) => Number(operator.equals !== void 0) + Number(operator.contains !== void 0) === 1, {
|
|
974
|
+
message: "Exactly one of equals or contains must be specified."
|
|
975
|
+
}).describe("Match operator for counter filters. Exactly one of equals or contains must be specified.");
|
|
976
|
+
var CounterDefZ = external_exports.object({
|
|
977
|
+
events: external_exports.array(CountableEventZ).min(1).describe("Event names to count. Use values from the countable events enum."),
|
|
978
|
+
match: external_exports.record(external_exports.string(), MatchOpZ).optional().describe("Property filters. Keys are event prop names or element-chain fields (tag_name, $el_text, attr__*). All entries AND together.")
|
|
979
|
+
}).describe("Defines what events to count. Registered as an accumulator predicate at config-load time.");
|
|
980
|
+
var EventCountConditionZ = external_exports.object({
|
|
981
|
+
type: external_exports.literal("event_count"),
|
|
982
|
+
key: external_exports.string().describe("Unique key for this counter (used for accumulator registration)"),
|
|
983
|
+
operator: external_exports.enum(["gte", "lte", "eq", "gt", "lt"]),
|
|
984
|
+
count: external_exports.number().int().min(0).describe("Target count threshold"),
|
|
985
|
+
withinMs: external_exports.number().positive().optional().describe("Time window in ms. Omit = count across entire session."),
|
|
986
|
+
counter: CounterDefZ.optional().describe("Inline counter definition. Defines what events to count.")
|
|
987
|
+
}).describe('Fires when accumulated event count crosses a threshold. Most powerful trigger type. Example: {"type": "event_count", "key": "pricing-clicks", "operator": "gte", "count": 3, "counter": {"events": ["ui.click"], "match": {"attr__data-cta": {"contains": "pricing"}}}}');
|
|
988
|
+
var ConditionZ = external_exports.discriminatedUnion("type", [
|
|
989
|
+
PageUrlConditionZ,
|
|
990
|
+
RouteConditionZ,
|
|
991
|
+
AnchorVisibleConditionZ,
|
|
992
|
+
EventOccurredConditionZ,
|
|
993
|
+
StateEqualsConditionZ,
|
|
994
|
+
ViewportConditionZ,
|
|
995
|
+
SessionMetricConditionZ,
|
|
996
|
+
DismissedConditionZ,
|
|
997
|
+
CooldownActiveConditionZ,
|
|
998
|
+
FrequencyLimitConditionZ,
|
|
999
|
+
EventCountConditionZ
|
|
1000
|
+
]);
|
|
1001
|
+
var RuleZ = external_exports.object({
|
|
1002
|
+
conditions: external_exports.array(ConditionZ).describe("Array of conditions \u2014 ALL must match (AND logic) for this rule to fire."),
|
|
1003
|
+
value: external_exports.unknown().describe("Value returned when all conditions match. For triggerWhen: true = fire the action.")
|
|
1004
|
+
}).describe("A single rule. ALL conditions must match (AND logic). Rules in a strategy are evaluated top-to-bottom \u2014 first rule where all conditions match wins and returns its value.");
|
|
1005
|
+
var RuleStrategyZ = external_exports.object({
|
|
1006
|
+
type: external_exports.literal("rules"),
|
|
1007
|
+
rules: external_exports.array(RuleZ).describe("Ordered list of rules. Evaluated top-to-bottom \u2014 first match wins."),
|
|
1008
|
+
default: external_exports.unknown().describe("Fallback value when no rule matches. For triggerWhen: false = do not fire by default.")
|
|
1009
|
+
}).describe("Rule-based strategy. Evaluates rules top-to-bottom. First rule where ALL conditions match returns its value. If no rule matches, returns default. For triggerWhen: set value=true on matching rules, default=false.");
|
|
1010
|
+
var ScoreStrategyZ = external_exports.object({
|
|
1011
|
+
type: external_exports.literal("score"),
|
|
1012
|
+
field: external_exports.string(),
|
|
1013
|
+
threshold: external_exports.number(),
|
|
1014
|
+
above: external_exports.unknown(),
|
|
1015
|
+
below: external_exports.unknown()
|
|
1016
|
+
}).describe("Score-based strategy. Compares a field value against a threshold.");
|
|
1017
|
+
var ModelStrategyZ = external_exports.object({
|
|
1018
|
+
type: external_exports.literal("model"),
|
|
1019
|
+
modelId: external_exports.string(),
|
|
1020
|
+
inputs: external_exports.array(external_exports.string()),
|
|
1021
|
+
outputMapping: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
1022
|
+
default: external_exports.unknown()
|
|
1023
|
+
}).describe("ML model strategy. Sends inputs to a model and maps outputs.");
|
|
1024
|
+
var ExternalStrategyZ = external_exports.object({
|
|
1025
|
+
type: external_exports.literal("external"),
|
|
1026
|
+
endpoint: external_exports.string(),
|
|
1027
|
+
method: external_exports.enum(["GET", "POST"]).optional(),
|
|
1028
|
+
default: external_exports.unknown(),
|
|
1029
|
+
timeoutMs: external_exports.number().optional()
|
|
1030
|
+
}).describe("External API strategy. Calls an endpoint to determine the value.");
|
|
1031
|
+
var DecisionStrategyZ = external_exports.discriminatedUnion("type", [
|
|
1032
|
+
RuleStrategyZ,
|
|
1033
|
+
ScoreStrategyZ,
|
|
1034
|
+
ModelStrategyZ,
|
|
1035
|
+
ExternalStrategyZ
|
|
1036
|
+
]);
|
|
1037
|
+
var TriggerWhenZ = DecisionStrategyZ.nullable().optional();
|
|
1038
|
+
var EventScopeZ = external_exports.object({
|
|
1039
|
+
events: external_exports.array(external_exports.string()),
|
|
1040
|
+
urlContains: external_exports.string().optional(),
|
|
1041
|
+
props: external_exports.record(external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()])).optional()
|
|
1042
|
+
});
|
|
1043
|
+
var NotifyZ = external_exports.object({
|
|
1044
|
+
title: external_exports.string().optional().describe("Notification title"),
|
|
1045
|
+
body: external_exports.string().optional().describe("Notification body text"),
|
|
1046
|
+
icon: external_exports.string().optional().describe("Notification icon (emoji or URL)")
|
|
1047
|
+
}).describe("Optional toast notification shown when this action triggers.").nullable().optional();
|
|
1048
|
+
|
|
509
1049
|
// src/reviewsFlip.ts
|
|
510
1050
|
import { html as html2, LitElement as LitElement2, nothing as nothing2 } from "lit";
|
|
511
1051
|
import { styleMap as styleMap2 } from "lit/directives/style-map.js";
|
|
@@ -526,6 +1066,7 @@ function reviewOneLiner(review) {
|
|
|
526
1066
|
const body = (review.body ?? "").trim().replace(/\s+/g, " ");
|
|
527
1067
|
return body.length > 90 ? `${body.slice(0, 89)}\u2026` : body;
|
|
528
1068
|
}
|
|
1069
|
+
var REVIEWS_FLIP_WIDGET_ID = "adaptive-reviews:reviews-flip";
|
|
529
1070
|
function defaultLoadReviewsByIds(ids) {
|
|
530
1071
|
const authedFetch = window.SynOS?.authedFetch;
|
|
531
1072
|
if (!authedFetch) {
|
|
@@ -599,20 +1140,49 @@ var SyntroReviewsFlip = class extends LitElement2 {
|
|
|
599
1140
|
}
|
|
600
1141
|
this._isLoading = true;
|
|
601
1142
|
this._error = null;
|
|
1143
|
+
const startedAt = performance.now();
|
|
602
1144
|
try {
|
|
603
1145
|
const loader = this.loadReviewsByIds ?? defaultLoadReviewsByIds;
|
|
604
1146
|
const data = await loader(ids);
|
|
605
1147
|
if (requestId !== this._requestId) return;
|
|
606
1148
|
const byId = new Map(data.reviews.map((r) => [r.id, r]));
|
|
607
1149
|
this._reviews = ids.map((id) => byId.get(id)).filter((r) => Boolean(r));
|
|
1150
|
+
this._reportLoad(startedAt, "ok", ids.length, this._reviews.length);
|
|
608
1151
|
} catch {
|
|
609
1152
|
if (requestId !== this._requestId) return;
|
|
610
1153
|
this._reviews = [];
|
|
611
1154
|
this._error = "Reviews are unavailable";
|
|
1155
|
+
this._reportLoad(startedAt, "error", ids.length, 0);
|
|
612
1156
|
} finally {
|
|
613
1157
|
if (requestId === this._requestId) this._isLoading = false;
|
|
614
1158
|
}
|
|
615
1159
|
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Report how long this load took. The reviews fetch is the network wait
|
|
1162
|
+
* between the concierge mounting this tile and the shopper seeing it
|
|
1163
|
+
* (BUG-1784767817 was a 30 s hang here), so it goes to telemetry on every
|
|
1164
|
+
* load rather than living only in the promotion playbook. Superseded loads
|
|
1165
|
+
* report nothing: their result is never shown.
|
|
1166
|
+
*/
|
|
1167
|
+
_reportLoad(startedAt, outcome, requested, received) {
|
|
1168
|
+
this.dispatchEvent(
|
|
1169
|
+
new CustomEvent(TELEMETRY_DOM_EVENT, {
|
|
1170
|
+
detail: {
|
|
1171
|
+
name: "syntro_tile_data_received",
|
|
1172
|
+
props: {
|
|
1173
|
+
tile_id: this._scopeId,
|
|
1174
|
+
tile_kind: REVIEWS_FLIP_WIDGET_ID,
|
|
1175
|
+
total_ms: Math.round(performance.now() - startedAt),
|
|
1176
|
+
outcome,
|
|
1177
|
+
requested,
|
|
1178
|
+
received
|
|
1179
|
+
}
|
|
1180
|
+
},
|
|
1181
|
+
bubbles: true,
|
|
1182
|
+
composed: true
|
|
1183
|
+
})
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
616
1186
|
_applyPendingFocus() {
|
|
617
1187
|
if (this._pendingFocus === null) return;
|
|
618
1188
|
const target = this._pendingFocus;
|
|
@@ -814,9 +1384,11 @@ var SyntroReviewsFlip = class extends LitElement2 {
|
|
|
814
1384
|
}
|
|
815
1385
|
_starsStyles() {
|
|
816
1386
|
return {
|
|
817
|
-
// Brand accent
|
|
818
|
-
// rating follows the workspace brand (not a hardcoded blue).
|
|
819
|
-
|
|
1387
|
+
// Brand accent, the SAME token the FAQ uses for its primary marks, so
|
|
1388
|
+
// the rating follows the workspace brand (not a hardcoded blue). On a
|
|
1389
|
+
// host card the accent is ink on the CHAT APP's ground, where it is only
|
|
1390
|
+
// a contrast risk, so the host's own ink wins when it sent one.
|
|
1391
|
+
color: "var(--sc-host-link-color, var(--sc-color-primary, #723b32))",
|
|
820
1392
|
fontSize: "13px",
|
|
821
1393
|
lineHeight: "18px",
|
|
822
1394
|
whiteSpace: "nowrap"
|
|
@@ -904,7 +1476,6 @@ if (!customElements.get("syntro-reviews-flip")) {
|
|
|
904
1476
|
// src/runtime.ts
|
|
905
1477
|
var REVIEWS_ADAPTIVE_APP_ID = "adaptive-reviews";
|
|
906
1478
|
var REVIEWS_ADAPTIVE_WIDGET_ID = "adaptive-reviews:reviews";
|
|
907
|
-
var REVIEWS_FLIP_WIDGET_ID = "adaptive-reviews:reviews-flip";
|
|
908
1479
|
function asPositiveLimit(value) {
|
|
909
1480
|
const numeric = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
910
1481
|
if (!Number.isFinite(numeric)) return void 0;
|
|
@@ -1008,13 +1579,41 @@ var reviewsAdaptiveRuntime = runtime;
|
|
|
1008
1579
|
var runtime_default = runtime;
|
|
1009
1580
|
|
|
1010
1581
|
export {
|
|
1582
|
+
REVIEWS_FLIP_WIDGET_ID,
|
|
1011
1583
|
REVIEWS_ADAPTIVE_APP_ID,
|
|
1012
1584
|
REVIEWS_ADAPTIVE_WIDGET_ID,
|
|
1013
|
-
REVIEWS_FLIP_WIDGET_ID,
|
|
1014
1585
|
reviewsWidget,
|
|
1015
1586
|
reviewsFlipWidget,
|
|
1016
1587
|
runtime,
|
|
1017
1588
|
reviewsAdaptiveRuntime,
|
|
1018
1589
|
runtime_default
|
|
1019
1590
|
};
|
|
1020
|
-
|
|
1591
|
+
/*! Bundled license information:
|
|
1592
|
+
|
|
1593
|
+
@lit/context/lib/context-request-event.js:
|
|
1594
|
+
@lit/context/lib/create-context.js:
|
|
1595
|
+
@lit/context/lib/controllers/context-consumer.js:
|
|
1596
|
+
@lit/context/lib/value-notifier.js:
|
|
1597
|
+
@lit/context/lib/controllers/context-provider.js:
|
|
1598
|
+
@lit/context/lib/context-root.js:
|
|
1599
|
+
(**
|
|
1600
|
+
* @license
|
|
1601
|
+
* Copyright 2021 Google LLC
|
|
1602
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
1603
|
+
*)
|
|
1604
|
+
|
|
1605
|
+
@lit/context/lib/decorators/provide.js:
|
|
1606
|
+
(**
|
|
1607
|
+
* @license
|
|
1608
|
+
* Copyright 2017 Google LLC
|
|
1609
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
1610
|
+
*)
|
|
1611
|
+
|
|
1612
|
+
@lit/context/lib/decorators/consume.js:
|
|
1613
|
+
(**
|
|
1614
|
+
* @license
|
|
1615
|
+
* Copyright 2022 Google LLC
|
|
1616
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
1617
|
+
*)
|
|
1618
|
+
*/
|
|
1619
|
+
//# sourceMappingURL=chunk-M64YCKZP.js.map
|