@guidelytech/shared 0.10.0 → 0.11.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/runtime.cjs CHANGED
@@ -20,16 +20,33 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/runtime.ts
21
21
  var runtime_exports = {};
22
22
  __export(runtime_exports, {
23
+ AGENT_METADATA_MAX_BYTES: () => AGENT_METADATA_MAX_BYTES,
24
+ AGENT_METADATA_MAX_KEYS: () => AGENT_METADATA_MAX_KEYS,
25
+ AGENT_OUTCOMES: () => AGENT_OUTCOMES,
26
+ AGENT_TOOL_CALLS_PER_TURN: () => AGENT_TOOL_CALLS_PER_TURN,
27
+ AGENT_TOOL_STATUSES: () => AGENT_TOOL_STATUSES,
28
+ AGENT_TURNS_PER_BATCH: () => AGENT_TURNS_PER_BATCH,
29
+ AGENT_TURN_MAX_TEXT: () => AGENT_TURN_MAX_TEXT,
30
+ AGENT_TURN_ROLES: () => AGENT_TURN_ROLES,
23
31
  ARTICLE_VISIBILITIES: () => ARTICLE_VISIBILITIES,
24
32
  CAPTURE_PARAM: () => CAPTURE_PARAM,
25
33
  CAPTURE_SCREENSHOT_MAX_BYTES: () => CAPTURE_SCREENSHOT_MAX_BYTES,
34
+ COMMON_LOCALES: () => COMMON_LOCALES,
26
35
  COMPLAINT_EVENTS: () => COMPLAINT_EVENTS,
27
36
  COMPLAINT_FIELD_TYPES: () => COMPLAINT_FIELD_TYPES,
28
37
  COMPLAINT_STATUSES: () => COMPLAINT_STATUSES,
38
+ CONTROL_VARIANT_ID: () => CONTROL_VARIANT_ID,
39
+ DEFAULT_FEATURE_ATTRIBUTE: () => DEFAULT_FEATURE_ATTRIBUTE,
40
+ DEFAULT_LOCALE: () => DEFAULT_LOCALE,
29
41
  DEFAULT_PLATFORMS: () => DEFAULT_PLATFORMS,
30
42
  EMBED_IFRAME_ATTRS: () => EMBED_IFRAME_ATTRS,
43
+ FEATURE_ATTRIBUTE_PATTERN: () => FEATURE_ATTRIBUTE_PATTERN,
44
+ FEATURE_RULE_LIMITS: () => FEATURE_RULE_LIMITS,
31
45
  FILE_LINK_CLASS: () => FILE_LINK_CLASS,
32
46
  GUIDELY_ID_ATTRIBUTE: () => GUIDELY_ID_ATTRIBUTE,
47
+ GUIDE_EXPERIMENT_GOALS: () => GUIDE_EXPERIMENT_GOALS,
48
+ GUIDE_EXPERIMENT_GOAL_LABELS: () => GUIDE_EXPERIMENT_GOAL_LABELS,
49
+ GUIDE_EXPERIMENT_STATUSES: () => GUIDE_EXPERIMENT_STATUSES,
33
50
  GUIDE_VISIBILITIES: () => GUIDE_VISIBILITIES,
34
51
  LOGGED_IN_AUDIENCE: () => LOGGED_IN_AUDIENCE,
35
52
  MAX_COLLECTION_DEPTH: () => MAX_COLLECTION_DEPTH,
@@ -38,6 +55,7 @@ __export(runtime_exports, {
38
55
  MAX_COMPLAINT_CHOICES: () => MAX_COMPLAINT_CHOICES,
39
56
  MAX_COMPLAINT_FIELDS: () => MAX_COMPLAINT_FIELDS,
40
57
  MAX_COMPLAINT_LENGTH: () => MAX_COMPLAINT_LENGTH,
58
+ MAX_CUSTOM_CSS: () => MAX_CUSTOM_CSS,
41
59
  MAX_PROPERTY_BYTES: () => MAX_PROPERTY_BYTES,
42
60
  MAX_STACK_FRAMES: () => MAX_STACK_FRAMES,
43
61
  MEDIA_FILE_MAX_BYTES: () => MEDIA_FILE_MAX_BYTES,
@@ -48,38 +66,65 @@ __export(runtime_exports, {
48
66
  MEDIA_VIDEO_MAX_BYTES: () => MEDIA_VIDEO_MAX_BYTES,
49
67
  MEDIA_VIDEO_TYPES: () => MEDIA_VIDEO_TYPES,
50
68
  NETWORK_PLUGIN: () => NETWORK_PLUGIN,
69
+ NOISY_DATA_ATTR: () => NOISY_DATA_ATTR,
70
+ PAGE_RULE_LIMITS: () => PAGE_RULE_LIMITS,
51
71
  PATH_ID_PATTERN: () => PATH_ID_PATTERN,
52
72
  PDF_BLOCK_CLASS: () => PDF_BLOCK_CLASS,
53
73
  PDF_MAX_PAGES: () => PDF_MAX_PAGES,
54
74
  PDF_PAGES_CLASS: () => PDF_PAGES_CLASS,
55
75
  PLATFORMS: () => PLATFORMS,
76
+ RC_MODULE_IDS: () => RC_MODULE_IDS,
77
+ RC_MODULE_LABELS: () => RC_MODULE_LABELS,
56
78
  RULE_FIELDS: () => RULE_FIELDS,
57
79
  RULE_OPERATORS: () => RULE_OPERATORS,
80
+ SCOUT_AGENT_ID: () => SCOUT_AGENT_ID,
58
81
  SENSITIVE_HEADER_PATTERN: () => SENSITIVE_HEADER_PATTERN,
59
82
  SENSITIVE_PARAM_PATTERN: () => SENSITIVE_PARAM_PATTERN,
60
83
  SENSITIVE_PROPERTY_PATTERN: () => SENSITIVE_PROPERTY_PATTERN,
61
84
  SERVER_ASSISTED_FIELDS: () => SERVER_ASSISTED_FIELDS,
62
85
  SHOW_ME_CLASS: () => SHOW_ME_CLASS,
63
86
  SHOW_ME_HREF_PREFIX: () => SHOW_ME_HREF_PREFIX,
87
+ SNAPSHOT_ATTRS: () => SNAPSHOT_ATTRS,
88
+ SNAPSHOT_LIMITS: () => SNAPSHOT_LIMITS,
64
89
  SOURCEMAP_MAX_FILES_PER_UPLOAD: () => SOURCEMAP_MAX_FILES_PER_UPLOAD,
65
90
  SOURCEMAP_MAX_FILE_BYTES: () => SOURCEMAP_MAX_FILE_BYTES,
91
+ SelectorSyntaxError: () => SelectorSyntaxError,
92
+ TAG_PROPERTY_LIMITS: () => TAG_PROPERTY_LIMITS,
66
93
  VIDEO_ATTRS: () => VIDEO_ATTRS,
94
+ WCAG_AA_LARGE: () => WCAG_AA_LARGE,
95
+ WCAG_AA_TEXT: () => WCAG_AA_TEXT,
67
96
  WS_MAX_MESSAGES_PER_SOCKET: () => WS_MAX_MESSAGES_PER_SOCKET,
68
97
  WS_MAX_MESSAGE_BYTES: () => WS_MAX_MESSAGE_BYTES,
98
+ applyGuideTranslations: () => applyGuideTranslations,
99
+ articleTextEntries: () => articleTextEntries,
100
+ assignGuideVariant: () => assignGuideVariant,
101
+ baseLanguage: () => baseLanguage,
69
102
  clamp: () => clamp,
70
103
  compare: () => compare,
71
104
  compareVersions: () => compareVersions,
72
105
  complaintReference: () => complaintReference,
106
+ compoundMatches: () => compoundMatches,
107
+ contrastIssues: () => contrastIssues,
108
+ contrastRatio: () => contrastRatio,
109
+ cssSelectorError: () => cssSelectorError,
73
110
  describeRule: () => describeRule,
74
111
  emptyRuleGroup: () => emptyRuleGroup,
75
112
  evaluateRule: () => evaluateRule,
76
113
  evaluateRules: () => evaluateRules,
114
+ evenWeights: () => evenWeights,
115
+ experimentBucketRules: () => experimentBucketRules,
116
+ experimentSalt: () => experimentSalt,
117
+ featureCssSelectors: () => featureCssSelectors,
118
+ featureRuleError: () => featureRuleError,
119
+ featureRuleMatchesClick: () => featureRuleMatchesClick,
77
120
  fingerprintError: () => fingerprintError,
78
121
  firstNameFromTraits: () => firstNameFromTraits,
79
122
  fnv1a: () => fnv1a,
80
123
  foldPathIds: () => foldPathIds,
81
124
  getPath: () => getPath,
125
+ guideLocales: () => guideLocales,
82
126
  guidePlatforms: () => guidePlatforms,
127
+ guideTextEntries: () => guideTextEntries,
83
128
  guidelyIdFromSelector: () => guidelyIdFromSelector,
84
129
  hasVideoMedia: () => hasVideoMedia,
85
130
  isAllowedEmbedSrc: () => isAllowedEmbedSrc,
@@ -92,6 +137,12 @@ __export(runtime_exports, {
92
137
  isRuleGroup: () => isRuleGroup,
93
138
  isSafeMediaUrl: () => isSafeMediaUrl,
94
139
  isVideoContentType: () => isVideoContentType,
140
+ localeName: () => localeName,
141
+ localizeAnnouncement: () => localizeAnnouncement,
142
+ localizeArticle: () => localizeArticle,
143
+ localizeGuide: () => localizeGuide,
144
+ localizeResourceCenter: () => localizeResourceCenter,
145
+ matchCssSelector: () => matchCssSelector,
95
146
  matchUrlPattern: () => matchUrlPattern,
96
147
  matchesAudiences: () => matchesAudiences,
97
148
  matchesHelpQuery: () => matchesHelpQuery,
@@ -99,25 +150,52 @@ __export(runtime_exports, {
99
150
  mediaMaxBytes: () => mediaMaxBytes,
100
151
  normalizeErrorMessage: () => normalizeErrorMessage,
101
152
  normalizeFrameFile: () => normalizeFrameFile,
153
+ normalizeLocale: () => normalizeLocale,
154
+ normalizeSnapshot: () => normalizeSnapshot,
155
+ orderedModules: () => orderedModules,
102
156
  pageLabel: () => pageLabel,
157
+ pagePattern: () => pagePattern,
158
+ pageRuleError: () => pageRuleError,
159
+ pageRuleMatches: () => pageRuleMatches,
160
+ pageRulesMatch: () => pageRulesMatch,
103
161
  pageTerms: () => pageTerms,
162
+ parseCssSelector: () => parseCssSelector,
163
+ parseHexColor: () => parseHexColor,
104
164
  parseStack: () => parseStack,
165
+ propertyAttributeAllowed: () => propertyAttributeAllowed,
166
+ queryConditionRegex: () => queryConditionRegex,
105
167
  randomId: () => randomId,
168
+ readableTextOn: () => readableTextOn,
169
+ relativeLuminance: () => relativeLuminance,
170
+ resolveLocale: () => resolveLocale,
171
+ resourceCenterLocales: () => resourceCenterLocales,
106
172
  resourceCenterSections: () => resourceCenterSections,
173
+ resourceCenterTextEntries: () => resourceCenterTextEntries,
107
174
  rolloutBucket: () => rolloutBucket,
108
175
  safeRegExp: () => safeRegExp,
176
+ sanitizeCustomCss: () => sanitizeCustomCss,
109
177
  sanitizeProperties: () => sanitizeProperties,
178
+ scopeCustomCss: () => scopeCustomCss,
110
179
  screenPath: () => screenPath,
111
180
  servedArticleAudiences: () => servedArticleAudiences,
112
181
  showMeHref: () => showMeHref,
113
182
  showMeKeyFromHref: () => showMeKeyFromHref,
114
183
  showMeKeys: () => showMeKeys,
115
184
  slugify: () => slugify,
185
+ snapshotFromSelector: () => snapshotFromSelector,
186
+ stableStringify: () => stableStringify,
116
187
  stripOrigin: () => stripOrigin,
117
188
  supportsPlatform: () => supportsPlatform,
118
189
  toEmbedUrl: () => toEmbedUrl,
190
+ translationCoverage: () => translationCoverage,
191
+ unreadAnnouncements: () => unreadAnnouncements,
192
+ variantProblems: () => variantProblems,
193
+ variantRanges: () => variantRanges,
194
+ visibleAnnouncements: () => visibleAnnouncements,
119
195
  visibleArticleIds: () => visibleArticleIds,
120
196
  visitorAudiences: () => visitorAudiences,
197
+ visitorLocales: () => visitorLocales,
198
+ withExperimentBucket: () => withExperimentBucket,
121
199
  withVisibility: () => withVisibility
122
200
  });
123
201
  module.exports = __toCommonJS(runtime_exports);
@@ -309,11 +387,15 @@ var RULE_FIELDS = [
309
387
  "datetime.dayOfWeek",
310
388
  "datetime.hour",
311
389
  "rollout.percentage",
390
+ /** Guide A/B tests: the visitor's 0-99 bucket for the salt in `key` (see guide-experiments.ts). */
391
+ "experiment.bucket",
312
392
  "environment",
313
393
  "locale",
314
394
  "app.platform",
315
395
  "app.screen",
316
- "app.version"
396
+ "app.version",
397
+ "page.views",
398
+ "feature.clicks"
317
399
  ];
318
400
  function isRuleGroup(r) {
319
401
  return r.rules !== void 0;
@@ -373,6 +455,8 @@ function resolveField(rule, ctx) {
373
455
  return ctx.now.getHours();
374
456
  case "rollout.percentage":
375
457
  return rolloutBucket(ctx.userId || ctx.anonymousId, ctx.rolloutSalt || "guidely");
458
+ case "experiment.bucket":
459
+ return rule.key ? rolloutBucket(ctx.userId || ctx.anonymousId, rule.key) : void 0;
376
460
  case "environment":
377
461
  return ctx.environment;
378
462
  case "locale":
@@ -383,6 +467,9 @@ function resolveField(rule, ctx) {
383
467
  return ctx.screen;
384
468
  case "app.version":
385
469
  return ctx.appVersion;
470
+ case "page.views":
471
+ case "feature.clicks":
472
+ return rule.key ? ctx.tagUsage?.[rule.key] ?? 0 : 0;
386
473
  default:
387
474
  return void 0;
388
475
  }
@@ -482,7 +569,7 @@ function evaluateRules(group, ctx) {
482
569
  const value = group.op === "and" ? results.every(Boolean) : results.some(Boolean);
483
570
  return group.negate ? !value : value;
484
571
  }
485
- var SERVER_ASSISTED_FIELDS = ["audience.member", "guide.completed", "guide.dismissed", "guide.seen", "checklist.completed"];
572
+ var SERVER_ASSISTED_FIELDS = ["audience.member", "guide.completed", "guide.dismissed", "guide.seen", "checklist.completed", "page.views", "feature.clicks"];
486
573
  var GUIDE_VISIBILITIES = ["everyone", "identified", "anonymous"];
487
574
  function withVisibility(targeting, visibility) {
488
575
  if (!visibility || visibility === "everyone") return targeting;
@@ -504,8 +591,8 @@ function sanitizeProperties(props) {
504
591
  for (const [k, v] of Object.entries(props)) {
505
592
  if (SENSITIVE_PROPERTY_PATTERN.test(k)) continue;
506
593
  if (typeof v === "function" || typeof v === "symbol") continue;
507
- const str = typeof v === "string" ? v : JSON.stringify(v ?? null);
508
- size += k.length + (str?.length ?? 0);
594
+ const str2 = typeof v === "string" ? v : JSON.stringify(v ?? null);
595
+ size += k.length + (str2?.length ?? 0);
509
596
  if (size > MAX_PROPERTY_BYTES) break;
510
597
  out[k.slice(0, 100)] = typeof v === "string" ? v.slice(0, 1e3) : v;
511
598
  }
@@ -783,6 +870,32 @@ function matchesHelpQuery(text, query) {
783
870
  const have = words(hay);
784
871
  return terms.every((t) => have.some((w) => w.startsWith(t) || t.length > 3 && t.endsWith("s") && w.startsWith(t.slice(0, -1))));
785
872
  }
873
+ var RC_MODULE_IDS = ["announcements", "search", "checklists", "guides", "articles", "links", "faqs"];
874
+ var RC_MODULE_LABELS = {
875
+ announcements: "What's new",
876
+ search: "Search",
877
+ checklists: "Checklists",
878
+ guides: "Guides & tours",
879
+ articles: "Articles",
880
+ links: "Links",
881
+ faqs: "FAQs"
882
+ };
883
+ function orderedModules(modules) {
884
+ const known = (modules ?? []).filter((m, i, all) => RC_MODULE_IDS.includes(m.id) && all.findIndex((x) => x.id === m.id) === i);
885
+ const missing = RC_MODULE_IDS.filter((id) => !known.some((m) => m.id === id)).map((id) => ({ id, enabled: true }));
886
+ return [...known, ...missing];
887
+ }
888
+ function visibleAnnouncements(items, member, now = Date.now()) {
889
+ return (items ?? []).filter((a) => {
890
+ const t = Date.parse(a.publishedAt);
891
+ return !Number.isNaN(t) && t <= now && matchesAudiences(a.audienceIds, member);
892
+ }).sort((a, b) => Date.parse(b.publishedAt) - Date.parse(a.publishedAt));
893
+ }
894
+ function unreadAnnouncements(items, lastSeenAt) {
895
+ const seen = lastSeenAt ? Date.parse(lastSeenAt) : NaN;
896
+ if (Number.isNaN(seen)) return items.length;
897
+ return items.filter((a) => Date.parse(a.publishedAt) > seen).length;
898
+ }
786
899
 
787
900
  // src/howto-limits.ts
788
901
  var CAPTURE_PARAM = "guidely_capture";
@@ -812,12 +925,556 @@ function pageLabel(path) {
812
925
  }
813
926
  function firstNameFromTraits(traits) {
814
927
  if (!traits) return null;
815
- const pick = (v) => typeof v === "string" ? v.trim() : "";
816
- const first = pick(traits.firstName) || pick(traits.first_name) || pick(traits.firstname) || pick(traits.name).split(/\s+/)[0] || "";
928
+ const pick2 = (v) => typeof v === "string" ? v.trim() : "";
929
+ const first = pick2(traits.firstName) || pick2(traits.first_name) || pick2(traits.firstname) || pick2(traits.name).split(/\s+/)[0] || "";
817
930
  if (!first || first.length > 40 || first.includes("@") || !/\p{L}/u.test(first)) return null;
818
931
  return first;
819
932
  }
820
933
 
934
+ // src/tagging.ts
935
+ var SNAPSHOT_LIMITS = { classes: 8, className: 60, attrs: 6, attrValue: 100, ancestors: 5, hosts: 3, tag: 40, id: 80 };
936
+ var SNAPSHOT_ATTRS = ["role", "name", "type", "aria-label", "title", "alt", "for", "href"];
937
+ var NOISY_DATA_ATTR = /^data-(state|v-|react|radix|headlessui|svelte|orientation|highlighted|focus|key|index|side|align|disabled|selected|active|open|rbd|dnd|sveltekit|guidely-(mask|ignore|block))/i;
938
+ var DEFAULT_FEATURE_ATTRIBUTE = "data-guidely-feature";
939
+ var FEATURE_ATTRIBUTE_PATTERN = /^data-[a-z0-9][a-z0-9_.-]{0,60}$/;
940
+ function str(v, max) {
941
+ return typeof v === "string" && v ? v.slice(0, max) : void 0;
942
+ }
943
+ function cleanNode(raw) {
944
+ if (!raw || typeof raw !== "object") return null;
945
+ const r = raw;
946
+ const t = str(r.t, SNAPSHOT_LIMITS.tag)?.toLowerCase();
947
+ if (!t || !/^[a-z][a-z0-9-]*$/.test(t)) return null;
948
+ const node = { t };
949
+ const id = str(r.id, SNAPSHOT_LIMITS.id);
950
+ if (id) node.id = id;
951
+ if (Array.isArray(r.c)) {
952
+ const c = r.c.filter((x) => typeof x === "string" && !!x && x.length <= SNAPSHOT_LIMITS.className && !/\s/.test(x)).slice(0, SNAPSHOT_LIMITS.classes);
953
+ if (c.length) node.c = c;
954
+ }
955
+ if (r.a && typeof r.a === "object" && !Array.isArray(r.a)) {
956
+ const a = {};
957
+ for (const [k, v] of Object.entries(r.a)) {
958
+ if (Object.keys(a).length >= SNAPSHOT_LIMITS.attrs) break;
959
+ const key = k.toLowerCase();
960
+ if (typeof v !== "string" || !(key.startsWith("data-") || SNAPSHOT_ATTRS.includes(key)) || key === "value" || key.length > 60) continue;
961
+ a[key] = v.slice(0, SNAPSHOT_LIMITS.attrValue);
962
+ }
963
+ if (Object.keys(a).length) node.a = a;
964
+ }
965
+ if (typeof r.n === "number" && Number.isInteger(r.n) && r.n >= 1 && r.n <= 1e4) node.n = r.n;
966
+ return node;
967
+ }
968
+ function normalizeSnapshot(raw) {
969
+ const el = cleanNode(raw);
970
+ if (!el) return null;
971
+ const r = raw;
972
+ const out = el;
973
+ if (Array.isArray(r.p)) {
974
+ const p = r.p.slice(0, SNAPSHOT_LIMITS.ancestors).map(cleanNode);
975
+ const cut = p.indexOf(null);
976
+ const list = cut === -1 ? p : p.slice(0, cut);
977
+ if (list.length) out.p = list;
978
+ }
979
+ if (Array.isArray(r.h)) {
980
+ const h = r.h.slice(0, SNAPSHOT_LIMITS.hosts).map(cleanNode).filter((x) => !!x);
981
+ if (h.length) out.h = h;
982
+ }
983
+ return out;
984
+ }
985
+ function stableStringify(v) {
986
+ if (Array.isArray(v)) return `[${v.map(stableStringify).join(",")}]`;
987
+ if (v && typeof v === "object") {
988
+ const o = v;
989
+ return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${stableStringify(o[k])}`).join(",")}}`;
990
+ }
991
+ return JSON.stringify(v ?? null);
992
+ }
993
+ var SelectorSyntaxError = class extends Error {
994
+ };
995
+ var IDENT_START = /[A-Za-z_\u00a0-\uffff-]/;
996
+ var IDENT_CHAR = /[A-Za-z0-9_\u00a0-\uffff-]/;
997
+ function parseCssSelector(input) {
998
+ const s = input.trim();
999
+ if (!s) throw new SelectorSyntaxError("Empty selector");
1000
+ if (s.length > 1e3) throw new SelectorSyntaxError("Selector is too long");
1001
+ let i = 0;
1002
+ const peek = () => s[i];
1003
+ const eof = () => i >= s.length;
1004
+ const fail = (msg) => {
1005
+ throw new SelectorSyntaxError(`${msg} at position ${i + 1}`);
1006
+ };
1007
+ const ws = () => {
1008
+ let any = false;
1009
+ while (!eof() && /\s/.test(s[i])) {
1010
+ i++;
1011
+ any = true;
1012
+ }
1013
+ return any;
1014
+ };
1015
+ const escape = () => {
1016
+ i++;
1017
+ const hex = /^[0-9a-fA-F]{1,6}/.exec(s.slice(i));
1018
+ if (hex) {
1019
+ i += hex[0].length;
1020
+ if (s[i] === " ") i++;
1021
+ return String.fromCodePoint(parseInt(hex[0], 16));
1022
+ }
1023
+ if (eof()) fail("Unfinished escape");
1024
+ return s[i++];
1025
+ };
1026
+ const ident = () => {
1027
+ let out = "";
1028
+ if (s[i] === "\\") out += escape();
1029
+ else if (!eof() && IDENT_START.test(s[i])) out += s[i++];
1030
+ else fail("Expected a name");
1031
+ while (!eof()) {
1032
+ if (s[i] === "\\") out += escape();
1033
+ else if (IDENT_CHAR.test(s[i])) out += s[i++];
1034
+ else break;
1035
+ }
1036
+ return out;
1037
+ };
1038
+ const quoted = () => {
1039
+ const q = s[i++];
1040
+ let out = "";
1041
+ while (!eof() && s[i] !== q) {
1042
+ if (s[i] === "\\") out += escape();
1043
+ else out += s[i++];
1044
+ }
1045
+ if (eof()) fail("Unterminated string");
1046
+ i++;
1047
+ return out;
1048
+ };
1049
+ const compound = () => {
1050
+ const c = { classes: [], attrs: [] };
1051
+ let any = false;
1052
+ if (peek() === "*") {
1053
+ i++;
1054
+ any = true;
1055
+ } else if (!eof() && (IDENT_START.test(peek()) || peek() === "\\")) {
1056
+ c.tag = ident().toLowerCase();
1057
+ any = true;
1058
+ }
1059
+ for (; ; ) {
1060
+ const ch = peek();
1061
+ if (ch === "#") {
1062
+ i++;
1063
+ c.id = ident();
1064
+ } else if (ch === ".") {
1065
+ i++;
1066
+ c.classes.push(ident());
1067
+ } else if (ch === "[") {
1068
+ i++;
1069
+ ws();
1070
+ const name = ident().toLowerCase();
1071
+ ws();
1072
+ let op = "exists";
1073
+ let value;
1074
+ let insensitive = false;
1075
+ if (peek() !== "]") {
1076
+ const m = /^(=|\^=|\$=|\*=|~=|\|=)/.exec(s.slice(i));
1077
+ if (!m) fail("Unsupported attribute operator");
1078
+ op = m[1];
1079
+ i += m[1].length;
1080
+ ws();
1081
+ value = peek() === '"' || peek() === "'" ? quoted() : ident();
1082
+ ws();
1083
+ if (peek() === "i" || peek() === "I") {
1084
+ i++;
1085
+ insensitive = true;
1086
+ ws();
1087
+ } else if (peek() === "s" || peek() === "S") {
1088
+ i++;
1089
+ ws();
1090
+ }
1091
+ }
1092
+ if (peek() !== "]") fail("Expected ]");
1093
+ i++;
1094
+ c.attrs.push({ name, op, ...value !== void 0 ? { value } : {}, ...insensitive ? { insensitive } : {} });
1095
+ } else if (ch === ":") {
1096
+ i++;
1097
+ if (peek() === ":") fail("Pseudo-elements are not supported");
1098
+ const name = ident().toLowerCase();
1099
+ if (name === "first-of-type") c.nth = { a: 0, b: 1 };
1100
+ else if (name === "nth-of-type") {
1101
+ if (peek() !== "(") fail("Expected (");
1102
+ i++;
1103
+ ws();
1104
+ const m = /^(odd|even|\d+)/i.exec(s.slice(i));
1105
+ if (!m) fail("Only :nth-of-type(n), odd or even are supported");
1106
+ i += m[0].length;
1107
+ ws();
1108
+ if (peek() !== ")") fail("Expected )");
1109
+ i++;
1110
+ const v = m[0].toLowerCase();
1111
+ c.nth = v === "odd" ? { a: 2, b: 1 } : v === "even" ? { a: 2, b: 0 } : { a: 0, b: Number(v) };
1112
+ } else fail(`Unsupported pseudo-class :${name}`);
1113
+ } else break;
1114
+ any = true;
1115
+ }
1116
+ return any ? c : null;
1117
+ };
1118
+ const list = [];
1119
+ for (; ; ) {
1120
+ ws();
1121
+ const sel = { compounds: [], combinators: [] };
1122
+ const first = compound();
1123
+ if (!first) fail("Expected a selector");
1124
+ sel.compounds.push(first);
1125
+ for (; ; ) {
1126
+ const hadWs = ws();
1127
+ if (eof() || peek() === ",") break;
1128
+ let comb = " ";
1129
+ if (s.startsWith(">>>", i)) {
1130
+ comb = ">>>";
1131
+ i += 3;
1132
+ ws();
1133
+ } else if (peek() === ">") {
1134
+ comb = ">";
1135
+ i++;
1136
+ ws();
1137
+ } else if (peek() === "+" || peek() === "~") fail("Sibling combinators are not supported");
1138
+ else if (!hadWs) fail("Unexpected character");
1139
+ const next = compound();
1140
+ if (!next) fail("Expected a selector after the combinator");
1141
+ sel.compounds.push(next);
1142
+ sel.combinators.push(comb);
1143
+ }
1144
+ list.push(sel);
1145
+ if (eof()) break;
1146
+ i++;
1147
+ }
1148
+ return list;
1149
+ }
1150
+ function cssSelectorError(selector) {
1151
+ try {
1152
+ parseCssSelector(selector);
1153
+ return null;
1154
+ } catch (e) {
1155
+ return e instanceof Error ? e.message : "Invalid selector";
1156
+ }
1157
+ }
1158
+ function attrMatches(test, node) {
1159
+ let actual;
1160
+ if (test.name === "id") actual = node.id;
1161
+ else if (test.name === "class") actual = node.c?.join(" ");
1162
+ else actual = node.a?.[test.name];
1163
+ if (actual === void 0) return false;
1164
+ if (test.op === "exists") return true;
1165
+ let v = test.value ?? "";
1166
+ if (test.insensitive) {
1167
+ actual = actual.toLowerCase();
1168
+ v = v.toLowerCase();
1169
+ }
1170
+ switch (test.op) {
1171
+ case "=":
1172
+ return actual === v;
1173
+ case "^=":
1174
+ return !!v && actual.startsWith(v);
1175
+ case "$=":
1176
+ return !!v && actual.endsWith(v);
1177
+ case "*=":
1178
+ return !!v && actual.includes(v);
1179
+ case "~=":
1180
+ return !!v && actual.split(/\s+/).includes(v);
1181
+ case "|=":
1182
+ return actual === v || actual.startsWith(`${v}-`);
1183
+ default:
1184
+ return false;
1185
+ }
1186
+ }
1187
+ function compoundMatches(c, node) {
1188
+ if (c.tag && c.tag !== node.t) return false;
1189
+ if (c.id !== void 0 && node.id !== c.id) return false;
1190
+ for (const cls of c.classes) if (!node.c?.includes(cls)) return false;
1191
+ for (const a of c.attrs) if (!attrMatches(a, node)) return false;
1192
+ if (c.nth) {
1193
+ if (!node.n) return false;
1194
+ const { a, b } = c.nth;
1195
+ if (a === 0 ? node.n !== b : (node.n - b) % a !== 0 || (node.n - b) / a < 0) return false;
1196
+ }
1197
+ return true;
1198
+ }
1199
+ function chainMatches(compounds, combinators, nodes, ci = compounds.length - 1, ni = 0) {
1200
+ if (!compoundMatches(compounds[ci], nodes[ni])) return false;
1201
+ if (ci === 0) return true;
1202
+ const comb = combinators[ci - 1];
1203
+ if (comb === ">") return ni + 1 < nodes.length && chainMatches(compounds, combinators, nodes, ci - 1, ni + 1);
1204
+ for (let k = ni + 1; k < nodes.length; k++) if (chainMatches(compounds, combinators, nodes, ci - 1, k)) return true;
1205
+ return false;
1206
+ }
1207
+ function complexMatches(sel, snap) {
1208
+ const segments = [{ compounds: [sel.compounds[0]], combinators: [] }];
1209
+ sel.combinators.forEach((comb, k) => {
1210
+ const next = sel.compounds[k + 1];
1211
+ if (comb === ">>>") segments.push({ compounds: [next], combinators: [] });
1212
+ else {
1213
+ const seg = segments[segments.length - 1];
1214
+ seg.compounds.push(next);
1215
+ seg.combinators.push(comb);
1216
+ }
1217
+ });
1218
+ const last = segments.pop();
1219
+ const nodes = [snap, ...snap.p ?? []];
1220
+ if (!chainMatches(last.compounds, last.combinators, nodes)) return false;
1221
+ const hosts = snap.h ?? [];
1222
+ let from = 0;
1223
+ for (let k = segments.length - 1; k >= 0; k--) {
1224
+ const target = segments[k].compounds[segments[k].compounds.length - 1];
1225
+ let found = -1;
1226
+ for (let j = from; j < hosts.length; j++) {
1227
+ if (compoundMatches(target, hosts[j])) {
1228
+ found = j;
1229
+ break;
1230
+ }
1231
+ }
1232
+ if (found === -1) return false;
1233
+ from = found + 1;
1234
+ }
1235
+ return true;
1236
+ }
1237
+ var parsedCache = /* @__PURE__ */ new Map();
1238
+ function matchCssSelector(selector, snap) {
1239
+ let list;
1240
+ if (typeof selector === "string") {
1241
+ if (!parsedCache.has(selector)) {
1242
+ if (parsedCache.size > 500) parsedCache.clear();
1243
+ try {
1244
+ parsedCache.set(selector, parseCssSelector(selector));
1245
+ } catch {
1246
+ parsedCache.set(selector, null);
1247
+ }
1248
+ }
1249
+ list = parsedCache.get(selector);
1250
+ } else list = selector;
1251
+ return !!list && list.some((sel) => complexMatches(sel, snap));
1252
+ }
1253
+ function nodeOfCompound(c) {
1254
+ const node = { t: c.tag ?? "*" };
1255
+ if (c.id) node.id = c.id;
1256
+ if (c.classes.length) node.c = c.classes.slice(0, SNAPSHOT_LIMITS.classes);
1257
+ const a = {};
1258
+ for (const t of c.attrs) if (t.op === "=" && t.value !== void 0) a[t.name] = t.value;
1259
+ if (Object.keys(a).length) node.a = a;
1260
+ if (c.nth && c.nth.a === 0) node.n = c.nth.b;
1261
+ return node;
1262
+ }
1263
+ function snapshotFromSelector(selector, tag) {
1264
+ let list;
1265
+ try {
1266
+ list = parseCssSelector(selector);
1267
+ } catch {
1268
+ return null;
1269
+ }
1270
+ if (list.length !== 1) return null;
1271
+ const sel = list[0];
1272
+ const segments = [[sel.compounds[0]]];
1273
+ sel.combinators.forEach((comb, k) => {
1274
+ if (comb === ">>>") segments.push([sel.compounds[k + 1]]);
1275
+ else segments[segments.length - 1].push(sel.compounds[k + 1]);
1276
+ });
1277
+ const own = segments.pop();
1278
+ const el = nodeOfCompound(own[own.length - 1]);
1279
+ if (el.t === "*" && tag && /^[a-z][a-z0-9-]*$/i.test(tag)) el.t = tag.toLowerCase();
1280
+ const snap = el;
1281
+ const p = own.slice(0, -1).reverse().map(nodeOfCompound);
1282
+ if (p.length) snap.p = p.slice(0, SNAPSHOT_LIMITS.ancestors);
1283
+ const h = segments.reverse().map((seg) => nodeOfCompound(seg[seg.length - 1]));
1284
+ if (h.length) snap.h = h.slice(0, SNAPSHOT_LIMITS.hosts);
1285
+ return snap;
1286
+ }
1287
+ var PAGE_RULE_LIMITS = { rules: 20, value: 500, query: 5 };
1288
+ var escapeRe = (s) => s.replace(/[.+?^${}()|[\]\\*]/g, "\\$&");
1289
+ function globToRegex(pattern) {
1290
+ let out = "";
1291
+ for (let k = 0; k < pattern.length; k++) {
1292
+ const ch = pattern[k];
1293
+ if (ch === "*") {
1294
+ if (pattern[k + 1] === "*") {
1295
+ out += ".*";
1296
+ k++;
1297
+ } else out += "[^/]*";
1298
+ } else if (ch === ":" && (k === 0 || pattern[k - 1] === "/") && /[A-Za-z_]/.test(pattern[k + 1] ?? "")) {
1299
+ let j = k + 1;
1300
+ while (j < pattern.length && /[\w]/.test(pattern[j])) j++;
1301
+ out += "[^/]+";
1302
+ k = j - 1;
1303
+ } else out += escapeRe(ch);
1304
+ }
1305
+ return out;
1306
+ }
1307
+ function pagePattern(raw) {
1308
+ let p = raw.trim();
1309
+ const query = [];
1310
+ const hash = p.indexOf("#");
1311
+ if (hash !== -1) p = p.slice(0, hash);
1312
+ const q = p.indexOf("?");
1313
+ if (q !== -1) {
1314
+ for (const part of p.slice(q + 1).split("&")) {
1315
+ if (!part) continue;
1316
+ const eq = part.indexOf("=");
1317
+ const name = decodeSafe(eq === -1 ? part : part.slice(0, eq));
1318
+ const value = eq === -1 ? void 0 : decodeSafe(part.slice(eq + 1));
1319
+ if (!name) continue;
1320
+ query.push(value === void 0 || value === "*" ? { name, op: "exists" } : { name, op: "equals", value });
1321
+ }
1322
+ p = p.slice(0, q);
1323
+ }
1324
+ let target = "path";
1325
+ if (p.startsWith("//*")) p = p.slice(3);
1326
+ let prefix = "";
1327
+ const abs = /^(https?:)?\/\/([^/]+)(.*)$/i.exec(p);
1328
+ if (abs) {
1329
+ target = "url";
1330
+ prefix = `https?://${globToRegex(abs[2].toLowerCase())}`;
1331
+ p = abs[3] || "/";
1332
+ }
1333
+ if (!p.startsWith("/")) p = `/${p}`;
1334
+ const body = p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
1335
+ const src = body === "/" ? "/?" : `${globToRegex(body)}/?`;
1336
+ return { source: `^${prefix}${src}$`, target, query: query.slice(0, PAGE_RULE_LIMITS.query) };
1337
+ }
1338
+ function decodeSafe(s) {
1339
+ try {
1340
+ return decodeURIComponent(s.replace(/\+/g, " "));
1341
+ } catch {
1342
+ return s;
1343
+ }
1344
+ }
1345
+ function queryConditionRegex(c) {
1346
+ const name = escapeRe(c.name);
1347
+ switch (c.op) {
1348
+ case "equals":
1349
+ return `[?&]${name}=${escapeRe(encodeLoose(c.value ?? ""))}(&|#|$)`;
1350
+ case "contains":
1351
+ return `[?&]${name}=[^&#]*${escapeRe(encodeLoose(c.value ?? ""))}`;
1352
+ default:
1353
+ return `[?&]${name}(=|&|#|$)`;
1354
+ }
1355
+ }
1356
+ function encodeLoose(v) {
1357
+ return encodeURIComponent(v).replace(/%2F/gi, "/").replace(/%3A/gi, ":").replace(/%2C/gi, ",").replace(/%40/gi, "@");
1358
+ }
1359
+ function pathOnly(ctx) {
1360
+ if (ctx.path) return ctx.path.split("?")[0] || "/";
1361
+ if (!ctx.url) return null;
1362
+ try {
1363
+ return new URL(ctx.url, "http://x.invalid").pathname;
1364
+ } catch {
1365
+ return null;
1366
+ }
1367
+ }
1368
+ function regexOrNull(source) {
1369
+ try {
1370
+ return new RegExp(source, "i");
1371
+ } catch {
1372
+ return null;
1373
+ }
1374
+ }
1375
+ function pageRuleMatches(rule, ctx) {
1376
+ const path = pathOnly(ctx);
1377
+ if (rule.type === "title") {
1378
+ const t = (ctx.title ?? "").toLowerCase();
1379
+ const v = rule.value.toLowerCase();
1380
+ return !!t && (rule.op === "contains" ? t.includes(v) : t === v);
1381
+ }
1382
+ if (rule.type === "regex") {
1383
+ const re2 = regexOrNull(rule.value);
1384
+ return !!re2 && path !== null && re2.test(path);
1385
+ }
1386
+ const pat = pagePattern(rule.value);
1387
+ const re = regexOrNull(pat.source);
1388
+ if (!re) return false;
1389
+ let subject = path;
1390
+ if (pat.target === "url") {
1391
+ if (!ctx.url) return false;
1392
+ subject = ctx.url.split(/[?#]/)[0];
1393
+ }
1394
+ if (subject === null || !re.test(subject)) return false;
1395
+ const conds = [...pat.query, ...rule.query ?? []];
1396
+ for (const c of conds) {
1397
+ const hit = !!ctx.url && new RegExp(queryConditionRegex(c), "i").test(ctx.url);
1398
+ if (c.op === "absent" ? hit : !hit) return false;
1399
+ }
1400
+ return true;
1401
+ }
1402
+ function pageRulesMatch(rules, ctx) {
1403
+ const include = rules.filter((r) => !r.exclude);
1404
+ if (!include.length || !include.some((r) => pageRuleMatches(r, ctx))) return false;
1405
+ return !rules.some((r) => r.exclude && pageRuleMatches(r, ctx));
1406
+ }
1407
+ function pageRuleError(rule) {
1408
+ const v = rule.value?.trim();
1409
+ if (!v) return "A rule needs a value";
1410
+ if (v.length > PAGE_RULE_LIMITS.value) return "The rule is too long";
1411
+ if (rule.type === "regex") {
1412
+ if (!regexOrNull(v)) return `"${v}" is not a valid regular expression`;
1413
+ if (/\(\?[<=!]/.test(v) || /\\[1-9]/.test(v)) return "Look-arounds and back-references are not supported";
1414
+ }
1415
+ if (rule.type === "url" && !regexOrNull(pagePattern(v).source)) return `"${v}" is not a valid URL rule`;
1416
+ for (const q of rule.query ?? []) if (!/^[A-Za-z0-9_.\-[\]]{1,60}$/.test(q.name)) return `"${q.name}" is not a query parameter name`;
1417
+ return null;
1418
+ }
1419
+ var FEATURE_RULE_LIMITS = { rules: 20, value: 1e3 };
1420
+ function globMatch(pattern, value) {
1421
+ const re = new RegExp(`^${pattern.split("*").map(escapeRe).join(".*")}$`, "s");
1422
+ return re.test(value);
1423
+ }
1424
+ function featureRuleMatchesClick(rule, click, css) {
1425
+ if (!rule.selector && !rule.text && !rule.tag && !rule.href) return false;
1426
+ if (rule.selector) {
1427
+ const sel = click.selector ?? "";
1428
+ const v = rule.selector.value;
1429
+ const ok = rule.selector.op === "css" ? css(v) : rule.selector.op === "exact" ? sel === v : rule.selector.op === "prefix" ? sel.startsWith(v) : rule.selector.op === "contains" ? sel.includes(v) : globMatch(v, sel);
1430
+ if (!ok) return false;
1431
+ }
1432
+ if (rule.text) {
1433
+ const t = (click.text ?? "").toLowerCase();
1434
+ const v = rule.text.value.toLowerCase();
1435
+ if (rule.text.op === "contains" ? !t.includes(v) : t !== v) return false;
1436
+ }
1437
+ if (rule.tag && (click.tag ?? "").toLowerCase() !== rule.tag.toLowerCase()) return false;
1438
+ if (rule.href) {
1439
+ const h = click.href ?? "";
1440
+ const v = rule.href.value;
1441
+ if (rule.href.op === "equals" ? h !== v : rule.href.op === "prefix" ? !h.startsWith(v) : !h.includes(v)) return false;
1442
+ }
1443
+ if (rule.platform && (click.platform ?? "web") !== rule.platform) return false;
1444
+ return true;
1445
+ }
1446
+ function featureRuleError(rule) {
1447
+ if (!rule.selector && !rule.text && !rule.tag && !rule.href) return "A feature rule needs a selector, text, tag or link condition";
1448
+ if (rule.selector) {
1449
+ const v = rule.selector.value?.trim();
1450
+ if (!v) return "The selector is empty";
1451
+ if (v.length > FEATURE_RULE_LIMITS.value) return "The selector is too long";
1452
+ if (rule.selector.op === "css") {
1453
+ const err = cssSelectorError(v);
1454
+ if (err) return `Unsupported CSS selector: ${err}`;
1455
+ }
1456
+ }
1457
+ if (rule.text && !rule.text.value.trim()) return "The text condition is empty";
1458
+ if (rule.href && !rule.href.value.trim()) return "The link condition is empty";
1459
+ if (rule.tag && !/^[a-z][a-z0-9-]{0,40}$/i.test(rule.tag)) return "The tag is not an element name";
1460
+ if (rule.page?.pattern) return pageRuleError({ type: "url", value: rule.page.pattern });
1461
+ return null;
1462
+ }
1463
+ function featureCssSelectors(rules) {
1464
+ const out = [];
1465
+ for (const r of rules) {
1466
+ if (r.exclude || !r.selector) continue;
1467
+ if ((r.selector.op === "css" || r.selector.op === "exact") && !cssSelectorError(r.selector.value)) out.push(r.selector.value);
1468
+ }
1469
+ return out;
1470
+ }
1471
+ var TAG_PROPERTY_LIMITS = { perTag: 10, total: 50, value: 100 };
1472
+ function propertyAttributeAllowed(name) {
1473
+ const n = name.toLowerCase();
1474
+ if (n === "value" || n.startsWith("on") || n === "style" || n === "srcdoc") return false;
1475
+ return /^[a-z][a-z0-9_:.-]{0,60}$/.test(n);
1476
+ }
1477
+
821
1478
  // src/complaints-limits.ts
822
1479
  var COMPLAINT_STATUSES = ["open", "in_review", "resolved"];
823
1480
  var COMPLAINT_EVENTS = ["complaint.filed", "complaint.status_changed", "complaint.replied", "complaint.resolved"];
@@ -828,18 +1485,506 @@ var MAX_COMPLAINT_LENGTH = 4e3;
828
1485
  var MAX_COMPLAINT_ANSWER_LENGTH = 500;
829
1486
  var MAX_COMPLAINT_ATTACHMENT_BYTES = 2 * 1024 * 1024;
830
1487
  var complaintReference = (number) => `CMP-${number}`;
1488
+
1489
+ // src/agents-limits.ts
1490
+ var AGENT_TURN_ROLES = ["user", "assistant", "tool", "system"];
1491
+ var AGENT_OUTCOMES = ["success", "failure", "escalated", "abandoned"];
1492
+ var AGENT_TOOL_STATUSES = ["ok", "error"];
1493
+ var AGENT_TURN_MAX_TEXT = 2e4;
1494
+ var AGENT_TURNS_PER_BATCH = 100;
1495
+ var AGENT_TOOL_CALLS_PER_TURN = 50;
1496
+ var AGENT_METADATA_MAX_KEYS = 30;
1497
+ var AGENT_METADATA_MAX_BYTES = 4096;
1498
+ var SCOUT_AGENT_ID = "scout";
1499
+
1500
+ // src/localization.ts
1501
+ var DEFAULT_LOCALE = "en";
1502
+ var COMMON_LOCALES = [
1503
+ { code: "en", name: "English" },
1504
+ { code: "en-GB", name: "English (UK)" },
1505
+ { code: "fr", name: "French" },
1506
+ { code: "fr-CA", name: "French (Canada)" },
1507
+ { code: "de", name: "German" },
1508
+ { code: "es", name: "Spanish" },
1509
+ { code: "es-MX", name: "Spanish (Mexico)" },
1510
+ { code: "it", name: "Italian" },
1511
+ { code: "pt", name: "Portuguese" },
1512
+ { code: "pt-BR", name: "Portuguese (Brazil)" },
1513
+ { code: "nl", name: "Dutch" },
1514
+ { code: "sv", name: "Swedish" },
1515
+ { code: "da", name: "Danish" },
1516
+ { code: "nb", name: "Norwegian" },
1517
+ { code: "fi", name: "Finnish" },
1518
+ { code: "pl", name: "Polish" },
1519
+ { code: "cs", name: "Czech" },
1520
+ { code: "tr", name: "Turkish" },
1521
+ { code: "ru", name: "Russian" },
1522
+ { code: "uk", name: "Ukrainian" },
1523
+ { code: "ar", name: "Arabic" },
1524
+ { code: "he", name: "Hebrew" },
1525
+ { code: "hi", name: "Hindi" },
1526
+ { code: "ja", name: "Japanese" },
1527
+ { code: "ko", name: "Korean" },
1528
+ { code: "zh-Hans", name: "Chinese (Simplified)" },
1529
+ { code: "zh-Hant", name: "Chinese (Traditional)" },
1530
+ { code: "id", name: "Indonesian" },
1531
+ { code: "th", name: "Thai" },
1532
+ { code: "vi", name: "Vietnamese" }
1533
+ ];
1534
+ function localeName(code) {
1535
+ const exact = COMMON_LOCALES.find((l) => l.code.toLowerCase() === code.toLowerCase());
1536
+ if (exact) return exact.name;
1537
+ const base = COMMON_LOCALES.find((l) => l.code.toLowerCase() === baseLanguage(code));
1538
+ return base ? `${base.name} (${code})` : code;
1539
+ }
1540
+ function normalizeLocale(input) {
1541
+ if (typeof input !== "string") return "";
1542
+ const parts = input.trim().replace(/_/g, "-").split("-").filter(Boolean);
1543
+ if (!parts.length || !/^[a-zA-Z]{2,3}$/.test(parts[0])) return "";
1544
+ return parts.slice(0, 3).map((p, i) => i === 0 ? p.toLowerCase() : p.length === 4 ? p[0].toUpperCase() + p.slice(1).toLowerCase() : p.length === 2 ? p.toUpperCase() : p).join("-");
1545
+ }
1546
+ function baseLanguage(locale) {
1547
+ return normalizeLocale(locale).split("-")[0] ?? "";
1548
+ }
1549
+ function resolveLocale(requested, available, defaultLocale = DEFAULT_LOCALE) {
1550
+ const def = normalizeLocale(defaultLocale) || DEFAULT_LOCALE;
1551
+ const avail = available.map(normalizeLocale).filter(Boolean);
1552
+ const all = [def, ...avail.filter((a) => a.toLowerCase() !== def.toLowerCase())];
1553
+ for (const raw of requested) {
1554
+ const want = normalizeLocale(raw);
1555
+ if (!want) continue;
1556
+ const exact = all.find((a) => a.toLowerCase() === want.toLowerCase());
1557
+ if (exact) return exact;
1558
+ const base = baseLanguage(want);
1559
+ const sameBase = all.find((a) => a.toLowerCase() === base) ?? all.find((a) => baseLanguage(a) === base);
1560
+ if (sameBase) return sameBase;
1561
+ }
1562
+ return def;
1563
+ }
1564
+ function visitorLocales(explicit, traits, platform = []) {
1565
+ const out = [];
1566
+ const push = (v) => {
1567
+ const n = normalizeLocale(v);
1568
+ if (n && !out.includes(n)) out.push(n);
1569
+ };
1570
+ push(explicit);
1571
+ push(traits?.language);
1572
+ push(traits?.locale);
1573
+ for (const p of platform) push(p);
1574
+ return out;
1575
+ }
1576
+ function guideTextEntries(config) {
1577
+ const out = [];
1578
+ const add = (key, source, kind, label) => {
1579
+ if (source && source.trim()) out.push({ key, source, kind, label });
1580
+ };
1581
+ config.steps.forEach((s, i) => {
1582
+ const at = `Step ${i + 1}`;
1583
+ add(`steps.${s.id}.title`, s.title, "text", `${at} \xB7 Title`);
1584
+ add(`steps.${s.id}.body`, s.body, "html", `${at} \xB7 Body`);
1585
+ add(`steps.${s.id}.customHtml`, s.customHtml, "html", `${at} \xB7 Custom HTML`);
1586
+ add(`steps.${s.id}.imageAlt`, s.imageAlt, "text", `${at} \xB7 Image description`);
1587
+ s.buttons.forEach((b) => add(`steps.${s.id}.buttons.${b.id}.label`, b.label, "text", `${at} \xB7 Button \u201C${b.label}\u201D`));
1588
+ });
1589
+ const c = config.checklist;
1590
+ if (c) {
1591
+ add("checklist.launcherLabel", c.launcherLabel, "text", "Checklist \xB7 Launcher label");
1592
+ add("checklist.completionTitle", c.completionTitle, "text", "Checklist \xB7 Completion title");
1593
+ add("checklist.completionBody", c.completionBody, "html", "Checklist \xB7 Completion message");
1594
+ c.tasks.forEach((t, i) => {
1595
+ add(`checklist.tasks.${t.id}.title`, t.title, "text", `Task ${i + 1} \xB7 Title`);
1596
+ add(`checklist.tasks.${t.id}.description`, t.description, "html", `Task ${i + 1} \xB7 Description`);
1597
+ });
1598
+ }
1599
+ const sv = config.survey;
1600
+ if (sv) {
1601
+ sv.questions.forEach((q, i) => {
1602
+ const at = `Question ${i + 1}`;
1603
+ add(`survey.questions.${q.id}.title`, q.title, "text", `${at} \xB7 Question`);
1604
+ add(`survey.questions.${q.id}.description`, q.description, "text", `${at} \xB7 Description`);
1605
+ add(`survey.questions.${q.id}.placeholder`, q.placeholder, "text", `${at} \xB7 Placeholder`);
1606
+ add(`survey.questions.${q.id}.lowLabel`, q.lowLabel, "text", `${at} \xB7 Low label`);
1607
+ add(`survey.questions.${q.id}.highLabel`, q.highLabel, "text", `${at} \xB7 High label`);
1608
+ q.options.forEach((o) => add(`survey.questions.${q.id}.options.${o.id}.label`, o.label, "text", `${at} \xB7 Option \u201C${o.label}\u201D`));
1609
+ });
1610
+ add("survey.thankYouTitle", sv.thankYouTitle, "text", "Survey \xB7 Thank-you title");
1611
+ add("survey.thankYouBody", sv.thankYouBody, "html", "Survey \xB7 Thank-you message");
1612
+ }
1613
+ return out;
1614
+ }
1615
+ function guideLocales(config) {
1616
+ const t = config.localization?.translations ?? {};
1617
+ return Object.keys(t).filter((l) => Object.values(t[l] ?? {}).some((v) => typeof v === "string" && v.trim() !== ""));
1618
+ }
1619
+ function pick(map, key, fallback) {
1620
+ const v = map?.[key];
1621
+ return typeof v === "string" && v.trim() !== "" ? v : fallback;
1622
+ }
1623
+ function applyGuideTranslations(config, map) {
1624
+ if (!map || !Object.keys(map).length) return config;
1625
+ return {
1626
+ ...config,
1627
+ steps: config.steps.map((s) => ({
1628
+ ...s,
1629
+ title: pick(map, `steps.${s.id}.title`, s.title),
1630
+ body: pick(map, `steps.${s.id}.body`, s.body),
1631
+ customHtml: pick(map, `steps.${s.id}.customHtml`, s.customHtml),
1632
+ imageAlt: pick(map, `steps.${s.id}.imageAlt`, s.imageAlt),
1633
+ buttons: s.buttons.map((b) => ({ ...b, label: pick(map, `steps.${s.id}.buttons.${b.id}.label`, b.label) }))
1634
+ })),
1635
+ checklist: config.checklist ? {
1636
+ ...config.checklist,
1637
+ launcherLabel: pick(map, "checklist.launcherLabel", config.checklist.launcherLabel),
1638
+ completionTitle: pick(map, "checklist.completionTitle", config.checklist.completionTitle),
1639
+ completionBody: pick(map, "checklist.completionBody", config.checklist.completionBody),
1640
+ tasks: config.checklist.tasks.map((t) => ({ ...t, title: pick(map, `checklist.tasks.${t.id}.title`, t.title), description: pick(map, `checklist.tasks.${t.id}.description`, t.description) }))
1641
+ } : config.checklist,
1642
+ survey: config.survey ? {
1643
+ ...config.survey,
1644
+ thankYouTitle: pick(map, "survey.thankYouTitle", config.survey.thankYouTitle),
1645
+ thankYouBody: pick(map, "survey.thankYouBody", config.survey.thankYouBody),
1646
+ questions: config.survey.questions.map((q) => ({
1647
+ ...q,
1648
+ title: pick(map, `survey.questions.${q.id}.title`, q.title),
1649
+ description: pick(map, `survey.questions.${q.id}.description`, q.description),
1650
+ placeholder: pick(map, `survey.questions.${q.id}.placeholder`, q.placeholder),
1651
+ lowLabel: pick(map, `survey.questions.${q.id}.lowLabel`, q.lowLabel),
1652
+ highLabel: pick(map, `survey.questions.${q.id}.highLabel`, q.highLabel),
1653
+ options: q.options.map((o) => ({ ...o, label: pick(map, `survey.questions.${q.id}.options.${o.id}.label`, o.label) }))
1654
+ }))
1655
+ } : config.survey
1656
+ };
1657
+ }
1658
+ function localizeGuide(guide, requested, defaultLocale = DEFAULT_LOCALE) {
1659
+ const translations = guide.config.localization?.translations ?? {};
1660
+ const locale = resolveLocale(requested, guideLocales(guide.config), defaultLocale);
1661
+ const key = Object.keys(translations).find((l) => normalizeLocale(l).toLowerCase() === locale.toLowerCase());
1662
+ if (!key) return { ...guide, locale };
1663
+ return { ...guide, locale, config: applyGuideTranslations(guide.config, translations[key]) };
1664
+ }
1665
+ function translationCoverage(entries, map, locale) {
1666
+ const keys = new Set(entries.map((e) => e.key));
1667
+ const missing = entries.filter((e) => !pick(map, e.key, void 0)).map((e) => e.key);
1668
+ const stale = Object.keys(map ?? {}).filter((k) => !keys.has(k) && (map?.[k] ?? "").trim() !== "");
1669
+ return { locale, total: entries.length, translated: entries.length - missing.length, missing, stale };
1670
+ }
1671
+ function resourceCenterTextEntries(rc) {
1672
+ const out = [];
1673
+ const add = (key, source, kind, label) => {
1674
+ if (source && source.trim()) out.push({ key, source, kind, label });
1675
+ };
1676
+ add("title", rc.title, "text", "Title");
1677
+ add("launcher.label", rc.launcher?.label, "text", "Launcher label");
1678
+ rc.collections.forEach((c) => {
1679
+ add(`collections.${c.id}.name`, c.name, "text", `Collection \u201C${c.name}\u201D`);
1680
+ add(`collections.${c.id}.description`, c.description, "text", `Collection \u201C${c.name}\u201D \xB7 Description`);
1681
+ });
1682
+ rc.links.forEach((l) => add(`links.${l.id}.label`, l.label, "text", `Link \u201C${l.label}\u201D`));
1683
+ rc.faqs.forEach((f, i) => {
1684
+ add(`faqs.${f.id}.question`, f.question, "text", `FAQ ${i + 1} \xB7 Question`);
1685
+ add(`faqs.${f.id}.answer`, f.answer, "html", `FAQ ${i + 1} \xB7 Answer`);
1686
+ });
1687
+ return out;
1688
+ }
1689
+ function articleTextEntries(a) {
1690
+ const out = [];
1691
+ if (a.title.trim()) out.push({ key: "title", source: a.title, kind: "text", label: "Title" });
1692
+ if (a.summary?.trim()) out.push({ key: "summary", source: a.summary, kind: "text", label: "Summary" });
1693
+ if (a.body?.trim()) out.push({ key: "body", source: a.body, kind: "html", label: "Body" });
1694
+ return out;
1695
+ }
1696
+ function resourceCenterLocales(rc) {
1697
+ const set = new Set(Object.keys(rc.translations ?? {}));
1698
+ for (const a of rc.articles) for (const l of Object.keys(a.translations ?? {})) set.add(l);
1699
+ for (const a of rc.announcements ?? []) for (const l of Object.keys(a.translations ?? {})) set.add(l);
1700
+ return [...set];
1701
+ }
1702
+ function translationFor(map, locale) {
1703
+ if (!map) return void 0;
1704
+ const key = Object.keys(map).find((l) => normalizeLocale(l).toLowerCase() === locale.toLowerCase());
1705
+ return key ? map[key] : void 0;
1706
+ }
1707
+ function localizeArticle(a, locale) {
1708
+ const t = translationFor(a.translations, locale);
1709
+ if (!t) return a;
1710
+ const body = t.body?.trim() ? t.body : a.body;
1711
+ return { ...a, title: t.title?.trim() ? t.title : a.title, summary: t.summary?.trim() ? t.summary : a.summary, body, ...t.bodyOmitted ? { bodyOmitted: true } : {} };
1712
+ }
1713
+ function localizeAnnouncement(a, locale) {
1714
+ const t = translationFor(a.translations, locale);
1715
+ if (!t) return a;
1716
+ return { ...a, title: t.title?.trim() ? t.title : a.title, body: t.body?.trim() ? t.body : a.body };
1717
+ }
1718
+ function localizeResourceCenter(rc, requested, defaultLocale = DEFAULT_LOCALE) {
1719
+ const locale = resolveLocale(requested, resourceCenterLocales(rc), defaultLocale);
1720
+ if (locale.toLowerCase() === normalizeLocale(defaultLocale).toLowerCase()) return { ...rc, locale };
1721
+ const map = translationFor(rc.translations, locale);
1722
+ return {
1723
+ ...rc,
1724
+ locale,
1725
+ title: pick(map, "title", rc.title),
1726
+ launcher: { ...rc.launcher, label: pick(map, "launcher.label", rc.launcher.label) },
1727
+ collections: rc.collections.map((c) => ({ ...c, name: pick(map, `collections.${c.id}.name`, c.name), description: pick(map, `collections.${c.id}.description`, c.description) })),
1728
+ links: rc.links.map((l) => ({ ...l, label: pick(map, `links.${l.id}.label`, l.label) })),
1729
+ faqs: rc.faqs.map((f) => ({ ...f, question: pick(map, `faqs.${f.id}.question`, f.question), answer: pick(map, `faqs.${f.id}.answer`, f.answer) })),
1730
+ articles: rc.articles.map((a) => localizeArticle(a, locale)),
1731
+ announcements: rc.announcements?.map((a) => localizeAnnouncement(a, locale))
1732
+ };
1733
+ }
1734
+
1735
+ // src/guide-experiments.ts
1736
+ var CONTROL_VARIANT_ID = "control";
1737
+ var GUIDE_EXPERIMENT_STATUSES = ["draft", "running", "completed"];
1738
+ var GUIDE_EXPERIMENT_GOALS = ["guide_completed", "event", "page_visit", "click_text"];
1739
+ var GUIDE_EXPERIMENT_GOAL_LABELS = {
1740
+ guide_completed: "Completes the guide",
1741
+ event: "Tracks an event",
1742
+ page_visit: "Visits a page",
1743
+ click_text: "Clicks an element with text"
1744
+ };
1745
+ function experimentSalt(experimentId) {
1746
+ return `gx:${experimentId}`;
1747
+ }
1748
+ function variantRanges(variants) {
1749
+ let at = 0;
1750
+ return variants.map((v) => {
1751
+ const from = at;
1752
+ at = Math.min(100, at + Math.max(0, Math.round(v.weight)));
1753
+ return { id: v.id, from, to: at };
1754
+ });
1755
+ }
1756
+ function assignGuideVariant(variants, experimentId, subject) {
1757
+ const bucket = rolloutBucket(subject, experimentSalt(experimentId));
1758
+ return variantRanges(variants).find((r) => bucket >= r.from && bucket < r.to)?.id ?? null;
1759
+ }
1760
+ function experimentBucketRules(salt, from, to) {
1761
+ return [
1762
+ { id: "experiment-from", field: "experiment.bucket", key: salt, op: "gte", value: from },
1763
+ { id: "experiment-to", field: "experiment.bucket", key: salt, op: "lt", value: to }
1764
+ ];
1765
+ }
1766
+ function withExperimentBucket(targeting, salt, from, to) {
1767
+ return { id: "experiment", op: "and", rules: [...experimentBucketRules(salt, from, to), targeting] };
1768
+ }
1769
+ function variantProblems(variants) {
1770
+ const problems = [];
1771
+ if (variants.length < 2) problems.push("An A/B test needs at least two variants.");
1772
+ if (variants.length > 5) problems.push("An A/B test can have at most five variants.");
1773
+ const guides = variants.filter((v) => v.guideId);
1774
+ if (guides.length === 0) problems.push("At least one variant must show a guide.");
1775
+ if (variants.filter((v) => !v.guideId).length > 1) problems.push("Only one variant can be the control group (no guide).");
1776
+ if (new Set(guides.map((v) => v.guideId)).size !== guides.length) problems.push("Each variant needs its own guide.");
1777
+ if (new Set(variants.map((v) => v.id)).size !== variants.length) problems.push("Variant ids must be unique.");
1778
+ if (variants.some((v) => !Number.isInteger(v.weight) || v.weight < 1)) problems.push("Every variant needs a whole-number share of at least 1%.");
1779
+ const total = variants.reduce((s, v) => s + v.weight, 0);
1780
+ if (total !== 100) problems.push(`Traffic shares must add up to 100% (now ${total}%).`);
1781
+ return problems;
1782
+ }
1783
+ function evenWeights(n) {
1784
+ if (n <= 0) return [];
1785
+ const base = Math.floor(100 / n);
1786
+ return Array.from({ length: n }, (_, i) => base + (i < 100 - base * n ? 1 : 0));
1787
+ }
1788
+
1789
+ // src/custom-css.ts
1790
+ var MAX_CUSTOM_CSS = 2e4;
1791
+ var BLOCKED_AT_RULES = /^@(import|charset|namespace|document|-moz-document)\b/i;
1792
+ var ALLOWED_AT_RULES = /^@(media|supports|container|layer|keyframes|-webkit-keyframes|font-face)\b/i;
1793
+ function splitBlocks(css) {
1794
+ const out = [];
1795
+ let i = 0;
1796
+ const n = css.length;
1797
+ while (i < n) {
1798
+ let prelude = "";
1799
+ let quote = null;
1800
+ while (i < n) {
1801
+ const ch = css[i];
1802
+ if (quote) {
1803
+ prelude += ch;
1804
+ if (ch === "\\") prelude += css[++i] ?? "";
1805
+ else if (ch === quote) quote = null;
1806
+ } else if (ch === '"' || ch === "'") {
1807
+ quote = ch;
1808
+ prelude += ch;
1809
+ } else if (ch === "{") break;
1810
+ else if (ch === ";") {
1811
+ prelude = "";
1812
+ } else if (ch === "}") {
1813
+ prelude = "";
1814
+ } else prelude += ch;
1815
+ i++;
1816
+ }
1817
+ if (i >= n) break;
1818
+ i++;
1819
+ let depth = 1;
1820
+ let body = "";
1821
+ quote = null;
1822
+ while (i < n && depth > 0) {
1823
+ const ch = css[i];
1824
+ if (quote) {
1825
+ body += ch;
1826
+ if (ch === "\\") body += css[++i] ?? "";
1827
+ else if (ch === quote) quote = null;
1828
+ } else if (ch === '"' || ch === "'") {
1829
+ quote = ch;
1830
+ body += ch;
1831
+ } else if (ch === "{") {
1832
+ depth++;
1833
+ body += ch;
1834
+ } else if (ch === "}") {
1835
+ depth--;
1836
+ if (depth > 0) body += ch;
1837
+ } else body += ch;
1838
+ i++;
1839
+ }
1840
+ out.push({ prelude: prelude.trim(), body });
1841
+ }
1842
+ return out;
1843
+ }
1844
+ function splitSelectors(list) {
1845
+ const out = [];
1846
+ let depth = 0;
1847
+ let cur = "";
1848
+ for (const ch of list) {
1849
+ if (ch === "(" || ch === "[") depth++;
1850
+ else if (ch === ")" || ch === "]") depth = Math.max(0, depth - 1);
1851
+ if (ch === "," && depth === 0) {
1852
+ out.push(cur.trim());
1853
+ cur = "";
1854
+ } else cur += ch;
1855
+ }
1856
+ if (cur.trim()) out.push(cur.trim());
1857
+ return out;
1858
+ }
1859
+ function safeUrl(raw) {
1860
+ const u = raw.trim().replace(/^['"]|['"]$/g, "").trim();
1861
+ return /^https:\/\//i.test(u) || /^data:image\/(png|jpe?g|gif|webp|svg\+xml)[;,]/i.test(u);
1862
+ }
1863
+ function cleanDeclarations(body) {
1864
+ return body.replace(/url\(([^)]*)\)/gi, (m, inner) => safeUrl(inner) ? m : "none").replace(/expression\s*\(/gi, "(").replace(/javascript\s*:/gi, "").replace(/(^|[;{\s])(-moz-binding|behavior)\s*:[^;]*;?/gi, "$1").replace(/[{}]/g, "").trim();
1865
+ }
1866
+ function sanitizeCustomCss(input) {
1867
+ if (!input) return "";
1868
+ let css = String(input).slice(0, MAX_CUSTOM_CSS);
1869
+ css = css.replace(/\/\*[\s\S]*?\*\//g, "").replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, "").replace(/<\/?[a-z!][^>]*>/gi, "").replace(/</g, "");
1870
+ return render(splitBlocks(css), 0);
1871
+ }
1872
+ function render(blocks, depth) {
1873
+ const parts = [];
1874
+ for (const b of blocks) {
1875
+ if (!b.prelude) continue;
1876
+ if (b.prelude.startsWith("@")) {
1877
+ if (BLOCKED_AT_RULES.test(b.prelude) || !ALLOWED_AT_RULES.test(b.prelude)) continue;
1878
+ if (/^@(-webkit-)?keyframes|^@font-face/i.test(b.prelude)) {
1879
+ const inner = /^@font-face/i.test(b.prelude) ? cleanDeclarations(b.body) : splitBlocks(b.body).map((k) => `${k.prelude}{${cleanDeclarations(k.body)}}`).join("");
1880
+ parts.push(`${b.prelude}{${inner}}`);
1881
+ } else if (depth < 3) parts.push(`${b.prelude}{${render(splitBlocks(b.body), depth + 1)}}`);
1882
+ continue;
1883
+ }
1884
+ const selectors = splitSelectors(b.prelude).filter((s) => !/:host|::slotted|::part/i.test(s));
1885
+ if (!selectors.length) continue;
1886
+ parts.push(`${selectors.join(",")}{${cleanDeclarations(b.body)}}`);
1887
+ }
1888
+ return parts.join("\n");
1889
+ }
1890
+ function scopeCustomCss(css, scope) {
1891
+ return scopeBlocks(splitBlocks(sanitizeCustomCss(css)), scope, 0);
1892
+ }
1893
+ function scopeBlocks(blocks, scope, depth) {
1894
+ const parts = [];
1895
+ for (const b of blocks) {
1896
+ if (b.prelude.startsWith("@")) {
1897
+ if (/^@(-webkit-)?keyframes|^@font-face/i.test(b.prelude)) parts.push(`${b.prelude}{${b.body}}`);
1898
+ else if (depth < 3) parts.push(`${b.prelude}{${scopeBlocks(splitBlocks(b.body), scope, depth + 1)}}`);
1899
+ continue;
1900
+ }
1901
+ const sels = splitSelectors(b.prelude).map((s) => {
1902
+ const self = s.match(/^(:root|html|body|:scope|&)(?![\w-])(.*)$/i);
1903
+ if (self) return `${scope}${self[2] ?? ""}`;
1904
+ return `${scope} ${s}`;
1905
+ });
1906
+ parts.push(`${sels.join(",")}{${b.body}}`);
1907
+ }
1908
+ return parts.join("\n");
1909
+ }
1910
+
1911
+ // src/contrast.ts
1912
+ var WCAG_AA_TEXT = 4.5;
1913
+ var WCAG_AA_LARGE = 3;
1914
+ function channel(c) {
1915
+ const s = c / 255;
1916
+ return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
1917
+ }
1918
+ function parseHexColor(hex) {
1919
+ if (!hex) return null;
1920
+ const m = hex.trim().match(/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i);
1921
+ if (!m) return null;
1922
+ const h = m[1].length === 3 ? m[1].split("").map((c) => c + c).join("") : m[1];
1923
+ return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
1924
+ }
1925
+ function relativeLuminance(hex) {
1926
+ const rgb = parseHexColor(hex);
1927
+ if (!rgb) return null;
1928
+ return 0.2126 * channel(rgb[0]) + 0.7152 * channel(rgb[1]) + 0.0722 * channel(rgb[2]);
1929
+ }
1930
+ function contrastRatio(a, b) {
1931
+ const la = relativeLuminance(a);
1932
+ const lb = relativeLuminance(b);
1933
+ if (la === null || lb === null) return null;
1934
+ const [hi, lo] = la > lb ? [la, lb] : [lb, la];
1935
+ return Math.round((hi + 0.05) / (lo + 0.05) * 100) / 100;
1936
+ }
1937
+ function readableTextOn(background) {
1938
+ const white = contrastRatio(background, "#ffffff") ?? 0;
1939
+ const dark = contrastRatio(background, "#111827") ?? 0;
1940
+ return white >= dark ? "#ffffff" : "#111827";
1941
+ }
1942
+ function contrastIssues(colors) {
1943
+ const issues = [];
1944
+ const { primaryColor, backgroundColor, textColor } = colors;
1945
+ if (textColor && backgroundColor) {
1946
+ const r = contrastRatio(textColor, backgroundColor);
1947
+ if (r !== null && r < WCAG_AA_TEXT) issues.push({ pair: "text", ratio: r, required: WCAG_AA_TEXT, message: `Text on the background has a contrast of ${r}:1; WCAG AA needs ${WCAG_AA_TEXT}:1.` });
1948
+ }
1949
+ if (primaryColor) {
1950
+ const label = contrastRatio(primaryColor, readableTextOn(primaryColor));
1951
+ if (label !== null && label < WCAG_AA_TEXT) issues.push({ pair: "button", ratio: label, required: WCAG_AA_TEXT, message: `Button labels on the primary colour have a contrast of ${label}:1; WCAG AA needs ${WCAG_AA_TEXT}:1.` });
1952
+ if (backgroundColor) {
1953
+ const link = contrastRatio(primaryColor, backgroundColor);
1954
+ if (link !== null && link < WCAG_AA_LARGE) issues.push({ pair: "link", ratio: link, required: WCAG_AA_LARGE, message: `The primary colour on the background (links, outlined buttons) has a contrast of ${link}:1; WCAG AA needs ${WCAG_AA_LARGE}:1.` });
1955
+ }
1956
+ }
1957
+ return issues;
1958
+ }
831
1959
  // Annotate the CommonJS export names for ESM import in node:
832
1960
  0 && (module.exports = {
1961
+ AGENT_METADATA_MAX_BYTES,
1962
+ AGENT_METADATA_MAX_KEYS,
1963
+ AGENT_OUTCOMES,
1964
+ AGENT_TOOL_CALLS_PER_TURN,
1965
+ AGENT_TOOL_STATUSES,
1966
+ AGENT_TURNS_PER_BATCH,
1967
+ AGENT_TURN_MAX_TEXT,
1968
+ AGENT_TURN_ROLES,
833
1969
  ARTICLE_VISIBILITIES,
834
1970
  CAPTURE_PARAM,
835
1971
  CAPTURE_SCREENSHOT_MAX_BYTES,
1972
+ COMMON_LOCALES,
836
1973
  COMPLAINT_EVENTS,
837
1974
  COMPLAINT_FIELD_TYPES,
838
1975
  COMPLAINT_STATUSES,
1976
+ CONTROL_VARIANT_ID,
1977
+ DEFAULT_FEATURE_ATTRIBUTE,
1978
+ DEFAULT_LOCALE,
839
1979
  DEFAULT_PLATFORMS,
840
1980
  EMBED_IFRAME_ATTRS,
1981
+ FEATURE_ATTRIBUTE_PATTERN,
1982
+ FEATURE_RULE_LIMITS,
841
1983
  FILE_LINK_CLASS,
842
1984
  GUIDELY_ID_ATTRIBUTE,
1985
+ GUIDE_EXPERIMENT_GOALS,
1986
+ GUIDE_EXPERIMENT_GOAL_LABELS,
1987
+ GUIDE_EXPERIMENT_STATUSES,
843
1988
  GUIDE_VISIBILITIES,
844
1989
  LOGGED_IN_AUDIENCE,
845
1990
  MAX_COLLECTION_DEPTH,
@@ -848,6 +1993,7 @@ var complaintReference = (number) => `CMP-${number}`;
848
1993
  MAX_COMPLAINT_CHOICES,
849
1994
  MAX_COMPLAINT_FIELDS,
850
1995
  MAX_COMPLAINT_LENGTH,
1996
+ MAX_CUSTOM_CSS,
851
1997
  MAX_PROPERTY_BYTES,
852
1998
  MAX_STACK_FRAMES,
853
1999
  MEDIA_FILE_MAX_BYTES,
@@ -858,38 +2004,65 @@ var complaintReference = (number) => `CMP-${number}`;
858
2004
  MEDIA_VIDEO_MAX_BYTES,
859
2005
  MEDIA_VIDEO_TYPES,
860
2006
  NETWORK_PLUGIN,
2007
+ NOISY_DATA_ATTR,
2008
+ PAGE_RULE_LIMITS,
861
2009
  PATH_ID_PATTERN,
862
2010
  PDF_BLOCK_CLASS,
863
2011
  PDF_MAX_PAGES,
864
2012
  PDF_PAGES_CLASS,
865
2013
  PLATFORMS,
2014
+ RC_MODULE_IDS,
2015
+ RC_MODULE_LABELS,
866
2016
  RULE_FIELDS,
867
2017
  RULE_OPERATORS,
2018
+ SCOUT_AGENT_ID,
868
2019
  SENSITIVE_HEADER_PATTERN,
869
2020
  SENSITIVE_PARAM_PATTERN,
870
2021
  SENSITIVE_PROPERTY_PATTERN,
871
2022
  SERVER_ASSISTED_FIELDS,
872
2023
  SHOW_ME_CLASS,
873
2024
  SHOW_ME_HREF_PREFIX,
2025
+ SNAPSHOT_ATTRS,
2026
+ SNAPSHOT_LIMITS,
874
2027
  SOURCEMAP_MAX_FILES_PER_UPLOAD,
875
2028
  SOURCEMAP_MAX_FILE_BYTES,
2029
+ SelectorSyntaxError,
2030
+ TAG_PROPERTY_LIMITS,
876
2031
  VIDEO_ATTRS,
2032
+ WCAG_AA_LARGE,
2033
+ WCAG_AA_TEXT,
877
2034
  WS_MAX_MESSAGES_PER_SOCKET,
878
2035
  WS_MAX_MESSAGE_BYTES,
2036
+ applyGuideTranslations,
2037
+ articleTextEntries,
2038
+ assignGuideVariant,
2039
+ baseLanguage,
879
2040
  clamp,
880
2041
  compare,
881
2042
  compareVersions,
882
2043
  complaintReference,
2044
+ compoundMatches,
2045
+ contrastIssues,
2046
+ contrastRatio,
2047
+ cssSelectorError,
883
2048
  describeRule,
884
2049
  emptyRuleGroup,
885
2050
  evaluateRule,
886
2051
  evaluateRules,
2052
+ evenWeights,
2053
+ experimentBucketRules,
2054
+ experimentSalt,
2055
+ featureCssSelectors,
2056
+ featureRuleError,
2057
+ featureRuleMatchesClick,
887
2058
  fingerprintError,
888
2059
  firstNameFromTraits,
889
2060
  fnv1a,
890
2061
  foldPathIds,
891
2062
  getPath,
2063
+ guideLocales,
892
2064
  guidePlatforms,
2065
+ guideTextEntries,
893
2066
  guidelyIdFromSelector,
894
2067
  hasVideoMedia,
895
2068
  isAllowedEmbedSrc,
@@ -902,6 +2075,12 @@ var complaintReference = (number) => `CMP-${number}`;
902
2075
  isRuleGroup,
903
2076
  isSafeMediaUrl,
904
2077
  isVideoContentType,
2078
+ localeName,
2079
+ localizeAnnouncement,
2080
+ localizeArticle,
2081
+ localizeGuide,
2082
+ localizeResourceCenter,
2083
+ matchCssSelector,
905
2084
  matchUrlPattern,
906
2085
  matchesAudiences,
907
2086
  matchesHelpQuery,
@@ -909,25 +2088,52 @@ var complaintReference = (number) => `CMP-${number}`;
909
2088
  mediaMaxBytes,
910
2089
  normalizeErrorMessage,
911
2090
  normalizeFrameFile,
2091
+ normalizeLocale,
2092
+ normalizeSnapshot,
2093
+ orderedModules,
912
2094
  pageLabel,
2095
+ pagePattern,
2096
+ pageRuleError,
2097
+ pageRuleMatches,
2098
+ pageRulesMatch,
913
2099
  pageTerms,
2100
+ parseCssSelector,
2101
+ parseHexColor,
914
2102
  parseStack,
2103
+ propertyAttributeAllowed,
2104
+ queryConditionRegex,
915
2105
  randomId,
2106
+ readableTextOn,
2107
+ relativeLuminance,
2108
+ resolveLocale,
2109
+ resourceCenterLocales,
916
2110
  resourceCenterSections,
2111
+ resourceCenterTextEntries,
917
2112
  rolloutBucket,
918
2113
  safeRegExp,
2114
+ sanitizeCustomCss,
919
2115
  sanitizeProperties,
2116
+ scopeCustomCss,
920
2117
  screenPath,
921
2118
  servedArticleAudiences,
922
2119
  showMeHref,
923
2120
  showMeKeyFromHref,
924
2121
  showMeKeys,
925
2122
  slugify,
2123
+ snapshotFromSelector,
2124
+ stableStringify,
926
2125
  stripOrigin,
927
2126
  supportsPlatform,
928
2127
  toEmbedUrl,
2128
+ translationCoverage,
2129
+ unreadAnnouncements,
2130
+ variantProblems,
2131
+ variantRanges,
2132
+ visibleAnnouncements,
929
2133
  visibleArticleIds,
930
2134
  visitorAudiences,
2135
+ visitorLocales,
2136
+ withExperimentBucket,
931
2137
  withVisibility
932
2138
  });
933
2139
  //# sourceMappingURL=runtime.cjs.map