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