@guidelytech/react-native 0.4.1 → 0.6.1

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-OLWGMDRX.js
10
10
  function fnv1a(input) {
11
11
  let h = 2166136261;
12
12
  for (let i = 0; i < input.length; i++) {
@@ -310,8 +310,194 @@ 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 MEDIA_FILE_MAX_BYTES = 50 * 1024 * 1024;
316
+ var MEDIA_MAX_BYTES = Math.max(MEDIA_IMAGE_MAX_BYTES, MEDIA_VIDEO_MAX_BYTES, MEDIA_FILE_MAX_BYTES);
317
+ var FILE_LINK_CLASS = "gd-file";
318
+ var PDF_BLOCK_CLASS = "gd-pdf";
319
+ function isFileLink(className, href) {
320
+ return !!className && className.split(/\s+/).includes(FILE_LINK_CLASS) && isSafeMediaUrl(href);
321
+ }
322
+ var YOUTUBE_ID = /^[A-Za-z0-9_-]{11}$/;
323
+ var VIMEO_ID = /^\d{1,12}$/;
324
+ var VIMEO_HASH = /^[0-9a-f]{6,20}$/i;
325
+ var LOOM_ID = /^[0-9a-f]{32}$/i;
326
+ var WISTIA_ID = /^[a-z0-9]{10}$/i;
327
+ function parseUrl(input) {
328
+ const s = input.trim();
329
+ if (!s) return null;
330
+ try {
331
+ const u = new URL(/^[a-z][a-z0-9+.-]*:/i.test(s) ? s : `https://${s}`);
332
+ if (u.protocol !== "https:" && u.protocol !== "http:") return null;
333
+ if (u.username || u.password || u.port && u.port !== "443" && u.port !== "80") return null;
334
+ return u;
335
+ } catch {
336
+ return null;
337
+ }
338
+ }
339
+ function parseStart(v) {
340
+ if (!v) return 0;
341
+ if (/^\d+$/.test(v)) return Math.min(Number(v), 86400);
342
+ const m = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/.exec(v);
343
+ if (!m || !m[0]) return 0;
344
+ return Math.min(Number(m[1] ?? 0) * 3600 + Number(m[2] ?? 0) * 60 + Number(m[3] ?? 0), 86400);
345
+ }
346
+ function youtube(id, start) {
347
+ if (!YOUTUBE_ID.test(id)) return null;
348
+ return {
349
+ provider: "youtube",
350
+ src: `https://www.youtube-nocookie.com/embed/${id}${start ? `?start=${start}` : ""}`,
351
+ watchUrl: `https://www.youtube.com/watch?v=${id}${start ? `&t=${start}s` : ""}`,
352
+ title: "YouTube video"
353
+ };
354
+ }
355
+ function vimeo(id, hash) {
356
+ if (!VIMEO_ID.test(id) || hash !== null && !VIMEO_HASH.test(hash)) return null;
357
+ const h = hash ? hash.toLowerCase() : null;
358
+ return { provider: "vimeo", src: `https://player.vimeo.com/video/${id}${h ? `?h=${h}` : ""}`, watchUrl: `https://vimeo.com/${id}${h ? `/${h}` : ""}`, title: "Vimeo video" };
359
+ }
360
+ function toEmbedUrl(input) {
361
+ const u = parseUrl(input);
362
+ if (!u) return null;
363
+ const host = u.hostname.toLowerCase().replace(/^www\./, "");
364
+ const parts = u.pathname.split("/").filter(Boolean);
365
+ switch (host) {
366
+ case "youtube.com":
367
+ case "m.youtube.com":
368
+ case "youtube-nocookie.com": {
369
+ const start = parseStart(u.searchParams.get("start") ?? u.searchParams.get("t"));
370
+ if (parts[0] === "watch" && parts.length === 1) return youtube(u.searchParams.get("v") ?? "", start);
371
+ if ((parts[0] === "embed" || parts[0] === "shorts" || parts[0] === "live") && parts.length === 2) return youtube(parts[1], start);
372
+ return null;
373
+ }
374
+ case "youtu.be":
375
+ return parts.length === 1 ? youtube(parts[0], parseStart(u.searchParams.get("t") ?? u.searchParams.get("start"))) : null;
376
+ case "vimeo.com":
377
+ if (parts.length === 1) return vimeo(parts[0], null);
378
+ if (parts.length === 2 && VIMEO_ID.test(parts[0])) return vimeo(parts[0], parts[1]);
379
+ return null;
380
+ case "player.vimeo.com":
381
+ return parts[0] === "video" && parts.length === 2 ? vimeo(parts[1], u.searchParams.get("h")) : null;
382
+ case "loom.com":
383
+ if ((parts[0] === "share" || parts[0] === "embed") && parts.length === 2 && LOOM_ID.test(parts[1])) {
384
+ const id = parts[1].toLowerCase();
385
+ return { provider: "loom", src: `https://www.loom.com/embed/${id}`, watchUrl: `https://www.loom.com/share/${id}`, title: "Loom video" };
386
+ }
387
+ return null;
388
+ default: {
389
+ let id;
390
+ if (host === "fast.wistia.net" || host === "fast.wistia.com") id = parts[0] === "embed" && parts[1] === "iframe" && parts.length === 3 ? parts[2] : void 0;
391
+ 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;
392
+ if (!id || !WISTIA_ID.test(id)) return null;
393
+ return { provider: "wistia", src: `https://fast.wistia.net/embed/iframe/${id}`, watchUrl: `https://fast.wistia.net/embed/iframe/${id}`, title: "Wistia video" };
394
+ }
395
+ }
396
+ }
397
+ function isAllowedEmbedSrc(src) {
398
+ if (!src) return false;
399
+ return toEmbedUrl(src)?.src === src;
400
+ }
401
+ function isSafeMediaUrl(url) {
402
+ if (!url) return false;
403
+ try {
404
+ const u = new URL(url);
405
+ return (u.protocol === "https:" || u.protocol === "http:") && !u.username && !u.password;
406
+ } catch {
407
+ return false;
408
+ }
409
+ }
410
+ var MAX_COLLECTION_DEPTH = 3;
411
+ function matchesAudiences(required, member) {
412
+ if (!required || required.length === 0) return true;
413
+ if (!member) return false;
414
+ const has2 = member instanceof Set ? (id) => member.has(id) : (id) => member.includes(id);
415
+ return required.some(has2);
416
+ }
417
+ function resourceCenterSections(collections, articles, member) {
418
+ const byId = new Map(collections.map((c) => [c.id, c]));
419
+ const children = /* @__PURE__ */ new Map();
420
+ for (const c of collections) {
421
+ const parent = c.parentId && c.parentId !== c.id && byId.has(c.parentId) ? c.parentId : null;
422
+ const list = children.get(parent) ?? [];
423
+ list.push(c);
424
+ children.set(parent, list);
425
+ }
426
+ for (const list of children.values()) list.sort((a, b) => a.order - b.order || a.name.localeCompare(b.name));
427
+ const direct = /* @__PURE__ */ new Map();
428
+ const uncategorized = [];
429
+ for (const a of articles) {
430
+ if (!matchesAudiences(a.audienceIds, member)) continue;
431
+ if (a.collectionId && byId.has(a.collectionId)) {
432
+ const list = direct.get(a.collectionId) ?? [];
433
+ list.push(a);
434
+ direct.set(a.collectionId, list);
435
+ } else uncategorized.push(a);
436
+ }
437
+ const sections = [];
438
+ const seen = /* @__PURE__ */ new Set();
439
+ const visit = (c, path) => {
440
+ if (seen.has(c.id)) return;
441
+ seen.add(c.id);
442
+ if (!matchesAudiences(c.audienceIds, member)) return;
443
+ const here = [...path, c.name];
444
+ const list = direct.get(c.id);
445
+ if (list?.length) sections.push({ collection: c, path: here, depth: here.length - 1, articles: list });
446
+ for (const child of children.get(c.id) ?? []) visit(child, here);
447
+ };
448
+ for (const root of children.get(null) ?? []) visit(root, []);
449
+ if (uncategorized.length) sections.push({ collection: null, path: [], depth: 0, articles: uncategorized });
450
+ return sections;
451
+ }
452
+ function isArticleListed(collections, article, member) {
453
+ if (!matchesAudiences(article.audienceIds, member)) return false;
454
+ const byId = new Map(collections.map((c) => [c.id, c]));
455
+ let cur = article.collectionId ? byId.get(article.collectionId) : void 0;
456
+ for (let guard = 0; cur && guard <= MAX_COLLECTION_DEPTH * 4; guard++) {
457
+ if (!matchesAudiences(cur.audienceIds, member)) return false;
458
+ cur = cur.parentId ? byId.get(cur.parentId) : void 0;
459
+ }
460
+ return true;
461
+ }
462
+ var CAPTURE_SCREENSHOT_MAX_BYTES = 8 * 1024 * 1024;
313
463
 
314
- // ../sdk-core/dist/chunk-GJT7CFAC.js
464
+ // ../sdk-core/dist/chunk-CYAG6ZYX.js
465
+ function readJson(store, key, fallback) {
466
+ const raw = store.get(key);
467
+ if (raw == null) return fallback;
468
+ try {
469
+ return JSON.parse(raw);
470
+ } catch {
471
+ return fallback;
472
+ }
473
+ }
474
+ function writeJson(store, key, value) {
475
+ try {
476
+ store.set(key, JSON.stringify(value));
477
+ } catch {
478
+ }
479
+ }
480
+ var KEYS = {
481
+ anon: "guidely:anon",
482
+ seen: "guidely:seen",
483
+ config: (key) => `guidely:config:${key}`,
484
+ etag: (key) => `guidely:etag:${key}`,
485
+ state: (key) => `guidely:state:${key}`,
486
+ user: (key) => `guidely:user:${key}`,
487
+ queue: (key) => `guidely:queue:${key}`,
488
+ session: "guidely:session",
489
+ active: "guidely:active",
490
+ authoring: "guidely:authoring",
491
+ /** How-to capture session marker ({ token, … }) and its not-yet-uploaded steps. */
492
+ capture: "guidely:capture",
493
+ capturePending: "guidely:capture:pending",
494
+ /** Messenger conversations this browser can read: [{ id, token }]. */
495
+ conversations: (key) => `guidely:conversations:${key}`,
496
+ /** Announcement ids opened in the messenger's News tab. */
497
+ newsSeen: (key) => `guidely:news:${key}`
498
+ };
499
+
500
+ // ../sdk-core/dist/chunk-AKNE7D5M.js
315
501
  var __defProp2 = Object.defineProperty;
316
502
  var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
317
503
  var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -333,7 +519,8 @@ var Api = class {
333
519
  const f = this.fetcher;
334
520
  if (!f) throw new Error("fetch is not available");
335
521
  const headers = { ...options.headers, "X-Guidely-Key": this.key, Accept: "application/json" };
336
- if (options.body !== void 0) headers["Content-Type"] = "application/json";
522
+ const form = typeof FormData !== "undefined" && options.body instanceof FormData;
523
+ if (options.body !== void 0 && !form) headers["Content-Type"] = "application/json";
337
524
  if (options.etag) headers["If-None-Match"] = options.etag;
338
525
  const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
339
526
  const timer = ac ? setTimeout(() => ac.abort(), options.timeoutMs ?? 1e4) : null;
@@ -341,7 +528,7 @@ var Api = class {
341
528
  const res = await f(this.url(path, options.params), {
342
529
  method,
343
530
  headers,
344
- body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
531
+ body: options.body === void 0 ? void 0 : form ? options.body : JSON.stringify(options.body),
345
532
  keepalive: options.keepalive,
346
533
  signal: ac?.signal,
347
534
  credentials: "omit",
@@ -366,6 +553,10 @@ var Api = class {
366
553
  post(path, body, options) {
367
554
  return this.request("POST", path, { body, ...options });
368
555
  }
556
+ /** Multipart POST (file uploads). No Content-Type header: the browser adds it with the boundary. */
557
+ postForm(path, form, options) {
558
+ return this.request("POST", path, { body: form, ...options });
559
+ }
369
560
  /**
370
561
  * POST that may answer with server-sent events. Each event is passed to `onEvent`; a JSON answer is
371
562
  * returned instead. No timeout: Scout replies can take a while and the server keeps the stream alive.
@@ -408,10 +599,10 @@ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
408
599
  function backoffMs(attempt, baseMs = 1e3, maxMs = 3e4) {
409
600
  return Math.min(maxMs, baseMs * 2 ** Math.max(0, attempt));
410
601
  }
411
- async function postWithRetry(api, path, body, attempts = 3, baseMs = 500) {
602
+ async function withRetry(send, attempts = 3, baseMs = 500) {
412
603
  for (let i = 0; i < attempts; i++) {
413
604
  try {
414
- const res = await api.post(path, body);
605
+ const res = await send();
415
606
  if (res.ok || res.status >= 400 && res.status < 500 && res.status !== 429) return res;
416
607
  } catch {
417
608
  }
@@ -419,6 +610,9 @@ async function postWithRetry(api, path, body, attempts = 3, baseMs = 500) {
419
610
  }
420
611
  return null;
421
612
  }
613
+ function postWithRetry(api, path, body, attempts = 3, baseMs = 500) {
614
+ return withRetry(() => api.post(path, body), attempts, baseMs);
615
+ }
422
616
  async function readEventStream(body, onEvent) {
423
617
  const reader = body.getReader();
424
618
  const decoder = new TextDecoder();
@@ -453,36 +647,7 @@ async function readEventStream(body, onEvent) {
453
647
  if (buffer.trim()) dispatch(buffer);
454
648
  }
455
649
 
456
- // ../sdk-core/dist/chunk-HLR3W63Z.js
457
- function readJson(store, key, fallback) {
458
- const raw = store.get(key);
459
- if (raw == null) return fallback;
460
- try {
461
- return JSON.parse(raw);
462
- } catch {
463
- return fallback;
464
- }
465
- }
466
- function writeJson(store, key, value) {
467
- try {
468
- store.set(key, JSON.stringify(value));
469
- } catch {
470
- }
471
- }
472
- var KEYS = {
473
- anon: "guidely:anon",
474
- seen: "guidely:seen",
475
- config: (key) => `guidely:config:${key}`,
476
- etag: (key) => `guidely:etag:${key}`,
477
- state: (key) => `guidely:state:${key}`,
478
- user: (key) => `guidely:user:${key}`,
479
- queue: (key) => `guidely:queue:${key}`,
480
- session: "guidely:session",
481
- active: "guidely:active",
482
- authoring: "guidely:authoring",
483
- /** Messenger conversations this browser can read: [{ id, token }]. */
484
- conversations: (key) => `guidely:conversations:${key}`
485
- };
650
+ // ../sdk-core/dist/chunk-GDF6RCHQ.js
486
651
  var EventQueue = class {
487
652
  constructor(opts) {
488
653
  __publicField2(this, "opts", opts);
@@ -1564,7 +1729,7 @@ var PersistentStore = class {
1564
1729
 
1565
1730
  // src/core/client.ts
1566
1731
  var SDK_NAME = "@guidelytech/react-native";
1567
- var SDK_VERSION = "0.4.1";
1732
+ var SDK_VERSION = "0.6.1";
1568
1733
  var DEFAULT_API_URL = "https://guidelytech.com";
1569
1734
  var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
1570
1735
  var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
@@ -2429,6 +2594,10 @@ var GuidelyClient = class {
2429
2594
  if (this.activeSurvey?.guide.id !== guideId) return;
2430
2595
  this.finishSurvey(this.surveyView?.submitted ? "submitted" : "dismissed");
2431
2596
  }
2597
+ /** The visitor's audiences (from the last identify), for audience-targeted help articles. */
2598
+ getAudienceIds() {
2599
+ return this.state.audienceIds;
2600
+ }
2432
2601
  getResourceCenter() {
2433
2602
  const rc = this.config?.resourceCenter;
2434
2603
  return rc && rc.enabled ? rc : null;
@@ -2693,11 +2862,11 @@ function forPlatform(config, platform) {
2693
2862
  }
2694
2863
 
2695
2864
  // 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";
2865
+ import { useCallback as useCallback2, useEffect as useEffect6, useRef as useRef3, useState as useState6, useSyncExternalStore as useSyncExternalStore2 } from "react";
2866
+ import { Platform as Platform5, Pressable as Pressable8, StatusBar, StyleSheet as StyleSheet9, Text as Text8, useColorScheme, useWindowDimensions, View as View9 } from "react-native";
2698
2867
 
2699
2868
  // src/ui/Checklist.tsx
2700
- import { Pressable as Pressable2, ScrollView, StyleSheet as StyleSheet3, Text as Text3, View as View3 } from "react-native";
2869
+ import { Pressable as Pressable3, ScrollView as ScrollView2, StyleSheet as StyleSheet3, Text as Text3, View as View3 } from "react-native";
2701
2870
 
2702
2871
  // src/ui/primitives.tsx
2703
2872
  import { Pressable, StyleSheet, Text, View } from "react-native";
@@ -2751,8 +2920,8 @@ var styles = StyleSheet.create({
2751
2920
  });
2752
2921
 
2753
2922
  // src/ui/RichText.tsx
2754
- import { useMemo } from "react";
2755
- import { Platform, StyleSheet as StyleSheet2, Text as Text2, View as View2 } from "react-native";
2923
+ import { useEffect, useMemo, useState } from "react";
2924
+ import { Image, Modal, Platform, Pressable as Pressable2, ScrollView, StyleSheet as StyleSheet2, Text as Text2, View as View2 } from "react-native";
2756
2925
 
2757
2926
  // src/core/html.ts
2758
2927
  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 +2935,8 @@ function decodeEntities(s) {
2766
2935
  });
2767
2936
  }
2768
2937
  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"]);
2938
+ var SKIP_TAGS = /* @__PURE__ */ new Set(["script", "style", "head", "title", "svg", "template", "iframe", "video"]);
2939
+ var PROVIDER_LABEL = { youtube: "Watch on YouTube", vimeo: "Watch on Vimeo", loom: "Watch on Loom", wistia: "Watch video" };
2770
2940
  function attr(attrs, name) {
2771
2941
  const m = attrs.match(new RegExp(`\\b${name}\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`, "i"));
2772
2942
  return m ? decodeEntities(m[2] ?? m[3] ?? m[4] ?? "") : void 0;
@@ -2778,6 +2948,12 @@ function parseHtml(html) {
2778
2948
  const lists = [];
2779
2949
  let current = null;
2780
2950
  let skipping = null;
2951
+ let pdf = null;
2952
+ let fileLink = null;
2953
+ const media = (block) => {
2954
+ flush();
2955
+ blocks.push({ level: 1, runs: [], ...block });
2956
+ };
2781
2957
  const flush = () => {
2782
2958
  if (current) {
2783
2959
  const runs = current.runs;
@@ -2808,12 +2984,30 @@ function parseHtml(html) {
2808
2984
  if (last && last.bold === run.bold && last.italic === run.italic && last.underline === run.underline && last.code === run.code && last.href === run.href) last.text += text;
2809
2985
  else current.runs.push(run);
2810
2986
  };
2987
+ const size = (attrs, name) => {
2988
+ const n = Number(attr(attrs, name));
2989
+ return n > 0 ? n : void 0;
2990
+ };
2991
+ const endPdf = (p) => {
2992
+ const name = p.name.replace(/\s+/g, " ").trim();
2993
+ if (p.href && p.pages.length) media({ kind: "pdf", href: p.href, alt: name || "PDF", pages: p.pages });
2994
+ else if (p.href) media({ kind: "file", href: p.href, alt: name || "Open PDF" });
2995
+ else for (const page of p.pages) media({ kind: "image", src: page.src, alt: page.alt, ...page.width && page.height ? { width: page.width, height: page.height } : {} });
2996
+ };
2811
2997
  const re = /<!--[\s\S]*?-->|<\/?([a-zA-Z][a-zA-Z0-9]*)\b([^>]*)>|([^<]+|<)/g;
2812
2998
  let m;
2813
2999
  while (m = re.exec(html || "")) {
2814
3000
  const [token, rawTag, attrs = "", text] = m;
2815
3001
  if (text !== void 0) {
2816
3002
  if (skipping) continue;
3003
+ if (pdf) {
3004
+ if (pdf.inLink) pdf.name += decodeEntities(text);
3005
+ continue;
3006
+ }
3007
+ if (fileLink) {
3008
+ fileLink.name += decodeEntities(text);
3009
+ continue;
3010
+ }
2817
3011
  push(decodeEntities(text.replace(/\s+/g, " ")));
2818
3012
  continue;
2819
3013
  }
@@ -2825,7 +3019,54 @@ function parseHtml(html) {
2825
3019
  continue;
2826
3020
  }
2827
3021
  if (SKIP_TAGS.has(tag)) {
2828
- if (!closing && !token.endsWith("/>")) skipping = tag;
3022
+ if (closing) continue;
3023
+ if (tag === "iframe") {
3024
+ const src = attr(attrs, "src");
3025
+ const embed = isAllowedEmbedSrc(src) ? toEmbedUrl(src) : null;
3026
+ if (embed) media({ kind: "video", href: embed.watchUrl, alt: PROVIDER_LABEL[embed.provider] });
3027
+ } else if (tag === "video") {
3028
+ const src = attr(attrs, "src");
3029
+ if (isSafeMediaUrl(src)) media({ kind: "video", href: src, alt: "Play video" });
3030
+ }
3031
+ if (!token.endsWith("/>")) skipping = tag;
3032
+ continue;
3033
+ }
3034
+ if (pdf) {
3035
+ if (tag === "div") {
3036
+ pdf.depth += closing ? -1 : 1;
3037
+ if (pdf.depth === 0) {
3038
+ endPdf(pdf);
3039
+ pdf = null;
3040
+ }
3041
+ } else if (tag === "a") {
3042
+ if (closing) pdf.inLink = false;
3043
+ else if (!pdf.href && isFileLink(attr(attrs, "class"), attr(attrs, "href"))) {
3044
+ pdf.href = attr(attrs, "href");
3045
+ pdf.inLink = true;
3046
+ }
3047
+ } else if (tag === "img" && !closing) {
3048
+ const src = attr(attrs, "src");
3049
+ const w = size(attrs, "width");
3050
+ const h = size(attrs, "height");
3051
+ if (isSafeMediaUrl(src)) pdf.pages.push({ src, alt: attr(attrs, "alt") ?? "", ...w && h ? { width: w, height: h } : {} });
3052
+ }
3053
+ continue;
3054
+ }
3055
+ if (fileLink) {
3056
+ if (closing && tag === "a") {
3057
+ media({ kind: "file", href: fileLink.href, alt: fileLink.name.replace(/\s+/g, " ").trim() || "Open PDF" });
3058
+ fileLink = null;
3059
+ }
3060
+ continue;
3061
+ }
3062
+ if (!closing && tag === "div" && (attr(attrs, "class") ?? "").split(/\s+/).includes(PDF_BLOCK_CLASS)) {
3063
+ flush();
3064
+ pdf = { depth: 1, name: "", inLink: false, pages: [] };
3065
+ continue;
3066
+ }
3067
+ if (!closing && tag === "a" && isFileLink(attr(attrs, "class"), attr(attrs, "href"))) {
3068
+ flush();
3069
+ fileLink = { href: attr(attrs, "href"), name: "" };
2829
3070
  continue;
2830
3071
  }
2831
3072
  if (closing) {
@@ -2886,16 +3127,23 @@ function parseHtml(html) {
2886
3127
  default:
2887
3128
  if (BLOCK_TAGS.has(tag)) open("paragraph");
2888
3129
  else if (tag === "img") {
2889
- const alt = attr(attrs, "alt");
2890
- if (alt) push(alt);
3130
+ const src = attr(attrs, "src") ?? "";
3131
+ const alt = attr(attrs, "alt") ?? "";
3132
+ if (isSafeMediaUrl(src) || /^data:image\//i.test(src)) {
3133
+ const w = Number(attr(attrs, "width"));
3134
+ const h = Number(attr(attrs, "height"));
3135
+ media({ kind: "image", src, alt, ...w > 0 && h > 0 ? { width: w, height: h } : {} });
3136
+ } else if (alt) push(alt);
2891
3137
  }
2892
3138
  }
2893
3139
  }
3140
+ if (pdf) endPdf(pdf);
3141
+ if (fileLink) media({ kind: "file", href: fileLink.href, alt: fileLink.name.replace(/\s+/g, " ").trim() || "Open PDF" });
2894
3142
  flush();
2895
3143
  return blocks;
2896
3144
  }
2897
3145
  function htmlToText(html) {
2898
- return parseHtml(html).map((b) => (b.marker ? `${b.marker} ` : "") + b.runs.map((r) => r.text).join("")).join("\n");
3146
+ return parseHtml(html).map((b) => b.kind === "image" || b.kind === "video" || b.kind === "pdf" || b.kind === "file" ? b.alt ?? "" : (b.marker ? `${b.marker} ` : "") + b.runs.map((r) => r.text).join("")).filter(Boolean).join("\n");
2899
3147
  }
2900
3148
 
2901
3149
  // src/ui/RichText.tsx
@@ -2912,11 +3160,76 @@ function runStyle(run, p) {
2912
3160
  color: run.href ? p.primary : void 0
2913
3161
  };
2914
3162
  }
3163
+ function BodyImage({ block, palette: p }) {
3164
+ const [ratio, setRatio] = useState(block.width && block.height ? block.width / block.height : 16 / 9);
3165
+ useEffect(() => {
3166
+ if (block.width && block.height) return;
3167
+ let live = true;
3168
+ Image.getSize(block.src, (w, h) => live && w > 0 && h > 0 && setRatio(w / h), () => {
3169
+ });
3170
+ return () => {
3171
+ live = false;
3172
+ };
3173
+ }, [block.src, block.width, block.height]);
3174
+ 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 }] });
3175
+ }
3176
+ function BodyVideo({ block, palette: p, onLink }) {
3177
+ 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: [
3178
+ /* @__PURE__ */ jsx2(Text2, { style: [styles2.play, { color: p.primary }], children: "\u25B6" }),
3179
+ /* @__PURE__ */ jsx2(Text2, { style: { color: p.primary, fontFamily: p.fontFamily, fontWeight: "600" }, children: block.alt })
3180
+ ] });
3181
+ }
3182
+ var PAGE_RATIO = 1 / 1.414;
3183
+ var pageRatio = (p) => p.width && p.height ? p.width / p.height : PAGE_RATIO;
3184
+ function PdfBadge() {
3185
+ return /* @__PURE__ */ jsx2(View2, { style: styles2.badge, children: /* @__PURE__ */ jsx2(Text2, { style: styles2.badgeText, children: "PDF" }) });
3186
+ }
3187
+ function BodyFile({ block, palette: p, onLink }) {
3188
+ return /* @__PURE__ */ jsxs(Pressable2, { accessibilityRole: "link", accessibilityLabel: `${block.alt}, PDF`, onPress: () => onLink(block.href), style: ({ pressed }) => [styles2.video, { borderColor: p.border, backgroundColor: p.surface, opacity: pressed ? 0.7 : 1 }], children: [
3189
+ /* @__PURE__ */ jsx2(PdfBadge, {}),
3190
+ /* @__PURE__ */ jsx2(Text2, { numberOfLines: 2, ellipsizeMode: "middle", style: [styles2.flex, { color: p.text, fontFamily: p.fontFamily, fontWeight: "600" }], children: block.alt })
3191
+ ] });
3192
+ }
3193
+ function BodyPdf({ block, palette: p, onLink }) {
3194
+ const pages = block.pages ?? [];
3195
+ const [width, setWidth] = useState(0);
3196
+ const [index, setIndex] = useState(0);
3197
+ const [reading, setReading] = useState(false);
3198
+ const height = width ? Math.max(...pages.map((pg) => width / pageRatio(pg))) : 0;
3199
+ const onScroll = (e) => width && setIndex(Math.round(e.nativeEvent.contentOffset.x / width));
3200
+ return /* @__PURE__ */ jsxs(View2, { style: [styles2.pdf, { borderColor: p.border, backgroundColor: p.surface }], children: [
3201
+ /* @__PURE__ */ jsxs(Pressable2, { accessibilityRole: "button", accessibilityLabel: `Read ${block.alt} full screen`, onPress: () => setReading(true), style: ({ pressed }) => [styles2.pdfHeader, { opacity: pressed ? 0.7 : 1 }], children: [
3202
+ /* @__PURE__ */ jsx2(PdfBadge, {}),
3203
+ /* @__PURE__ */ jsx2(Text2, { numberOfLines: 1, ellipsizeMode: "middle", style: [styles2.flex, { color: p.text, fontFamily: p.fontFamily, fontWeight: "600" }], children: block.alt }),
3204
+ /* @__PURE__ */ jsx2(Text2, { style: { color: p.muted, fontSize: 16 }, children: "\u2922" })
3205
+ ] }),
3206
+ /* @__PURE__ */ jsxs(View2, { style: styles2.pdfPages, onLayout: (e) => setWidth(e.nativeEvent.layout.width), children: [
3207
+ width > 0 && /* @__PURE__ */ jsx2(ScrollView, { horizontal: true, pagingEnabled: true, showsHorizontalScrollIndicator: false, onScroll, scrollEventThrottle: 32, accessibilityLabel: `${block.alt}, page ${index + 1} of ${pages.length}`, children: pages.map((pg, i) => /* @__PURE__ */ jsx2(Pressable2, { onPress: () => setReading(true), accessibilityRole: "imagebutton", accessibilityLabel: pg.alt || `Page ${i + 1}`, accessibilityHint: "Opens the document full screen", style: { width, height, alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx2(Image, { source: { uri: pg.src }, resizeMode: "contain", style: [styles2.page, { width: Math.min(width, height * pageRatio(pg)), aspectRatio: pageRatio(pg) }] }) }, i)) }),
3208
+ pages.length > 1 && /* @__PURE__ */ jsx2(View2, { pointerEvents: "none", style: styles2.count, children: /* @__PURE__ */ jsxs(Text2, { style: styles2.countText, children: [
3209
+ index + 1,
3210
+ " / ",
3211
+ pages.length
3212
+ ] }) })
3213
+ ] }),
3214
+ /* @__PURE__ */ jsx2(Modal, { visible: reading, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: () => setReading(false), children: /* @__PURE__ */ jsxs(View2, { style: styles2.reader, children: [
3215
+ /* @__PURE__ */ jsxs(View2, { style: styles2.readerBar, children: [
3216
+ /* @__PURE__ */ jsx2(Text2, { numberOfLines: 1, ellipsizeMode: "middle", style: [styles2.flex, styles2.readerTitle], accessibilityRole: "header", children: block.alt }),
3217
+ /* @__PURE__ */ jsx2(Pressable2, { accessibilityRole: "link", onPress: () => onLink(block.href), style: styles2.readerBtn, children: /* @__PURE__ */ jsx2(Text2, { style: styles2.readerBtnText, children: "Open PDF \u2197" }) }),
3218
+ /* @__PURE__ */ jsx2(Pressable2, { accessibilityRole: "button", accessibilityLabel: "Close", onPress: () => setReading(false), style: styles2.readerBtn, children: /* @__PURE__ */ jsx2(Text2, { style: styles2.readerBtnText, children: "Done" }) })
3219
+ ] }),
3220
+ /* @__PURE__ */ jsx2(ScrollView, { contentContainerStyle: styles2.readerPages, children: pages.map((pg, i) => /* @__PURE__ */ jsx2(Image, { source: { uri: pg.src }, accessible: true, accessibilityLabel: pg.alt || `Page ${i + 1}`, resizeMode: "contain", style: [styles2.readerPage, { aspectRatio: pageRatio(pg) }] }, i)) })
3221
+ ] }) })
3222
+ ] });
3223
+ }
2915
3224
  function RichText({ html, palette: p, onLink, size = 15 }) {
2916
3225
  const blocks = useMemo(() => parseHtml(html), [html]);
2917
3226
  if (!blocks.length) return null;
2918
3227
  const base = { color: p.text, fontSize: size, lineHeight: Math.round(size * 1.42), fontFamily: p.fontFamily };
2919
3228
  return /* @__PURE__ */ jsx2(View2, { style: styles2.wrap, children: blocks.map((b, i) => {
3229
+ if (b.kind === "image") return /* @__PURE__ */ jsx2(BodyImage, { block: b, palette: p }, i);
3230
+ if (b.kind === "video") return /* @__PURE__ */ jsx2(BodyVideo, { block: b, palette: p, onLink }, i);
3231
+ if (b.kind === "pdf") return /* @__PURE__ */ jsx2(BodyPdf, { block: b, palette: p, onLink }, i);
3232
+ if (b.kind === "file") return /* @__PURE__ */ jsx2(BodyFile, { block: b, palette: p, onLink }, i);
2920
3233
  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
3234
  if (b.kind === "list-item") {
2922
3235
  return /* @__PURE__ */ jsxs(View2, { style: [styles2.li, { paddingLeft: (b.level - 1) * 14 }], children: [
@@ -2935,7 +3248,25 @@ var styles2 = StyleSheet2.create({
2935
3248
  wrap: { gap: 6 },
2936
3249
  li: { flexDirection: "row", gap: 6 },
2937
3250
  marker: { minWidth: 14 },
2938
- flex: { flex: 1 }
3251
+ flex: { flex: 1 },
3252
+ image: { width: "100%", borderRadius: 6 },
3253
+ video: { flexDirection: "row", alignItems: "center", gap: 8, paddingVertical: 12, paddingHorizontal: 14, borderWidth: 1, borderRadius: 8 },
3254
+ play: { fontSize: 14 },
3255
+ badge: { backgroundColor: "#e5484d", borderRadius: 4, paddingHorizontal: 5, paddingVertical: 3 },
3256
+ badgeText: { color: "#fff", fontSize: 9, fontWeight: "700", letterSpacing: 0.4 },
3257
+ pdf: { borderWidth: 1, borderRadius: 10, overflow: "hidden" },
3258
+ pdfHeader: { flexDirection: "row", alignItems: "center", gap: 8, minHeight: 44, paddingHorizontal: 12 },
3259
+ pdfPages: { marginHorizontal: 10, marginBottom: 10 },
3260
+ page: { backgroundColor: "#fff", borderRadius: 3 },
3261
+ count: { position: "absolute", right: 8, bottom: 8, backgroundColor: "rgba(17,17,17,0.72)", borderRadius: 999, paddingHorizontal: 8, paddingVertical: 2 },
3262
+ countText: { color: "#fff", fontSize: 11, fontWeight: "500", fontVariant: ["tabular-nums"] },
3263
+ reader: { flex: 1, backgroundColor: "#0b0b0d" },
3264
+ readerBar: { flexDirection: "row", alignItems: "center", gap: 8, paddingTop: Platform.OS === "ios" ? 52 : 12, paddingBottom: 10, paddingHorizontal: 16 },
3265
+ readerTitle: { color: "#fff", fontSize: 16, fontWeight: "600" },
3266
+ readerBtn: { minHeight: 36, justifyContent: "center", paddingHorizontal: 12, borderRadius: 8, backgroundColor: "rgba(255,255,255,0.12)" },
3267
+ readerBtnText: { color: "#fff", fontWeight: "600" },
3268
+ readerPages: { gap: 12, paddingHorizontal: 12, paddingBottom: 32 },
3269
+ readerPage: { width: "100%", backgroundColor: "#fff", borderRadius: 2 }
2939
3270
  });
2940
3271
 
2941
3272
  // src/ui/Checklist.tsx
@@ -2969,11 +3300,11 @@ function Checklist({ view, client, palette: p, geometry, showLauncher, launcherB
2969
3300
  ] }),
2970
3301
  /* @__PURE__ */ jsx3(View3, { style: [styles3.bar, { backgroundColor: p.surface }], accessibilityRole: "progressbar", accessibilityValue: { min: 0, max: total, now: completed }, children: /* @__PURE__ */ jsx3(View3, { style: [styles3.fill, { backgroundColor: p.primary, width: `${total ? completed / total * 100 : 0}%` }] }) }),
2971
3302
  finished && config.completionBody ? /* @__PURE__ */ jsx3(RichText, { html: config.completionBody, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null,
2972
- /* @__PURE__ */ jsx3(ScrollView, { style: styles3.tasks, contentContainerStyle: styles3.tasksContent, children: config.tasks.map((task) => {
3303
+ /* @__PURE__ */ jsx3(ScrollView2, { style: styles3.tasks, contentContainerStyle: styles3.tasksContent, children: config.tasks.map((task) => {
2973
3304
  const isDone = done.has(task.id);
2974
3305
  const locked = !!nextOpen && !isDone && task.id !== nextOpen;
2975
3306
  return /* @__PURE__ */ jsxs2(
2976
- Pressable2,
3307
+ Pressable3,
2977
3308
  {
2978
3309
  disabled: locked,
2979
3310
  accessibilityRole: "button",
@@ -2998,7 +3329,7 @@ function Checklist({ view, client, palette: p, geometry, showLauncher, launcherB
2998
3329
  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
3330
  ] }) }) : null,
3000
3331
  showLauncher ? /* @__PURE__ */ jsxs2(
3001
- Pressable2,
3332
+ Pressable3,
3002
3333
  {
3003
3334
  accessibilityRole: "button",
3004
3335
  accessibilityLabel: `${config.launcherLabel}, ${completed} of ${total} complete`,
@@ -3051,8 +3382,8 @@ var styles3 = StyleSheet3.create({
3051
3382
  });
3052
3383
 
3053
3384
  // 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";
3385
+ import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useRef, useState as useState2, useSyncExternalStore } from "react";
3386
+ import { ActivityIndicator, Keyboard, Modal as Modal2, Platform as Platform2, Pressable as Pressable4, ScrollView as ScrollView3, StyleSheet as StyleSheet4, Text as Text4, TextInput, View as View4 } from "react-native";
3056
3387
  import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
3057
3388
  function useMessenger(store) {
3058
3389
  return useSyncExternalStore(
@@ -3062,8 +3393,8 @@ function useMessenger(store) {
3062
3393
  );
3063
3394
  }
3064
3395
  function useKeyboardHeight() {
3065
- const [h, setH] = useState(0);
3066
- useEffect(() => {
3396
+ const [h, setH] = useState2(0);
3397
+ useEffect2(() => {
3067
3398
  if (Platform2.OS !== "ios") return;
3068
3399
  const show = Keyboard.addListener("keyboardWillShow", (e) => setH(e.endCoordinates.height));
3069
3400
  const hide = Keyboard.addListener("keyboardWillHide", () => setH(0));
@@ -3085,7 +3416,7 @@ function MessengerLauncher({ store, palette: p, style }) {
3085
3416
  const snap = useMessenger(store);
3086
3417
  if (snap.open) return null;
3087
3418
  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: [
3419
+ 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
3420
  /* @__PURE__ */ jsx4(Text4, { style: [styles4.launcherIcon, { color: p.onPrimary }], children: "\u{1F4AC}" }),
3090
3421
  snap.unread ? /* @__PURE__ */ jsx4(View4, { style: styles4.badge, children: /* @__PURE__ */ jsx4(Text4, { style: styles4.badgeText, children: snap.unread }) }) : null
3091
3422
  ] });
@@ -3095,9 +3426,9 @@ function MessengerSheet({ store, client, palette: p, topInset, bottomInset }) {
3095
3426
  const keyboard = useKeyboardHeight();
3096
3427
  const v = snap.view;
3097
3428
  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
- 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: [
3429
+ return /* @__PURE__ */ jsx4(Modal2, { 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
3430
  /* @__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,
3431
+ 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
3432
  /* @__PURE__ */ jsx4(Text4, { accessibilityRole: "header", numberOfLines: 1, style: [styles4.title, { color: p.text, fontFamily: p.fontFamily }], children: title }),
3102
3433
  /* @__PURE__ */ jsx4(CloseButton, { palette: p, onPress: () => store.close(), label: `Close ${snap.config.name}` })
3103
3434
  ] }),
@@ -3114,7 +3445,7 @@ function Tabs({ store, active, palette: p, bottomInset }) {
3114
3445
  ];
3115
3446
  return /* @__PURE__ */ jsx4(View4, { accessibilityRole: "tablist", style: [styles4.tabs, { borderColor: p.border, paddingBottom: Math.max(8, bottomInset) }], children: tabs.map(([tab, icon, label]) => {
3116
3447
  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: [
3448
+ 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
3449
  /* @__PURE__ */ jsx4(Text4, { style: [styles4.tabIcon, { color: on ? p.primary : p.muted }], children: icon }),
3119
3450
  /* @__PURE__ */ jsx4(Text4, { style: [styles4.tabLabel, { color: on ? p.primary : p.muted }], children: label }),
3120
3451
  tab === "messages" && unread ? /* @__PURE__ */ jsx4(View4, { style: [styles4.dot, styles4.tabDot] }) : null
@@ -3124,8 +3455,8 @@ function Tabs({ store, active, palette: p, bottomInset }) {
3124
3455
  function Home({ store, palette: p }) {
3125
3456
  const snap = useMessenger(store);
3126
3457
  const recent = snap.conversations[0];
3127
- const [q, setQ] = useState("");
3128
- return /* @__PURE__ */ jsxs3(ScrollView2, { contentContainerStyle: styles4.content, keyboardShouldPersistTaps: "handled", children: [
3458
+ const [q, setQ] = useState2("");
3459
+ return /* @__PURE__ */ jsxs3(ScrollView3, { contentContainerStyle: styles4.content, keyboardShouldPersistTaps: "handled", children: [
3129
3460
  /* @__PURE__ */ jsxs3(View4, { style: [styles4.hero, { backgroundColor: p.primary, borderRadius: p.radius }], children: [
3130
3461
  /* @__PURE__ */ jsx4(Text4, { style: [styles4.heroTitle, { color: p.onPrimary }], children: snap.config.greeting || `Hi! I'm ${snap.config.name}.` }),
3131
3462
  /* @__PURE__ */ jsx4(Text4, { style: [styles4.heroSub, { color: p.onPrimary }], children: snap.config.aiAvailable ? `${snap.config.name} answers right away${snap.config.handoffEnabled ? ", and the team is here when you need a person" : ""}.` : "The team usually replies within a few hours." })
@@ -3158,7 +3489,7 @@ function Home({ store, palette: p }) {
3158
3489
  }
3159
3490
  var pendingSearch = { current: "" };
3160
3491
  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: [
3492
+ 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
3493
  /* @__PURE__ */ jsxs3(View4, { style: styles4.flex, children: [
3163
3494
  /* @__PURE__ */ jsx4(Text4, { numberOfLines: 1, style: [styles4.rowTitle, { color: p.text }], children: c.subject || "Conversation" }),
3164
3495
  /* @__PURE__ */ jsx4(Text4, { numberOfLines: 1, style: [styles4.rowSub, { color: p.muted }], children: c.lastMessagePreview || (c.status === "closed" ? "Closed" : " ") })
@@ -3171,7 +3502,7 @@ function ConversationRow({ c, palette: p, onPress }) {
3171
3502
  }
3172
3503
  function Conversations({ store, palette: p }) {
3173
3504
  const snap = useMessenger(store);
3174
- return /* @__PURE__ */ jsxs3(ScrollView2, { contentContainerStyle: styles4.content, children: [
3505
+ return /* @__PURE__ */ jsxs3(ScrollView3, { contentContainerStyle: styles4.content, children: [
3175
3506
  snap.conversations.length ? snap.conversations.map((c) => /* @__PURE__ */ jsx4(ConversationRow, { c, palette: p, onPress: () => void store.openConversation(c.id) }, c.id)) : /* @__PURE__ */ jsx4(Text4, { style: [styles4.empty, { color: p.muted }], children: "No conversations yet." }),
3176
3507
  /* @__PURE__ */ jsx4(GButton, { label: "New conversation", palette: p, onPress: () => store.showNewMessage(), style: styles4.fullWidth })
3177
3508
  ] });
@@ -3184,11 +3515,11 @@ function Bubble({ m, text, name, palette: p, onArticle, onGuide }) {
3184
3515
  !mine ? /* @__PURE__ */ jsx4(Text4, { style: [styles4.author, { color: p.muted }], children: m.author === "agent" ? m.authorName || "Team" : name }) : null,
3185
3516
  /* @__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
3517
  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: [
3518
+ 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
3519
  "\u{1F4C4} ",
3189
3520
  c.title
3190
3521
  ] }) }, 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: [
3522
+ 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
3523
  "\u25B6 Show me: ",
3193
3524
  g.name
3194
3525
  ] }) }, g.key))
@@ -3201,10 +3532,10 @@ function TypingDots({ color }) {
3201
3532
  function Thread({ store, palette: p, bottomInset }) {
3202
3533
  const snap = useMessenger(store);
3203
3534
  const t = snap.thread;
3204
- const [text, setText] = useState(t?.draft ?? "");
3535
+ const [text, setText] = useState2(t?.draft ?? "");
3205
3536
  const scroll = useRef(null);
3206
3537
  const convo = t?.conversation ?? null;
3207
- useEffect(() => {
3538
+ useEffect2(() => {
3208
3539
  if (t?.draft) setText(t.draft);
3209
3540
  }, [t?.draft]);
3210
3541
  const streamingIds = useMemo2(() => Object.keys(t?.streaming ?? {}).filter((id) => !t?.messages.some((m) => m.id === id)), [t]);
@@ -3216,7 +3547,7 @@ function Thread({ store, palette: p, bottomInset }) {
3216
3547
  };
3217
3548
  const canHandoff = snap.config.handoffEnabled && snap.config.aiAvailable && convo?.status === "bot" && !t.sending;
3218
3549
  return /* @__PURE__ */ jsxs3(View4, { style: styles4.flex, children: [
3219
- /* @__PURE__ */ jsxs3(ScrollView2, { ref: scroll, contentContainerStyle: styles4.thread, onContentSizeChange: () => scroll.current?.scrollToEnd({ animated: true }), keyboardShouldPersistTaps: "handled", children: [
3550
+ /* @__PURE__ */ jsxs3(ScrollView3, { ref: scroll, contentContainerStyle: styles4.thread, onContentSizeChange: () => scroll.current?.scrollToEnd({ animated: true }), keyboardShouldPersistTaps: "handled", children: [
3220
3551
  !t.messages.length && !t.sending ? /* @__PURE__ */ jsx4(Text4, { style: [styles4.empty, { color: p.muted }], children: snap.config.greeting || "Ask us anything." }) : null,
3221
3552
  t.messages.map((m) => /* @__PURE__ */ jsx4(Bubble, { m, text: t.streaming[m.id] ?? m.body, name: snap.config.name, palette: p, onArticle: (id) => void store.openArticle(id), onGuide: (k) => store.startGuide(k) }, m.id)),
3222
3553
  streamingIds.map((id) => /* @__PURE__ */ jsx4(Bubble, { m: { id, author: "scout", body: "", citations: [], suggestedGuides: [], status: "pending", createdAt: (/* @__PURE__ */ new Date()).toISOString() }, text: t.streaming[id] ?? "", name: snap.config.name, palette: p, onArticle: () => {
@@ -3248,19 +3579,19 @@ function Thread({ store, palette: p, bottomInset }) {
3248
3579
  function Rating({ palette: p, onRate }) {
3249
3580
  return /* @__PURE__ */ jsxs3(View4, { style: styles4.rating, children: [
3250
3581
  /* @__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)) })
3582
+ /* @__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
3583
  ] });
3253
3584
  }
3254
3585
  function Help({ store, client, palette: p }) {
3255
3586
  const snap = useMessenger(store);
3256
3587
  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(() => {
3588
+ const [q, setQ] = useState2(pendingSearch.current);
3589
+ const [results, setResults] = useState2(null);
3590
+ const [faq, setFaq] = useState2(null);
3591
+ useEffect2(() => {
3261
3592
  pendingSearch.current = "";
3262
3593
  }, []);
3263
- useEffect(() => {
3594
+ useEffect2(() => {
3264
3595
  const query = q.trim();
3265
3596
  if (query.length < 2) return setResults(null);
3266
3597
  let alive = true;
@@ -3273,7 +3604,7 @@ function Help({ store, client, palette: p }) {
3273
3604
  const guides = client.getGuides();
3274
3605
  const recommended = (rc?.recommendedGuideIds ?? []).map((id) => guides.find((g) => g.id === id || g.key === id)).filter((g) => !!g);
3275
3606
  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: [
3607
+ 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
3608
  /* @__PURE__ */ jsxs3(View4, { style: styles4.flex, children: [
3278
3609
  /* @__PURE__ */ jsx4(Text4, { style: [styles4.rowTitle, { color: p.text }], children: title }),
3279
3610
  sub ? /* @__PURE__ */ jsx4(Text4, { numberOfLines: 2, style: [styles4.rowSub, { color: p.muted }], children: sub }) : null
@@ -3285,22 +3616,19 @@ function Help({ store, client, palette: p }) {
3285
3616
  /* @__PURE__ */ jsx4(Text4, { accessibilityRole: "header", style: [styles4.sectionTitle, { color: p.muted }], children: title.toUpperCase() }),
3286
3617
  children
3287
3618
  ] }, 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));
3290
- return /* @__PURE__ */ jsxs3(ScrollView2, { contentContainerStyle: styles4.content, keyboardShouldPersistTaps: "handled", children: [
3619
+ const audiences = client.getAudienceIds();
3620
+ const sections = resourceCenterSections(articles?.collections ?? [], articles?.articles ?? [], audiences);
3621
+ const listed = results?.filter((a) => isArticleListed(articles?.collections ?? [], a, audiences));
3622
+ return /* @__PURE__ */ jsxs3(ScrollView3, { contentContainerStyle: styles4.content, keyboardShouldPersistTaps: "handled", children: [
3291
3623
  /* @__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: [
3624
+ 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
3625
  recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => store.startGuide(g.key), "\u25B6"))) : null,
3294
3626
  !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,
3627
+ 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
3628
  rc?.faqs.length ? section(
3301
3629
  "FAQ",
3302
3630
  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: [
3631
+ /* @__PURE__ */ jsxs3(Pressable4, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles4.faqHead, children: [
3304
3632
  /* @__PURE__ */ jsx4(Text4, { style: [styles4.rowTitle, styles4.flex, { color: p.text }], children: f.question }),
3305
3633
  /* @__PURE__ */ jsx4(Text4, { style: [styles4.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
3306
3634
  ] }),
@@ -3313,7 +3641,7 @@ function Help({ store, client, palette: p }) {
3313
3641
  }
3314
3642
  function Article({ article, palette: p, client }) {
3315
3643
  if (!article) return /* @__PURE__ */ jsx4(ActivityIndicator, { style: styles4.loading, color: p.muted });
3316
- return /* @__PURE__ */ jsxs3(ScrollView2, { contentContainerStyle: styles4.content, children: [
3644
+ return /* @__PURE__ */ jsxs3(ScrollView3, { contentContainerStyle: styles4.content, children: [
3317
3645
  /* @__PURE__ */ jsx4(Text4, { accessibilityRole: "header", style: [styles4.articleTitle, { color: p.text, fontFamily: p.fontFamily }], children: article.title }),
3318
3646
  /* @__PURE__ */ jsx4(RichText, { html: article.body, palette: p, onLink: (u) => client.navigate(u) })
3319
3647
  ] });
@@ -3375,23 +3703,23 @@ var styles4 = StyleSheet4.create({
3375
3703
  });
3376
3704
 
3377
3705
  // 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";
3706
+ import { useEffect as useEffect3, useMemo as useMemo3, useState as useState3 } from "react";
3707
+ import { Modal as Modal3, Platform as Platform3, Pressable as Pressable5, ScrollView as ScrollView4, StyleSheet as StyleSheet5, Text as Text5, TextInput as TextInput2, View as View5 } from "react-native";
3380
3708
  import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
3381
3709
  function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
3382
3710
  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(() => {
3711
+ const [query, setQuery] = useState3("");
3712
+ const [results, setResults] = useState3(null);
3713
+ const [article, setArticle] = useState3(null);
3714
+ const [faq, setFaq] = useState3(null);
3715
+ useEffect3(() => {
3388
3716
  if (!open) {
3389
3717
  setQuery("");
3390
3718
  setResults(null);
3391
3719
  setArticle(null);
3392
3720
  }
3393
3721
  }, [open]);
3394
- useEffect2(() => {
3722
+ useEffect3(() => {
3395
3723
  const q = query.trim();
3396
3724
  if (q.length < 2) {
3397
3725
  setResults(null);
@@ -3416,7 +3744,7 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
3416
3744
  client.articleViewed(a);
3417
3745
  setArticle(a);
3418
3746
  };
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: [
3747
+ 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
3748
  /* @__PURE__ */ jsxs4(View5, { style: styles5.flex, children: [
3421
3749
  /* @__PURE__ */ jsx5(Text5, { style: [styles5.rowTitle, { color: p.text, fontFamily: p.fontFamily }], children: title }),
3422
3750
  subtitle ? /* @__PURE__ */ jsx5(Text5, { style: [styles5.rowSub, { color: p.muted }], numberOfLines: 2, children: subtitle }) : null
@@ -3427,18 +3755,19 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
3427
3755
  /* @__PURE__ */ jsx5(Text5, { accessibilityRole: "header", style: [styles5.sectionTitle, { color: p.muted }], children: title.toUpperCase() }),
3428
3756
  children
3429
3757
  ] }, 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));
3432
- 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: [
3758
+ const audiences = client.getAudienceIds();
3759
+ const sections = resourceCenterSections(rc.collections, rc.articles, audiences);
3760
+ const listed = results?.filter((a) => isArticleListed(rc.collections, a, audiences));
3761
+ return /* @__PURE__ */ jsx5(Modal3, { 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
3762
  /* @__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,
3763
+ 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
3764
  /* @__PURE__ */ jsx5(Text5, { accessibilityRole: "header", style: [styles5.title, { color: p.text, fontFamily: p.fontFamily }], numberOfLines: 1, children: article ? "" : rc.title }),
3436
3765
  /* @__PURE__ */ jsx5(CloseButton, { palette: p, onPress: close, label: "Close help" })
3437
3766
  ] }),
3438
- article ? /* @__PURE__ */ jsxs4(ScrollView3, { contentContainerStyle: [styles5.content, { paddingBottom: bottomInset + 24 }], children: [
3767
+ article ? /* @__PURE__ */ jsxs4(ScrollView4, { contentContainerStyle: [styles5.content, { paddingBottom: bottomInset + 24 }], children: [
3439
3768
  /* @__PURE__ */ jsx5(Text5, { accessibilityRole: "header", style: [styles5.articleTitle, { color: p.text, fontFamily: p.fontFamily }], children: article.title }),
3440
3769
  /* @__PURE__ */ jsx5(RichText, { html: article.body, palette: p, onLink: (u) => client.navigate(u) })
3441
- ] }) : /* @__PURE__ */ jsxs4(ScrollView3, { contentContainerStyle: [styles5.content, { paddingBottom: bottomInset + 24 }], keyboardShouldPersistTaps: "handled", children: [
3770
+ ] }) : /* @__PURE__ */ jsxs4(ScrollView4, { contentContainerStyle: [styles5.content, { paddingBottom: bottomInset + 24 }], keyboardShouldPersistTaps: "handled", children: [
3442
3771
  /* @__PURE__ */ jsx5(
3443
3772
  TextInput2,
3444
3773
  {
@@ -3452,21 +3781,17 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
3452
3781
  style: [styles5.search, { color: p.text, backgroundColor: p.surface, borderRadius: Math.min(10, p.radius) }]
3453
3782
  }
3454
3783
  ),
3455
- results ? section(
3784
+ listed ? section(
3456
3785
  `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." })
3786
+ 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
3787
  ) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
3459
3788
  recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => (close(), client.startGuide(g.id)), "\u25B6"))) : null,
3460
3789
  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,
3790
+ 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
3791
  rc.faqs.length ? section(
3467
3792
  "FAQ",
3468
3793
  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: [
3794
+ /* @__PURE__ */ jsxs4(Pressable5, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles5.faqHead, children: [
3470
3795
  /* @__PURE__ */ jsx5(Text5, { style: [styles5.rowTitle, styles5.flex, { color: p.text }], children: f.question }),
3471
3796
  /* @__PURE__ */ jsx5(Text5, { style: [styles5.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
3472
3797
  ] }),
@@ -3498,8 +3823,8 @@ var styles5 = StyleSheet5.create({
3498
3823
  });
3499
3824
 
3500
3825
  // 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";
3826
+ import { useEffect as useEffect4, useRef as useRef2, useState as useState4 } from "react";
3827
+ import { AccessibilityInfo, Animated, Easing, Pressable as Pressable6, StyleSheet as StyleSheet7, View as View7 } from "react-native";
3503
3828
 
3504
3829
  // src/core/position.ts
3505
3830
  var MARGIN = 12;
@@ -3539,7 +3864,7 @@ function positionCard(target, size, viewport, insets, placement, offset) {
3539
3864
  }
3540
3865
 
3541
3866
  // src/ui/StepCard.tsx
3542
- import { Image, StyleSheet as StyleSheet6, Text as Text6, View as View6 } from "react-native";
3867
+ import { Image as Image2, StyleSheet as StyleSheet6, Text as Text6, View as View6 } from "react-native";
3543
3868
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
3544
3869
  function StepCard({ view, client, palette: p, width, style, compact }) {
3545
3870
  const { step, guide } = view;
@@ -3548,7 +3873,7 @@ function StepCard({ view, client, palette: p, width, style, compact }) {
3548
3873
  const showProgress = step.showProgress && view.total > 1;
3549
3874
  const onLink = (url) => client.navigate(url);
3550
3875
  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,
3876
+ 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
3877
  step.title ? /* @__PURE__ */ jsxs5(View6, { style: styles6.header, children: [
3553
3878
  /* @__PURE__ */ jsx6(Text6, { accessibilityRole: "header", style: [styles6.title, { color: p.text, fontFamily: p.fontFamily }], children: step.title }),
3554
3879
  step.showClose ? /* @__PURE__ */ jsx6(CloseButton, { palette: p, onPress: () => client.closeStep(guide.id), label: "Dismiss guide" }) : null
@@ -3591,8 +3916,8 @@ var styles6 = StyleSheet6.create({
3591
3916
  // src/ui/StepLayer.tsx
3592
3917
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3593
3918
  function useTargetRect(client, targetId, origin) {
3594
- const [rect, setRect] = useState3(null);
3595
- useEffect3(() => {
3919
+ const [rect, setRect] = useState4(null);
3920
+ useEffect4(() => {
3596
3921
  if (!targetId) {
3597
3922
  setRect(null);
3598
3923
  return;
@@ -3620,7 +3945,7 @@ function useTargetRect(client, targetId, origin) {
3620
3945
  }
3621
3946
  function useFadeIn(key) {
3622
3947
  const opacity = useRef2(new Animated.Value(0)).current;
3623
- useEffect3(() => {
3948
+ useEffect4(() => {
3624
3949
  opacity.setValue(0);
3625
3950
  Animated.timing(opacity, { toValue: 1, duration: 160, easing: Easing.out(Easing.quad), useNativeDriver: true }).start();
3626
3951
  }, [key, opacity]);
@@ -3632,11 +3957,11 @@ function StepLayer({ view, client, palette: p, geometry }) {
3632
3957
  const opacity = useFadeIn(view.key);
3633
3958
  const { viewport, insets } = geometry;
3634
3959
  const cardWidth = Math.min(viewport.width - 32, step.width ?? 340);
3635
- useEffect3(() => {
3960
+ useEffect4(() => {
3636
3961
  if (step.type === "hotspot" && !view.expanded) return;
3637
3962
  AccessibilityInfo.announceForAccessibility?.(announcement(view));
3638
3963
  }, [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 });
3964
+ const backdrop = (children) => /* @__PURE__ */ jsx7(Pressable6, { accessible: false, style: [StyleSheet7.absoluteFill, { backgroundColor: p.overlay }], onPress: () => client.backdropPressed(view.guide.id), children });
3640
3965
  const anchored = !view.asModal && ["tooltip", "popover", "spotlight", "hotspot"].includes(step.type);
3641
3966
  if (!anchored && step.type === "banner") {
3642
3967
  const top = step.position.startsWith("top");
@@ -3675,10 +4000,10 @@ function StepLayer({ view, client, palette: p, geometry }) {
3675
4000
  [hole.x + hole.width - r, hole.y + hole.height - r, -2 * r, -2 * r]
3676
4001
  ];
3677
4002
  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 }] }),
4003
+ /* @__PURE__ */ jsx7(Pressable6, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, right: 0, top: 0, height: Math.max(0, hole.y) }] }),
4004
+ /* @__PURE__ */ jsx7(Pressable6, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, right: 0, top: hole.y + hole.height, bottom: 0 }] }),
4005
+ /* @__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 }] }),
4006
+ /* @__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
4007
  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
4008
  /* @__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
4009
  /* @__PURE__ */ jsx7(Anchored, { view, client, palette: p, geometry, rect: hole, width: cardWidth })
@@ -3691,7 +4016,7 @@ function StepLayer({ view, client, palette: p, geometry }) {
3691
4016
  }
3692
4017
  var ARROW = 12;
3693
4018
  function Anchored({ view, client, palette: p, geometry, rect, width }) {
3694
- const [height, setHeight] = useState3(0);
4019
+ const [height, setHeight] = useState4(0);
3695
4020
  const onLayout = (e) => setHeight(Math.round(e.nativeEvent.layout.height));
3696
4021
  const offset = view.step.offset + ARROW / 2;
3697
4022
  const pos = positionCard(rect, { width, height: height || 120 }, geometry.viewport, geometry.insets, view.step.placement, offset);
@@ -3707,8 +4032,8 @@ function Arrow({ side, at, color, border }) {
3707
4032
  }
3708
4033
  function Hotspot({ x, y, size, pulse, color, onPress, label }) {
3709
4034
  const scale = useRef2(new Animated.Value(0)).current;
3710
- const [reduceMotion, setReduceMotion] = useState3(false);
3711
- useEffect3(() => {
4035
+ const [reduceMotion, setReduceMotion] = useState4(false);
4036
+ useEffect4(() => {
3712
4037
  let alive = true;
3713
4038
  AccessibilityInfo.isReduceMotionEnabled?.().then((v) => alive && setReduceMotion(v), () => {
3714
4039
  });
@@ -3716,13 +4041,13 @@ function Hotspot({ x, y, size, pulse, color, onPress, label }) {
3716
4041
  alive = false;
3717
4042
  };
3718
4043
  }, []);
3719
- useEffect3(() => {
4044
+ useEffect4(() => {
3720
4045
  if (!pulse || reduceMotion) return;
3721
4046
  const loop = Animated.loop(Animated.timing(scale, { toValue: 1, duration: 1400, easing: Easing.out(Easing.ease), useNativeDriver: true }));
3722
4047
  loop.start();
3723
4048
  return () => loop.stop();
3724
4049
  }, [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: [
4050
+ return /* @__PURE__ */ jsxs6(Pressable6, { accessibilityRole: "button", accessibilityLabel: label, onPress, hitSlop: 14, style: [styles7.abs, { left: x, top: y, width: size, height: size }], children: [
3726
4051
  pulse && !reduceMotion ? /* @__PURE__ */ jsx7(
3727
4052
  Animated.View,
3728
4053
  {
@@ -3741,13 +4066,13 @@ var styles7 = StyleSheet7.create({
3741
4066
  });
3742
4067
 
3743
4068
  // src/ui/Survey.tsx
3744
- import { useEffect as useEffect4, useState as useState4 } from "react";
4069
+ import { useEffect as useEffect5, useState as useState5 } from "react";
3745
4070
  import {
3746
4071
  AccessibilityInfo as AccessibilityInfo2,
3747
4072
  KeyboardAvoidingView,
3748
4073
  Platform as Platform4,
3749
- Pressable as Pressable6,
3750
- ScrollView as ScrollView4,
4074
+ Pressable as Pressable7,
4075
+ ScrollView as ScrollView5,
3751
4076
  StyleSheet as StyleSheet8,
3752
4077
  Text as Text7,
3753
4078
  TextInput as TextInput3,
@@ -3761,9 +4086,9 @@ function Survey({
3761
4086
  geometry
3762
4087
  }) {
3763
4088
  const config = view.guide.config.survey;
3764
- const [answers, setAnswers] = useState4({});
3765
- const [error, setError] = useState4("");
3766
- useEffect4(() => {
4089
+ const [answers, setAnswers] = useState5({});
4090
+ const [error, setError] = useState5("");
4091
+ useEffect5(() => {
3767
4092
  setAnswers({});
3768
4093
  setError("");
3769
4094
  AccessibilityInfo2.announceForAccessibility?.(
@@ -3804,7 +4129,7 @@ function Survey({
3804
4129
  style: StyleSheet8.absoluteFill,
3805
4130
  children: [
3806
4131
  centered ? /* @__PURE__ */ jsx8(
3807
- Pressable6,
4132
+ Pressable7,
3808
4133
  {
3809
4134
  accessible: false,
3810
4135
  style: [StyleSheet8.absoluteFill, { backgroundColor: p.overlay }],
@@ -3843,7 +4168,7 @@ function Survey({
3843
4168
  }
3844
4169
  ) : null,
3845
4170
  /* @__PURE__ */ jsx8(GButton, { label: "Done", palette: p, onPress: close, style: styles8.end })
3846
- ] }) : /* @__PURE__ */ jsxs7(ScrollView4, { contentContainerStyle: styles8.inner, keyboardShouldPersistTaps: "handled", children: [
4171
+ ] }) : /* @__PURE__ */ jsxs7(ScrollView5, { contentContainerStyle: styles8.inner, keyboardShouldPersistTaps: "handled", children: [
3847
4172
  config.questions.filter((q) => isQuestionVisible(q, answers)).map((q, i) => /* @__PURE__ */ jsxs7(View8, { style: styles8.question, children: [
3848
4173
  /* @__PURE__ */ jsxs7(View8, { style: styles8.head, children: [
3849
4174
  /* @__PURE__ */ jsxs7(Text7, { style: [styles8.qTitle, { color: p.text, fontFamily: p.fontFamily }], children: [
@@ -3901,7 +4226,7 @@ function QuestionInput({
3901
4226
  const values = Array.from({ length: max - min + 1 }, (_, i) => min + i);
3902
4227
  return /* @__PURE__ */ jsxs7(View8, { children: [
3903
4228
  /* @__PURE__ */ jsx8(View8, { style: styles8.scale, accessibilityRole: "radiogroup", accessibilityLabel: q.title, children: values.map((n) => /* @__PURE__ */ jsx8(
3904
- Pressable6,
4229
+ Pressable7,
3905
4230
  {
3906
4231
  accessibilityRole: "radio",
3907
4232
  accessibilityState: { selected: value === n },
@@ -3921,7 +4246,7 @@ function QuestionInput({
3921
4246
  case "rating": {
3922
4247
  const max = q.scaleMax ?? 5;
3923
4248
  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,
4249
+ Pressable7,
3925
4250
  {
3926
4251
  accessibilityRole: "radio",
3927
4252
  accessibilityLabel: `${n} of ${max}`,
@@ -3947,7 +4272,7 @@ function QuestionInput({
3947
4272
  [true, "\u{1F44D}", "Thumbs up"],
3948
4273
  [false, "\u{1F44E}", "Thumbs down"]
3949
4274
  ].map(([val, icon, label]) => /* @__PURE__ */ jsx8(
3950
- Pressable6,
4275
+ Pressable7,
3951
4276
  {
3952
4277
  accessibilityRole: "radio",
3953
4278
  accessibilityLabel: label,
@@ -3967,7 +4292,7 @@ function QuestionInput({
3967
4292
  return /* @__PURE__ */ jsx8(View8, { style: styles8.choices, children: q.options.map((o) => {
3968
4293
  const on = selected.has(o.id);
3969
4294
  return /* @__PURE__ */ jsxs7(
3970
- Pressable6,
4295
+ Pressable7,
3971
4296
  {
3972
4297
  accessibilityRole: multi ? "checkbox" : "radio",
3973
4298
  accessibilityState: { checked: on },
@@ -4132,8 +4457,8 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
4132
4457
  const { width, height } = useWindowDimensions();
4133
4458
  const scheme = useColorScheme();
4134
4459
  const ref = useRef3(null);
4135
- const [origin, setOrigin] = useState5({ x: 0, y: 0 });
4136
- useEffect5(() => {
4460
+ const [origin, setOrigin] = useState6({ x: 0, y: 0 });
4461
+ useEffect6(() => {
4137
4462
  client.targets.setViewport(width, height);
4138
4463
  }, [client, width, height]);
4139
4464
  const measureOrigin = useCallback2(() => {
@@ -4151,7 +4476,7 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
4151
4476
  return /* @__PURE__ */ jsxs8(View9, { ref, collapsable: false, pointerEvents: "box-none", style: StyleSheet9.absoluteFill, onLayout: measureOrigin, children: [
4152
4477
  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
4478
  showRcLauncher ? /* @__PURE__ */ jsx9(
4154
- Pressable7,
4479
+ Pressable8,
4155
4480
  {
4156
4481
  accessibilityRole: "button",
4157
4482
  accessibilityLabel: client.getResourceCenter()?.launcher.label || "Help",
@@ -4172,7 +4497,7 @@ var styles9 = StyleSheet9.create({
4172
4497
  });
4173
4498
 
4174
4499
  // src/ui/Target.tsx
4175
- import { createContext, useCallback as useCallback3, useContext, useEffect as useEffect6, useRef as useRef4 } from "react";
4500
+ import { createContext, useCallback as useCallback3, useContext, useEffect as useEffect7, useRef as useRef4 } from "react";
4176
4501
  import { View as View10 } from "react-native";
4177
4502
  import { jsx as jsx10 } from "react/jsx-runtime";
4178
4503
  var GuidelyContext = createContext(null);
@@ -4189,7 +4514,7 @@ function useGuidelyTarget(id) {
4189
4514
  const screen = useContext(GuidelyScreenContext);
4190
4515
  const ref = useRef4(null);
4191
4516
  const start = useRef4(null);
4192
- useEffect6(() => {
4517
+ useEffect7(() => {
4193
4518
  if (!client || !id) return;
4194
4519
  return client.targets.register(id, {
4195
4520
  screen,
@@ -4262,11 +4587,11 @@ function defaultBridge(appVersion) {
4262
4587
  };
4263
4588
  }
4264
4589
  function GuidelyProvider({ children, user, insets, launcherOffset = 72, ...options }) {
4265
- const [client, setClient] = useState6(null);
4590
+ const [client, setClient] = useState7(null);
4266
4591
  const optionsRef = useRef5(options);
4267
4592
  optionsRef.current = options;
4268
4593
  const { projectId, environment, apiUrl, storage } = options;
4269
- useEffect7(() => {
4594
+ useEffect8(() => {
4270
4595
  if (!projectId) return;
4271
4596
  let c;
4272
4597
  try {
@@ -4290,7 +4615,7 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, ...optio
4290
4615
  }, [projectId, environment, apiUrl, storage]);
4291
4616
  const userKey = user ? JSON.stringify([user.id, user.traits ?? null, user.account ?? null]) : "";
4292
4617
  const hadUser = useRef5(false);
4293
- useEffect7(() => {
4618
+ useEffect8(() => {
4294
4619
  if (!client) return;
4295
4620
  if (user && user.id) {
4296
4621
  hadUser.current = true;
@@ -4339,8 +4664,8 @@ function useGuidely() {
4339
4664
  }
4340
4665
  function useGuidelyReady() {
4341
4666
  const client = useContext2(GuidelyContext);
4342
- const [ready, setReady] = useState6(() => !!client?.isClientReady());
4343
- useEffect7(() => {
4667
+ const [ready, setReady] = useState7(() => !!client?.isClientReady());
4668
+ useEffect8(() => {
4344
4669
  if (!client) return setReady(false);
4345
4670
  setReady(client.isClientReady());
4346
4671
  const off = client.on("ready", () => setReady(true));
@@ -4352,7 +4677,7 @@ function useGuidelyReady() {
4352
4677
  function useGuidelyIdentify(userId, traits, account) {
4353
4678
  const client = useContext2(GuidelyContext);
4354
4679
  const key = JSON.stringify([userId ?? null, traits ?? null, account ?? null]);
4355
- useEffect7(() => {
4680
+ useEffect8(() => {
4356
4681
  if (client && userId) void client.identify(userId, traits, account);
4357
4682
  }, [client, key]);
4358
4683
  }
@@ -4360,14 +4685,14 @@ function useGuidelyEvent(event, handler) {
4360
4685
  const client = useContext2(GuidelyContext);
4361
4686
  const ref = useRef5(handler);
4362
4687
  ref.current = handler;
4363
- useEffect7(() => {
4688
+ useEffect8(() => {
4364
4689
  if (!client) return;
4365
4690
  return client.on(event, ((...args) => ref.current(...args)));
4366
4691
  }, [client, event]);
4367
4692
  }
4368
4693
  function useGuidelyScreen(name, properties) {
4369
4694
  const client = useContext2(GuidelyContext);
4370
- useEffect7(() => {
4695
+ useEffect8(() => {
4371
4696
  if (client && name) client.screen(name, properties);
4372
4697
  }, [client, name]);
4373
4698
  }
@@ -4379,15 +4704,15 @@ function useGuidelyNavigation(navigationRef, options = {}) {
4379
4704
  const route = navigationRef.getCurrentRoute?.();
4380
4705
  if (client && route?.name) client.screen(nameRef.current ? nameRef.current(route) : route.name);
4381
4706
  };
4382
- useEffect7(report, [client]);
4707
+ useEffect8(report, [client]);
4383
4708
  return { onReady: report, onStateChange: report };
4384
4709
  }
4385
4710
  function useUiSelector(select, fallback) {
4386
4711
  const client = useContext2(GuidelyContext);
4387
4712
  const selectRef = useRef5(select);
4388
4713
  selectRef.current = select;
4389
- const [value, setValue] = useState6(() => client ? select(client) : fallback);
4390
- useEffect7(() => {
4714
+ const [value, setValue] = useState7(() => client ? select(client) : fallback);
4715
+ useEffect8(() => {
4391
4716
  if (!client) return;
4392
4717
  let last = JSON.stringify(selectRef.current(client));
4393
4718
  setValue(selectRef.current(client));