@guidelytech/react-native 0.4.1 → 0.5.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/index.js CHANGED
@@ -3,10 +3,10 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
 
5
5
  // src/index.tsx
6
- import { useContext as useContext2, useEffect as useEffect7, useRef as useRef5, useState as useState6 } from "react";
6
+ import { useContext as useContext2, useEffect as useEffect8, useRef as useRef5, useState as useState7 } from "react";
7
7
  import { AppState, Dimensions, Linking, Platform as Platform6 } from "react-native";
8
8
 
9
- // ../shared/dist/chunk-QUKMOTT4.js
9
+ // ../shared/dist/chunk-5IQVBZO6.js
10
10
  function fnv1a(input) {
11
11
  let h = 2166136261;
12
12
  for (let i = 0; i < input.length; i++) {
@@ -310,6 +310,148 @@ function sanitizeProperties(props) {
310
310
  return out;
311
311
  }
312
312
  var SOURCEMAP_MAX_FILE_BYTES = 25 * 1024 * 1024;
313
+ var MEDIA_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
314
+ var MEDIA_VIDEO_MAX_BYTES = 200 * 1024 * 1024;
315
+ var YOUTUBE_ID = /^[A-Za-z0-9_-]{11}$/;
316
+ var VIMEO_ID = /^\d{1,12}$/;
317
+ var VIMEO_HASH = /^[0-9a-f]{6,20}$/i;
318
+ var LOOM_ID = /^[0-9a-f]{32}$/i;
319
+ var WISTIA_ID = /^[a-z0-9]{10}$/i;
320
+ function parseUrl(input) {
321
+ const s = input.trim();
322
+ if (!s) return null;
323
+ try {
324
+ const u = new URL(/^[a-z][a-z0-9+.-]*:/i.test(s) ? s : `https://${s}`);
325
+ if (u.protocol !== "https:" && u.protocol !== "http:") return null;
326
+ if (u.username || u.password || u.port && u.port !== "443" && u.port !== "80") return null;
327
+ return u;
328
+ } catch {
329
+ return null;
330
+ }
331
+ }
332
+ function parseStart(v) {
333
+ if (!v) return 0;
334
+ if (/^\d+$/.test(v)) return Math.min(Number(v), 86400);
335
+ const m = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/.exec(v);
336
+ if (!m || !m[0]) return 0;
337
+ return Math.min(Number(m[1] ?? 0) * 3600 + Number(m[2] ?? 0) * 60 + Number(m[3] ?? 0), 86400);
338
+ }
339
+ function youtube(id, start) {
340
+ if (!YOUTUBE_ID.test(id)) return null;
341
+ return {
342
+ provider: "youtube",
343
+ src: `https://www.youtube-nocookie.com/embed/${id}${start ? `?start=${start}` : ""}`,
344
+ watchUrl: `https://www.youtube.com/watch?v=${id}${start ? `&t=${start}s` : ""}`,
345
+ title: "YouTube video"
346
+ };
347
+ }
348
+ function vimeo(id, hash) {
349
+ if (!VIMEO_ID.test(id) || hash !== null && !VIMEO_HASH.test(hash)) return null;
350
+ const h = hash ? hash.toLowerCase() : null;
351
+ return { provider: "vimeo", src: `https://player.vimeo.com/video/${id}${h ? `?h=${h}` : ""}`, watchUrl: `https://vimeo.com/${id}${h ? `/${h}` : ""}`, title: "Vimeo video" };
352
+ }
353
+ function toEmbedUrl(input) {
354
+ const u = parseUrl(input);
355
+ if (!u) return null;
356
+ const host = u.hostname.toLowerCase().replace(/^www\./, "");
357
+ const parts = u.pathname.split("/").filter(Boolean);
358
+ switch (host) {
359
+ case "youtube.com":
360
+ case "m.youtube.com":
361
+ case "youtube-nocookie.com": {
362
+ const start = parseStart(u.searchParams.get("start") ?? u.searchParams.get("t"));
363
+ if (parts[0] === "watch" && parts.length === 1) return youtube(u.searchParams.get("v") ?? "", start);
364
+ if ((parts[0] === "embed" || parts[0] === "shorts" || parts[0] === "live") && parts.length === 2) return youtube(parts[1], start);
365
+ return null;
366
+ }
367
+ case "youtu.be":
368
+ return parts.length === 1 ? youtube(parts[0], parseStart(u.searchParams.get("t") ?? u.searchParams.get("start"))) : null;
369
+ case "vimeo.com":
370
+ if (parts.length === 1) return vimeo(parts[0], null);
371
+ if (parts.length === 2 && VIMEO_ID.test(parts[0])) return vimeo(parts[0], parts[1]);
372
+ return null;
373
+ case "player.vimeo.com":
374
+ return parts[0] === "video" && parts.length === 2 ? vimeo(parts[1], u.searchParams.get("h")) : null;
375
+ case "loom.com":
376
+ if ((parts[0] === "share" || parts[0] === "embed") && parts.length === 2 && LOOM_ID.test(parts[1])) {
377
+ const id = parts[1].toLowerCase();
378
+ return { provider: "loom", src: `https://www.loom.com/embed/${id}`, watchUrl: `https://www.loom.com/share/${id}`, title: "Loom video" };
379
+ }
380
+ return null;
381
+ default: {
382
+ let id;
383
+ if (host === "fast.wistia.net" || host === "fast.wistia.com") id = parts[0] === "embed" && parts[1] === "iframe" && parts.length === 3 ? parts[2] : void 0;
384
+ else if (host === "wi.st" || host === "wistia.com" || /^[a-z0-9-]+\.wistia\.com$/.test(host)) id = parts[0] === "medias" && parts.length === 2 ? parts[1] : void 0;
385
+ if (!id || !WISTIA_ID.test(id)) return null;
386
+ return { provider: "wistia", src: `https://fast.wistia.net/embed/iframe/${id}`, watchUrl: `https://fast.wistia.net/embed/iframe/${id}`, title: "Wistia video" };
387
+ }
388
+ }
389
+ }
390
+ function isAllowedEmbedSrc(src) {
391
+ if (!src) return false;
392
+ return toEmbedUrl(src)?.src === src;
393
+ }
394
+ function isSafeMediaUrl(url) {
395
+ if (!url) return false;
396
+ try {
397
+ const u = new URL(url);
398
+ return (u.protocol === "https:" || u.protocol === "http:") && !u.username && !u.password;
399
+ } catch {
400
+ return false;
401
+ }
402
+ }
403
+ var MAX_COLLECTION_DEPTH = 3;
404
+ function matchesAudiences(required, member) {
405
+ if (!required || required.length === 0) return true;
406
+ if (!member) return false;
407
+ const has2 = member instanceof Set ? (id) => member.has(id) : (id) => member.includes(id);
408
+ return required.some(has2);
409
+ }
410
+ function resourceCenterSections(collections, articles, member) {
411
+ const byId = new Map(collections.map((c) => [c.id, c]));
412
+ const children = /* @__PURE__ */ new Map();
413
+ for (const c of collections) {
414
+ const parent = c.parentId && c.parentId !== c.id && byId.has(c.parentId) ? c.parentId : null;
415
+ const list = children.get(parent) ?? [];
416
+ list.push(c);
417
+ children.set(parent, list);
418
+ }
419
+ for (const list of children.values()) list.sort((a, b) => a.order - b.order || a.name.localeCompare(b.name));
420
+ const direct = /* @__PURE__ */ new Map();
421
+ const uncategorized = [];
422
+ for (const a of articles) {
423
+ if (!matchesAudiences(a.audienceIds, member)) continue;
424
+ if (a.collectionId && byId.has(a.collectionId)) {
425
+ const list = direct.get(a.collectionId) ?? [];
426
+ list.push(a);
427
+ direct.set(a.collectionId, list);
428
+ } else uncategorized.push(a);
429
+ }
430
+ const sections = [];
431
+ const seen = /* @__PURE__ */ new Set();
432
+ const visit = (c, path) => {
433
+ if (seen.has(c.id)) return;
434
+ seen.add(c.id);
435
+ if (!matchesAudiences(c.audienceIds, member)) return;
436
+ const here = [...path, c.name];
437
+ const list = direct.get(c.id);
438
+ if (list?.length) sections.push({ collection: c, path: here, depth: here.length - 1, articles: list });
439
+ for (const child of children.get(c.id) ?? []) visit(child, here);
440
+ };
441
+ for (const root of children.get(null) ?? []) visit(root, []);
442
+ if (uncategorized.length) sections.push({ collection: null, path: [], depth: 0, articles: uncategorized });
443
+ return sections;
444
+ }
445
+ function isArticleListed(collections, article, member) {
446
+ if (!matchesAudiences(article.audienceIds, member)) return false;
447
+ const byId = new Map(collections.map((c) => [c.id, c]));
448
+ let cur = article.collectionId ? byId.get(article.collectionId) : void 0;
449
+ for (let guard = 0; cur && guard <= MAX_COLLECTION_DEPTH * 4; guard++) {
450
+ if (!matchesAudiences(cur.audienceIds, member)) return false;
451
+ cur = cur.parentId ? byId.get(cur.parentId) : void 0;
452
+ }
453
+ return true;
454
+ }
313
455
 
314
456
  // ../sdk-core/dist/chunk-GJT7CFAC.js
315
457
  var __defProp2 = Object.defineProperty;
@@ -1564,7 +1706,7 @@ var PersistentStore = class {
1564
1706
 
1565
1707
  // src/core/client.ts
1566
1708
  var SDK_NAME = "@guidelytech/react-native";
1567
- var SDK_VERSION = "0.4.1";
1709
+ var SDK_VERSION = "0.5.0";
1568
1710
  var DEFAULT_API_URL = "https://guidelytech.com";
1569
1711
  var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
1570
1712
  var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
@@ -2429,6 +2571,10 @@ var GuidelyClient = class {
2429
2571
  if (this.activeSurvey?.guide.id !== guideId) return;
2430
2572
  this.finishSurvey(this.surveyView?.submitted ? "submitted" : "dismissed");
2431
2573
  }
2574
+ /** The visitor's audiences (from the last identify), for audience-targeted help articles. */
2575
+ getAudienceIds() {
2576
+ return this.state.audienceIds;
2577
+ }
2432
2578
  getResourceCenter() {
2433
2579
  const rc = this.config?.resourceCenter;
2434
2580
  return rc && rc.enabled ? rc : null;
@@ -2693,11 +2839,11 @@ function forPlatform(config, platform) {
2693
2839
  }
2694
2840
 
2695
2841
  // src/ui/Overlay.tsx
2696
- import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef3, useState as useState5, useSyncExternalStore as useSyncExternalStore2 } from "react";
2697
- import { Platform as Platform5, Pressable as Pressable7, StatusBar, StyleSheet as StyleSheet9, Text as Text8, useColorScheme, useWindowDimensions, View as View9 } from "react-native";
2842
+ import { useCallback as useCallback2, useEffect as useEffect6, useRef as useRef3, useState as useState6, useSyncExternalStore as useSyncExternalStore2 } from "react";
2843
+ import { Platform as Platform5, Pressable as Pressable8, StatusBar, StyleSheet as StyleSheet9, Text as Text8, useColorScheme, useWindowDimensions, View as View9 } from "react-native";
2698
2844
 
2699
2845
  // src/ui/Checklist.tsx
2700
- import { Pressable as Pressable2, ScrollView, StyleSheet as StyleSheet3, Text as Text3, View as View3 } from "react-native";
2846
+ import { Pressable as Pressable3, ScrollView, StyleSheet as StyleSheet3, Text as Text3, View as View3 } from "react-native";
2701
2847
 
2702
2848
  // src/ui/primitives.tsx
2703
2849
  import { Pressable, StyleSheet, Text, View } from "react-native";
@@ -2751,8 +2897,8 @@ var styles = StyleSheet.create({
2751
2897
  });
2752
2898
 
2753
2899
  // src/ui/RichText.tsx
2754
- import { useMemo } from "react";
2755
- import { Platform, StyleSheet as StyleSheet2, Text as Text2, View as View2 } from "react-native";
2900
+ import { useEffect, useMemo, useState } from "react";
2901
+ import { Image, Platform, Pressable as Pressable2, StyleSheet as StyleSheet2, Text as Text2, View as View2 } from "react-native";
2756
2902
 
2757
2903
  // src/core/html.ts
2758
2904
  var ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: "\xA0", mdash: "\u2014", ndash: "\u2013", hellip: "\u2026", rsquo: "\u2019", lsquo: "\u2018", rdquo: "\u201D", ldquo: "\u201C", copy: "\xA9", reg: "\xAE", trade: "\u2122", times: "\xD7", rarr: "\u2192", larr: "\u2190", bull: "\u2022" };
@@ -2766,7 +2912,8 @@ function decodeEntities(s) {
2766
2912
  });
2767
2913
  }
2768
2914
  var BLOCK_TAGS = /* @__PURE__ */ new Set(["p", "div", "section", "article", "header", "footer", "h1", "h2", "h3", "h4", "h5", "h6", "li", "blockquote", "ul", "ol", "table", "tr", "pre"]);
2769
- var SKIP_TAGS = /* @__PURE__ */ new Set(["script", "style", "head", "title", "svg", "template"]);
2915
+ var SKIP_TAGS = /* @__PURE__ */ new Set(["script", "style", "head", "title", "svg", "template", "iframe", "video"]);
2916
+ var PROVIDER_LABEL = { youtube: "Watch on YouTube", vimeo: "Watch on Vimeo", loom: "Watch on Loom", wistia: "Watch video" };
2770
2917
  function attr(attrs, name) {
2771
2918
  const m = attrs.match(new RegExp(`\\b${name}\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`, "i"));
2772
2919
  return m ? decodeEntities(m[2] ?? m[3] ?? m[4] ?? "") : void 0;
@@ -2778,6 +2925,10 @@ function parseHtml(html) {
2778
2925
  const lists = [];
2779
2926
  let current = null;
2780
2927
  let skipping = null;
2928
+ const media = (block) => {
2929
+ flush();
2930
+ blocks.push({ level: 1, runs: [], ...block });
2931
+ };
2781
2932
  const flush = () => {
2782
2933
  if (current) {
2783
2934
  const runs = current.runs;
@@ -2825,7 +2976,16 @@ function parseHtml(html) {
2825
2976
  continue;
2826
2977
  }
2827
2978
  if (SKIP_TAGS.has(tag)) {
2828
- if (!closing && !token.endsWith("/>")) skipping = tag;
2979
+ if (closing) continue;
2980
+ if (tag === "iframe") {
2981
+ const src = attr(attrs, "src");
2982
+ const embed = isAllowedEmbedSrc(src) ? toEmbedUrl(src) : null;
2983
+ if (embed) media({ kind: "video", href: embed.watchUrl, alt: PROVIDER_LABEL[embed.provider] });
2984
+ } else if (tag === "video") {
2985
+ const src = attr(attrs, "src");
2986
+ if (isSafeMediaUrl(src)) media({ kind: "video", href: src, alt: "Play video" });
2987
+ }
2988
+ if (!token.endsWith("/>")) skipping = tag;
2829
2989
  continue;
2830
2990
  }
2831
2991
  if (closing) {
@@ -2886,8 +3046,13 @@ function parseHtml(html) {
2886
3046
  default:
2887
3047
  if (BLOCK_TAGS.has(tag)) open("paragraph");
2888
3048
  else if (tag === "img") {
2889
- const alt = attr(attrs, "alt");
2890
- if (alt) push(alt);
3049
+ const src = attr(attrs, "src") ?? "";
3050
+ const alt = attr(attrs, "alt") ?? "";
3051
+ if (isSafeMediaUrl(src) || /^data:image\//i.test(src)) {
3052
+ const w = Number(attr(attrs, "width"));
3053
+ const h = Number(attr(attrs, "height"));
3054
+ media({ kind: "image", src, alt, ...w > 0 && h > 0 ? { width: w, height: h } : {} });
3055
+ } else if (alt) push(alt);
2891
3056
  }
2892
3057
  }
2893
3058
  }
@@ -2895,7 +3060,7 @@ function parseHtml(html) {
2895
3060
  return blocks;
2896
3061
  }
2897
3062
  function htmlToText(html) {
2898
- return parseHtml(html).map((b) => (b.marker ? `${b.marker} ` : "") + b.runs.map((r) => r.text).join("")).join("\n");
3063
+ return parseHtml(html).map((b) => b.kind === "image" || b.kind === "video" ? b.alt ?? "" : (b.marker ? `${b.marker} ` : "") + b.runs.map((r) => r.text).join("")).filter(Boolean).join("\n");
2899
3064
  }
2900
3065
 
2901
3066
  // src/ui/RichText.tsx
@@ -2912,11 +3077,32 @@ function runStyle(run, p) {
2912
3077
  color: run.href ? p.primary : void 0
2913
3078
  };
2914
3079
  }
3080
+ function BodyImage({ block, palette: p }) {
3081
+ const [ratio, setRatio] = useState(block.width && block.height ? block.width / block.height : 16 / 9);
3082
+ useEffect(() => {
3083
+ if (block.width && block.height) return;
3084
+ let live = true;
3085
+ Image.getSize(block.src, (w, h) => live && w > 0 && h > 0 && setRatio(w / h), () => {
3086
+ });
3087
+ return () => {
3088
+ live = false;
3089
+ };
3090
+ }, [block.src, block.width, block.height]);
3091
+ return /* @__PURE__ */ jsx2(Image, { source: { uri: block.src }, accessible: true, accessibilityRole: "image", accessibilityLabel: block.alt || void 0, resizeMode: "contain", style: [styles2.image, { aspectRatio: ratio, backgroundColor: p.surface }] });
3092
+ }
3093
+ function BodyVideo({ block, palette: p, onLink }) {
3094
+ return /* @__PURE__ */ jsxs(Pressable2, { accessibilityRole: "link", accessibilityLabel: block.alt, onPress: () => onLink(block.href), style: ({ pressed }) => [styles2.video, { borderColor: p.border, backgroundColor: p.surface, opacity: pressed ? 0.7 : 1 }], children: [
3095
+ /* @__PURE__ */ jsx2(Text2, { style: [styles2.play, { color: p.primary }], children: "\u25B6" }),
3096
+ /* @__PURE__ */ jsx2(Text2, { style: { color: p.primary, fontFamily: p.fontFamily, fontWeight: "600" }, children: block.alt })
3097
+ ] });
3098
+ }
2915
3099
  function RichText({ html, palette: p, onLink, size = 15 }) {
2916
3100
  const blocks = useMemo(() => parseHtml(html), [html]);
2917
3101
  if (!blocks.length) return null;
2918
3102
  const base = { color: p.text, fontSize: size, lineHeight: Math.round(size * 1.42), fontFamily: p.fontFamily };
2919
3103
  return /* @__PURE__ */ jsx2(View2, { style: styles2.wrap, children: blocks.map((b, i) => {
3104
+ if (b.kind === "image") return /* @__PURE__ */ jsx2(BodyImage, { block: b, palette: p }, i);
3105
+ if (b.kind === "video") return /* @__PURE__ */ jsx2(BodyVideo, { block: b, palette: p, onLink }, i);
2920
3106
  const runs = b.runs.map((r, j) => /* @__PURE__ */ jsx2(Text2, { style: runStyle(r, p), onPress: r.href ? () => onLink(r.href) : void 0, accessibilityRole: r.href ? "link" : void 0, children: r.text }, j));
2921
3107
  if (b.kind === "list-item") {
2922
3108
  return /* @__PURE__ */ jsxs(View2, { style: [styles2.li, { paddingLeft: (b.level - 1) * 14 }], children: [
@@ -2935,7 +3121,10 @@ var styles2 = StyleSheet2.create({
2935
3121
  wrap: { gap: 6 },
2936
3122
  li: { flexDirection: "row", gap: 6 },
2937
3123
  marker: { minWidth: 14 },
2938
- flex: { flex: 1 }
3124
+ flex: { flex: 1 },
3125
+ image: { width: "100%", borderRadius: 6 },
3126
+ video: { flexDirection: "row", alignItems: "center", gap: 8, paddingVertical: 12, paddingHorizontal: 14, borderWidth: 1, borderRadius: 8 },
3127
+ play: { fontSize: 14 }
2939
3128
  });
2940
3129
 
2941
3130
  // src/ui/Checklist.tsx
@@ -2973,7 +3162,7 @@ function Checklist({ view, client, palette: p, geometry, showLauncher, launcherB
2973
3162
  const isDone = done.has(task.id);
2974
3163
  const locked = !!nextOpen && !isDone && task.id !== nextOpen;
2975
3164
  return /* @__PURE__ */ jsxs2(
2976
- Pressable2,
3165
+ Pressable3,
2977
3166
  {
2978
3167
  disabled: locked,
2979
3168
  accessibilityRole: "button",
@@ -2998,7 +3187,7 @@ function Checklist({ view, client, palette: p, geometry, showLauncher, launcherB
2998
3187
  config.dismissible ? /* @__PURE__ */ jsx3(GButton, { label: finished ? "Close for good" : "Dismiss checklist", variant: "ghost", palette: p, onPress: () => client.dismissChecklist(view.guide.id), style: styles3.dismiss }) : null
2999
3188
  ] }) }) : null,
3000
3189
  showLauncher ? /* @__PURE__ */ jsxs2(
3001
- Pressable2,
3190
+ Pressable3,
3002
3191
  {
3003
3192
  accessibilityRole: "button",
3004
3193
  accessibilityLabel: `${config.launcherLabel}, ${completed} of ${total} complete`,
@@ -3051,8 +3240,8 @@ var styles3 = StyleSheet3.create({
3051
3240
  });
3052
3241
 
3053
3242
  // src/ui/Messenger.tsx
3054
- import { useCallback, useEffect, useMemo as useMemo2, useRef, useState, useSyncExternalStore } from "react";
3055
- import { ActivityIndicator, Keyboard, Modal, Platform as Platform2, Pressable as Pressable3, ScrollView as ScrollView2, StyleSheet as StyleSheet4, Text as Text4, TextInput, View as View4 } from "react-native";
3243
+ import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useRef, useState as useState2, useSyncExternalStore } from "react";
3244
+ import { ActivityIndicator, Keyboard, Modal, Platform as Platform2, Pressable as Pressable4, ScrollView as ScrollView2, StyleSheet as StyleSheet4, Text as Text4, TextInput, View as View4 } from "react-native";
3056
3245
  import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
3057
3246
  function useMessenger(store) {
3058
3247
  return useSyncExternalStore(
@@ -3062,8 +3251,8 @@ function useMessenger(store) {
3062
3251
  );
3063
3252
  }
3064
3253
  function useKeyboardHeight() {
3065
- const [h, setH] = useState(0);
3066
- useEffect(() => {
3254
+ const [h, setH] = useState2(0);
3255
+ useEffect2(() => {
3067
3256
  if (Platform2.OS !== "ios") return;
3068
3257
  const show = Keyboard.addListener("keyboardWillShow", (e) => setH(e.endCoordinates.height));
3069
3258
  const hide = Keyboard.addListener("keyboardWillHide", () => setH(0));
@@ -3085,7 +3274,7 @@ function MessengerLauncher({ store, palette: p, style }) {
3085
3274
  const snap = useMessenger(store);
3086
3275
  if (snap.open) return null;
3087
3276
  const label = snap.unread ? `Open ${snap.config.name}, ${snap.unread} unread` : `Open ${snap.config.name}`;
3088
- return /* @__PURE__ */ jsxs3(Pressable3, { accessibilityRole: "button", accessibilityLabel: label, onPress: () => store.open(), style: ({ pressed }) => [styles4.launcher, style, { backgroundColor: p.primary, opacity: pressed ? 0.85 : 1 }], children: [
3277
+ return /* @__PURE__ */ jsxs3(Pressable4, { accessibilityRole: "button", accessibilityLabel: label, onPress: () => store.open(), style: ({ pressed }) => [styles4.launcher, style, { backgroundColor: p.primary, opacity: pressed ? 0.85 : 1 }], children: [
3089
3278
  /* @__PURE__ */ jsx4(Text4, { style: [styles4.launcherIcon, { color: p.onPrimary }], children: "\u{1F4AC}" }),
3090
3279
  snap.unread ? /* @__PURE__ */ jsx4(View4, { style: styles4.badge, children: /* @__PURE__ */ jsx4(Text4, { style: styles4.badgeText, children: snap.unread }) }) : null
3091
3280
  ] });
@@ -3097,7 +3286,7 @@ function MessengerSheet({ store, client, palette: p, topInset, bottomInset }) {
3097
3286
  const title = v.kind === "thread" ? snap.thread?.conversation?.subject || "New conversation" : v.kind === "article" ? "" : v.tab === "home" ? snap.config.name : v.tab === "messages" ? "Messages" : "Help";
3098
3287
  return /* @__PURE__ */ jsx4(Modal, { visible: snap.open, animationType: "slide", presentationStyle: Platform2.OS === "ios" ? "pageSheet" : "fullScreen", onRequestClose: () => v.kind === "tab" ? store.close() : store.back(), children: /* @__PURE__ */ jsxs3(View4, { style: [styles4.sheet, { backgroundColor: p.background, paddingTop: Platform2.OS === "ios" ? 8 : topInset, paddingBottom: keyboard }], children: [
3099
3288
  /* @__PURE__ */ jsxs3(View4, { style: [styles4.header, { borderColor: p.border }], children: [
3100
- v.kind !== "tab" ? /* @__PURE__ */ jsx4(Pressable3, { accessibilityRole: "button", accessibilityLabel: "Back", onPress: () => store.back(), hitSlop: 10, children: /* @__PURE__ */ jsx4(Text4, { style: [styles4.back, { color: p.primary }], children: "\u2039 Back" }) }) : null,
3289
+ v.kind !== "tab" ? /* @__PURE__ */ jsx4(Pressable4, { accessibilityRole: "button", accessibilityLabel: "Back", onPress: () => store.back(), hitSlop: 10, children: /* @__PURE__ */ jsx4(Text4, { style: [styles4.back, { color: p.primary }], children: "\u2039 Back" }) }) : null,
3101
3290
  /* @__PURE__ */ jsx4(Text4, { accessibilityRole: "header", numberOfLines: 1, style: [styles4.title, { color: p.text, fontFamily: p.fontFamily }], children: title }),
3102
3291
  /* @__PURE__ */ jsx4(CloseButton, { palette: p, onPress: () => store.close(), label: `Close ${snap.config.name}` })
3103
3292
  ] }),
@@ -3114,7 +3303,7 @@ function Tabs({ store, active, palette: p, bottomInset }) {
3114
3303
  ];
3115
3304
  return /* @__PURE__ */ jsx4(View4, { accessibilityRole: "tablist", style: [styles4.tabs, { borderColor: p.border, paddingBottom: Math.max(8, bottomInset) }], children: tabs.map(([tab, icon, label]) => {
3116
3305
  const on = tab === active;
3117
- return /* @__PURE__ */ jsxs3(Pressable3, { accessibilityRole: "tab", accessibilityState: { selected: on }, accessibilityLabel: tab === "messages" && unread ? `${label}, ${unread} unread` : label, onPress: () => store.setTab(tab), style: styles4.tab, children: [
3306
+ return /* @__PURE__ */ jsxs3(Pressable4, { accessibilityRole: "tab", accessibilityState: { selected: on }, accessibilityLabel: tab === "messages" && unread ? `${label}, ${unread} unread` : label, onPress: () => store.setTab(tab), style: styles4.tab, children: [
3118
3307
  /* @__PURE__ */ jsx4(Text4, { style: [styles4.tabIcon, { color: on ? p.primary : p.muted }], children: icon }),
3119
3308
  /* @__PURE__ */ jsx4(Text4, { style: [styles4.tabLabel, { color: on ? p.primary : p.muted }], children: label }),
3120
3309
  tab === "messages" && unread ? /* @__PURE__ */ jsx4(View4, { style: [styles4.dot, styles4.tabDot] }) : null
@@ -3124,7 +3313,7 @@ function Tabs({ store, active, palette: p, bottomInset }) {
3124
3313
  function Home({ store, palette: p }) {
3125
3314
  const snap = useMessenger(store);
3126
3315
  const recent = snap.conversations[0];
3127
- const [q, setQ] = useState("");
3316
+ const [q, setQ] = useState2("");
3128
3317
  return /* @__PURE__ */ jsxs3(ScrollView2, { contentContainerStyle: styles4.content, keyboardShouldPersistTaps: "handled", children: [
3129
3318
  /* @__PURE__ */ jsxs3(View4, { style: [styles4.hero, { backgroundColor: p.primary, borderRadius: p.radius }], children: [
3130
3319
  /* @__PURE__ */ jsx4(Text4, { style: [styles4.heroTitle, { color: p.onPrimary }], children: snap.config.greeting || `Hi! I'm ${snap.config.name}.` }),
@@ -3158,7 +3347,7 @@ function Home({ store, palette: p }) {
3158
3347
  }
3159
3348
  var pendingSearch = { current: "" };
3160
3349
  function ConversationRow({ c, palette: p, onPress }) {
3161
- return /* @__PURE__ */ jsxs3(Pressable3, { accessibilityRole: "button", accessibilityLabel: `${c.subject || "Conversation"}${c.unread ? ", unread" : ""}`, onPress, style: ({ pressed }) => [styles4.row, { borderColor: p.border, opacity: pressed ? 0.7 : 1 }], children: [
3350
+ return /* @__PURE__ */ jsxs3(Pressable4, { accessibilityRole: "button", accessibilityLabel: `${c.subject || "Conversation"}${c.unread ? ", unread" : ""}`, onPress, style: ({ pressed }) => [styles4.row, { borderColor: p.border, opacity: pressed ? 0.7 : 1 }], children: [
3162
3351
  /* @__PURE__ */ jsxs3(View4, { style: styles4.flex, children: [
3163
3352
  /* @__PURE__ */ jsx4(Text4, { numberOfLines: 1, style: [styles4.rowTitle, { color: p.text }], children: c.subject || "Conversation" }),
3164
3353
  /* @__PURE__ */ jsx4(Text4, { numberOfLines: 1, style: [styles4.rowSub, { color: p.muted }], children: c.lastMessagePreview || (c.status === "closed" ? "Closed" : " ") })
@@ -3184,11 +3373,11 @@ function Bubble({ m, text, name, palette: p, onArticle, onGuide }) {
3184
3373
  !mine ? /* @__PURE__ */ jsx4(Text4, { style: [styles4.author, { color: p.muted }], children: m.author === "agent" ? m.authorName || "Team" : name }) : null,
3185
3374
  /* @__PURE__ */ jsx4(View4, { style: [styles4.bubble, { borderRadius: Math.max(12, p.radius) }, mine ? { backgroundColor: p.primary } : { backgroundColor: p.surface }], children: m.status === "pending" && !text ? /* @__PURE__ */ jsx4(TypingDots, { color: p.muted }) : /* @__PURE__ */ jsx4(Text4, { style: [styles4.bubbleText, { color: mine ? p.onPrimary : p.text }], children: text }) }),
3186
3375
  m.citations.length || m.suggestedGuides.length ? /* @__PURE__ */ jsxs3(View4, { style: styles4.chips, children: [
3187
- m.citations.map((c) => /* @__PURE__ */ jsx4(Pressable3, { accessibilityRole: "link", accessibilityLabel: `Article: ${c.title}`, onPress: () => onArticle(c.articleId), style: [styles4.chip, { borderColor: p.border }], children: /* @__PURE__ */ jsxs3(Text4, { style: [styles4.chipText, { color: p.text }], children: [
3376
+ m.citations.map((c) => /* @__PURE__ */ jsx4(Pressable4, { accessibilityRole: "link", accessibilityLabel: `Article: ${c.title}`, onPress: () => onArticle(c.articleId), style: [styles4.chip, { borderColor: p.border }], children: /* @__PURE__ */ jsxs3(Text4, { style: [styles4.chipText, { color: p.text }], children: [
3188
3377
  "\u{1F4C4} ",
3189
3378
  c.title
3190
3379
  ] }) }, c.articleId)),
3191
- m.suggestedGuides.map((g) => /* @__PURE__ */ jsx4(Pressable3, { accessibilityRole: "button", accessibilityLabel: `Show me: ${g.name}`, onPress: () => onGuide(g.key), style: [styles4.chip, { borderColor: p.primary }], children: /* @__PURE__ */ jsxs3(Text4, { style: [styles4.chipText, { color: p.primary }], children: [
3380
+ m.suggestedGuides.map((g) => /* @__PURE__ */ jsx4(Pressable4, { accessibilityRole: "button", accessibilityLabel: `Show me: ${g.name}`, onPress: () => onGuide(g.key), style: [styles4.chip, { borderColor: p.primary }], children: /* @__PURE__ */ jsxs3(Text4, { style: [styles4.chipText, { color: p.primary }], children: [
3192
3381
  "\u25B6 Show me: ",
3193
3382
  g.name
3194
3383
  ] }) }, g.key))
@@ -3201,10 +3390,10 @@ function TypingDots({ color }) {
3201
3390
  function Thread({ store, palette: p, bottomInset }) {
3202
3391
  const snap = useMessenger(store);
3203
3392
  const t = snap.thread;
3204
- const [text, setText] = useState(t?.draft ?? "");
3393
+ const [text, setText] = useState2(t?.draft ?? "");
3205
3394
  const scroll = useRef(null);
3206
3395
  const convo = t?.conversation ?? null;
3207
- useEffect(() => {
3396
+ useEffect2(() => {
3208
3397
  if (t?.draft) setText(t.draft);
3209
3398
  }, [t?.draft]);
3210
3399
  const streamingIds = useMemo2(() => Object.keys(t?.streaming ?? {}).filter((id) => !t?.messages.some((m) => m.id === id)), [t]);
@@ -3248,19 +3437,19 @@ function Thread({ store, palette: p, bottomInset }) {
3248
3437
  function Rating({ palette: p, onRate }) {
3249
3438
  return /* @__PURE__ */ jsxs3(View4, { style: styles4.rating, children: [
3250
3439
  /* @__PURE__ */ jsx4(Text4, { style: [styles4.ratingTitle, { color: p.text }], children: "How did we do?" }),
3251
- /* @__PURE__ */ jsx4(View4, { accessibilityRole: "radiogroup", style: styles4.stars, children: [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ jsx4(Pressable3, { accessibilityRole: "radio", accessibilityLabel: `${n} of 5`, onPress: () => onRate(n), hitSlop: 6, children: /* @__PURE__ */ jsx4(Text4, { style: [styles4.star, { color: p.primary }], children: "\u2605" }) }, n)) })
3440
+ /* @__PURE__ */ jsx4(View4, { accessibilityRole: "radiogroup", style: styles4.stars, children: [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ jsx4(Pressable4, { accessibilityRole: "radio", accessibilityLabel: `${n} of 5`, onPress: () => onRate(n), hitSlop: 6, children: /* @__PURE__ */ jsx4(Text4, { style: [styles4.star, { color: p.primary }], children: "\u2605" }) }, n)) })
3252
3441
  ] });
3253
3442
  }
3254
3443
  function Help({ store, client, palette: p }) {
3255
3444
  const snap = useMessenger(store);
3256
3445
  const rc = client.getResourceCenter();
3257
- const [q, setQ] = useState(pendingSearch.current);
3258
- const [results, setResults] = useState(null);
3259
- const [faq, setFaq] = useState(null);
3260
- useEffect(() => {
3446
+ const [q, setQ] = useState2(pendingSearch.current);
3447
+ const [results, setResults] = useState2(null);
3448
+ const [faq, setFaq] = useState2(null);
3449
+ useEffect2(() => {
3261
3450
  pendingSearch.current = "";
3262
3451
  }, []);
3263
- useEffect(() => {
3452
+ useEffect2(() => {
3264
3453
  const query = q.trim();
3265
3454
  if (query.length < 2) return setResults(null);
3266
3455
  let alive = true;
@@ -3273,7 +3462,7 @@ function Help({ store, client, palette: p }) {
3273
3462
  const guides = client.getGuides();
3274
3463
  const recommended = (rc?.recommendedGuideIds ?? []).map((id) => guides.find((g) => g.id === id || g.key === id)).filter((g) => !!g);
3275
3464
  const articles = snap.articles;
3276
- const row = (key, title, sub, onPress, icon = "\u203A") => /* @__PURE__ */ jsxs3(Pressable3, { accessibilityRole: "button", accessibilityLabel: title, onPress, style: ({ pressed }) => [styles4.row, { borderColor: p.border, opacity: pressed ? 0.7 : 1 }], children: [
3465
+ const row = (key, title, sub, onPress, icon = "\u203A") => /* @__PURE__ */ jsxs3(Pressable4, { accessibilityRole: "button", accessibilityLabel: title, onPress, style: ({ pressed }) => [styles4.row, { borderColor: p.border, opacity: pressed ? 0.7 : 1 }], children: [
3277
3466
  /* @__PURE__ */ jsxs3(View4, { style: styles4.flex, children: [
3278
3467
  /* @__PURE__ */ jsx4(Text4, { style: [styles4.rowTitle, { color: p.text }], children: title }),
3279
3468
  sub ? /* @__PURE__ */ jsx4(Text4, { numberOfLines: 2, style: [styles4.rowSub, { color: p.muted }], children: sub }) : null
@@ -3285,22 +3474,19 @@ function Help({ store, client, palette: p }) {
3285
3474
  /* @__PURE__ */ jsx4(Text4, { accessibilityRole: "header", style: [styles4.sectionTitle, { color: p.muted }], children: title.toUpperCase() }),
3286
3475
  children
3287
3476
  ] }, title);
3288
- const collections = [...articles?.collections ?? []].sort((a, b) => a.order - b.order);
3289
- const loose = (articles?.articles ?? []).filter((a) => !a.collectionId || !collections.some((c) => c.id === a.collectionId));
3477
+ const audiences = client.getAudienceIds();
3478
+ const sections = resourceCenterSections(articles?.collections ?? [], articles?.articles ?? [], audiences);
3479
+ const listed = results?.filter((a) => isArticleListed(articles?.collections ?? [], a, audiences));
3290
3480
  return /* @__PURE__ */ jsxs3(ScrollView2, { contentContainerStyle: styles4.content, keyboardShouldPersistTaps: "handled", children: [
3291
3481
  /* @__PURE__ */ jsx4(TextInput, { value: q, onChangeText: setQ, placeholder: "Search articles", placeholderTextColor: p.muted, accessibilityLabel: "Search help articles", returnKeyType: "search", style: [styles4.search, { color: p.text, backgroundColor: p.surface, borderRadius: Math.min(10, p.radius) }] }),
3292
- results ? section(`Results for \u201C${q.trim()}\u201D`, results.length ? results.map((a) => row(a.id, a.title, a.summary, () => open(a))) : /* @__PURE__ */ jsx4(Text4, { style: [styles4.rowSub, { color: p.muted }], children: "No articles found." })) : /* @__PURE__ */ jsxs3(Fragment, { children: [
3482
+ listed ? section(`Results for \u201C${q.trim()}\u201D`, listed.length ? listed.map((a) => row(a.id, a.title, a.summary, () => open(a))) : /* @__PURE__ */ jsx4(Text4, { style: [styles4.rowSub, { color: p.muted }], children: "No articles found." })) : /* @__PURE__ */ jsxs3(Fragment, { children: [
3293
3483
  recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => store.startGuide(g.key), "\u25B6"))) : null,
3294
3484
  !articles ? /* @__PURE__ */ jsx4(ActivityIndicator, { color: p.muted }) : null,
3295
- collections.map((c) => {
3296
- const items = (articles?.articles ?? []).filter((a) => a.collectionId === c.id);
3297
- return items.length ? section(c.name, items.map((a) => row(a.id, a.title, a.summary, () => open(a)))) : null;
3298
- }),
3299
- loose.length ? section("Articles", loose.map((a) => row(a.id, a.title, a.summary, () => open(a)))) : null,
3485
+ sections.map((s) => section(s.collection ? s.path.join(" \u203A ") : "Articles", s.articles.map((a) => row(a.id, a.title, a.summary, () => open(a))))),
3300
3486
  rc?.faqs.length ? section(
3301
3487
  "FAQ",
3302
3488
  rc.faqs.map((f) => /* @__PURE__ */ jsxs3(View4, { style: [styles4.row, styles4.faq, { borderColor: p.border }], children: [
3303
- /* @__PURE__ */ jsxs3(Pressable3, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles4.faqHead, children: [
3489
+ /* @__PURE__ */ jsxs3(Pressable4, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles4.faqHead, children: [
3304
3490
  /* @__PURE__ */ jsx4(Text4, { style: [styles4.rowTitle, styles4.flex, { color: p.text }], children: f.question }),
3305
3491
  /* @__PURE__ */ jsx4(Text4, { style: [styles4.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
3306
3492
  ] }),
@@ -3375,23 +3561,23 @@ var styles4 = StyleSheet4.create({
3375
3561
  });
3376
3562
 
3377
3563
  // src/ui/ResourceCenter.tsx
3378
- import { useEffect as useEffect2, useMemo as useMemo3, useState as useState2 } from "react";
3379
- import { Modal as Modal2, Platform as Platform3, Pressable as Pressable4, ScrollView as ScrollView3, StyleSheet as StyleSheet5, Text as Text5, TextInput as TextInput2, View as View5 } from "react-native";
3564
+ import { useEffect as useEffect3, useMemo as useMemo3, useState as useState3 } from "react";
3565
+ import { Modal as Modal2, Platform as Platform3, Pressable as Pressable5, ScrollView as ScrollView3, StyleSheet as StyleSheet5, Text as Text5, TextInput as TextInput2, View as View5 } from "react-native";
3380
3566
  import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
3381
3567
  function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
3382
3568
  const rc = client.getResourceCenter();
3383
- const [query, setQuery] = useState2("");
3384
- const [results, setResults] = useState2(null);
3385
- const [article, setArticle] = useState2(null);
3386
- const [faq, setFaq] = useState2(null);
3387
- useEffect2(() => {
3569
+ const [query, setQuery] = useState3("");
3570
+ const [results, setResults] = useState3(null);
3571
+ const [article, setArticle] = useState3(null);
3572
+ const [faq, setFaq] = useState3(null);
3573
+ useEffect3(() => {
3388
3574
  if (!open) {
3389
3575
  setQuery("");
3390
3576
  setResults(null);
3391
3577
  setArticle(null);
3392
3578
  }
3393
3579
  }, [open]);
3394
- useEffect2(() => {
3580
+ useEffect3(() => {
3395
3581
  const q = query.trim();
3396
3582
  if (q.length < 2) {
3397
3583
  setResults(null);
@@ -3416,7 +3602,7 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
3416
3602
  client.articleViewed(a);
3417
3603
  setArticle(a);
3418
3604
  };
3419
- const row = (key, title, subtitle, onPress, icon = "\u203A") => /* @__PURE__ */ jsxs4(Pressable4, { accessibilityRole: "button", accessibilityLabel: title, onPress, style: ({ pressed }) => [styles5.row, { borderColor: p.border, opacity: pressed ? 0.7 : 1 }], children: [
3605
+ const row = (key, title, subtitle, onPress, icon = "\u203A") => /* @__PURE__ */ jsxs4(Pressable5, { accessibilityRole: "button", accessibilityLabel: title, onPress, style: ({ pressed }) => [styles5.row, { borderColor: p.border, opacity: pressed ? 0.7 : 1 }], children: [
3420
3606
  /* @__PURE__ */ jsxs4(View5, { style: styles5.flex, children: [
3421
3607
  /* @__PURE__ */ jsx5(Text5, { style: [styles5.rowTitle, { color: p.text, fontFamily: p.fontFamily }], children: title }),
3422
3608
  subtitle ? /* @__PURE__ */ jsx5(Text5, { style: [styles5.rowSub, { color: p.muted }], numberOfLines: 2, children: subtitle }) : null
@@ -3427,11 +3613,12 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
3427
3613
  /* @__PURE__ */ jsx5(Text5, { accessibilityRole: "header", style: [styles5.sectionTitle, { color: p.muted }], children: title.toUpperCase() }),
3428
3614
  children
3429
3615
  ] }, title);
3430
- const collections = [...rc.collections].sort((a, b) => a.order - b.order);
3431
- const loose = rc.articles.filter((a) => !a.collectionId || !rc.collections.some((c) => c.id === a.collectionId));
3616
+ const audiences = client.getAudienceIds();
3617
+ const sections = resourceCenterSections(rc.collections, rc.articles, audiences);
3618
+ const listed = results?.filter((a) => isArticleListed(rc.collections, a, audiences));
3432
3619
  return /* @__PURE__ */ jsx5(Modal2, { visible: open, animationType: "slide", presentationStyle: Platform3.OS === "ios" ? "pageSheet" : "fullScreen", onRequestClose: () => article ? setArticle(null) : close(), children: /* @__PURE__ */ jsxs4(View5, { style: [styles5.sheet, { backgroundColor: p.background, paddingTop: Platform3.OS === "ios" ? 8 : topInset }], children: [
3433
3620
  /* @__PURE__ */ jsxs4(View5, { style: [styles5.header, { borderColor: p.border }], children: [
3434
- article ? /* @__PURE__ */ jsx5(Pressable4, { accessibilityRole: "button", accessibilityLabel: "Back", onPress: () => setArticle(null), hitSlop: 10, children: /* @__PURE__ */ jsx5(Text5, { style: [styles5.back, { color: p.primary }], children: "\u2039 Back" }) }) : null,
3621
+ article ? /* @__PURE__ */ jsx5(Pressable5, { accessibilityRole: "button", accessibilityLabel: "Back", onPress: () => setArticle(null), hitSlop: 10, children: /* @__PURE__ */ jsx5(Text5, { style: [styles5.back, { color: p.primary }], children: "\u2039 Back" }) }) : null,
3435
3622
  /* @__PURE__ */ jsx5(Text5, { accessibilityRole: "header", style: [styles5.title, { color: p.text, fontFamily: p.fontFamily }], numberOfLines: 1, children: article ? "" : rc.title }),
3436
3623
  /* @__PURE__ */ jsx5(CloseButton, { palette: p, onPress: close, label: "Close help" })
3437
3624
  ] }),
@@ -3452,21 +3639,17 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
3452
3639
  style: [styles5.search, { color: p.text, backgroundColor: p.surface, borderRadius: Math.min(10, p.radius) }]
3453
3640
  }
3454
3641
  ),
3455
- results ? section(
3642
+ listed ? section(
3456
3643
  `Results for \u201C${query.trim()}\u201D`,
3457
- results.length ? results.map((a) => row(a.id, a.title, a.summary, () => openArticle(a))) : /* @__PURE__ */ jsx5(Text5, { style: [styles5.rowSub, { color: p.muted }], children: "No articles found." })
3644
+ listed.length ? listed.map((a) => row(a.id, a.title, a.summary, () => openArticle(a))) : /* @__PURE__ */ jsx5(Text5, { style: [styles5.rowSub, { color: p.muted }], children: "No articles found." })
3458
3645
  ) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
3459
3646
  recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => (close(), client.startGuide(g.id)), "\u25B6"))) : null,
3460
3647
  checklists.length ? section("Checklists", checklists.map((g) => row(g.id, g.name, void 0, () => (close(), client.openChecklist(g.id)), "\u2611"))) : null,
3461
- collections.map((c) => {
3462
- const items = rc.articles.filter((a) => a.collectionId === c.id);
3463
- return items.length ? section(c.name, items.map((a) => row(a.id, a.title, a.summary, () => openArticle(a)))) : null;
3464
- }),
3465
- loose.length ? section("Articles", loose.map((a) => row(a.id, a.title, a.summary, () => openArticle(a)))) : null,
3648
+ sections.map((s) => section(s.collection ? s.path.join(" \u203A ") : "Articles", s.articles.map((a) => row(a.id, a.title, a.summary, () => openArticle(a))))),
3466
3649
  rc.faqs.length ? section(
3467
3650
  "FAQ",
3468
3651
  rc.faqs.map((f) => /* @__PURE__ */ jsxs4(View5, { style: [styles5.row, styles5.faq, { borderColor: p.border }], children: [
3469
- /* @__PURE__ */ jsxs4(Pressable4, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles5.faqHead, children: [
3652
+ /* @__PURE__ */ jsxs4(Pressable5, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles5.faqHead, children: [
3470
3653
  /* @__PURE__ */ jsx5(Text5, { style: [styles5.rowTitle, styles5.flex, { color: p.text }], children: f.question }),
3471
3654
  /* @__PURE__ */ jsx5(Text5, { style: [styles5.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
3472
3655
  ] }),
@@ -3498,8 +3681,8 @@ var styles5 = StyleSheet5.create({
3498
3681
  });
3499
3682
 
3500
3683
  // src/ui/StepLayer.tsx
3501
- import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
3502
- import { AccessibilityInfo, Animated, Easing, Pressable as Pressable5, StyleSheet as StyleSheet7, View as View7 } from "react-native";
3684
+ import { useEffect as useEffect4, useRef as useRef2, useState as useState4 } from "react";
3685
+ import { AccessibilityInfo, Animated, Easing, Pressable as Pressable6, StyleSheet as StyleSheet7, View as View7 } from "react-native";
3503
3686
 
3504
3687
  // src/core/position.ts
3505
3688
  var MARGIN = 12;
@@ -3539,7 +3722,7 @@ function positionCard(target, size, viewport, insets, placement, offset) {
3539
3722
  }
3540
3723
 
3541
3724
  // src/ui/StepCard.tsx
3542
- import { Image, StyleSheet as StyleSheet6, Text as Text6, View as View6 } from "react-native";
3725
+ import { Image as Image2, StyleSheet as StyleSheet6, Text as Text6, View as View6 } from "react-native";
3543
3726
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
3544
3727
  function StepCard({ view, client, palette: p, width, style, compact }) {
3545
3728
  const { step, guide } = view;
@@ -3548,7 +3731,7 @@ function StepCard({ view, client, palette: p, width, style, compact }) {
3548
3731
  const showProgress = step.showProgress && view.total > 1;
3549
3732
  const onLink = (url) => client.navigate(url);
3550
3733
  return /* @__PURE__ */ jsx6(Surface, { palette: p, style: [{ width }, style], children: /* @__PURE__ */ jsxs5(View6, { accessible: false, accessibilityViewIsModal: view.asModal || step.type === "modal", style: compact ? styles6.compact : styles6.inner, children: [
3551
- step.imageUrl ? /* @__PURE__ */ jsx6(Image, { source: { uri: step.imageUrl }, style: [styles6.image, { borderRadius: Math.max(0, p.radius - 4) }], resizeMode: "cover", accessibilityIgnoresInvertColors: true }) : null,
3734
+ step.imageUrl ? /* @__PURE__ */ jsx6(Image2, { source: { uri: step.imageUrl }, style: [styles6.image, { borderRadius: Math.max(0, p.radius - 4) }], resizeMode: "cover", accessibilityIgnoresInvertColors: true }) : null,
3552
3735
  step.title ? /* @__PURE__ */ jsxs5(View6, { style: styles6.header, children: [
3553
3736
  /* @__PURE__ */ jsx6(Text6, { accessibilityRole: "header", style: [styles6.title, { color: p.text, fontFamily: p.fontFamily }], children: step.title }),
3554
3737
  step.showClose ? /* @__PURE__ */ jsx6(CloseButton, { palette: p, onPress: () => client.closeStep(guide.id), label: "Dismiss guide" }) : null
@@ -3591,8 +3774,8 @@ var styles6 = StyleSheet6.create({
3591
3774
  // src/ui/StepLayer.tsx
3592
3775
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3593
3776
  function useTargetRect(client, targetId, origin) {
3594
- const [rect, setRect] = useState3(null);
3595
- useEffect3(() => {
3777
+ const [rect, setRect] = useState4(null);
3778
+ useEffect4(() => {
3596
3779
  if (!targetId) {
3597
3780
  setRect(null);
3598
3781
  return;
@@ -3620,7 +3803,7 @@ function useTargetRect(client, targetId, origin) {
3620
3803
  }
3621
3804
  function useFadeIn(key) {
3622
3805
  const opacity = useRef2(new Animated.Value(0)).current;
3623
- useEffect3(() => {
3806
+ useEffect4(() => {
3624
3807
  opacity.setValue(0);
3625
3808
  Animated.timing(opacity, { toValue: 1, duration: 160, easing: Easing.out(Easing.quad), useNativeDriver: true }).start();
3626
3809
  }, [key, opacity]);
@@ -3632,11 +3815,11 @@ function StepLayer({ view, client, palette: p, geometry }) {
3632
3815
  const opacity = useFadeIn(view.key);
3633
3816
  const { viewport, insets } = geometry;
3634
3817
  const cardWidth = Math.min(viewport.width - 32, step.width ?? 340);
3635
- useEffect3(() => {
3818
+ useEffect4(() => {
3636
3819
  if (step.type === "hotspot" && !view.expanded) return;
3637
3820
  AccessibilityInfo.announceForAccessibility?.(announcement(view));
3638
3821
  }, [view.key, view.expanded]);
3639
- const backdrop = (children) => /* @__PURE__ */ jsx7(Pressable5, { accessible: false, style: [StyleSheet7.absoluteFill, { backgroundColor: p.overlay }], onPress: () => client.backdropPressed(view.guide.id), children });
3822
+ const backdrop = (children) => /* @__PURE__ */ jsx7(Pressable6, { accessible: false, style: [StyleSheet7.absoluteFill, { backgroundColor: p.overlay }], onPress: () => client.backdropPressed(view.guide.id), children });
3640
3823
  const anchored = !view.asModal && ["tooltip", "popover", "spotlight", "hotspot"].includes(step.type);
3641
3824
  if (!anchored && step.type === "banner") {
3642
3825
  const top = step.position.startsWith("top");
@@ -3675,10 +3858,10 @@ function StepLayer({ view, client, palette: p, geometry }) {
3675
3858
  [hole.x + hole.width - r, hole.y + hole.height - r, -2 * r, -2 * r]
3676
3859
  ];
3677
3860
  return /* @__PURE__ */ jsxs6(Animated.View, { pointerEvents: "box-none", style: [StyleSheet7.absoluteFill, { opacity }], children: [
3678
- /* @__PURE__ */ jsx7(Pressable5, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, right: 0, top: 0, height: Math.max(0, hole.y) }] }),
3679
- /* @__PURE__ */ jsx7(Pressable5, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, right: 0, top: hole.y + hole.height, bottom: 0 }] }),
3680
- /* @__PURE__ */ jsx7(Pressable5, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, width: Math.max(0, hole.x), top: hole.y, height: hole.height }] }),
3681
- /* @__PURE__ */ jsx7(Pressable5, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: hole.x + hole.width, right: 0, top: hole.y, height: hole.height }] }),
3861
+ /* @__PURE__ */ jsx7(Pressable6, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, right: 0, top: 0, height: Math.max(0, hole.y) }] }),
3862
+ /* @__PURE__ */ jsx7(Pressable6, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, right: 0, top: hole.y + hole.height, bottom: 0 }] }),
3863
+ /* @__PURE__ */ jsx7(Pressable6, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, width: Math.max(0, hole.x), top: hole.y, height: hole.height }] }),
3864
+ /* @__PURE__ */ jsx7(Pressable6, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: hole.x + hole.width, right: 0, top: hole.y, height: hole.height }] }),
3682
3865
  r > 0 ? corners.map(([left, top, dx, dy], i) => /* @__PURE__ */ jsx7(View7, { pointerEvents: "none", style: [styles7.abs, { left, top, width: r, height: r, overflow: "hidden" }], children: /* @__PURE__ */ jsx7(View7, { style: [styles7.abs, { left: dx, top: dy, width: 4 * r, height: 4 * r, borderRadius: 2 * r, borderWidth: r, borderColor: p.overlay }] }) }, i)) : null,
3683
3866
  /* @__PURE__ */ jsx7(View7, { pointerEvents: "none", style: [styles7.abs, { left: hole.x, top: hole.y, width: hole.width, height: hole.height, borderRadius: r, borderWidth: 2, borderColor: p.primary }] }),
3684
3867
  /* @__PURE__ */ jsx7(Anchored, { view, client, palette: p, geometry, rect: hole, width: cardWidth })
@@ -3691,7 +3874,7 @@ function StepLayer({ view, client, palette: p, geometry }) {
3691
3874
  }
3692
3875
  var ARROW = 12;
3693
3876
  function Anchored({ view, client, palette: p, geometry, rect, width }) {
3694
- const [height, setHeight] = useState3(0);
3877
+ const [height, setHeight] = useState4(0);
3695
3878
  const onLayout = (e) => setHeight(Math.round(e.nativeEvent.layout.height));
3696
3879
  const offset = view.step.offset + ARROW / 2;
3697
3880
  const pos = positionCard(rect, { width, height: height || 120 }, geometry.viewport, geometry.insets, view.step.placement, offset);
@@ -3707,8 +3890,8 @@ function Arrow({ side, at, color, border }) {
3707
3890
  }
3708
3891
  function Hotspot({ x, y, size, pulse, color, onPress, label }) {
3709
3892
  const scale = useRef2(new Animated.Value(0)).current;
3710
- const [reduceMotion, setReduceMotion] = useState3(false);
3711
- useEffect3(() => {
3893
+ const [reduceMotion, setReduceMotion] = useState4(false);
3894
+ useEffect4(() => {
3712
3895
  let alive = true;
3713
3896
  AccessibilityInfo.isReduceMotionEnabled?.().then((v) => alive && setReduceMotion(v), () => {
3714
3897
  });
@@ -3716,13 +3899,13 @@ function Hotspot({ x, y, size, pulse, color, onPress, label }) {
3716
3899
  alive = false;
3717
3900
  };
3718
3901
  }, []);
3719
- useEffect3(() => {
3902
+ useEffect4(() => {
3720
3903
  if (!pulse || reduceMotion) return;
3721
3904
  const loop = Animated.loop(Animated.timing(scale, { toValue: 1, duration: 1400, easing: Easing.out(Easing.ease), useNativeDriver: true }));
3722
3905
  loop.start();
3723
3906
  return () => loop.stop();
3724
3907
  }, [pulse, reduceMotion, scale]);
3725
- return /* @__PURE__ */ jsxs6(Pressable5, { accessibilityRole: "button", accessibilityLabel: label, onPress, hitSlop: 14, style: [styles7.abs, { left: x, top: y, width: size, height: size }], children: [
3908
+ return /* @__PURE__ */ jsxs6(Pressable6, { accessibilityRole: "button", accessibilityLabel: label, onPress, hitSlop: 14, style: [styles7.abs, { left: x, top: y, width: size, height: size }], children: [
3726
3909
  pulse && !reduceMotion ? /* @__PURE__ */ jsx7(
3727
3910
  Animated.View,
3728
3911
  {
@@ -3741,12 +3924,12 @@ var styles7 = StyleSheet7.create({
3741
3924
  });
3742
3925
 
3743
3926
  // src/ui/Survey.tsx
3744
- import { useEffect as useEffect4, useState as useState4 } from "react";
3927
+ import { useEffect as useEffect5, useState as useState5 } from "react";
3745
3928
  import {
3746
3929
  AccessibilityInfo as AccessibilityInfo2,
3747
3930
  KeyboardAvoidingView,
3748
3931
  Platform as Platform4,
3749
- Pressable as Pressable6,
3932
+ Pressable as Pressable7,
3750
3933
  ScrollView as ScrollView4,
3751
3934
  StyleSheet as StyleSheet8,
3752
3935
  Text as Text7,
@@ -3761,9 +3944,9 @@ function Survey({
3761
3944
  geometry
3762
3945
  }) {
3763
3946
  const config = view.guide.config.survey;
3764
- const [answers, setAnswers] = useState4({});
3765
- const [error, setError] = useState4("");
3766
- useEffect4(() => {
3947
+ const [answers, setAnswers] = useState5({});
3948
+ const [error, setError] = useState5("");
3949
+ useEffect5(() => {
3767
3950
  setAnswers({});
3768
3951
  setError("");
3769
3952
  AccessibilityInfo2.announceForAccessibility?.(
@@ -3804,7 +3987,7 @@ function Survey({
3804
3987
  style: StyleSheet8.absoluteFill,
3805
3988
  children: [
3806
3989
  centered ? /* @__PURE__ */ jsx8(
3807
- Pressable6,
3990
+ Pressable7,
3808
3991
  {
3809
3992
  accessible: false,
3810
3993
  style: [StyleSheet8.absoluteFill, { backgroundColor: p.overlay }],
@@ -3901,7 +4084,7 @@ function QuestionInput({
3901
4084
  const values = Array.from({ length: max - min + 1 }, (_, i) => min + i);
3902
4085
  return /* @__PURE__ */ jsxs7(View8, { children: [
3903
4086
  /* @__PURE__ */ jsx8(View8, { style: styles8.scale, accessibilityRole: "radiogroup", accessibilityLabel: q.title, children: values.map((n) => /* @__PURE__ */ jsx8(
3904
- Pressable6,
4087
+ Pressable7,
3905
4088
  {
3906
4089
  accessibilityRole: "radio",
3907
4090
  accessibilityState: { selected: value === n },
@@ -3921,7 +4104,7 @@ function QuestionInput({
3921
4104
  case "rating": {
3922
4105
  const max = q.scaleMax ?? 5;
3923
4106
  return /* @__PURE__ */ jsx8(View8, { style: styles8.stars, accessibilityRole: "radiogroup", accessibilityLabel: q.title, children: Array.from({ length: max }, (_, i) => i + 1).map((n) => /* @__PURE__ */ jsx8(
3924
- Pressable6,
4107
+ Pressable7,
3925
4108
  {
3926
4109
  accessibilityRole: "radio",
3927
4110
  accessibilityLabel: `${n} of ${max}`,
@@ -3947,7 +4130,7 @@ function QuestionInput({
3947
4130
  [true, "\u{1F44D}", "Thumbs up"],
3948
4131
  [false, "\u{1F44E}", "Thumbs down"]
3949
4132
  ].map(([val, icon, label]) => /* @__PURE__ */ jsx8(
3950
- Pressable6,
4133
+ Pressable7,
3951
4134
  {
3952
4135
  accessibilityRole: "radio",
3953
4136
  accessibilityLabel: label,
@@ -3967,7 +4150,7 @@ function QuestionInput({
3967
4150
  return /* @__PURE__ */ jsx8(View8, { style: styles8.choices, children: q.options.map((o) => {
3968
4151
  const on = selected.has(o.id);
3969
4152
  return /* @__PURE__ */ jsxs7(
3970
- Pressable6,
4153
+ Pressable7,
3971
4154
  {
3972
4155
  accessibilityRole: multi ? "checkbox" : "radio",
3973
4156
  accessibilityState: { checked: on },
@@ -4132,8 +4315,8 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
4132
4315
  const { width, height } = useWindowDimensions();
4133
4316
  const scheme = useColorScheme();
4134
4317
  const ref = useRef3(null);
4135
- const [origin, setOrigin] = useState5({ x: 0, y: 0 });
4136
- useEffect5(() => {
4318
+ const [origin, setOrigin] = useState6({ x: 0, y: 0 });
4319
+ useEffect6(() => {
4137
4320
  client.targets.setViewport(width, height);
4138
4321
  }, [client, width, height]);
4139
4322
  const measureOrigin = useCallback2(() => {
@@ -4151,7 +4334,7 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
4151
4334
  return /* @__PURE__ */ jsxs8(View9, { ref, collapsable: false, pointerEvents: "box-none", style: StyleSheet9.absoluteFill, onLayout: measureOrigin, children: [
4152
4335
  ui.checklists.map((c) => /* @__PURE__ */ jsx9(Checklist, { view: c, client, palette: palette(client.themeFor(c.guide), scheme), geometry, showLauncher: client.options.showChecklistLauncher !== false, launcherBottom: launcherOffset }, c.guide.id)),
4153
4336
  showRcLauncher ? /* @__PURE__ */ jsx9(
4154
- Pressable7,
4337
+ Pressable8,
4155
4338
  {
4156
4339
  accessibilityRole: "button",
4157
4340
  accessibilityLabel: client.getResourceCenter()?.launcher.label || "Help",
@@ -4172,7 +4355,7 @@ var styles9 = StyleSheet9.create({
4172
4355
  });
4173
4356
 
4174
4357
  // src/ui/Target.tsx
4175
- import { createContext, useCallback as useCallback3, useContext, useEffect as useEffect6, useRef as useRef4 } from "react";
4358
+ import { createContext, useCallback as useCallback3, useContext, useEffect as useEffect7, useRef as useRef4 } from "react";
4176
4359
  import { View as View10 } from "react-native";
4177
4360
  import { jsx as jsx10 } from "react/jsx-runtime";
4178
4361
  var GuidelyContext = createContext(null);
@@ -4189,7 +4372,7 @@ function useGuidelyTarget(id) {
4189
4372
  const screen = useContext(GuidelyScreenContext);
4190
4373
  const ref = useRef4(null);
4191
4374
  const start = useRef4(null);
4192
- useEffect6(() => {
4375
+ useEffect7(() => {
4193
4376
  if (!client || !id) return;
4194
4377
  return client.targets.register(id, {
4195
4378
  screen,
@@ -4262,11 +4445,11 @@ function defaultBridge(appVersion) {
4262
4445
  };
4263
4446
  }
4264
4447
  function GuidelyProvider({ children, user, insets, launcherOffset = 72, ...options }) {
4265
- const [client, setClient] = useState6(null);
4448
+ const [client, setClient] = useState7(null);
4266
4449
  const optionsRef = useRef5(options);
4267
4450
  optionsRef.current = options;
4268
4451
  const { projectId, environment, apiUrl, storage } = options;
4269
- useEffect7(() => {
4452
+ useEffect8(() => {
4270
4453
  if (!projectId) return;
4271
4454
  let c;
4272
4455
  try {
@@ -4290,7 +4473,7 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, ...optio
4290
4473
  }, [projectId, environment, apiUrl, storage]);
4291
4474
  const userKey = user ? JSON.stringify([user.id, user.traits ?? null, user.account ?? null]) : "";
4292
4475
  const hadUser = useRef5(false);
4293
- useEffect7(() => {
4476
+ useEffect8(() => {
4294
4477
  if (!client) return;
4295
4478
  if (user && user.id) {
4296
4479
  hadUser.current = true;
@@ -4339,8 +4522,8 @@ function useGuidely() {
4339
4522
  }
4340
4523
  function useGuidelyReady() {
4341
4524
  const client = useContext2(GuidelyContext);
4342
- const [ready, setReady] = useState6(() => !!client?.isClientReady());
4343
- useEffect7(() => {
4525
+ const [ready, setReady] = useState7(() => !!client?.isClientReady());
4526
+ useEffect8(() => {
4344
4527
  if (!client) return setReady(false);
4345
4528
  setReady(client.isClientReady());
4346
4529
  const off = client.on("ready", () => setReady(true));
@@ -4352,7 +4535,7 @@ function useGuidelyReady() {
4352
4535
  function useGuidelyIdentify(userId, traits, account) {
4353
4536
  const client = useContext2(GuidelyContext);
4354
4537
  const key = JSON.stringify([userId ?? null, traits ?? null, account ?? null]);
4355
- useEffect7(() => {
4538
+ useEffect8(() => {
4356
4539
  if (client && userId) void client.identify(userId, traits, account);
4357
4540
  }, [client, key]);
4358
4541
  }
@@ -4360,14 +4543,14 @@ function useGuidelyEvent(event, handler) {
4360
4543
  const client = useContext2(GuidelyContext);
4361
4544
  const ref = useRef5(handler);
4362
4545
  ref.current = handler;
4363
- useEffect7(() => {
4546
+ useEffect8(() => {
4364
4547
  if (!client) return;
4365
4548
  return client.on(event, ((...args) => ref.current(...args)));
4366
4549
  }, [client, event]);
4367
4550
  }
4368
4551
  function useGuidelyScreen(name, properties) {
4369
4552
  const client = useContext2(GuidelyContext);
4370
- useEffect7(() => {
4553
+ useEffect8(() => {
4371
4554
  if (client && name) client.screen(name, properties);
4372
4555
  }, [client, name]);
4373
4556
  }
@@ -4379,15 +4562,15 @@ function useGuidelyNavigation(navigationRef, options = {}) {
4379
4562
  const route = navigationRef.getCurrentRoute?.();
4380
4563
  if (client && route?.name) client.screen(nameRef.current ? nameRef.current(route) : route.name);
4381
4564
  };
4382
- useEffect7(report, [client]);
4565
+ useEffect8(report, [client]);
4383
4566
  return { onReady: report, onStateChange: report };
4384
4567
  }
4385
4568
  function useUiSelector(select, fallback) {
4386
4569
  const client = useContext2(GuidelyContext);
4387
4570
  const selectRef = useRef5(select);
4388
4571
  selectRef.current = select;
4389
- const [value, setValue] = useState6(() => client ? select(client) : fallback);
4390
- useEffect7(() => {
4572
+ const [value, setValue] = useState7(() => client ? select(client) : fallback);
4573
+ useEffect8(() => {
4391
4574
  if (!client) return;
4392
4575
  let last = JSON.stringify(selectRef.current(client));
4393
4576
  setValue(selectRef.current(client));