@guidelytech/react-native 0.6.1 → 0.7.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.cjs CHANGED
@@ -23,15 +23,21 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
23
23
  var index_exports = {};
24
24
  __export(index_exports, {
25
25
  DEFAULT_API_URL: () => DEFAULT_API_URL,
26
+ Feature: () => Feature,
27
+ FeatureFlag: () => FeatureFlag,
28
+ FeatureValue: () => FeatureValue,
29
+ FlagsProvider: () => FlagsProvider,
26
30
  GuidelyClient: () => GuidelyClient,
27
31
  GuidelyOverlay: () => GuidelyOverlay,
28
32
  GuidelyProvider: () => GuidelyProvider,
29
33
  GuidelyScreenScope: () => GuidelyScreenScope,
30
34
  GuidelyTarget: () => GuidelyTarget,
35
+ InMemoryStickyBucketStore: () => import_flags6.InMemoryStickyBucketStore,
31
36
  Messenger: () => Messenger,
32
37
  SDK_NAME: () => SDK_NAME,
33
38
  SDK_VERSION: () => SDK_VERSION,
34
39
  TargetRegistry: () => TargetRegistry,
40
+ createFlagsClient: () => import_flags6.createFlagsClient,
35
41
  createSseParser: () => createSseParser,
36
42
  guidelyScreenLayout: () => guidelyScreenLayout,
37
43
  htmlToText: () => htmlToText,
@@ -41,6 +47,12 @@ __export(index_exports, {
41
47
  targetIdsFor: () => targetIdsFor,
42
48
  useActiveGuide: () => useActiveGuide,
43
49
  useChecklistProgress: () => useChecklistProgress,
50
+ useFeatureValue: () => useFeatureValue,
51
+ useFlag: () => useFlag,
52
+ useFlagDetails: () => useFlagDetails,
53
+ useFlags: () => useFlags,
54
+ useFlagsClient: () => useFlagsClient,
55
+ useFlagsReady: () => useFlagsReady,
44
56
  useGuideState: () => useGuideState,
45
57
  useGuidely: () => useGuidely,
46
58
  useGuidelyEvent: () => useGuidelyEvent,
@@ -51,8 +63,98 @@ __export(index_exports, {
51
63
  useGuidelyTarget: () => useGuidelyTarget
52
64
  });
53
65
  module.exports = __toCommonJS(index_exports);
54
- var import_react8 = require("react");
55
- var import_react_native11 = require("react-native");
66
+ var import_react9 = require("react");
67
+ var import_react_native12 = require("react-native");
68
+ var import_flags3 = require("@guidelytech/flags");
69
+
70
+ // src/flags.tsx
71
+ var import_flags = require("@guidelytech/flags");
72
+ var import_react = require("react");
73
+ var import_react_native = require("react-native");
74
+ var import_jsx_runtime = require("react/jsx-runtime");
75
+ var FlagsContext = (0, import_react.createContext)(null);
76
+ var EMPTY = (0, import_flags.createStaticFlags)();
77
+ function useFlagsValue(select) {
78
+ const src = (0, import_react.useContext)(FlagsContext);
79
+ const reader = src?.client ?? src?.bootstrap ?? EMPTY;
80
+ const cache = (0, import_react.useRef)(null);
81
+ const get = () => {
82
+ const value = select(reader);
83
+ const c = cache.current;
84
+ if (c && c.reader === reader && (0, import_flags.jsonEquals)(c.value, value)) return c.value;
85
+ cache.current = { reader, value };
86
+ return value;
87
+ };
88
+ return (0, import_react.useSyncExternalStore)(reader.subscribe, get, get);
89
+ }
90
+ function useFlag(key, fallback = false) {
91
+ return useFlagsValue((r) => r.isEnabled(key, fallback));
92
+ }
93
+ function useFeatureValue(key, fallback) {
94
+ return useFlagsValue((r) => r.getValue(key, fallback));
95
+ }
96
+ function useFlagDetails(key, fallback) {
97
+ return useFlagsValue((r) => r.getDetails(key, fallback));
98
+ }
99
+ function useFlags() {
100
+ return useFlagsValue((r) => r.getAll());
101
+ }
102
+ function useFlagsReady() {
103
+ return useFlagsValue((r) => r.isReady());
104
+ }
105
+ function useFlagsClient() {
106
+ return (0, import_react.useContext)(FlagsContext)?.client ?? null;
107
+ }
108
+ function Feature({ flag, children, fallback = null }) {
109
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: useFlag(flag) ? children : fallback });
110
+ }
111
+ var FeatureFlag = Feature;
112
+ function FeatureValue({ flag, value, children, fallback = null }) {
113
+ const current = useFeatureValue(flag, null);
114
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: (0, import_flags.jsonEquals)(current, value) ? children : fallback });
115
+ }
116
+ function useBootstrap(payload, attributes) {
117
+ const key = (0, import_flags.stableStringify)(attributes);
118
+ return (0, import_react.useMemo)(() => payload ? (0, import_flags.createStaticFlags)({ payload, attributes }) : EMPTY, [payload, key]);
119
+ }
120
+ function FlagsProvider({ children, client: external, payload, ...options }) {
121
+ const [created, setCreated] = (0, import_react.useState)(null);
122
+ const optionsRef = (0, import_react.useRef)(options);
123
+ optionsRef.current = options;
124
+ const payloadRef = (0, import_react.useRef)(payload);
125
+ payloadRef.current = payload;
126
+ const sdkKey = options.sdkKey ?? options.projectId;
127
+ const { apiUrl, environment } = options;
128
+ (0, import_react.useEffect)(() => {
129
+ if (external || !sdkKey) return;
130
+ let c;
131
+ try {
132
+ c = (0, import_flags.createFlagsClient)({ ...optionsRef.current, sdkKey, payload: payloadRef.current ?? void 0 });
133
+ } catch (err) {
134
+ optionsRef.current.onError?.(err instanceof Error ? err : new Error(String(err)));
135
+ return;
136
+ }
137
+ setCreated(c);
138
+ const sub = import_react_native.AppState.addEventListener("change", (s) => {
139
+ if (s === "active") void c.refresh();
140
+ else if (s === "background") void c.flush();
141
+ });
142
+ return () => {
143
+ sub.remove();
144
+ c.destroy();
145
+ setCreated((cur) => cur === c ? null : cur);
146
+ };
147
+ }, [external, sdkKey, apiUrl, environment]);
148
+ const client = external ?? created;
149
+ const attributes = options.attributes ?? {};
150
+ const attrsKey = (0, import_flags.stableStringify)(attributes);
151
+ (0, import_react.useEffect)(() => {
152
+ if (client && options.attributes) client.setAttributes(options.attributes);
153
+ }, [client, attrsKey]);
154
+ const bootstrap = useBootstrap(payload, attributes);
155
+ const value = (0, import_react.useMemo)(() => ({ client, bootstrap }), [client, bootstrap]);
156
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FlagsContext.Provider, { value, children });
157
+ }
56
158
 
57
159
  // ../shared/dist/chunk-OLWGMDRX.js
58
160
  function fnv1a(input) {
@@ -882,6 +984,9 @@ function isWithinSchedule(guide, now = Date.now()) {
882
984
  return true;
883
985
  }
884
986
 
987
+ // src/core/client.ts
988
+ var import_flags2 = require("@guidelytech/flags");
989
+
885
990
  // src/core/targets.ts
886
991
  var TargetTimeoutError = class extends Error {
887
992
  constructor(ids) {
@@ -1777,7 +1882,7 @@ var PersistentStore = class {
1777
1882
 
1778
1883
  // src/core/client.ts
1779
1884
  var SDK_NAME = "@guidelytech/react-native";
1780
- var SDK_VERSION = "0.6.1";
1885
+ var SDK_VERSION = "0.7.0";
1781
1886
  var DEFAULT_API_URL = "https://guidelytech.com";
1782
1887
  var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
1783
1888
  var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
@@ -1824,6 +1929,10 @@ var GuidelyClient = class {
1824
1929
  __publicField(this, "messenger", null);
1825
1930
  __publicField(this, "ui", { steps: [], checklists: [], survey: null, resourceCenterOpen: false, theme: {}, messenger: false });
1826
1931
  __publicField(this, "uiListeners", /* @__PURE__ */ new Set());
1932
+ /** Feature flags for this environment: `guidely.flags.isEnabled("new-checkout")`. */
1933
+ __publicField(this, "flags");
1934
+ /** Experiments already reported this session (`experiment_viewed` is sent once per session). */
1935
+ __publicField(this, "exposures", /* @__PURE__ */ new Set());
1827
1936
  __publicField(this, "customListeners", /* @__PURE__ */ new Map());
1828
1937
  __publicField(this, "eventBus", {
1829
1938
  on: (name, cb) => {
@@ -1852,6 +1961,40 @@ var GuidelyClient = class {
1852
1961
  this.store = new PersistentStore(options.storage ?? null, (err) => this.log("storage error", err));
1853
1962
  if (!options.storage) this.log("no `storage` passed: identity and guide history last only for this app session. Pass AsyncStorage to persist them.");
1854
1963
  this.cleanups.push(this.targets.onTap((id) => this.handleTap(id)));
1964
+ if (options.user) this.user = { ...options.user };
1965
+ this.flags = this.createFlags(options.flags);
1966
+ }
1967
+ createFlags(opts) {
1968
+ const base = { sdkKey: this.options.projectId, apiUrl: this.options.apiUrl, fetch: this.options.fetch, sdk: { name: SDK_NAME, version: SDK_VERSION } };
1969
+ if (opts === false) return (0, import_flags2.createFlagsClient)({ ...base, overrideBehaviour: "localOnly", offline: true, reportUsage: false, trackExposures: false, storage: false, logger: null });
1970
+ const o = opts ?? {};
1971
+ return (0, import_flags2.createFlagsClient)({
1972
+ ...base,
1973
+ storage: this.options.storage ?? false,
1974
+ reportUsage: this.options.analytics,
1975
+ // Failures surface through onError (and `debug` logs); development warnings (unknown flag keys) reach the console.
1976
+ logger: { warn: (...a) => console.warn("[Guidely]", ...a), error: (...a) => this.log(...a) },
1977
+ ...o,
1978
+ contextAttributes: this.flagContext(),
1979
+ // Exposures travel through the event pipeline (with the session) as `experiment_viewed`.
1980
+ trackExposures: false,
1981
+ onFlagEvaluated: (result) => {
1982
+ this.trackExposure(result);
1983
+ o.onFlagEvaluated?.(result);
1984
+ },
1985
+ onReady: () => {
1986
+ this.emitter.emit("flags:ready");
1987
+ o.onReady?.();
1988
+ },
1989
+ onChange: (data) => {
1990
+ this.emitter.emit("flags:change");
1991
+ o.onChange?.(data);
1992
+ },
1993
+ onError: (err) => {
1994
+ this.reportError(err);
1995
+ o.onError?.(err);
1996
+ }
1997
+ });
1855
1998
  }
1856
1999
  // ---------------------------------------------------------------------------
1857
2000
  // Lifecycle
@@ -1882,6 +2025,7 @@ var GuidelyClient = class {
1882
2025
  this.session = saved?.id && !this.sessionExpired(Date.parse(saved.lastActiveAt)) ? saved : newSession();
1883
2026
  this.persistSession();
1884
2027
  this.lastAudiences = new Set(this.state.audienceIds);
2028
+ this.syncFlagAttributes();
1885
2029
  this.queue = new EventQueue({
1886
2030
  store: this.store,
1887
2031
  storageKey: KEYS.queue(key),
@@ -1971,6 +2115,7 @@ var GuidelyClient = class {
1971
2115
  this.persistSession();
1972
2116
  void this.queue?.flush();
1973
2117
  void this.store.flush();
2118
+ void this.flags.flush();
1974
2119
  this.messenger?.setForeground(false);
1975
2120
  } else if (state === "active" && this.backgroundedAt !== null) {
1976
2121
  this.messenger?.setForeground(true);
@@ -1981,6 +2126,7 @@ var GuidelyClient = class {
1981
2126
  this.persistSession();
1982
2127
  this.firedDelays.clear();
1983
2128
  }
2129
+ void this.flags.refresh();
1984
2130
  if (Date.now() - this.configFetchedAt > this.options.configRefreshMs) void this.refresh();
1985
2131
  else if (this.isReady) this.evaluate("ready");
1986
2132
  }
@@ -2007,6 +2153,7 @@ var GuidelyClient = class {
2007
2153
  this.cleanups.splice(0).forEach((fn) => fn());
2008
2154
  void this.queue?.flush();
2009
2155
  this.queue?.destroy();
2156
+ this.flags.destroy();
2010
2157
  this.emitter.clear();
2011
2158
  this.uiListeners.clear();
2012
2159
  }
@@ -2021,6 +2168,7 @@ var GuidelyClient = class {
2021
2168
  const same = this.user?.id === id;
2022
2169
  this.user = { id, traits: { ...same ? this.user?.traits : {}, ...traits || {} }, account: account ?? (same ? this.user?.account : void 0) };
2023
2170
  writeJson(this.store, KEYS.user(this.options.projectId), this.user);
2171
+ this.syncFlagAttributes();
2024
2172
  this.enqueue("identify", { properties: { traits: sanitizeProperties(this.user.traits) } });
2025
2173
  void this.syncIdentify().then(() => {
2026
2174
  this.emitter.emit("identify", { userId: id, anonymousId: this.anonymousId });
@@ -2064,6 +2212,7 @@ var GuidelyClient = class {
2064
2212
  };
2065
2213
  if (data.firstSeenAt && Date.now() - Date.parse(data.firstSeenAt) > 6e4) this.firstVisit = false;
2066
2214
  this.persistState();
2215
+ this.syncFlagAttributes();
2067
2216
  this.notify();
2068
2217
  }
2069
2218
  /** Sets or replaces the account (company / organisation) for the current user. */
@@ -2072,6 +2221,7 @@ var GuidelyClient = class {
2072
2221
  if (!this.user?.id) {
2073
2222
  this.user = { id: "", traits: this.user?.traits, account };
2074
2223
  writeJson(this.store, KEYS.user(this.options.projectId), this.user);
2224
+ this.syncFlagAttributes();
2075
2225
  return;
2076
2226
  }
2077
2227
  await this.identify(this.user.id, void 0, account);
@@ -2081,6 +2231,7 @@ var GuidelyClient = class {
2081
2231
  await this.init();
2082
2232
  if (!this.user?.id) {
2083
2233
  this.user = { id: "", traits: { ...this.user?.traits || {}, ...traits }, account: this.user?.account };
2234
+ this.syncFlagAttributes();
2084
2235
  return;
2085
2236
  }
2086
2237
  await this.identify(this.user.id, traits, this.user.account);
@@ -2108,6 +2259,8 @@ var GuidelyClient = class {
2108
2259
  this.lastAudiences = /* @__PURE__ */ new Set();
2109
2260
  this.firedDelays.clear();
2110
2261
  this.firstVisit = true;
2262
+ this.exposures.clear();
2263
+ this.syncFlagAttributes();
2111
2264
  this.emitter.emit("identify", { userId: null, anonymousId: this.anonymousId });
2112
2265
  this.notify();
2113
2266
  if (this.isReady) this.evaluate("identify");
@@ -2181,6 +2334,7 @@ var GuidelyClient = class {
2181
2334
  const changed = name !== this.screenName;
2182
2335
  this.screenName = name;
2183
2336
  this.targets.setScreen(name);
2337
+ if (changed) this.syncFlagAttributes();
2184
2338
  const url = this.currentUrl();
2185
2339
  if (changed) this.enqueue("page_view", { properties: { ...properties, path: url, screen: name } });
2186
2340
  this.completeChecklistTasksBy("url_visited", url);
@@ -2851,6 +3005,58 @@ var GuidelyClient = class {
2851
3005
  showNewMessage(text) {
2852
3006
  this.whenHydrated(() => this.messenger?.showNewMessage(text));
2853
3007
  }
3008
+ // ---------------------------------------------------------------------------
3009
+ // Feature flags
3010
+ // ---------------------------------------------------------------------------
3011
+ /** Boolean flag value for the current user; `fallback` (default `false`) until loaded or when missing. */
3012
+ isFeatureEnabled(key, fallback = false) {
3013
+ return this.flags.isEnabled(key, fallback);
3014
+ }
3015
+ /** Flag value for the current user; `fallback` when missing or of a different type. */
3016
+ getFeatureValue(key, fallback) {
3017
+ return this.flags.getValue(key, fallback);
3018
+ }
3019
+ /** Flag value plus why it was served (reason, rule, variation, experiment). */
3020
+ getFeatureDetails(key, fallback) {
3021
+ return this.flags.getDetails(key, fallback);
3022
+ }
3023
+ /** Calls `cb` whenever flag values may have changed. Returns an unsubscribe function. */
3024
+ onFlagsChange(cb) {
3025
+ return this.flags.subscribe(cb);
3026
+ }
3027
+ /** Attributes flags are evaluated with: identity, traits, account, audiences, screen and device. */
3028
+ flagContext() {
3029
+ const d = this.bridge.device;
3030
+ const account = this.user?.account;
3031
+ return {
3032
+ platform: d.platform,
3033
+ deviceType: d.deviceType,
3034
+ locale: d.locale,
3035
+ appVersion: this.options.appVersion ?? d.appVersion,
3036
+ environment: this.options.environment,
3037
+ ...this.screenName ? { screen: this.screenName, path: this.currentUrl() } : {},
3038
+ ...this.user?.traits ?? {},
3039
+ id: this.user?.id || this.anonymousId || void 0,
3040
+ anonymousId: this.anonymousId || void 0,
3041
+ account: account ? { ...account.traits ?? {}, id: account.id } : void 0,
3042
+ $audiences: [...this.state.audienceIds]
3043
+ };
3044
+ }
3045
+ syncFlagAttributes() {
3046
+ if (!this.destroyed) this.flags?.setContextAttributes(this.flagContext());
3047
+ }
3048
+ /** `experiment_viewed`, once per session and assignment, through the event queue (consent applies). */
3049
+ trackExposure(result) {
3050
+ const exp = result.inExperiment ? result.experiment : void 0;
3051
+ if (!exp || this.destroyed) return;
3052
+ this.whenHydrated(() => {
3053
+ const key = `${this.session.id}|${exp.key}|${exp.hashValue}|${exp.variationId}`;
3054
+ if (this.exposures.has(key)) return;
3055
+ if (this.exposures.size > 500) this.exposures.clear();
3056
+ this.exposures.add(key);
3057
+ this.enqueue("experiment_viewed", { properties: { experimentKey: exp.key, variationId: exp.variationId, flagKey: result.key, hashAttribute: exp.hashAttribute, ...exp.variationName ? { variationName: exp.variationName } : {} } });
3058
+ });
3059
+ }
2854
3060
  on(event, cb) {
2855
3061
  return this.emitter.on(event, cb);
2856
3062
  }
@@ -2879,7 +3085,8 @@ var GuidelyClient = class {
2879
3085
  audienceIds: this.state.audienceIds,
2880
3086
  session: this.session,
2881
3087
  pendingEvents: this.queue?.size ?? 0,
2882
- targets: this.targets.ids()
3088
+ targets: this.targets.ids(),
3089
+ flagsReady: this.flags.isReady()
2883
3090
  };
2884
3091
  }
2885
3092
  // ---------------------------------------------------------------------------
@@ -2910,20 +3117,20 @@ function forPlatform(config, platform) {
2910
3117
  }
2911
3118
 
2912
3119
  // src/ui/Overlay.tsx
2913
- var import_react6 = require("react");
2914
- var import_react_native9 = require("react-native");
3120
+ var import_react7 = require("react");
3121
+ var import_react_native10 = require("react-native");
2915
3122
 
2916
3123
  // src/ui/Checklist.tsx
2917
- var import_react_native3 = require("react-native");
3124
+ var import_react_native4 = require("react-native");
2918
3125
 
2919
3126
  // src/ui/primitives.tsx
2920
- var import_react_native = require("react-native");
2921
- var import_jsx_runtime = require("react/jsx-runtime");
3127
+ var import_react_native2 = require("react-native");
3128
+ var import_jsx_runtime2 = require("react/jsx-runtime");
2922
3129
  function GButton({ label, variant = "primary", onPress, palette: p, style, accessibilityHint, disabled }) {
2923
3130
  const filled = variant === "primary";
2924
3131
  const outlined = variant === "secondary";
2925
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2926
- import_react_native.Pressable,
3132
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3133
+ import_react_native2.Pressable,
2927
3134
  {
2928
3135
  accessibilityRole: "button",
2929
3136
  accessibilityLabel: label,
@@ -2940,17 +3147,17 @@ function GButton({ label, variant = "primary", onPress, palette: p, style, acces
2940
3147
  variant === "link" && styles.link,
2941
3148
  style
2942
3149
  ],
2943
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.Text, { style: [styles.btnText, { color: filled ? p.onPrimary : variant === "link" ? p.primary : variant === "ghost" ? p.muted : p.text, fontFamily: p.fontFamily }, variant === "link" && styles.underline], numberOfLines: 1, children: label })
3150
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [styles.btnText, { color: filled ? p.onPrimary : variant === "link" ? p.primary : variant === "ghost" ? p.muted : p.text, fontFamily: p.fontFamily }, variant === "link" && styles.underline], numberOfLines: 1, children: label })
2944
3151
  }
2945
3152
  );
2946
3153
  }
2947
3154
  function CloseButton({ onPress, palette: p, label = "Close" }) {
2948
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.Pressable, { accessibilityRole: "button", accessibilityLabel: label, onPress, hitSlop: 10, style: ({ pressed }) => [styles.close, { opacity: pressed ? 0.6 : 1 }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.Text, { style: [styles.closeText, { color: p.muted }], children: "\xD7" }) });
3155
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Pressable, { accessibilityRole: "button", accessibilityLabel: label, onPress, hitSlop: 10, style: ({ pressed }) => [styles.close, { opacity: pressed ? 0.6 : 1 }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [styles.closeText, { color: p.muted }], children: "\xD7" }) });
2949
3156
  }
2950
3157
  function Surface({ palette: p, children, style }) {
2951
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { style: [styles.surface, { backgroundColor: p.background, borderRadius: p.radius, borderColor: p.border }, style], children });
3158
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [styles.surface, { backgroundColor: p.background, borderRadius: p.radius, borderColor: p.border }, style], children });
2952
3159
  }
2953
- var styles = import_react_native.StyleSheet.create({
3160
+ var styles = import_react_native2.StyleSheet.create({
2954
3161
  btn: { minHeight: 36, paddingHorizontal: 14, paddingVertical: 8, alignItems: "center", justifyContent: "center" },
2955
3162
  link: { paddingHorizontal: 4 },
2956
3163
  btnText: { fontSize: 14, fontWeight: "600" },
@@ -2958,7 +3165,7 @@ var styles = import_react_native.StyleSheet.create({
2958
3165
  close: { width: 28, height: 28, alignItems: "center", justifyContent: "center", marginTop: -4, marginRight: -6 },
2959
3166
  closeText: { fontSize: 24, lineHeight: 26, fontWeight: "400" },
2960
3167
  surface: {
2961
- borderWidth: import_react_native.StyleSheet.hairlineWidth,
3168
+ borderWidth: import_react_native2.StyleSheet.hairlineWidth,
2962
3169
  shadowColor: "#000",
2963
3170
  shadowOpacity: 0.18,
2964
3171
  shadowRadius: 18,
@@ -2968,8 +3175,8 @@ var styles = import_react_native.StyleSheet.create({
2968
3175
  });
2969
3176
 
2970
3177
  // src/ui/RichText.tsx
2971
- var import_react = require("react");
2972
- var import_react_native2 = require("react-native");
3178
+ var import_react2 = require("react");
3179
+ var import_react_native3 = require("react-native");
2973
3180
 
2974
3181
  // src/core/html.ts
2975
3182
  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" };
@@ -3195,8 +3402,8 @@ function htmlToText(html) {
3195
3402
  }
3196
3403
 
3197
3404
  // src/ui/RichText.tsx
3198
- var import_jsx_runtime2 = require("react/jsx-runtime");
3199
- var MONO = import_react_native2.Platform.select({ ios: "Menlo", android: "monospace", default: "monospace" });
3405
+ var import_jsx_runtime3 = require("react/jsx-runtime");
3406
+ var MONO = import_react_native3.Platform.select({ ios: "Menlo", android: "monospace", default: "monospace" });
3200
3407
  var HEADING_SIZE = [0, 20, 17, 15];
3201
3408
  function runStyle(run, p) {
3202
3409
  return {
@@ -3209,90 +3416,90 @@ function runStyle(run, p) {
3209
3416
  };
3210
3417
  }
3211
3418
  function BodyImage({ block, palette: p }) {
3212
- const [ratio, setRatio] = (0, import_react.useState)(block.width && block.height ? block.width / block.height : 16 / 9);
3213
- (0, import_react.useEffect)(() => {
3419
+ const [ratio, setRatio] = (0, import_react2.useState)(block.width && block.height ? block.width / block.height : 16 / 9);
3420
+ (0, import_react2.useEffect)(() => {
3214
3421
  if (block.width && block.height) return;
3215
3422
  let live = true;
3216
- import_react_native2.Image.getSize(block.src, (w, h) => live && w > 0 && h > 0 && setRatio(w / h), () => {
3423
+ import_react_native3.Image.getSize(block.src, (w, h) => live && w > 0 && h > 0 && setRatio(w / h), () => {
3217
3424
  });
3218
3425
  return () => {
3219
3426
  live = false;
3220
3427
  };
3221
3428
  }, [block.src, block.width, block.height]);
3222
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Image, { source: { uri: block.src }, accessible: true, accessibilityRole: "image", accessibilityLabel: block.alt || void 0, resizeMode: "contain", style: [styles2.image, { aspectRatio: ratio, backgroundColor: p.surface }] });
3429
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Image, { source: { uri: block.src }, accessible: true, accessibilityRole: "image", accessibilityLabel: block.alt || void 0, resizeMode: "contain", style: [styles2.image, { aspectRatio: ratio, backgroundColor: p.surface }] });
3223
3430
  }
3224
3431
  function BodyVideo({ block, palette: p, onLink }) {
3225
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.Pressable, { 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: [
3226
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [styles2.play, { color: p.primary }], children: "\u25B6" }),
3227
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: { color: p.primary, fontFamily: p.fontFamily, fontWeight: "600" }, children: block.alt })
3432
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.Pressable, { 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: [
3433
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [styles2.play, { color: p.primary }], children: "\u25B6" }),
3434
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: { color: p.primary, fontFamily: p.fontFamily, fontWeight: "600" }, children: block.alt })
3228
3435
  ] });
3229
3436
  }
3230
3437
  var PAGE_RATIO = 1 / 1.414;
3231
3438
  var pageRatio = (p) => p.width && p.height ? p.width / p.height : PAGE_RATIO;
3232
3439
  function PdfBadge() {
3233
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.badge, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles2.badgeText, children: "PDF" }) });
3440
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: styles2.badge, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles2.badgeText, children: "PDF" }) });
3234
3441
  }
3235
3442
  function BodyFile({ block, palette: p, onLink }) {
3236
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.Pressable, { 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: [
3237
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PdfBadge, {}),
3238
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { numberOfLines: 2, ellipsizeMode: "middle", style: [styles2.flex, { color: p.text, fontFamily: p.fontFamily, fontWeight: "600" }], children: block.alt })
3443
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.Pressable, { 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: [
3444
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PdfBadge, {}),
3445
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { numberOfLines: 2, ellipsizeMode: "middle", style: [styles2.flex, { color: p.text, fontFamily: p.fontFamily, fontWeight: "600" }], children: block.alt })
3239
3446
  ] });
3240
3447
  }
3241
3448
  function BodyPdf({ block, palette: p, onLink }) {
3242
3449
  const pages = block.pages ?? [];
3243
- const [width, setWidth] = (0, import_react.useState)(0);
3244
- const [index, setIndex] = (0, import_react.useState)(0);
3245
- const [reading, setReading] = (0, import_react.useState)(false);
3450
+ const [width, setWidth] = (0, import_react2.useState)(0);
3451
+ const [index, setIndex] = (0, import_react2.useState)(0);
3452
+ const [reading, setReading] = (0, import_react2.useState)(false);
3246
3453
  const height = width ? Math.max(...pages.map((pg) => width / pageRatio(pg))) : 0;
3247
3454
  const onScroll = (e) => width && setIndex(Math.round(e.nativeEvent.contentOffset.x / width));
3248
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: [styles2.pdf, { borderColor: p.border, backgroundColor: p.surface }], children: [
3249
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.Pressable, { accessibilityRole: "button", accessibilityLabel: `Read ${block.alt} full screen`, onPress: () => setReading(true), style: ({ pressed }) => [styles2.pdfHeader, { opacity: pressed ? 0.7 : 1 }], children: [
3250
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PdfBadge, {}),
3251
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { numberOfLines: 1, ellipsizeMode: "middle", style: [styles2.flex, { color: p.text, fontFamily: p.fontFamily, fontWeight: "600" }], children: block.alt }),
3252
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: { color: p.muted, fontSize: 16 }, children: "\u2922" })
3455
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: [styles2.pdf, { borderColor: p.border, backgroundColor: p.surface }], children: [
3456
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.Pressable, { accessibilityRole: "button", accessibilityLabel: `Read ${block.alt} full screen`, onPress: () => setReading(true), style: ({ pressed }) => [styles2.pdfHeader, { opacity: pressed ? 0.7 : 1 }], children: [
3457
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PdfBadge, {}),
3458
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { numberOfLines: 1, ellipsizeMode: "middle", style: [styles2.flex, { color: p.text, fontFamily: p.fontFamily, fontWeight: "600" }], children: block.alt }),
3459
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: { color: p.muted, fontSize: 16 }, children: "\u2922" })
3253
3460
  ] }),
3254
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.pdfPages, onLayout: (e) => setWidth(e.nativeEvent.layout.width), children: [
3255
- width > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.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__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Pressable, { 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__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Image, { source: { uri: pg.src }, resizeMode: "contain", style: [styles2.page, { width: Math.min(width, height * pageRatio(pg)), aspectRatio: pageRatio(pg) }] }) }, i)) }),
3256
- pages.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { pointerEvents: "none", style: styles2.count, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.Text, { style: styles2.countText, children: [
3461
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles2.pdfPages, onLayout: (e) => setWidth(e.nativeEvent.layout.width), children: [
3462
+ width > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.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__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Pressable, { 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__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Image, { source: { uri: pg.src }, resizeMode: "contain", style: [styles2.page, { width: Math.min(width, height * pageRatio(pg)), aspectRatio: pageRatio(pg) }] }) }, i)) }),
3463
+ pages.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { pointerEvents: "none", style: styles2.count, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.Text, { style: styles2.countText, children: [
3257
3464
  index + 1,
3258
3465
  " / ",
3259
3466
  pages.length
3260
3467
  ] }) })
3261
3468
  ] }),
3262
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Modal, { visible: reading, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: () => setReading(false), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.reader, children: [
3263
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.readerBar, children: [
3264
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { numberOfLines: 1, ellipsizeMode: "middle", style: [styles2.flex, styles2.readerTitle], accessibilityRole: "header", children: block.alt }),
3265
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Pressable, { accessibilityRole: "link", onPress: () => onLink(block.href), style: styles2.readerBtn, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles2.readerBtnText, children: "Open PDF \u2197" }) }),
3266
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Pressable, { accessibilityRole: "button", accessibilityLabel: "Close", onPress: () => setReading(false), style: styles2.readerBtn, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles2.readerBtnText, children: "Done" }) })
3469
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Modal, { visible: reading, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: () => setReading(false), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles2.reader, children: [
3470
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles2.readerBar, children: [
3471
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { numberOfLines: 1, ellipsizeMode: "middle", style: [styles2.flex, styles2.readerTitle], accessibilityRole: "header", children: block.alt }),
3472
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Pressable, { accessibilityRole: "link", onPress: () => onLink(block.href), style: styles2.readerBtn, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles2.readerBtnText, children: "Open PDF \u2197" }) }),
3473
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Pressable, { accessibilityRole: "button", accessibilityLabel: "Close", onPress: () => setReading(false), style: styles2.readerBtn, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles2.readerBtnText, children: "Done" }) })
3267
3474
  ] }),
3268
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.ScrollView, { contentContainerStyle: styles2.readerPages, children: pages.map((pg, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Image, { source: { uri: pg.src }, accessible: true, accessibilityLabel: pg.alt || `Page ${i + 1}`, resizeMode: "contain", style: [styles2.readerPage, { aspectRatio: pageRatio(pg) }] }, i)) })
3475
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.ScrollView, { contentContainerStyle: styles2.readerPages, children: pages.map((pg, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Image, { source: { uri: pg.src }, accessible: true, accessibilityLabel: pg.alt || `Page ${i + 1}`, resizeMode: "contain", style: [styles2.readerPage, { aspectRatio: pageRatio(pg) }] }, i)) })
3269
3476
  ] }) })
3270
3477
  ] });
3271
3478
  }
3272
3479
  function RichText({ html, palette: p, onLink, size = 15 }) {
3273
- const blocks = (0, import_react.useMemo)(() => parseHtml(html), [html]);
3480
+ const blocks = (0, import_react2.useMemo)(() => parseHtml(html), [html]);
3274
3481
  if (!blocks.length) return null;
3275
3482
  const base = { color: p.text, fontSize: size, lineHeight: Math.round(size * 1.42), fontFamily: p.fontFamily };
3276
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.wrap, children: blocks.map((b, i) => {
3277
- if (b.kind === "image") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BodyImage, { block: b, palette: p }, i);
3278
- if (b.kind === "video") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BodyVideo, { block: b, palette: p, onLink }, i);
3279
- if (b.kind === "pdf") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BodyPdf, { block: b, palette: p, onLink }, i);
3280
- if (b.kind === "file") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BodyFile, { block: b, palette: p, onLink }, i);
3281
- const runs = b.runs.map((r, j) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: runStyle(r, p), onPress: r.href ? () => onLink(r.href) : void 0, accessibilityRole: r.href ? "link" : void 0, children: r.text }, j));
3483
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: styles2.wrap, children: blocks.map((b, i) => {
3484
+ if (b.kind === "image") return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BodyImage, { block: b, palette: p }, i);
3485
+ if (b.kind === "video") return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BodyVideo, { block: b, palette: p, onLink }, i);
3486
+ if (b.kind === "pdf") return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BodyPdf, { block: b, palette: p, onLink }, i);
3487
+ if (b.kind === "file") return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BodyFile, { block: b, palette: p, onLink }, i);
3488
+ const runs = b.runs.map((r, j) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: runStyle(r, p), onPress: r.href ? () => onLink(r.href) : void 0, accessibilityRole: r.href ? "link" : void 0, children: r.text }, j));
3282
3489
  if (b.kind === "list-item") {
3283
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: [styles2.li, { paddingLeft: (b.level - 1) * 14 }], children: [
3284
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [base, styles2.marker], children: b.marker }),
3285
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [base, styles2.flex], children: runs })
3490
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: [styles2.li, { paddingLeft: (b.level - 1) * 14 }], children: [
3491
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [base, styles2.marker], children: b.marker }),
3492
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [base, styles2.flex], children: runs })
3286
3493
  ] }, i);
3287
3494
  }
3288
3495
  if (b.kind === "heading") {
3289
3496
  const fs = HEADING_SIZE[b.level] ?? 15;
3290
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { accessibilityRole: "header", style: [base, { fontSize: fs, lineHeight: Math.round(fs * 1.3), fontWeight: "700" }], children: runs }, i);
3497
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { accessibilityRole: "header", style: [base, { fontSize: fs, lineHeight: Math.round(fs * 1.3), fontWeight: "700" }], children: runs }, i);
3291
3498
  }
3292
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [base, b.kind === "quote" && { borderLeftWidth: 3, borderLeftColor: p.border, paddingLeft: 8, color: p.muted }], children: runs }, i);
3499
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [base, b.kind === "quote" && { borderLeftWidth: 3, borderLeftColor: p.border, paddingLeft: 8, color: p.muted }], children: runs }, i);
3293
3500
  }) });
3294
3501
  }
3295
- var styles2 = import_react_native2.StyleSheet.create({
3502
+ var styles2 = import_react_native3.StyleSheet.create({
3296
3503
  wrap: { gap: 6 },
3297
3504
  li: { flexDirection: "row", gap: 6 },
3298
3505
  marker: { minWidth: 14 },
@@ -3309,7 +3516,7 @@ var styles2 = import_react_native2.StyleSheet.create({
3309
3516
  count: { position: "absolute", right: 8, bottom: 8, backgroundColor: "rgba(17,17,17,0.72)", borderRadius: 999, paddingHorizontal: 8, paddingVertical: 2 },
3310
3517
  countText: { color: "#fff", fontSize: 11, fontWeight: "500", fontVariant: ["tabular-nums"] },
3311
3518
  reader: { flex: 1, backgroundColor: "#0b0b0d" },
3312
- readerBar: { flexDirection: "row", alignItems: "center", gap: 8, paddingTop: import_react_native2.Platform.OS === "ios" ? 52 : 12, paddingBottom: 10, paddingHorizontal: 16 },
3519
+ readerBar: { flexDirection: "row", alignItems: "center", gap: 8, paddingTop: import_react_native3.Platform.OS === "ios" ? 52 : 12, paddingBottom: 10, paddingHorizontal: 16 },
3313
3520
  readerTitle: { color: "#fff", fontSize: 16, fontWeight: "600" },
3314
3521
  readerBtn: { minHeight: 36, justifyContent: "center", paddingHorizontal: 12, borderRadius: 8, backgroundColor: "rgba(255,255,255,0.12)" },
3315
3522
  readerBtnText: { color: "#fff", fontWeight: "600" },
@@ -3318,7 +3525,7 @@ var styles2 = import_react_native2.StyleSheet.create({
3318
3525
  });
3319
3526
 
3320
3527
  // src/ui/Checklist.tsx
3321
- var import_jsx_runtime3 = require("react/jsx-runtime");
3528
+ var import_jsx_runtime4 = require("react/jsx-runtime");
3322
3529
  function Checklist({ view, client, palette: p, geometry, showLauncher, launcherBottom }) {
3323
3530
  const config = view.guide.config.checklist;
3324
3531
  if (!config) return null;
@@ -3332,27 +3539,27 @@ function Checklist({ view, client, palette: p, geometry, showLauncher, launcherB
3332
3539
  const width = Math.min(viewport.width - insets.left - insets.right - 32, 360);
3333
3540
  const nextOpen = config.ordered ? config.tasks.find((t) => !done.has(t.id))?.id : void 0;
3334
3541
  const bottom = insets.bottom + launcherBottom;
3335
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { pointerEvents: "box-none", style: import_react_native3.StyleSheet.absoluteFill, children: [
3336
- view.open ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Surface, { palette: p, style: [styles3.panel, side, { width, bottom: bottom + (showLauncher ? 56 : 0), maxHeight: viewport.height - insets.top - bottom - 80 }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { accessibilityViewIsModal: true, style: styles3.panelInner, children: [
3337
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.head, children: [
3338
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.flex, children: [
3339
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { accessibilityRole: "header", style: [styles3.title, { color: p.text, fontFamily: p.fontFamily }], children: finished ? config.completionTitle : config.launcherLabel }),
3340
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.Text, { style: [styles3.sub, { color: p.muted }], children: [
3542
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { pointerEvents: "box-none", style: import_react_native4.StyleSheet.absoluteFill, children: [
3543
+ view.open ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Surface, { palette: p, style: [styles3.panel, side, { width, bottom: bottom + (showLauncher ? 56 : 0), maxHeight: viewport.height - insets.top - bottom - 80 }], children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { accessibilityViewIsModal: true, style: styles3.panelInner, children: [
3544
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles3.head, children: [
3545
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles3.flex, children: [
3546
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { accessibilityRole: "header", style: [styles3.title, { color: p.text, fontFamily: p.fontFamily }], children: finished ? config.completionTitle : config.launcherLabel }),
3547
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.Text, { style: [styles3.sub, { color: p.muted }], children: [
3341
3548
  completed,
3342
3549
  " of ",
3343
3550
  total,
3344
3551
  " complete"
3345
3552
  ] })
3346
3553
  ] }),
3347
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseButton, { palette: p, onPress: () => client.setChecklistOpen(view.guide.id, false), label: "Close checklist" })
3554
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CloseButton, { palette: p, onPress: () => client.setChecklistOpen(view.guide.id, false), label: "Close checklist" })
3348
3555
  ] }),
3349
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [styles3.bar, { backgroundColor: p.surface }], accessibilityRole: "progressbar", accessibilityValue: { min: 0, max: total, now: completed }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [styles3.fill, { backgroundColor: p.primary, width: `${total ? completed / total * 100 : 0}%` }] }) }),
3350
- finished && config.completionBody ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RichText, { html: config.completionBody, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null,
3351
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.ScrollView, { style: styles3.tasks, contentContainerStyle: styles3.tasksContent, children: config.tasks.map((task) => {
3556
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: [styles3.bar, { backgroundColor: p.surface }], accessibilityRole: "progressbar", accessibilityValue: { min: 0, max: total, now: completed }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: [styles3.fill, { backgroundColor: p.primary, width: `${total ? completed / total * 100 : 0}%` }] }) }),
3557
+ finished && config.completionBody ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RichText, { html: config.completionBody, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null,
3558
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.ScrollView, { style: styles3.tasks, contentContainerStyle: styles3.tasksContent, children: config.tasks.map((task) => {
3352
3559
  const isDone = done.has(task.id);
3353
3560
  const locked = !!nextOpen && !isDone && task.id !== nextOpen;
3354
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3355
- import_react_native3.Pressable,
3561
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3562
+ import_react_native4.Pressable,
3356
3563
  {
3357
3564
  disabled: locked,
3358
3565
  accessibilityRole: "button",
@@ -3361,23 +3568,23 @@ function Checklist({ view, client, palette: p, geometry, showLauncher, launcherB
3361
3568
  onPress: () => client.checklistTaskPressed(view.guide.id, task.id),
3362
3569
  style: ({ pressed }) => [styles3.task, { borderColor: p.border, opacity: locked ? 0.5 : pressed ? 0.7 : 1 }],
3363
3570
  children: [
3364
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [styles3.check, isDone ? { backgroundColor: p.primary, borderColor: p.primary } : { borderColor: p.border }], children: isDone ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [styles3.tick, { color: p.onPrimary }], children: "\u2713" }) : null }),
3365
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.flex, children: [
3366
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.Text, { style: [styles3.taskTitle, { color: p.text, fontFamily: p.fontFamily }, isDone && { textDecorationLine: "line-through", color: p.muted }], children: [
3571
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: [styles3.check, isDone ? { backgroundColor: p.primary, borderColor: p.primary } : { borderColor: p.border }], children: isDone ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles3.tick, { color: p.onPrimary }], children: "\u2713" }) : null }),
3572
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles3.flex, children: [
3573
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.Text, { style: [styles3.taskTitle, { color: p.text, fontFamily: p.fontFamily }, isDone && { textDecorationLine: "line-through", color: p.muted }], children: [
3367
3574
  task.title,
3368
- !task.required ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: { color: p.muted, fontWeight: "400" }, children: " (optional)" }) : null
3575
+ !task.required ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: { color: p.muted, fontWeight: "400" }, children: " (optional)" }) : null
3369
3576
  ] }),
3370
- task.description ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [styles3.taskDesc, { color: p.muted }], children: task.description }) : null
3577
+ task.description ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles3.taskDesc, { color: p.muted }], children: task.description }) : null
3371
3578
  ] })
3372
3579
  ]
3373
3580
  },
3374
3581
  task.id
3375
3582
  );
3376
3583
  }) }),
3377
- config.dismissible ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(GButton, { label: finished ? "Close for good" : "Dismiss checklist", variant: "ghost", palette: p, onPress: () => client.dismissChecklist(view.guide.id), style: styles3.dismiss }) : null
3584
+ config.dismissible ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GButton, { label: finished ? "Close for good" : "Dismiss checklist", variant: "ghost", palette: p, onPress: () => client.dismissChecklist(view.guide.id), style: styles3.dismiss }) : null
3378
3585
  ] }) }) : null,
3379
- showLauncher ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3380
- import_react_native3.Pressable,
3586
+ showLauncher ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3587
+ import_react_native4.Pressable,
3381
3588
  {
3382
3589
  accessibilityRole: "button",
3383
3590
  accessibilityLabel: `${config.launcherLabel}, ${completed} of ${total} complete`,
@@ -3385,14 +3592,14 @@ function Checklist({ view, client, palette: p, geometry, showLauncher, launcherB
3385
3592
  onPress: () => client.setChecklistOpen(view.guide.id, !view.open),
3386
3593
  style: ({ pressed }) => [styles3.launcher, side, { bottom, backgroundColor: p.primary, opacity: pressed ? 0.85 : 1 }],
3387
3594
  children: [
3388
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [styles3.count, { borderColor: p.onPrimary }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [styles3.countText, { color: p.onPrimary }], children: finished ? "\u2713" : `${completed}/${total}` }) }),
3389
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [styles3.launcherText, { color: p.onPrimary, fontFamily: p.fontFamily }], numberOfLines: 1, children: config.launcherLabel })
3595
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: [styles3.count, { borderColor: p.onPrimary }], children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles3.countText, { color: p.onPrimary }], children: finished ? "\u2713" : `${completed}/${total}` }) }),
3596
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles3.launcherText, { color: p.onPrimary, fontFamily: p.fontFamily }], numberOfLines: 1, children: config.launcherLabel })
3390
3597
  ]
3391
3598
  }
3392
3599
  ) : null
3393
3600
  ] });
3394
3601
  }
3395
- var styles3 = import_react_native3.StyleSheet.create({
3602
+ var styles3 = import_react_native4.StyleSheet.create({
3396
3603
  flex: { flex: 1 },
3397
3604
  panel: { position: "absolute" },
3398
3605
  panelInner: { padding: 16, gap: 10, flexShrink: 1 },
@@ -3403,7 +3610,7 @@ var styles3 = import_react_native3.StyleSheet.create({
3403
3610
  fill: { height: 6, borderRadius: 3 },
3404
3611
  tasks: { flexGrow: 0 },
3405
3612
  tasksContent: { gap: 8 },
3406
- task: { flexDirection: "row", gap: 10, alignItems: "flex-start", borderWidth: import_react_native3.StyleSheet.hairlineWidth, borderRadius: 10, padding: 10 },
3613
+ task: { flexDirection: "row", gap: 10, alignItems: "flex-start", borderWidth: import_react_native4.StyleSheet.hairlineWidth, borderRadius: 10, padding: 10 },
3407
3614
  check: { width: 22, height: 22, borderRadius: 11, borderWidth: 2, alignItems: "center", justifyContent: "center", marginTop: 1 },
3408
3615
  tick: { fontSize: 13, fontWeight: "800" },
3409
3616
  taskTitle: { fontSize: 15, fontWeight: "600" },
@@ -3430,22 +3637,22 @@ var styles3 = import_react_native3.StyleSheet.create({
3430
3637
  });
3431
3638
 
3432
3639
  // src/ui/Messenger.tsx
3433
- var import_react2 = require("react");
3434
- var import_react_native4 = require("react-native");
3435
- var import_jsx_runtime4 = require("react/jsx-runtime");
3640
+ var import_react3 = require("react");
3641
+ var import_react_native5 = require("react-native");
3642
+ var import_jsx_runtime5 = require("react/jsx-runtime");
3436
3643
  function useMessenger(store) {
3437
- return (0, import_react2.useSyncExternalStore)(
3438
- (0, import_react2.useCallback)((cb) => store.subscribe(cb), [store]),
3644
+ return (0, import_react3.useSyncExternalStore)(
3645
+ (0, import_react3.useCallback)((cb) => store.subscribe(cb), [store]),
3439
3646
  () => store.getSnapshot(),
3440
3647
  () => store.getSnapshot()
3441
3648
  );
3442
3649
  }
3443
3650
  function useKeyboardHeight() {
3444
- const [h, setH] = (0, import_react2.useState)(0);
3445
- (0, import_react2.useEffect)(() => {
3446
- if (import_react_native4.Platform.OS !== "ios") return;
3447
- const show = import_react_native4.Keyboard.addListener("keyboardWillShow", (e) => setH(e.endCoordinates.height));
3448
- const hide = import_react_native4.Keyboard.addListener("keyboardWillHide", () => setH(0));
3651
+ const [h, setH] = (0, import_react3.useState)(0);
3652
+ (0, import_react3.useEffect)(() => {
3653
+ if (import_react_native5.Platform.OS !== "ios") return;
3654
+ const show = import_react_native5.Keyboard.addListener("keyboardWillShow", (e) => setH(e.endCoordinates.height));
3655
+ const hide = import_react_native5.Keyboard.addListener("keyboardWillHide", () => setH(0));
3449
3656
  return () => {
3450
3657
  show.remove();
3451
3658
  hide.remove();
@@ -3464,9 +3671,9 @@ function MessengerLauncher({ store, palette: p, style }) {
3464
3671
  const snap = useMessenger(store);
3465
3672
  if (snap.open) return null;
3466
3673
  const label = snap.unread ? `Open ${snap.config.name}, ${snap.unread} unread` : `Open ${snap.config.name}`;
3467
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.Pressable, { accessibilityRole: "button", accessibilityLabel: label, onPress: () => store.open(), style: ({ pressed }) => [styles4.launcher, style, { backgroundColor: p.primary, opacity: pressed ? 0.85 : 1 }], children: [
3468
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.launcherIcon, { color: p.onPrimary }], children: "\u{1F4AC}" }),
3469
- snap.unread ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: styles4.badge, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.badgeText, children: snap.unread }) }) : null
3674
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.Pressable, { accessibilityRole: "button", accessibilityLabel: label, onPress: () => store.open(), style: ({ pressed }) => [styles4.launcher, style, { backgroundColor: p.primary, opacity: pressed ? 0.85 : 1 }], children: [
3675
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.launcherIcon, { color: p.onPrimary }], children: "\u{1F4AC}" }),
3676
+ snap.unread ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { style: styles4.badge, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: styles4.badgeText, children: snap.unread }) }) : null
3470
3677
  ] });
3471
3678
  }
3472
3679
  function MessengerSheet({ store, client, palette: p, topInset, bottomInset }) {
@@ -3474,14 +3681,14 @@ function MessengerSheet({ store, client, palette: p, topInset, bottomInset }) {
3474
3681
  const keyboard = useKeyboardHeight();
3475
3682
  const v = snap.view;
3476
3683
  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";
3477
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Modal, { visible: snap.open, animationType: "slide", presentationStyle: import_react_native4.Platform.OS === "ios" ? "pageSheet" : "fullScreen", onRequestClose: () => v.kind === "tab" ? store.close() : store.back(), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: [styles4.sheet, { backgroundColor: p.background, paddingTop: import_react_native4.Platform.OS === "ios" ? 8 : topInset, paddingBottom: keyboard }], children: [
3478
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: [styles4.header, { borderColor: p.border }], children: [
3479
- v.kind !== "tab" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Pressable, { accessibilityRole: "button", accessibilityLabel: "Back", onPress: () => store.back(), hitSlop: 10, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.back, { color: p.primary }], children: "\u2039 Back" }) }) : null,
3480
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { accessibilityRole: "header", numberOfLines: 1, style: [styles4.title, { color: p.text, fontFamily: p.fontFamily }], children: title }),
3481
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CloseButton, { palette: p, onPress: () => store.close(), label: `Close ${snap.config.name}` })
3684
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Modal, { visible: snap.open, animationType: "slide", presentationStyle: import_react_native5.Platform.OS === "ios" ? "pageSheet" : "fullScreen", onRequestClose: () => v.kind === "tab" ? store.close() : store.back(), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles4.sheet, { backgroundColor: p.background, paddingTop: import_react_native5.Platform.OS === "ios" ? 8 : topInset, paddingBottom: keyboard }], children: [
3685
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles4.header, { borderColor: p.border }], children: [
3686
+ v.kind !== "tab" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Pressable, { accessibilityRole: "button", accessibilityLabel: "Back", onPress: () => store.back(), hitSlop: 10, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.back, { color: p.primary }], children: "\u2039 Back" }) }) : null,
3687
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { accessibilityRole: "header", numberOfLines: 1, style: [styles4.title, { color: p.text, fontFamily: p.fontFamily }], children: title }),
3688
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseButton, { palette: p, onPress: () => store.close(), label: `Close ${snap.config.name}` })
3482
3689
  ] }),
3483
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: styles4.flex, children: v.kind === "thread" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Thread, { store, palette: p, bottomInset: keyboard ? 0 : bottomInset }) : v.kind === "article" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Article, { article: snap.article, palette: p, client }) : v.tab === "home" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Home, { store, palette: p }) : v.tab === "messages" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Conversations, { store, palette: p }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Help, { store, client, palette: p }) }),
3484
- v.kind === "tab" && !keyboard ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tabs, { store, active: v.tab, palette: p, bottomInset }) : null
3690
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { style: styles4.flex, children: v.kind === "thread" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Thread, { store, palette: p, bottomInset: keyboard ? 0 : bottomInset }) : v.kind === "article" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Article, { article: snap.article, palette: p, client }) : v.tab === "home" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Home, { store, palette: p }) : v.tab === "messages" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Conversations, { store, palette: p }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Help, { store, client, palette: p }) }),
3691
+ v.kind === "tab" && !keyboard ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Tabs, { store, active: v.tab, palette: p, bottomInset }) : null
3485
3692
  ] }) });
3486
3693
  }
3487
3694
  function Tabs({ store, active, palette: p, bottomInset }) {
@@ -3491,33 +3698,33 @@ function Tabs({ store, active, palette: p, bottomInset }) {
3491
3698
  ["messages", "\u2709\uFE0E", "Messages"],
3492
3699
  ["help", "?", "Help"]
3493
3700
  ];
3494
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { accessibilityRole: "tablist", style: [styles4.tabs, { borderColor: p.border, paddingBottom: Math.max(8, bottomInset) }], children: tabs.map(([tab, icon, label]) => {
3701
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { accessibilityRole: "tablist", style: [styles4.tabs, { borderColor: p.border, paddingBottom: Math.max(8, bottomInset) }], children: tabs.map(([tab, icon, label]) => {
3495
3702
  const on = tab === active;
3496
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.Pressable, { accessibilityRole: "tab", accessibilityState: { selected: on }, accessibilityLabel: tab === "messages" && unread ? `${label}, ${unread} unread` : label, onPress: () => store.setTab(tab), style: styles4.tab, children: [
3497
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.tabIcon, { color: on ? p.primary : p.muted }], children: icon }),
3498
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.tabLabel, { color: on ? p.primary : p.muted }], children: label }),
3499
- tab === "messages" && unread ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: [styles4.dot, styles4.tabDot] }) : null
3703
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.Pressable, { accessibilityRole: "tab", accessibilityState: { selected: on }, accessibilityLabel: tab === "messages" && unread ? `${label}, ${unread} unread` : label, onPress: () => store.setTab(tab), style: styles4.tab, children: [
3704
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.tabIcon, { color: on ? p.primary : p.muted }], children: icon }),
3705
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.tabLabel, { color: on ? p.primary : p.muted }], children: label }),
3706
+ tab === "messages" && unread ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { style: [styles4.dot, styles4.tabDot] }) : null
3500
3707
  ] }, tab);
3501
3708
  }) });
3502
3709
  }
3503
3710
  function Home({ store, palette: p }) {
3504
3711
  const snap = useMessenger(store);
3505
3712
  const recent = snap.conversations[0];
3506
- const [q, setQ] = (0, import_react2.useState)("");
3507
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.ScrollView, { contentContainerStyle: styles4.content, keyboardShouldPersistTaps: "handled", children: [
3508
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: [styles4.hero, { backgroundColor: p.primary, borderRadius: p.radius }], children: [
3509
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.heroTitle, { color: p.onPrimary }], children: snap.config.greeting || `Hi! I'm ${snap.config.name}.` }),
3510
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { 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." })
3713
+ const [q, setQ] = (0, import_react3.useState)("");
3714
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.ScrollView, { contentContainerStyle: styles4.content, keyboardShouldPersistTaps: "handled", children: [
3715
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles4.hero, { backgroundColor: p.primary, borderRadius: p.radius }], children: [
3716
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.heroTitle, { color: p.onPrimary }], children: snap.config.greeting || `Hi! I'm ${snap.config.name}.` }),
3717
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { 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." })
3511
3718
  ] }),
3512
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GButton, { label: "Send us a message", palette: p, onPress: () => store.showNewMessage(), style: styles4.fullWidth }),
3513
- recent ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles4.section, children: [
3514
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.sectionTitle, { color: p.muted }], children: "RECENT" }),
3515
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ConversationRow, { c: recent, palette: p, onPress: () => void store.openConversation(recent.id) })
3719
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(GButton, { label: "Send us a message", palette: p, onPress: () => store.showNewMessage(), style: styles4.fullWidth }),
3720
+ recent ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: styles4.section, children: [
3721
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.sectionTitle, { color: p.muted }], children: "RECENT" }),
3722
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ConversationRow, { c: recent, palette: p, onPress: () => void store.openConversation(recent.id) })
3516
3723
  ] }) : null,
3517
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles4.section, children: [
3518
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.sectionTitle, { color: p.muted }], children: "SEARCH FOR HELP" }),
3519
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3520
- import_react_native4.TextInput,
3724
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: styles4.section, children: [
3725
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.sectionTitle, { color: p.muted }], children: "SEARCH FOR HELP" }),
3726
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3727
+ import_react_native5.TextInput,
3521
3728
  {
3522
3729
  value: q,
3523
3730
  onChangeText: setQ,
@@ -3537,37 +3744,37 @@ function Home({ store, palette: p }) {
3537
3744
  }
3538
3745
  var pendingSearch = { current: "" };
3539
3746
  function ConversationRow({ c, palette: p, onPress }) {
3540
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.Pressable, { accessibilityRole: "button", accessibilityLabel: `${c.subject || "Conversation"}${c.unread ? ", unread" : ""}`, onPress, style: ({ pressed }) => [styles4.row, { borderColor: p.border, opacity: pressed ? 0.7 : 1 }], children: [
3541
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles4.flex, children: [
3542
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { numberOfLines: 1, style: [styles4.rowTitle, { color: p.text }], children: c.subject || "Conversation" }),
3543
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { numberOfLines: 1, style: [styles4.rowSub, { color: p.muted }], children: c.lastMessagePreview || (c.status === "closed" ? "Closed" : " ") })
3747
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.Pressable, { accessibilityRole: "button", accessibilityLabel: `${c.subject || "Conversation"}${c.unread ? ", unread" : ""}`, onPress, style: ({ pressed }) => [styles4.row, { borderColor: p.border, opacity: pressed ? 0.7 : 1 }], children: [
3748
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: styles4.flex, children: [
3749
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { numberOfLines: 1, style: [styles4.rowTitle, { color: p.text }], children: c.subject || "Conversation" }),
3750
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { numberOfLines: 1, style: [styles4.rowSub, { color: p.muted }], children: c.lastMessagePreview || (c.status === "closed" ? "Closed" : " ") })
3544
3751
  ] }),
3545
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles4.rowEnd, children: [
3546
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.time, { color: p.muted }], children: ago(c.lastMessageAt) }),
3547
- c.unread ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: [styles4.dot, { backgroundColor: p.primary }] }) : null
3752
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: styles4.rowEnd, children: [
3753
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.time, { color: p.muted }], children: ago(c.lastMessageAt) }),
3754
+ c.unread ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { style: [styles4.dot, { backgroundColor: p.primary }] }) : null
3548
3755
  ] })
3549
3756
  ] });
3550
3757
  }
3551
3758
  function Conversations({ store, palette: p }) {
3552
3759
  const snap = useMessenger(store);
3553
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.ScrollView, { contentContainerStyle: styles4.content, children: [
3554
- snap.conversations.length ? snap.conversations.map((c) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ConversationRow, { c, palette: p, onPress: () => void store.openConversation(c.id) }, c.id)) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.empty, { color: p.muted }], children: "No conversations yet." }),
3555
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GButton, { label: "New conversation", palette: p, onPress: () => store.showNewMessage(), style: styles4.fullWidth })
3760
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.ScrollView, { contentContainerStyle: styles4.content, children: [
3761
+ snap.conversations.length ? snap.conversations.map((c) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ConversationRow, { c, palette: p, onPress: () => void store.openConversation(c.id) }, c.id)) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.empty, { color: p.muted }], children: "No conversations yet." }),
3762
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(GButton, { label: "New conversation", palette: p, onPress: () => store.showNewMessage(), style: styles4.fullWidth })
3556
3763
  ] });
3557
3764
  }
3558
3765
  function Bubble({ m, text, name, palette: p, onArticle, onGuide }) {
3559
3766
  if (m.author === "system")
3560
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { accessibilityRole: "text", style: [styles4.system, { color: p.muted }], children: text });
3767
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { accessibilityRole: "text", style: [styles4.system, { color: p.muted }], children: text });
3561
3768
  const mine = m.author === "user";
3562
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: [styles4.bubbleWrap, mine ? styles4.mine : styles4.theirs], children: [
3563
- !mine ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.author, { color: p.muted }], children: m.author === "agent" ? m.authorName || "Team" : name }) : null,
3564
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: [styles4.bubble, { borderRadius: Math.max(12, p.radius) }, mine ? { backgroundColor: p.primary } : { backgroundColor: p.surface }], children: m.status === "pending" && !text ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TypingDots, { color: p.muted }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.bubbleText, { color: mine ? p.onPrimary : p.text }], children: text }) }),
3565
- m.citations.length || m.suggestedGuides.length ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles4.chips, children: [
3566
- m.citations.map((c) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Pressable, { accessibilityRole: "link", accessibilityLabel: `Article: ${c.title}`, onPress: () => onArticle(c.articleId), style: [styles4.chip, { borderColor: p.border }], children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.Text, { style: [styles4.chipText, { color: p.text }], children: [
3769
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles4.bubbleWrap, mine ? styles4.mine : styles4.theirs], children: [
3770
+ !mine ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.author, { color: p.muted }], children: m.author === "agent" ? m.authorName || "Team" : name }) : null,
3771
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { style: [styles4.bubble, { borderRadius: Math.max(12, p.radius) }, mine ? { backgroundColor: p.primary } : { backgroundColor: p.surface }], children: m.status === "pending" && !text ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TypingDots, { color: p.muted }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.bubbleText, { color: mine ? p.onPrimary : p.text }], children: text }) }),
3772
+ m.citations.length || m.suggestedGuides.length ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: styles4.chips, children: [
3773
+ m.citations.map((c) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Pressable, { accessibilityRole: "link", accessibilityLabel: `Article: ${c.title}`, onPress: () => onArticle(c.articleId), style: [styles4.chip, { borderColor: p.border }], children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.Text, { style: [styles4.chipText, { color: p.text }], children: [
3567
3774
  "\u{1F4C4} ",
3568
3775
  c.title
3569
3776
  ] }) }, c.articleId)),
3570
- m.suggestedGuides.map((g) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Pressable, { accessibilityRole: "button", accessibilityLabel: `Show me: ${g.name}`, onPress: () => onGuide(g.key), style: [styles4.chip, { borderColor: p.primary }], children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.Text, { style: [styles4.chipText, { color: p.primary }], children: [
3777
+ m.suggestedGuides.map((g) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Pressable, { accessibilityRole: "button", accessibilityLabel: `Show me: ${g.name}`, onPress: () => onGuide(g.key), style: [styles4.chip, { borderColor: p.primary }], children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.Text, { style: [styles4.chipText, { color: p.primary }], children: [
3571
3778
  "\u25B6 Show me: ",
3572
3779
  g.name
3573
3780
  ] }) }, g.key))
@@ -3575,18 +3782,18 @@ function Bubble({ m, text, name, palette: p, onArticle, onGuide }) {
3575
3782
  ] });
3576
3783
  }
3577
3784
  function TypingDots({ color }) {
3578
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.ActivityIndicator, { size: "small", color, accessibilityLabel: "Typing" });
3785
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.ActivityIndicator, { size: "small", color, accessibilityLabel: "Typing" });
3579
3786
  }
3580
3787
  function Thread({ store, palette: p, bottomInset }) {
3581
3788
  const snap = useMessenger(store);
3582
3789
  const t = snap.thread;
3583
- const [text, setText] = (0, import_react2.useState)(t?.draft ?? "");
3584
- const scroll = (0, import_react2.useRef)(null);
3790
+ const [text, setText] = (0, import_react3.useState)(t?.draft ?? "");
3791
+ const scroll = (0, import_react3.useRef)(null);
3585
3792
  const convo = t?.conversation ?? null;
3586
- (0, import_react2.useEffect)(() => {
3793
+ (0, import_react3.useEffect)(() => {
3587
3794
  if (t?.draft) setText(t.draft);
3588
3795
  }, [t?.draft]);
3589
- const streamingIds = (0, import_react2.useMemo)(() => Object.keys(t?.streaming ?? {}).filter((id) => !t?.messages.some((m) => m.id === id)), [t]);
3796
+ const streamingIds = (0, import_react3.useMemo)(() => Object.keys(t?.streaming ?? {}).filter((id) => !t?.messages.some((m) => m.id === id)), [t]);
3590
3797
  if (!t) return null;
3591
3798
  const send = () => {
3592
3799
  const value = text;
@@ -3594,22 +3801,22 @@ function Thread({ store, palette: p, bottomInset }) {
3594
3801
  void store.send(value);
3595
3802
  };
3596
3803
  const canHandoff = snap.config.handoffEnabled && snap.config.aiAvailable && convo?.status === "bot" && !t.sending;
3597
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles4.flex, children: [
3598
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.ScrollView, { ref: scroll, contentContainerStyle: styles4.thread, onContentSizeChange: () => scroll.current?.scrollToEnd({ animated: true }), keyboardShouldPersistTaps: "handled", children: [
3599
- !t.messages.length && !t.sending ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.empty, { color: p.muted }], children: snap.config.greeting || "Ask us anything." }) : null,
3600
- t.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(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)),
3601
- streamingIds.map((id) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(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: () => {
3804
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: styles4.flex, children: [
3805
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.ScrollView, { ref: scroll, contentContainerStyle: styles4.thread, onContentSizeChange: () => scroll.current?.scrollToEnd({ animated: true }), keyboardShouldPersistTaps: "handled", children: [
3806
+ !t.messages.length && !t.sending ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.empty, { color: p.muted }], children: snap.config.greeting || "Ask us anything." }) : null,
3807
+ t.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(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)),
3808
+ streamingIds.map((id) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(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: () => {
3602
3809
  }, onGuide: () => {
3603
3810
  } }, id)),
3604
- t.sending && !streamingIds.length && snap.config.aiAvailable && t.messages.length ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TypingDots, { color: p.muted }) : null,
3605
- t.error ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { accessibilityLiveRegion: "polite", style: styles4.error, children: t.error }) : null,
3606
- convo?.status === "closed" && convo.rating == null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Rating, { palette: p, onRate: (n) => void store.rate(n) }) : null,
3607
- convo?.status === "closed" && convo.rating != null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.system, { color: p.muted }], children: "Thanks for rating this conversation." }) : null
3811
+ t.sending && !streamingIds.length && snap.config.aiAvailable && t.messages.length ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TypingDots, { color: p.muted }) : null,
3812
+ t.error ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { accessibilityLiveRegion: "polite", style: styles4.error, children: t.error }) : null,
3813
+ convo?.status === "closed" && convo.rating == null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rating, { palette: p, onRate: (n) => void store.rate(n) }) : null,
3814
+ convo?.status === "closed" && convo.rating != null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.system, { color: p.muted }], children: "Thanks for rating this conversation." }) : null
3608
3815
  ] }),
3609
- canHandoff ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GButton, { label: "Talk to a person", variant: "secondary", palette: p, onPress: () => void store.handoff(), style: styles4.handoff }) : null,
3610
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: [styles4.composer, { borderColor: p.border, paddingBottom: Math.max(10, bottomInset) }], children: [
3611
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3612
- import_react_native4.TextInput,
3816
+ canHandoff ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(GButton, { label: "Talk to a person", variant: "secondary", palette: p, onPress: () => void store.handoff(), style: styles4.handoff }) : null,
3817
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles4.composer, { borderColor: p.border, paddingBottom: Math.max(10, bottomInset) }], children: [
3818
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3819
+ import_react_native5.TextInput,
3613
3820
  {
3614
3821
  value: text,
3615
3822
  onChangeText: (v) => setText(v.slice(0, 4e3)),
@@ -3620,26 +3827,26 @@ function Thread({ store, palette: p, bottomInset }) {
3620
3827
  style: [styles4.input, { color: p.text, backgroundColor: p.surface, borderRadius: Math.min(18, p.radius + 6) }]
3621
3828
  }
3622
3829
  ),
3623
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GButton, { label: "Send", palette: p, onPress: send, disabled: !text.trim() || t.sending })
3830
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(GButton, { label: "Send", palette: p, onPress: send, disabled: !text.trim() || t.sending })
3624
3831
  ] })
3625
3832
  ] });
3626
3833
  }
3627
3834
  function Rating({ palette: p, onRate }) {
3628
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles4.rating, children: [
3629
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.ratingTitle, { color: p.text }], children: "How did we do?" }),
3630
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { accessibilityRole: "radiogroup", style: styles4.stars, children: [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Pressable, { accessibilityRole: "radio", accessibilityLabel: `${n} of 5`, onPress: () => onRate(n), hitSlop: 6, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.star, { color: p.primary }], children: "\u2605" }) }, n)) })
3835
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: styles4.rating, children: [
3836
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.ratingTitle, { color: p.text }], children: "How did we do?" }),
3837
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { accessibilityRole: "radiogroup", style: styles4.stars, children: [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Pressable, { accessibilityRole: "radio", accessibilityLabel: `${n} of 5`, onPress: () => onRate(n), hitSlop: 6, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.star, { color: p.primary }], children: "\u2605" }) }, n)) })
3631
3838
  ] });
3632
3839
  }
3633
3840
  function Help({ store, client, palette: p }) {
3634
3841
  const snap = useMessenger(store);
3635
3842
  const rc = client.getResourceCenter();
3636
- const [q, setQ] = (0, import_react2.useState)(pendingSearch.current);
3637
- const [results, setResults] = (0, import_react2.useState)(null);
3638
- const [faq, setFaq] = (0, import_react2.useState)(null);
3639
- (0, import_react2.useEffect)(() => {
3843
+ const [q, setQ] = (0, import_react3.useState)(pendingSearch.current);
3844
+ const [results, setResults] = (0, import_react3.useState)(null);
3845
+ const [faq, setFaq] = (0, import_react3.useState)(null);
3846
+ (0, import_react3.useEffect)(() => {
3640
3847
  pendingSearch.current = "";
3641
3848
  }, []);
3642
- (0, import_react2.useEffect)(() => {
3849
+ (0, import_react3.useEffect)(() => {
3643
3850
  const query = q.trim();
3644
3851
  if (query.length < 2) return setResults(null);
3645
3852
  let alive = true;
@@ -3652,35 +3859,35 @@ function Help({ store, client, palette: p }) {
3652
3859
  const guides = client.getGuides();
3653
3860
  const recommended = (rc?.recommendedGuideIds ?? []).map((id) => guides.find((g) => g.id === id || g.key === id)).filter((g) => !!g);
3654
3861
  const articles = snap.articles;
3655
- const row = (key, title, sub, onPress, icon = "\u203A") => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.Pressable, { accessibilityRole: "button", accessibilityLabel: title, onPress, style: ({ pressed }) => [styles4.row, { borderColor: p.border, opacity: pressed ? 0.7 : 1 }], children: [
3656
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles4.flex, children: [
3657
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.rowTitle, { color: p.text }], children: title }),
3658
- sub ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { numberOfLines: 2, style: [styles4.rowSub, { color: p.muted }], children: sub }) : null
3862
+ const row = (key, title, sub, onPress, icon = "\u203A") => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.Pressable, { accessibilityRole: "button", accessibilityLabel: title, onPress, style: ({ pressed }) => [styles4.row, { borderColor: p.border, opacity: pressed ? 0.7 : 1 }], children: [
3863
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: styles4.flex, children: [
3864
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.rowTitle, { color: p.text }], children: title }),
3865
+ sub ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { numberOfLines: 2, style: [styles4.rowSub, { color: p.muted }], children: sub }) : null
3659
3866
  ] }),
3660
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.chev, { color: p.muted }], children: icon })
3867
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.chev, { color: p.muted }], children: icon })
3661
3868
  ] }, key);
3662
3869
  const open = (a) => a.externalUrl ? client.navigate(a.externalUrl) : void store.openArticle(a.id);
3663
- const section = (title, children) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles4.section, children: [
3664
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { accessibilityRole: "header", style: [styles4.sectionTitle, { color: p.muted }], children: title.toUpperCase() }),
3870
+ const section = (title, children) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: styles4.section, children: [
3871
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { accessibilityRole: "header", style: [styles4.sectionTitle, { color: p.muted }], children: title.toUpperCase() }),
3665
3872
  children
3666
3873
  ] }, title);
3667
3874
  const audiences = client.getAudienceIds();
3668
3875
  const sections = resourceCenterSections(articles?.collections ?? [], articles?.articles ?? [], audiences);
3669
3876
  const listed = results?.filter((a) => isArticleListed(articles?.collections ?? [], a, audiences));
3670
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.ScrollView, { contentContainerStyle: styles4.content, keyboardShouldPersistTaps: "handled", children: [
3671
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.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) }] }),
3672
- listed ? section(`Results for \u201C${q.trim()}\u201D`, listed.length ? listed.map((a) => row(a.id, a.title, a.summary, () => open(a))) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.rowSub, { color: p.muted }], children: "No articles found." })) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
3877
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.ScrollView, { contentContainerStyle: styles4.content, keyboardShouldPersistTaps: "handled", children: [
3878
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.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) }] }),
3879
+ listed ? section(`Results for \u201C${q.trim()}\u201D`, listed.length ? listed.map((a) => row(a.id, a.title, a.summary, () => open(a))) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.rowSub, { color: p.muted }], children: "No articles found." })) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
3673
3880
  recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => store.startGuide(g.key), "\u25B6"))) : null,
3674
- !articles ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.ActivityIndicator, { color: p.muted }) : null,
3881
+ !articles ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.ActivityIndicator, { color: p.muted }) : null,
3675
3882
  sections.map((s) => section(s.collection ? s.path.join(" \u203A ") : "Articles", s.articles.map((a) => row(a.id, a.title, a.summary, () => open(a))))),
3676
3883
  rc?.faqs.length ? section(
3677
3884
  "FAQ",
3678
- rc.faqs.map((f) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: [styles4.row, styles4.faq, { borderColor: p.border }], children: [
3679
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.Pressable, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles4.faqHead, children: [
3680
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.rowTitle, styles4.flex, { color: p.text }], children: f.question }),
3681
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
3885
+ rc.faqs.map((f) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles4.row, styles4.faq, { borderColor: p.border }], children: [
3886
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.Pressable, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles4.faqHead, children: [
3887
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.rowTitle, styles4.flex, { color: p.text }], children: f.question }),
3888
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles4.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
3682
3889
  ] }),
3683
- faq === f.id ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RichText, { html: f.answer, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null
3890
+ faq === f.id ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RichText, { html: f.answer, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null
3684
3891
  ] }, f.id))
3685
3892
  ) : null,
3686
3893
  rc?.links.length ? section("Links", rc.links.map((l) => row(l.id, l.label, void 0, () => client.navigate(l.url), "\u2197"))) : null
@@ -3688,23 +3895,23 @@ function Help({ store, client, palette: p }) {
3688
3895
  ] });
3689
3896
  }
3690
3897
  function Article({ article, palette: p, client }) {
3691
- if (!article) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.ActivityIndicator, { style: styles4.loading, color: p.muted });
3692
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.ScrollView, { contentContainerStyle: styles4.content, children: [
3693
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { accessibilityRole: "header", style: [styles4.articleTitle, { color: p.text, fontFamily: p.fontFamily }], children: article.title }),
3694
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RichText, { html: article.body, palette: p, onLink: (u) => client.navigate(u) })
3898
+ if (!article) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.ActivityIndicator, { style: styles4.loading, color: p.muted });
3899
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.ScrollView, { contentContainerStyle: styles4.content, children: [
3900
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { accessibilityRole: "header", style: [styles4.articleTitle, { color: p.text, fontFamily: p.fontFamily }], children: article.title }),
3901
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RichText, { html: article.body, palette: p, onLink: (u) => client.navigate(u) })
3695
3902
  ] });
3696
3903
  }
3697
- var styles4 = import_react_native4.StyleSheet.create({
3904
+ var styles4 = import_react_native5.StyleSheet.create({
3698
3905
  flex: { flex: 1 },
3699
3906
  launcher: { position: "absolute", width: 54, height: 54, borderRadius: 27, alignItems: "center", justifyContent: "center", elevation: 8, shadowColor: "#000", shadowOpacity: 0.22, shadowRadius: 12, shadowOffset: { width: 0, height: 4 } },
3700
3907
  launcherIcon: { fontSize: 24 },
3701
3908
  badge: { position: "absolute", top: -2, right: -2, minWidth: 20, height: 20, borderRadius: 10, backgroundColor: "#dc2626", alignItems: "center", justifyContent: "center", paddingHorizontal: 5, borderWidth: 2, borderColor: "#ffffff" },
3702
3909
  badgeText: { color: "#ffffff", fontSize: 11, fontWeight: "800" },
3703
3910
  sheet: { flex: 1 },
3704
- header: { flexDirection: "row", alignItems: "center", gap: 12, paddingHorizontal: 16, paddingVertical: 12, borderBottomWidth: import_react_native4.StyleSheet.hairlineWidth },
3911
+ header: { flexDirection: "row", alignItems: "center", gap: 12, paddingHorizontal: 16, paddingVertical: 12, borderBottomWidth: import_react_native5.StyleSheet.hairlineWidth },
3705
3912
  back: { fontSize: 16, fontWeight: "600" },
3706
3913
  title: { flex: 1, fontSize: 18, fontWeight: "700" },
3707
- tabs: { flexDirection: "row", borderTopWidth: import_react_native4.StyleSheet.hairlineWidth, paddingTop: 8 },
3914
+ tabs: { flexDirection: "row", borderTopWidth: import_react_native5.StyleSheet.hairlineWidth, paddingTop: 8 },
3708
3915
  tab: { flex: 1, alignItems: "center", gap: 2 },
3709
3916
  tabIcon: { fontSize: 18 },
3710
3917
  tabLabel: { fontSize: 11, fontWeight: "600" },
@@ -3718,7 +3925,7 @@ var styles4 = import_react_native4.StyleSheet.create({
3718
3925
  section: { gap: 8 },
3719
3926
  sectionTitle: { fontSize: 12, fontWeight: "700", letterSpacing: 0.6 },
3720
3927
  search: { height: 42, paddingHorizontal: 12, fontSize: 15 },
3721
- row: { flexDirection: "row", alignItems: "center", gap: 10, borderWidth: import_react_native4.StyleSheet.hairlineWidth, borderRadius: 10, paddingVertical: 12, paddingHorizontal: 12 },
3928
+ row: { flexDirection: "row", alignItems: "center", gap: 10, borderWidth: import_react_native5.StyleSheet.hairlineWidth, borderRadius: 10, paddingVertical: 12, paddingHorizontal: 12 },
3722
3929
  rowTitle: { fontSize: 15, fontWeight: "600" },
3723
3930
  rowSub: { fontSize: 13, marginTop: 2 },
3724
3931
  rowEnd: { alignItems: "flex-end", gap: 6 },
@@ -3740,7 +3947,7 @@ var styles4 = import_react_native4.StyleSheet.create({
3740
3947
  chipText: { fontSize: 13, fontWeight: "600" },
3741
3948
  error: { color: "#dc2626", fontSize: 13, textAlign: "center" },
3742
3949
  handoff: { alignSelf: "center", marginBottom: 8 },
3743
- composer: { flexDirection: "row", alignItems: "flex-end", gap: 8, paddingHorizontal: 12, paddingTop: 10, borderTopWidth: import_react_native4.StyleSheet.hairlineWidth },
3950
+ composer: { flexDirection: "row", alignItems: "flex-end", gap: 8, paddingHorizontal: 12, paddingTop: 10, borderTopWidth: import_react_native5.StyleSheet.hairlineWidth },
3744
3951
  input: { flex: 1, minHeight: 40, maxHeight: 120, paddingHorizontal: 14, paddingTop: 10, paddingBottom: 10, fontSize: 15 },
3745
3952
  rating: { alignItems: "center", gap: 6, paddingVertical: 8 },
3746
3953
  ratingTitle: { fontSize: 15, fontWeight: "600" },
@@ -3751,23 +3958,23 @@ var styles4 = import_react_native4.StyleSheet.create({
3751
3958
  });
3752
3959
 
3753
3960
  // src/ui/ResourceCenter.tsx
3754
- var import_react3 = require("react");
3755
- var import_react_native5 = require("react-native");
3756
- var import_jsx_runtime5 = require("react/jsx-runtime");
3961
+ var import_react4 = require("react");
3962
+ var import_react_native6 = require("react-native");
3963
+ var import_jsx_runtime6 = require("react/jsx-runtime");
3757
3964
  function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
3758
3965
  const rc = client.getResourceCenter();
3759
- const [query, setQuery] = (0, import_react3.useState)("");
3760
- const [results, setResults] = (0, import_react3.useState)(null);
3761
- const [article, setArticle] = (0, import_react3.useState)(null);
3762
- const [faq, setFaq] = (0, import_react3.useState)(null);
3763
- (0, import_react3.useEffect)(() => {
3966
+ const [query, setQuery] = (0, import_react4.useState)("");
3967
+ const [results, setResults] = (0, import_react4.useState)(null);
3968
+ const [article, setArticle] = (0, import_react4.useState)(null);
3969
+ const [faq, setFaq] = (0, import_react4.useState)(null);
3970
+ (0, import_react4.useEffect)(() => {
3764
3971
  if (!open) {
3765
3972
  setQuery("");
3766
3973
  setResults(null);
3767
3974
  setArticle(null);
3768
3975
  }
3769
3976
  }, [open]);
3770
- (0, import_react3.useEffect)(() => {
3977
+ (0, import_react4.useEffect)(() => {
3771
3978
  const q = query.trim();
3772
3979
  if (q.length < 2) {
3773
3980
  setResults(null);
@@ -3783,8 +3990,8 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
3783
3990
  };
3784
3991
  }, [client, query]);
3785
3992
  const guides = client.getGuides();
3786
- const recommended = (0, import_react3.useMemo)(() => (rc?.recommendedGuideIds ?? []).map((id) => guides.find((g) => g.id === id || g.key === id)).filter((g) => !!g && g.kind !== "checklist"), [rc, guides]);
3787
- const checklists = (0, import_react3.useMemo)(() => (rc?.checklistGuideIds ?? []).map((id) => guides.find((g) => g.id === id || g.key === id)).filter((g) => !!g && g.kind === "checklist"), [rc, guides]);
3993
+ const recommended = (0, import_react4.useMemo)(() => (rc?.recommendedGuideIds ?? []).map((id) => guides.find((g) => g.id === id || g.key === id)).filter((g) => !!g && g.kind !== "checklist"), [rc, guides]);
3994
+ const checklists = (0, import_react4.useMemo)(() => (rc?.checklistGuideIds ?? []).map((id) => guides.find((g) => g.id === id || g.key === id)).filter((g) => !!g && g.kind === "checklist"), [rc, guides]);
3788
3995
  if (!rc) return null;
3789
3996
  const close = () => client.closeResourceCenter();
3790
3997
  const openArticle = (a) => {
@@ -3792,32 +3999,32 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
3792
3999
  client.articleViewed(a);
3793
4000
  setArticle(a);
3794
4001
  };
3795
- const row = (key, title, subtitle, onPress, icon = "\u203A") => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.Pressable, { accessibilityRole: "button", accessibilityLabel: title, onPress, style: ({ pressed }) => [styles5.row, { borderColor: p.border, opacity: pressed ? 0.7 : 1 }], children: [
3796
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: styles5.flex, children: [
3797
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles5.rowTitle, { color: p.text, fontFamily: p.fontFamily }], children: title }),
3798
- subtitle ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles5.rowSub, { color: p.muted }], numberOfLines: 2, children: subtitle }) : null
4002
+ const row = (key, title, subtitle, onPress, icon = "\u203A") => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.Pressable, { accessibilityRole: "button", accessibilityLabel: title, onPress, style: ({ pressed }) => [styles5.row, { borderColor: p.border, opacity: pressed ? 0.7 : 1 }], children: [
4003
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: styles5.flex, children: [
4004
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.rowTitle, { color: p.text, fontFamily: p.fontFamily }], children: title }),
4005
+ subtitle ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.rowSub, { color: p.muted }], numberOfLines: 2, children: subtitle }) : null
3799
4006
  ] }),
3800
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles5.chev, { color: p.muted }], children: icon })
4007
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.chev, { color: p.muted }], children: icon })
3801
4008
  ] }, key);
3802
- const section = (title, children) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: styles5.section, children: [
3803
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { accessibilityRole: "header", style: [styles5.sectionTitle, { color: p.muted }], children: title.toUpperCase() }),
4009
+ const section = (title, children) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: styles5.section, children: [
4010
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { accessibilityRole: "header", style: [styles5.sectionTitle, { color: p.muted }], children: title.toUpperCase() }),
3804
4011
  children
3805
4012
  ] }, title);
3806
4013
  const audiences = client.getAudienceIds();
3807
4014
  const sections = resourceCenterSections(rc.collections, rc.articles, audiences);
3808
4015
  const listed = results?.filter((a) => isArticleListed(rc.collections, a, audiences));
3809
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Modal, { visible: open, animationType: "slide", presentationStyle: import_react_native5.Platform.OS === "ios" ? "pageSheet" : "fullScreen", onRequestClose: () => article ? setArticle(null) : close(), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles5.sheet, { backgroundColor: p.background, paddingTop: import_react_native5.Platform.OS === "ios" ? 8 : topInset }], children: [
3810
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles5.header, { borderColor: p.border }], children: [
3811
- article ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Pressable, { accessibilityRole: "button", accessibilityLabel: "Back", onPress: () => setArticle(null), hitSlop: 10, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles5.back, { color: p.primary }], children: "\u2039 Back" }) }) : null,
3812
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { accessibilityRole: "header", style: [styles5.title, { color: p.text, fontFamily: p.fontFamily }], numberOfLines: 1, children: article ? "" : rc.title }),
3813
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseButton, { palette: p, onPress: close, label: "Close help" })
4016
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Modal, { visible: open, animationType: "slide", presentationStyle: import_react_native6.Platform.OS === "ios" ? "pageSheet" : "fullScreen", onRequestClose: () => article ? setArticle(null) : close(), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: [styles5.sheet, { backgroundColor: p.background, paddingTop: import_react_native6.Platform.OS === "ios" ? 8 : topInset }], children: [
4017
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: [styles5.header, { borderColor: p.border }], children: [
4018
+ article ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Pressable, { accessibilityRole: "button", accessibilityLabel: "Back", onPress: () => setArticle(null), hitSlop: 10, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.back, { color: p.primary }], children: "\u2039 Back" }) }) : null,
4019
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { accessibilityRole: "header", style: [styles5.title, { color: p.text, fontFamily: p.fontFamily }], numberOfLines: 1, children: article ? "" : rc.title }),
4020
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseButton, { palette: p, onPress: close, label: "Close help" })
3814
4021
  ] }),
3815
- article ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.ScrollView, { contentContainerStyle: [styles5.content, { paddingBottom: bottomInset + 24 }], children: [
3816
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { accessibilityRole: "header", style: [styles5.articleTitle, { color: p.text, fontFamily: p.fontFamily }], children: article.title }),
3817
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RichText, { html: article.body, palette: p, onLink: (u) => client.navigate(u) })
3818
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.ScrollView, { contentContainerStyle: [styles5.content, { paddingBottom: bottomInset + 24 }], keyboardShouldPersistTaps: "handled", children: [
3819
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3820
- import_react_native5.TextInput,
4022
+ article ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.ScrollView, { contentContainerStyle: [styles5.content, { paddingBottom: bottomInset + 24 }], children: [
4023
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { accessibilityRole: "header", style: [styles5.articleTitle, { color: p.text, fontFamily: p.fontFamily }], children: article.title }),
4024
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RichText, { html: article.body, palette: p, onLink: (u) => client.navigate(u) })
4025
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.ScrollView, { contentContainerStyle: [styles5.content, { paddingBottom: bottomInset + 24 }], keyboardShouldPersistTaps: "handled", children: [
4026
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4027
+ import_react_native6.TextInput,
3821
4028
  {
3822
4029
  value: query,
3823
4030
  onChangeText: setQuery,
@@ -3831,19 +4038,19 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
3831
4038
  ),
3832
4039
  listed ? section(
3833
4040
  `Results for \u201C${query.trim()}\u201D`,
3834
- listed.length ? listed.map((a) => row(a.id, a.title, a.summary, () => openArticle(a))) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles5.rowSub, { color: p.muted }], children: "No articles found." })
3835
- ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
4041
+ listed.length ? listed.map((a) => row(a.id, a.title, a.summary, () => openArticle(a))) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.rowSub, { color: p.muted }], children: "No articles found." })
4042
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
3836
4043
  recommended.length ? section("Recommended", recommended.map((g) => row(g.id, g.name, void 0, () => (close(), client.startGuide(g.id)), "\u25B6"))) : null,
3837
4044
  checklists.length ? section("Checklists", checklists.map((g) => row(g.id, g.name, void 0, () => (close(), client.openChecklist(g.id)), "\u2611"))) : null,
3838
4045
  sections.map((s) => section(s.collection ? s.path.join(" \u203A ") : "Articles", s.articles.map((a) => row(a.id, a.title, a.summary, () => openArticle(a))))),
3839
4046
  rc.faqs.length ? section(
3840
4047
  "FAQ",
3841
- rc.faqs.map((f) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles5.row, styles5.faq, { borderColor: p.border }], children: [
3842
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.Pressable, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles5.faqHead, children: [
3843
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles5.rowTitle, styles5.flex, { color: p.text }], children: f.question }),
3844
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles5.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
4048
+ rc.faqs.map((f) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: [styles5.row, styles5.faq, { borderColor: p.border }], children: [
4049
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.Pressable, { accessibilityRole: "button", accessibilityState: { expanded: faq === f.id }, onPress: () => setFaq(faq === f.id ? null : f.id), style: styles5.faqHead, children: [
4050
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.rowTitle, styles5.flex, { color: p.text }], children: f.question }),
4051
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { style: [styles5.chev, { color: p.muted }], children: faq === f.id ? "\u2212" : "+" })
3845
4052
  ] }),
3846
- faq === f.id ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RichText, { html: f.answer, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null
4053
+ faq === f.id ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RichText, { html: f.answer, palette: p, onLink: (u) => client.navigate(u), size: 14 }) : null
3847
4054
  ] }, f.id))
3848
4055
  ) : null,
3849
4056
  rc.links.length ? section("Links", rc.links.map((l) => row(l.id, l.label, void 0, () => client.navigate(l.url), "\u2197"))) : null
@@ -3851,17 +4058,17 @@ function ResourceCenter({ client, palette: p, open, topInset, bottomInset }) {
3851
4058
  ] })
3852
4059
  ] }) });
3853
4060
  }
3854
- var styles5 = import_react_native5.StyleSheet.create({
4061
+ var styles5 = import_react_native6.StyleSheet.create({
3855
4062
  flex: { flex: 1 },
3856
4063
  sheet: { flex: 1 },
3857
- header: { flexDirection: "row", alignItems: "center", gap: 12, paddingHorizontal: 16, paddingVertical: 12, borderBottomWidth: import_react_native5.StyleSheet.hairlineWidth },
4064
+ header: { flexDirection: "row", alignItems: "center", gap: 12, paddingHorizontal: 16, paddingVertical: 12, borderBottomWidth: import_react_native6.StyleSheet.hairlineWidth },
3858
4065
  back: { fontSize: 16, fontWeight: "600" },
3859
4066
  title: { flex: 1, fontSize: 18, fontWeight: "700" },
3860
4067
  content: { padding: 16, gap: 18 },
3861
4068
  search: { height: 42, paddingHorizontal: 12, fontSize: 15 },
3862
4069
  section: { gap: 8 },
3863
4070
  sectionTitle: { fontSize: 12, fontWeight: "700", letterSpacing: 0.6 },
3864
- row: { flexDirection: "row", alignItems: "center", gap: 10, borderWidth: import_react_native5.StyleSheet.hairlineWidth, borderRadius: 10, paddingVertical: 12, paddingHorizontal: 12 },
4071
+ row: { flexDirection: "row", alignItems: "center", gap: 10, borderWidth: import_react_native6.StyleSheet.hairlineWidth, borderRadius: 10, paddingVertical: 12, paddingHorizontal: 12 },
3865
4072
  rowTitle: { fontSize: 15, fontWeight: "600" },
3866
4073
  rowSub: { fontSize: 13, marginTop: 2 },
3867
4074
  chev: { fontSize: 18 },
@@ -3871,8 +4078,8 @@ var styles5 = import_react_native5.StyleSheet.create({
3871
4078
  });
3872
4079
 
3873
4080
  // src/ui/StepLayer.tsx
3874
- var import_react4 = require("react");
3875
- var import_react_native7 = require("react-native");
4081
+ var import_react5 = require("react");
4082
+ var import_react_native8 = require("react-native");
3876
4083
 
3877
4084
  // src/core/position.ts
3878
4085
  var MARGIN = 12;
@@ -3912,32 +4119,32 @@ function positionCard(target, size, viewport, insets, placement, offset) {
3912
4119
  }
3913
4120
 
3914
4121
  // src/ui/StepCard.tsx
3915
- var import_react_native6 = require("react-native");
3916
- var import_jsx_runtime6 = require("react/jsx-runtime");
4122
+ var import_react_native7 = require("react-native");
4123
+ var import_jsx_runtime7 = require("react/jsx-runtime");
3917
4124
  function StepCard({ view, client, palette: p, width, style, compact }) {
3918
4125
  const { step, guide } = view;
3919
4126
  const buttons = visibleButtons(step, view.canGoBack);
3920
4127
  const body = step.type === "custom" ? step.customHtml || step.body : step.body;
3921
4128
  const showProgress = step.showProgress && view.total > 1;
3922
4129
  const onLink = (url) => client.navigate(url);
3923
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Surface, { palette: p, style: [{ width }, style], children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { accessible: false, accessibilityViewIsModal: view.asModal || step.type === "modal", style: compact ? styles6.compact : styles6.inner, children: [
3924
- step.imageUrl ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Image, { source: { uri: step.imageUrl }, style: [styles6.image, { borderRadius: Math.max(0, p.radius - 4) }], resizeMode: "cover", accessibilityIgnoresInvertColors: true }) : null,
3925
- step.title ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: styles6.header, children: [
3926
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Text, { accessibilityRole: "header", style: [styles6.title, { color: p.text, fontFamily: p.fontFamily }], children: step.title }),
3927
- step.showClose ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseButton, { palette: p, onPress: () => client.closeStep(guide.id), label: "Dismiss guide" }) : null
4130
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Surface, { palette: p, style: [{ width }, style], children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.View, { accessible: false, accessibilityViewIsModal: view.asModal || step.type === "modal", style: compact ? styles6.compact : styles6.inner, children: [
4131
+ step.imageUrl ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.Image, { source: { uri: step.imageUrl }, style: [styles6.image, { borderRadius: Math.max(0, p.radius - 4) }], resizeMode: "cover", accessibilityIgnoresInvertColors: true }) : null,
4132
+ step.title ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.View, { style: styles6.header, children: [
4133
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.Text, { accessibilityRole: "header", style: [styles6.title, { color: p.text, fontFamily: p.fontFamily }], children: step.title }),
4134
+ step.showClose ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CloseButton, { palette: p, onPress: () => client.closeStep(guide.id), label: "Dismiss guide" }) : null
3928
4135
  ] }) : null,
3929
4136
  body ? (
3930
4137
  // Without a title the close button floats beside the body instead of taking a row.
3931
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.View, { style: !step.title && step.showClose ? styles6.bodyBesideClose : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RichText, { html: body, palette: p, onLink }) })
4138
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.View, { style: !step.title && step.showClose ? styles6.bodyBesideClose : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(RichText, { html: body, palette: p, onLink }) })
3932
4139
  ) : null,
3933
- !step.title && step.showClose ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.View, { style: compact ? styles6.floatingCloseCompact : styles6.floatingClose, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseButton, { palette: p, onPress: () => client.closeStep(guide.id), label: "Dismiss guide" }) }) : null,
3934
- showProgress || buttons.length ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: styles6.footer, children: [
3935
- showProgress ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.Text, { style: [styles6.progress, { color: p.muted }], accessibilityLabel: `Step ${view.index + 1} of ${view.total}`, children: [
4140
+ !step.title && step.showClose ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.View, { style: compact ? styles6.floatingCloseCompact : styles6.floatingClose, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CloseButton, { palette: p, onPress: () => client.closeStep(guide.id), label: "Dismiss guide" }) }) : null,
4141
+ showProgress || buttons.length ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.View, { style: styles6.footer, children: [
4142
+ showProgress ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.Text, { style: [styles6.progress, { color: p.muted }], accessibilityLabel: `Step ${view.index + 1} of ${view.total}`, children: [
3936
4143
  view.index + 1,
3937
4144
  " of ",
3938
4145
  view.total
3939
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.View, { style: styles6.flex }),
3940
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.View, { style: styles6.buttons, children: buttons.map((b) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(GButton, { label: b.label, variant: b.variant, palette: p, onPress: () => client.pressButton(guide.id, b) }, b.id)) })
4146
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.View, { style: styles6.flex }),
4147
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.View, { style: styles6.buttons, children: buttons.map((b) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(GButton, { label: b.label, variant: b.variant, palette: p, onPress: () => client.pressButton(guide.id, b) }, b.id)) })
3941
4148
  ] }) : null
3942
4149
  ] }) });
3943
4150
  }
@@ -3946,7 +4153,7 @@ function announcement(view) {
3946
4153
  if (view.total > 1) parts.push(`Step ${view.index + 1} of ${view.total}`);
3947
4154
  return parts.join(". ");
3948
4155
  }
3949
- var styles6 = import_react_native6.StyleSheet.create({
4156
+ var styles6 = import_react_native7.StyleSheet.create({
3950
4157
  inner: { padding: 16, gap: 10 },
3951
4158
  compact: { paddingHorizontal: 14, paddingVertical: 10, gap: 6 },
3952
4159
  image: { width: "100%", height: 140, marginBottom: 2 },
@@ -3962,10 +4169,10 @@ var styles6 = import_react_native6.StyleSheet.create({
3962
4169
  });
3963
4170
 
3964
4171
  // src/ui/StepLayer.tsx
3965
- var import_jsx_runtime7 = require("react/jsx-runtime");
4172
+ var import_jsx_runtime8 = require("react/jsx-runtime");
3966
4173
  function useTargetRect(client, targetId, origin) {
3967
- const [rect, setRect] = (0, import_react4.useState)(null);
3968
- (0, import_react4.useEffect)(() => {
4174
+ const [rect, setRect] = (0, import_react5.useState)(null);
4175
+ (0, import_react5.useEffect)(() => {
3969
4176
  if (!targetId) {
3970
4177
  setRect(null);
3971
4178
  return;
@@ -3992,10 +4199,10 @@ function useTargetRect(client, targetId, origin) {
3992
4199
  return rect;
3993
4200
  }
3994
4201
  function useFadeIn(key) {
3995
- const opacity = (0, import_react4.useRef)(new import_react_native7.Animated.Value(0)).current;
3996
- (0, import_react4.useEffect)(() => {
4202
+ const opacity = (0, import_react5.useRef)(new import_react_native8.Animated.Value(0)).current;
4203
+ (0, import_react5.useEffect)(() => {
3997
4204
  opacity.setValue(0);
3998
- import_react_native7.Animated.timing(opacity, { toValue: 1, duration: 160, easing: import_react_native7.Easing.out(import_react_native7.Easing.quad), useNativeDriver: true }).start();
4205
+ import_react_native8.Animated.timing(opacity, { toValue: 1, duration: 160, easing: import_react_native8.Easing.out(import_react_native8.Easing.quad), useNativeDriver: true }).start();
3999
4206
  }, [key, opacity]);
4000
4207
  return opacity;
4001
4208
  }
@@ -4005,34 +4212,34 @@ function StepLayer({ view, client, palette: p, geometry }) {
4005
4212
  const opacity = useFadeIn(view.key);
4006
4213
  const { viewport, insets } = geometry;
4007
4214
  const cardWidth = Math.min(viewport.width - 32, step.width ?? 340);
4008
- (0, import_react4.useEffect)(() => {
4215
+ (0, import_react5.useEffect)(() => {
4009
4216
  if (step.type === "hotspot" && !view.expanded) return;
4010
- import_react_native7.AccessibilityInfo.announceForAccessibility?.(announcement(view));
4217
+ import_react_native8.AccessibilityInfo.announceForAccessibility?.(announcement(view));
4011
4218
  }, [view.key, view.expanded]);
4012
- const backdrop = (children) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.Pressable, { accessible: false, style: [import_react_native7.StyleSheet.absoluteFill, { backgroundColor: p.overlay }], onPress: () => client.backdropPressed(view.guide.id), children });
4219
+ const backdrop = (children) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.Pressable, { accessible: false, style: [import_react_native8.StyleSheet.absoluteFill, { backgroundColor: p.overlay }], onPress: () => client.backdropPressed(view.guide.id), children });
4013
4220
  const anchored = !view.asModal && ["tooltip", "popover", "spotlight", "hotspot"].includes(step.type);
4014
4221
  if (!anchored && step.type === "banner") {
4015
4222
  const top = step.position.startsWith("top");
4016
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.Animated.View, { pointerEvents: "box-none", style: [styles7.banner, top ? { top: insets.top + 8 } : { bottom: insets.bottom + 8 }, { left: insets.left + 8, right: insets.right + 8, opacity }], children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StepCard, { view, client, palette: p, width: viewport.width - insets.left - insets.right - 16, compact: true }) });
4223
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.Animated.View, { pointerEvents: "box-none", style: [styles7.banner, top ? { top: insets.top + 8 } : { bottom: insets.bottom + 8 }, { left: insets.left + 8, right: insets.right + 8, opacity }], children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(StepCard, { view, client, palette: p, width: viewport.width - insets.left - insets.right - 16, compact: true }) });
4017
4224
  }
4018
4225
  if (!anchored && step.type === "slideout") {
4019
4226
  const wide = viewport.width >= 600;
4020
4227
  const right = !step.position.endsWith("left");
4021
4228
  const width = wide ? Math.min(380, cardWidth) : viewport.width - insets.left - insets.right - 24;
4022
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.Animated.View, { pointerEvents: "box-none", style: [styles7.abs, { bottom: insets.bottom + 12, opacity }, wide ? right ? { right: insets.right + 16 } : { left: insets.left + 16 } : { left: insets.left + 12 }], children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StepCard, { view, client, palette: p, width }) });
4229
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.Animated.View, { pointerEvents: "box-none", style: [styles7.abs, { bottom: insets.bottom + 12, opacity }, wide ? right ? { right: insets.right + 16 } : { left: insets.left + 16 } : { left: insets.left + 12 }], children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(StepCard, { view, client, palette: p, width }) });
4023
4230
  }
4024
4231
  if (!anchored) {
4025
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.Animated.View, { pointerEvents: "box-none", style: [import_react_native7.StyleSheet.absoluteFill, styles7.center, { opacity }], children: [
4232
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.Animated.View, { pointerEvents: "box-none", style: [import_react_native8.StyleSheet.absoluteFill, styles7.center, { opacity }], children: [
4026
4233
  step.showOverlay ? backdrop() : null,
4027
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StepCard, { view, client, palette: p, width: Math.min(viewport.width - 40, step.width ?? 360) })
4234
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(StepCard, { view, client, palette: p, width: Math.min(viewport.width - 40, step.width ?? 360) })
4028
4235
  ] });
4029
4236
  }
4030
4237
  if (!rect) return null;
4031
4238
  if (step.type === "hotspot") {
4032
4239
  const size = step.hotspot?.size ?? 14;
4033
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.View, { pointerEvents: "box-none", style: import_react_native7.StyleSheet.absoluteFill, children: [
4034
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Hotspot, { x: rect.x + rect.width - size / 2, y: rect.y - size / 2, size, pulse: step.hotspot?.pulse ?? true, color: p.primary, onPress: () => client.toggleHotspot(view.guide.id), label: step.title || "Show tip" }),
4035
- view.expanded ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Anchored, { view, client, palette: p, geometry, rect, width: cardWidth }) : null
4240
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.View, { pointerEvents: "box-none", style: import_react_native8.StyleSheet.absoluteFill, children: [
4241
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Hotspot, { x: rect.x + rect.width - size / 2, y: rect.y - size / 2, size, pulse: step.hotspot?.pulse ?? true, color: p.primary, onPress: () => client.toggleHotspot(view.guide.id), label: step.title || "Show tip" }),
4242
+ view.expanded ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Anchored, { view, client, palette: p, geometry, rect, width: cardWidth }) : null
4036
4243
  ] });
4037
4244
  }
4038
4245
  if (step.type === "spotlight") {
@@ -4047,76 +4254,76 @@ function StepLayer({ view, client, palette: p, geometry }) {
4047
4254
  [hole.x, hole.y + hole.height - r, -r, -2 * r],
4048
4255
  [hole.x + hole.width - r, hole.y + hole.height - r, -2 * r, -2 * r]
4049
4256
  ];
4050
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.Animated.View, { pointerEvents: "box-none", style: [import_react_native7.StyleSheet.absoluteFill, { opacity }], children: [
4051
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.Pressable, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, right: 0, top: 0, height: Math.max(0, hole.y) }] }),
4052
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.Pressable, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, right: 0, top: hole.y + hole.height, bottom: 0 }] }),
4053
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.Pressable, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, width: Math.max(0, hole.x), top: hole.y, height: hole.height }] }),
4054
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.Pressable, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: hole.x + hole.width, right: 0, top: hole.y, height: hole.height }] }),
4055
- r > 0 ? corners.map(([left, top, dx, dy], i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.View, { pointerEvents: "none", style: [styles7.abs, { left, top, width: r, height: r, overflow: "hidden" }], children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.View, { style: [styles7.abs, { left: dx, top: dy, width: 4 * r, height: 4 * r, borderRadius: 2 * r, borderWidth: r, borderColor: p.overlay }] }) }, i)) : null,
4056
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.View, { pointerEvents: "none", style: [styles7.abs, { left: hole.x, top: hole.y, width: hole.width, height: hole.height, borderRadius: r, borderWidth: 2, borderColor: p.primary }] }),
4057
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Anchored, { view, client, palette: p, geometry, rect: hole, width: cardWidth })
4257
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.Animated.View, { pointerEvents: "box-none", style: [import_react_native8.StyleSheet.absoluteFill, { opacity }], children: [
4258
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.Pressable, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, right: 0, top: 0, height: Math.max(0, hole.y) }] }),
4259
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.Pressable, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, right: 0, top: hole.y + hole.height, bottom: 0 }] }),
4260
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.Pressable, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: 0, width: Math.max(0, hole.x), top: hole.y, height: hole.height }] }),
4261
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.Pressable, { accessible: false, onPress: press, style: [styles7.abs, shade, { left: hole.x + hole.width, right: 0, top: hole.y, height: hole.height }] }),
4262
+ r > 0 ? corners.map(([left, top, dx, dy], i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.View, { pointerEvents: "none", style: [styles7.abs, { left, top, width: r, height: r, overflow: "hidden" }], children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.View, { style: [styles7.abs, { left: dx, top: dy, width: 4 * r, height: 4 * r, borderRadius: 2 * r, borderWidth: r, borderColor: p.overlay }] }) }, i)) : null,
4263
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.View, { pointerEvents: "none", style: [styles7.abs, { left: hole.x, top: hole.y, width: hole.width, height: hole.height, borderRadius: r, borderWidth: 2, borderColor: p.primary }] }),
4264
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Anchored, { view, client, palette: p, geometry, rect: hole, width: cardWidth })
4058
4265
  ] });
4059
4266
  }
4060
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.Animated.View, { pointerEvents: "box-none", style: [import_react_native7.StyleSheet.absoluteFill, { opacity }], children: [
4267
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.Animated.View, { pointerEvents: "box-none", style: [import_react_native8.StyleSheet.absoluteFill, { opacity }], children: [
4061
4268
  step.showOverlay ? backdrop() : null,
4062
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Anchored, { view, client, palette: p, geometry, rect, width: cardWidth })
4269
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Anchored, { view, client, palette: p, geometry, rect, width: cardWidth })
4063
4270
  ] });
4064
4271
  }
4065
4272
  var ARROW = 12;
4066
4273
  function Anchored({ view, client, palette: p, geometry, rect, width }) {
4067
- const [height, setHeight] = (0, import_react4.useState)(0);
4274
+ const [height, setHeight] = (0, import_react5.useState)(0);
4068
4275
  const onLayout = (e) => setHeight(Math.round(e.nativeEvent.layout.height));
4069
4276
  const offset = view.step.offset + ARROW / 2;
4070
4277
  const pos = positionCard(rect, { width, height: height || 120 }, geometry.viewport, geometry.insets, view.step.placement, offset);
4071
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.View, { pointerEvents: "box-none", style: [styles7.abs, { left: pos.x, top: pos.y, width, opacity: height ? 1 : 0 }], onLayout, children: [
4072
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Arrow, { side: pos.side, at: pos.arrow, color: p.background, border: p.border }),
4073
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(StepCard, { view, client, palette: p, width })
4278
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.View, { pointerEvents: "box-none", style: [styles7.abs, { left: pos.x, top: pos.y, width, opacity: height ? 1 : 0 }], onLayout, children: [
4279
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Arrow, { side: pos.side, at: pos.arrow, color: p.background, border: p.border }),
4280
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(StepCard, { view, client, palette: p, width })
4074
4281
  ] });
4075
4282
  }
4076
4283
  function Arrow({ side, at, color, border }) {
4077
4284
  const half = ARROW / 2;
4078
4285
  const place = side === "bottom" ? { top: -half, left: at - half } : side === "top" ? { bottom: -half, left: at - half } : side === "right" ? { left: -half, top: at - half } : { right: -half, top: at - half };
4079
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.View, { pointerEvents: "none", style: [styles7.abs, styles7.arrow, place, { backgroundColor: color, borderColor: border }] });
4286
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.View, { pointerEvents: "none", style: [styles7.abs, styles7.arrow, place, { backgroundColor: color, borderColor: border }] });
4080
4287
  }
4081
4288
  function Hotspot({ x, y, size, pulse, color, onPress, label }) {
4082
- const scale = (0, import_react4.useRef)(new import_react_native7.Animated.Value(0)).current;
4083
- const [reduceMotion, setReduceMotion] = (0, import_react4.useState)(false);
4084
- (0, import_react4.useEffect)(() => {
4289
+ const scale = (0, import_react5.useRef)(new import_react_native8.Animated.Value(0)).current;
4290
+ const [reduceMotion, setReduceMotion] = (0, import_react5.useState)(false);
4291
+ (0, import_react5.useEffect)(() => {
4085
4292
  let alive = true;
4086
- import_react_native7.AccessibilityInfo.isReduceMotionEnabled?.().then((v) => alive && setReduceMotion(v), () => {
4293
+ import_react_native8.AccessibilityInfo.isReduceMotionEnabled?.().then((v) => alive && setReduceMotion(v), () => {
4087
4294
  });
4088
4295
  return () => {
4089
4296
  alive = false;
4090
4297
  };
4091
4298
  }, []);
4092
- (0, import_react4.useEffect)(() => {
4299
+ (0, import_react5.useEffect)(() => {
4093
4300
  if (!pulse || reduceMotion) return;
4094
- const loop = import_react_native7.Animated.loop(import_react_native7.Animated.timing(scale, { toValue: 1, duration: 1400, easing: import_react_native7.Easing.out(import_react_native7.Easing.ease), useNativeDriver: true }));
4301
+ const loop = import_react_native8.Animated.loop(import_react_native8.Animated.timing(scale, { toValue: 1, duration: 1400, easing: import_react_native8.Easing.out(import_react_native8.Easing.ease), useNativeDriver: true }));
4095
4302
  loop.start();
4096
4303
  return () => loop.stop();
4097
4304
  }, [pulse, reduceMotion, scale]);
4098
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.Pressable, { accessibilityRole: "button", accessibilityLabel: label, onPress, hitSlop: 14, style: [styles7.abs, { left: x, top: y, width: size, height: size }], children: [
4099
- pulse && !reduceMotion ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4100
- import_react_native7.Animated.View,
4305
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.Pressable, { accessibilityRole: "button", accessibilityLabel: label, onPress, hitSlop: 14, style: [styles7.abs, { left: x, top: y, width: size, height: size }], children: [
4306
+ pulse && !reduceMotion ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4307
+ import_react_native8.Animated.View,
4101
4308
  {
4102
4309
  pointerEvents: "none",
4103
4310
  style: [styles7.abs, { width: size, height: size, borderRadius: size / 2, backgroundColor: color, opacity: scale.interpolate({ inputRange: [0, 1], outputRange: [0.55, 0] }), transform: [{ scale: scale.interpolate({ inputRange: [0, 1], outputRange: [1, 2.6] }) }] }]
4104
4311
  }
4105
4312
  ) : null,
4106
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.View, { style: { width: size, height: size, borderRadius: size / 2, backgroundColor: color, borderWidth: 2, borderColor: "#ffffff" } })
4313
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.View, { style: { width: size, height: size, borderRadius: size / 2, backgroundColor: color, borderWidth: 2, borderColor: "#ffffff" } })
4107
4314
  ] });
4108
4315
  }
4109
- var styles7 = import_react_native7.StyleSheet.create({
4316
+ var styles7 = import_react_native8.StyleSheet.create({
4110
4317
  abs: { position: "absolute" },
4111
4318
  center: { alignItems: "center", justifyContent: "center" },
4112
4319
  banner: { position: "absolute" },
4113
- arrow: { width: ARROW, height: ARROW, transform: [{ rotate: "45deg" }], borderWidth: import_react_native7.StyleSheet.hairlineWidth }
4320
+ arrow: { width: ARROW, height: ARROW, transform: [{ rotate: "45deg" }], borderWidth: import_react_native8.StyleSheet.hairlineWidth }
4114
4321
  });
4115
4322
 
4116
4323
  // src/ui/Survey.tsx
4117
- var import_react5 = require("react");
4118
- var import_react_native8 = require("react-native");
4119
- var import_jsx_runtime8 = require("react/jsx-runtime");
4324
+ var import_react6 = require("react");
4325
+ var import_react_native9 = require("react-native");
4326
+ var import_jsx_runtime9 = require("react/jsx-runtime");
4120
4327
  function Survey({
4121
4328
  view,
4122
4329
  client,
@@ -4124,12 +4331,12 @@ function Survey({
4124
4331
  geometry
4125
4332
  }) {
4126
4333
  const config = view.guide.config.survey;
4127
- const [answers, setAnswers] = (0, import_react5.useState)({});
4128
- const [error, setError] = (0, import_react5.useState)("");
4129
- (0, import_react5.useEffect)(() => {
4334
+ const [answers, setAnswers] = (0, import_react6.useState)({});
4335
+ const [error, setError] = (0, import_react6.useState)("");
4336
+ (0, import_react6.useEffect)(() => {
4130
4337
  setAnswers({});
4131
4338
  setError("");
4132
- import_react_native8.AccessibilityInfo.announceForAccessibility?.(
4339
+ import_react_native9.AccessibilityInfo.announceForAccessibility?.(
4133
4340
  `Survey: ${config?.questions[0]?.title ?? view.guide.name}`
4134
4341
  );
4135
4342
  }, [view.key]);
@@ -4146,11 +4353,11 @@ function Survey({
4146
4353
  return next;
4147
4354
  });
4148
4355
  const submit = () => {
4149
- const missing = config.questions.filter(
4356
+ const missing2 = config.questions.filter(
4150
4357
  (q) => q.required && isQuestionVisible(q, answers) && answers[q.id] === void 0
4151
4358
  );
4152
- if (missing.length) {
4153
- setError(`Please answer: ${missing.map((q) => q.title).join(", ")}`);
4359
+ if (missing2.length) {
4360
+ setError(`Please answer: ${missing2.map((q) => q.title).join(", ")}`);
4154
4361
  return;
4155
4362
  }
4156
4363
  setError("");
@@ -4159,23 +4366,23 @@ function Survey({
4159
4366
  const close = () => client.closeSurvey(view.guide.id);
4160
4367
  return (
4161
4368
  // KeyboardAvoidingView owns its bottom padding, so the safe-area padding lives on an inner view.
4162
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4163
- import_react_native8.KeyboardAvoidingView,
4369
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4370
+ import_react_native9.KeyboardAvoidingView,
4164
4371
  {
4165
4372
  pointerEvents: "box-none",
4166
- behavior: import_react_native8.Platform.OS === "ios" ? "padding" : void 0,
4167
- style: import_react_native8.StyleSheet.absoluteFill,
4373
+ behavior: import_react_native9.Platform.OS === "ios" ? "padding" : void 0,
4374
+ style: import_react_native9.StyleSheet.absoluteFill,
4168
4375
  children: [
4169
- centered ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4170
- import_react_native8.Pressable,
4376
+ centered ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4377
+ import_react_native9.Pressable,
4171
4378
  {
4172
4379
  accessible: false,
4173
- style: [import_react_native8.StyleSheet.absoluteFill, { backgroundColor: p.overlay }],
4380
+ style: [import_react_native9.StyleSheet.absoluteFill, { backgroundColor: p.overlay }],
4174
4381
  onPress: close
4175
4382
  }
4176
4383
  ) : null,
4177
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4178
- import_react_native8.View,
4384
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4385
+ import_react_native9.View,
4179
4386
  {
4180
4387
  pointerEvents: "box-none",
4181
4388
  style: [
@@ -4183,21 +4390,21 @@ function Survey({
4183
4390
  centered ? styles8.center : styles8.bottom,
4184
4391
  !centered && { paddingBottom: insets.bottom + 12 }
4185
4392
  ],
4186
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4393
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4187
4394
  Surface,
4188
4395
  {
4189
4396
  palette: p,
4190
4397
  style: { width, maxHeight: viewport.height - insets.top - insets.bottom - 40 },
4191
- children: view.submitted ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.View, { style: styles8.inner, children: [
4192
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4193
- import_react_native8.Text,
4398
+ children: view.submitted ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native9.View, { style: styles8.inner, children: [
4399
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4400
+ import_react_native9.Text,
4194
4401
  {
4195
4402
  accessibilityRole: "header",
4196
4403
  style: [styles8.title, { color: p.text, fontFamily: p.fontFamily }],
4197
4404
  children: config.thankYouTitle
4198
4405
  }
4199
4406
  ),
4200
- config.thankYouBody ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4407
+ config.thankYouBody ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4201
4408
  RichText,
4202
4409
  {
4203
4410
  html: config.thankYouBody,
@@ -4205,18 +4412,18 @@ function Survey({
4205
4412
  onLink: (u) => client.navigate(u)
4206
4413
  }
4207
4414
  ) : null,
4208
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(GButton, { label: "Done", palette: p, onPress: close, style: styles8.end })
4209
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.ScrollView, { contentContainerStyle: styles8.inner, keyboardShouldPersistTaps: "handled", children: [
4210
- config.questions.filter((q) => isQuestionVisible(q, answers)).map((q, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.View, { style: styles8.question, children: [
4211
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.View, { style: styles8.head, children: [
4212
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.Text, { style: [styles8.qTitle, { color: p.text, fontFamily: p.fontFamily }], children: [
4415
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(GButton, { label: "Done", palette: p, onPress: close, style: styles8.end })
4416
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native9.ScrollView, { contentContainerStyle: styles8.inner, keyboardShouldPersistTaps: "handled", children: [
4417
+ config.questions.filter((q) => isQuestionVisible(q, answers)).map((q, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native9.View, { style: styles8.question, children: [
4418
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native9.View, { style: styles8.head, children: [
4419
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native9.Text, { style: [styles8.qTitle, { color: p.text, fontFamily: p.fontFamily }], children: [
4213
4420
  q.title,
4214
- !q.required ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.Text, { style: { color: p.muted, fontWeight: "400" }, children: " (optional)" }) : null
4421
+ !q.required ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.Text, { style: { color: p.muted, fontWeight: "400" }, children: " (optional)" }) : null
4215
4422
  ] }),
4216
- i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CloseButton, { palette: p, onPress: close, label: "Dismiss survey" }) : null
4423
+ i === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(CloseButton, { palette: p, onPress: close, label: "Dismiss survey" }) : null
4217
4424
  ] }),
4218
- q.description ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.Text, { style: [styles8.desc, { color: p.muted }], children: q.description }) : null,
4219
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4425
+ q.description ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.Text, { style: [styles8.desc, { color: p.muted }], children: q.description }) : null,
4426
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4220
4427
  QuestionInput,
4221
4428
  {
4222
4429
  q,
@@ -4226,8 +4433,8 @@ function Survey({
4226
4433
  }
4227
4434
  )
4228
4435
  ] }, q.id)),
4229
- error ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.Text, { accessibilityLiveRegion: "polite", style: [styles8.error], children: error }) : null,
4230
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(GButton, { label: "Submit", palette: p, onPress: submit, style: styles8.end })
4436
+ error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.Text, { accessibilityLiveRegion: "polite", style: [styles8.error], children: error }) : null,
4437
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(GButton, { label: "Submit", palette: p, onPress: submit, style: styles8.end })
4231
4438
  ] })
4232
4439
  }
4233
4440
  )
@@ -4262,37 +4469,37 @@ function QuestionInput({
4262
4469
  const min = q.type === "nps" ? 0 : 1;
4263
4470
  const max = q.type === "nps" ? 10 : q.scaleMax ?? 5;
4264
4471
  const values = Array.from({ length: max - min + 1 }, (_, i) => min + i);
4265
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.View, { children: [
4266
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.View, { style: styles8.scale, accessibilityRole: "radiogroup", accessibilityLabel: q.title, children: values.map((n) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4267
- import_react_native8.Pressable,
4472
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native9.View, { children: [
4473
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.View, { style: styles8.scale, accessibilityRole: "radiogroup", accessibilityLabel: q.title, children: values.map((n) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4474
+ import_react_native9.Pressable,
4268
4475
  {
4269
4476
  accessibilityRole: "radio",
4270
4477
  accessibilityState: { selected: value === n },
4271
4478
  accessibilityLabel: String(n),
4272
4479
  onPress: () => onChange(n),
4273
4480
  style: [...chip(value === n), styles8.scaleItem],
4274
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.Text, { style: chipText(value === n), children: n })
4481
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.Text, { style: chipText(value === n), children: n })
4275
4482
  },
4276
4483
  n
4277
4484
  )) }),
4278
- q.lowLabel || q.highLabel ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.View, { style: styles8.labels, children: [
4279
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.Text, { style: [styles8.small, { color: p.muted }], children: q.lowLabel }),
4280
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.Text, { style: [styles8.small, { color: p.muted }], children: q.highLabel })
4485
+ q.lowLabel || q.highLabel ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native9.View, { style: styles8.labels, children: [
4486
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.Text, { style: [styles8.small, { color: p.muted }], children: q.lowLabel }),
4487
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.Text, { style: [styles8.small, { color: p.muted }], children: q.highLabel })
4281
4488
  ] }) : null
4282
4489
  ] });
4283
4490
  }
4284
4491
  case "rating": {
4285
4492
  const max = q.scaleMax ?? 5;
4286
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.View, { style: styles8.stars, accessibilityRole: "radiogroup", accessibilityLabel: q.title, children: Array.from({ length: max }, (_, i) => i + 1).map((n) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4287
- import_react_native8.Pressable,
4493
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.View, { style: styles8.stars, accessibilityRole: "radiogroup", accessibilityLabel: q.title, children: Array.from({ length: max }, (_, i) => i + 1).map((n) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4494
+ import_react_native9.Pressable,
4288
4495
  {
4289
4496
  accessibilityRole: "radio",
4290
4497
  accessibilityLabel: `${n} of ${max}`,
4291
4498
  accessibilityState: { selected: value === n },
4292
4499
  onPress: () => onChange(n),
4293
4500
  hitSlop: 4,
4294
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4295
- import_react_native8.Text,
4501
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4502
+ import_react_native9.Text,
4296
4503
  {
4297
4504
  style: [
4298
4505
  styles8.star,
@@ -4306,18 +4513,18 @@ function QuestionInput({
4306
4513
  )) });
4307
4514
  }
4308
4515
  case "thumbs":
4309
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.View, { style: styles8.row, accessibilityRole: "radiogroup", children: [
4516
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.View, { style: styles8.row, accessibilityRole: "radiogroup", children: [
4310
4517
  [true, "\u{1F44D}", "Thumbs up"],
4311
4518
  [false, "\u{1F44E}", "Thumbs down"]
4312
- ].map(([val, icon, label]) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4313
- import_react_native8.Pressable,
4519
+ ].map(([val, icon, label]) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4520
+ import_react_native9.Pressable,
4314
4521
  {
4315
4522
  accessibilityRole: "radio",
4316
4523
  accessibilityLabel: label,
4317
4524
  accessibilityState: { selected: value === val },
4318
4525
  onPress: () => onChange(val),
4319
4526
  style: [...chip(value === val), styles8.thumb],
4320
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.Text, { style: styles8.thumbText, children: icon })
4527
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.Text, { style: styles8.thumbText, children: icon })
4321
4528
  },
4322
4529
  String(val)
4323
4530
  )) });
@@ -4327,10 +4534,10 @@ function QuestionInput({
4327
4534
  const selected = new Set(
4328
4535
  multi ? value ?? [] : value !== void 0 ? [String(value)] : []
4329
4536
  );
4330
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.View, { style: styles8.choices, children: q.options.map((o) => {
4537
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.View, { style: styles8.choices, children: q.options.map((o) => {
4331
4538
  const on = selected.has(o.id);
4332
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4333
- import_react_native8.Pressable,
4539
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4540
+ import_react_native9.Pressable,
4334
4541
  {
4335
4542
  accessibilityRole: multi ? "checkbox" : "radio",
4336
4543
  accessibilityState: { checked: on },
@@ -4347,8 +4554,8 @@ function QuestionInput({
4347
4554
  { borderColor: on ? p.primary : p.border, borderRadius: Math.min(10, p.radius) }
4348
4555
  ],
4349
4556
  children: [
4350
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4351
- import_react_native8.View,
4557
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4558
+ import_react_native9.View,
4352
4559
  {
4353
4560
  style: [
4354
4561
  styles8.dot,
@@ -4360,7 +4567,7 @@ function QuestionInput({
4360
4567
  ]
4361
4568
  }
4362
4569
  ),
4363
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.Text, { style: [styles8.choiceText, { color: p.text }], children: o.label })
4570
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.Text, { style: [styles8.choiceText, { color: p.text }], children: o.label })
4364
4571
  ]
4365
4572
  },
4366
4573
  o.id
@@ -4369,8 +4576,8 @@ function QuestionInput({
4369
4576
  }
4370
4577
  case "text":
4371
4578
  default:
4372
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4373
- import_react_native8.TextInput,
4579
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4580
+ import_react_native9.TextInput,
4374
4581
  {
4375
4582
  accessibilityLabel: q.title,
4376
4583
  multiline: true,
@@ -4391,7 +4598,7 @@ function QuestionInput({
4391
4598
  );
4392
4599
  }
4393
4600
  }
4394
- var styles8 = import_react_native8.StyleSheet.create({
4601
+ var styles8 = import_react_native9.StyleSheet.create({
4395
4602
  fill: { flex: 1 },
4396
4603
  center: { alignItems: "center", justifyContent: "center" },
4397
4604
  bottom: { alignItems: "center", justifyContent: "flex-end" },
@@ -4477,29 +4684,29 @@ function palette(theme, systemScheme, explicit) {
4477
4684
  }
4478
4685
 
4479
4686
  // src/ui/Overlay.tsx
4480
- var import_jsx_runtime9 = require("react/jsx-runtime");
4687
+ var import_jsx_runtime10 = require("react/jsx-runtime");
4481
4688
  function defaultInsets(width, height) {
4482
- if (import_react_native9.Platform.OS === "ios") {
4483
- const notch = Math.max(width, height) >= 812 && !import_react_native9.Platform.isPad;
4689
+ if (import_react_native10.Platform.OS === "ios") {
4690
+ const notch = Math.max(width, height) >= 812 && !import_react_native10.Platform.isPad;
4484
4691
  const landscape = width > height;
4485
4692
  return { top: landscape ? 0 : notch ? 50 : 20, bottom: notch ? landscape ? 21 : 34 : 0, left: landscape && notch ? 44 : 0, right: landscape && notch ? 44 : 0 };
4486
4693
  }
4487
- return { top: import_react_native9.StatusBar.currentHeight ?? 24, bottom: 16, left: 0, right: 0 };
4694
+ return { top: import_react_native10.StatusBar.currentHeight ?? 24, bottom: 16, left: 0, right: 0 };
4488
4695
  }
4489
4696
  function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
4490
- const ui = (0, import_react6.useSyncExternalStore)(
4491
- (0, import_react6.useCallback)((cb) => client.subscribeUi(cb), [client]),
4697
+ const ui = (0, import_react7.useSyncExternalStore)(
4698
+ (0, import_react7.useCallback)((cb) => client.subscribeUi(cb), [client]),
4492
4699
  () => client.getUiState(),
4493
4700
  () => client.getUiState()
4494
4701
  );
4495
- const { width, height } = (0, import_react_native9.useWindowDimensions)();
4496
- const scheme = (0, import_react_native9.useColorScheme)();
4497
- const ref = (0, import_react6.useRef)(null);
4498
- const [origin, setOrigin] = (0, import_react6.useState)({ x: 0, y: 0 });
4499
- (0, import_react6.useEffect)(() => {
4702
+ const { width, height } = (0, import_react_native10.useWindowDimensions)();
4703
+ const scheme = (0, import_react_native10.useColorScheme)();
4704
+ const ref = (0, import_react7.useRef)(null);
4705
+ const [origin, setOrigin] = (0, import_react7.useState)({ x: 0, y: 0 });
4706
+ (0, import_react7.useEffect)(() => {
4500
4707
  client.targets.setViewport(width, height);
4501
4708
  }, [client, width, height]);
4502
- const measureOrigin = (0, import_react6.useCallback)(() => {
4709
+ const measureOrigin = (0, import_react7.useCallback)(() => {
4503
4710
  ref.current?.measureInWindow?.((x, y) => {
4504
4711
  if (Number.isFinite(x) && Number.isFinite(y)) setOrigin((o) => o.x === x && o.y === y ? o : { x, y });
4505
4712
  });
@@ -4511,48 +4718,48 @@ function GuidelyOverlay({ client, insets: insetsProp, launcherOffset }) {
4511
4718
  const showRcLauncher = !messenger && !!client.options.showResourceCenterLauncher && !!client.getResourceCenter();
4512
4719
  const messengerLeft = messenger?.getSnapshot().config.launcher.position === "bottom-left";
4513
4720
  const checklistOnSameSide = ui.checklists.some((c) => c.guide.config.checklist?.position === "bottom-left" === messengerLeft) && client.options.showChecklistLauncher !== false;
4514
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native9.View, { ref, collapsable: false, pointerEvents: "box-none", style: import_react_native9.StyleSheet.absoluteFill, onLayout: measureOrigin, children: [
4515
- ui.checklists.map((c) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Checklist, { view: c, client, palette: palette(client.themeFor(c.guide), scheme), geometry, showLauncher: client.options.showChecklistLauncher !== false, launcherBottom: launcherOffset }, c.guide.id)),
4516
- showRcLauncher ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4517
- import_react_native9.Pressable,
4721
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native10.View, { ref, collapsable: false, pointerEvents: "box-none", style: import_react_native10.StyleSheet.absoluteFill, onLayout: measureOrigin, children: [
4722
+ ui.checklists.map((c) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Checklist, { view: c, client, palette: palette(client.themeFor(c.guide), scheme), geometry, showLauncher: client.options.showChecklistLauncher !== false, launcherBottom: launcherOffset }, c.guide.id)),
4723
+ showRcLauncher ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4724
+ import_react_native10.Pressable,
4518
4725
  {
4519
4726
  accessibilityRole: "button",
4520
4727
  accessibilityLabel: client.getResourceCenter()?.launcher.label || "Help",
4521
4728
  onPress: () => client.openResourceCenter(),
4522
4729
  style: [styles9.rc, client.getResourceCenter()?.launcher.position === "bottom-left" ? { left: insets.left + 16 } : { right: insets.right + 16 }, { bottom: insets.bottom + launcherOffset + (ui.checklists.length ? 56 : 0), backgroundColor: basePalette.primary }],
4523
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.Text, { style: [styles9.rcText, { color: basePalette.onPrimary }], children: "?" })
4730
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.Text, { style: [styles9.rcText, { color: basePalette.onPrimary }], children: "?" })
4524
4731
  }
4525
4732
  ) : null,
4526
- messenger && client.options.showMessengerLauncher !== false ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MessengerLauncher, { store: messenger, palette: basePalette, style: [messengerLeft ? { left: insets.left + 16 } : { right: insets.right + 16 }, { bottom: insets.bottom + launcherOffset + (checklistOnSameSide ? 56 : 0) }] }) : null,
4527
- ui.survey ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Survey, { view: ui.survey, client, palette: palette(client.themeFor(ui.survey.guide), scheme), geometry }) : null,
4528
- ui.steps.map((s) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StepLayer, { view: s, client, palette: palette(client.themeFor(s.guide), scheme, s.guide.config.appearance), geometry }, s.key)),
4529
- messenger ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MessengerSheet, { store: messenger, client, palette: basePalette, topInset: insets.top, bottomInset: insets.bottom }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ResourceCenter, { client, palette: basePalette, open: ui.resourceCenterOpen, topInset: insets.top, bottomInset: insets.bottom })
4733
+ messenger && client.options.showMessengerLauncher !== false ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessengerLauncher, { store: messenger, palette: basePalette, style: [messengerLeft ? { left: insets.left + 16 } : { right: insets.right + 16 }, { bottom: insets.bottom + launcherOffset + (checklistOnSameSide ? 56 : 0) }] }) : null,
4734
+ ui.survey ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Survey, { view: ui.survey, client, palette: palette(client.themeFor(ui.survey.guide), scheme), geometry }) : null,
4735
+ ui.steps.map((s) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StepLayer, { view: s, client, palette: palette(client.themeFor(s.guide), scheme, s.guide.config.appearance), geometry }, s.key)),
4736
+ messenger ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessengerSheet, { store: messenger, client, palette: basePalette, topInset: insets.top, bottomInset: insets.bottom }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ResourceCenter, { client, palette: basePalette, open: ui.resourceCenterOpen, topInset: insets.top, bottomInset: insets.bottom })
4530
4737
  ] });
4531
4738
  }
4532
- var styles9 = import_react_native9.StyleSheet.create({
4739
+ var styles9 = import_react_native10.StyleSheet.create({
4533
4740
  rc: { position: "absolute", width: 48, height: 48, borderRadius: 24, alignItems: "center", justifyContent: "center", elevation: 8, shadowColor: "#000", shadowOpacity: 0.2, shadowRadius: 10, shadowOffset: { width: 0, height: 4 } },
4534
4741
  rcText: { fontSize: 22, fontWeight: "800" }
4535
4742
  });
4536
4743
 
4537
4744
  // src/ui/Target.tsx
4538
- var import_react7 = require("react");
4539
- var import_react_native10 = require("react-native");
4540
- var import_jsx_runtime10 = require("react/jsx-runtime");
4541
- var GuidelyContext = (0, import_react7.createContext)(null);
4542
- var GuidelyScreenContext = (0, import_react7.createContext)(null);
4745
+ var import_react8 = require("react");
4746
+ var import_react_native11 = require("react-native");
4747
+ var import_jsx_runtime11 = require("react/jsx-runtime");
4748
+ var GuidelyContext = (0, import_react8.createContext)(null);
4749
+ var GuidelyScreenContext = (0, import_react8.createContext)(null);
4543
4750
  function GuidelyScreenScope({ name, children }) {
4544
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(GuidelyScreenContext.Provider, { value: name, children });
4751
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(GuidelyScreenContext.Provider, { value: name, children });
4545
4752
  }
4546
4753
  function guidelyScreenLayout({ route, children }) {
4547
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(GuidelyScreenScope, { name: route.name, children });
4754
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(GuidelyScreenScope, { name: route.name, children });
4548
4755
  }
4549
4756
  var TAP_SLOP = 10;
4550
4757
  function useGuidelyTarget(id) {
4551
- const client = (0, import_react7.useContext)(GuidelyContext);
4552
- const screen = (0, import_react7.useContext)(GuidelyScreenContext);
4553
- const ref = (0, import_react7.useRef)(null);
4554
- const start = (0, import_react7.useRef)(null);
4555
- (0, import_react7.useEffect)(() => {
4758
+ const client = (0, import_react8.useContext)(GuidelyContext);
4759
+ const screen = (0, import_react8.useContext)(GuidelyScreenContext);
4760
+ const ref = (0, import_react8.useRef)(null);
4761
+ const start = (0, import_react8.useRef)(null);
4762
+ (0, import_react8.useEffect)(() => {
4556
4763
  if (!client || !id) return;
4557
4764
  return client.targets.register(id, {
4558
4765
  screen,
@@ -4563,11 +4770,11 @@ function useGuidelyTarget(id) {
4563
4770
  })
4564
4771
  });
4565
4772
  }, [client, id, screen]);
4566
- const onLayout = (0, import_react7.useCallback)(() => client?.targets.changed(), [client]);
4567
- const onTouchStart = (0, import_react7.useCallback)((e) => {
4773
+ const onLayout = (0, import_react8.useCallback)(() => client?.targets.changed(), [client]);
4774
+ const onTouchStart = (0, import_react8.useCallback)((e) => {
4568
4775
  start.current = { x: e.nativeEvent.pageX, y: e.nativeEvent.pageY };
4569
4776
  }, []);
4570
- const onTouchEnd = (0, import_react7.useCallback)(
4777
+ const onTouchEnd = (0, import_react8.useCallback)(
4571
4778
  (e) => {
4572
4779
  const s = start.current;
4573
4780
  start.current = null;
@@ -4579,8 +4786,8 @@ function useGuidelyTarget(id) {
4579
4786
  }
4580
4787
  function GuidelyTarget({ id, children, onLayout, onTouchStart, onTouchEnd, ...rest }) {
4581
4788
  const t = useGuidelyTarget(id);
4582
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4583
- import_react_native10.View,
4789
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4790
+ import_react_native11.View,
4584
4791
  {
4585
4792
  ...rest,
4586
4793
  ref: t.ref,
@@ -4603,7 +4810,8 @@ function GuidelyTarget({ id, children, onLayout, onTouchStart, onTouchEnd, ...re
4603
4810
  }
4604
4811
 
4605
4812
  // src/index.tsx
4606
- var import_jsx_runtime11 = require("react/jsx-runtime");
4813
+ var import_flags6 = require("@guidelytech/flags");
4814
+ var import_jsx_runtime12 = require("react/jsx-runtime");
4607
4815
  function deviceLocale() {
4608
4816
  try {
4609
4817
  return Intl.DateTimeFormat().resolvedOptions().locale;
@@ -4612,30 +4820,50 @@ function deviceLocale() {
4612
4820
  }
4613
4821
  }
4614
4822
  function defaultBridge(appVersion) {
4615
- const { width, height } = import_react_native11.Dimensions.get("window");
4823
+ const { width, height } = import_react_native12.Dimensions.get("window");
4616
4824
  return {
4617
4825
  device: {
4618
- platform: import_react_native11.Platform.OS === "android" ? "android" : "ios",
4619
- deviceType: import_react_native11.Platform.OS === "ios" && import_react_native11.Platform.isPad || Math.min(width, height) >= 600 ? "tablet" : "mobile",
4826
+ platform: import_react_native12.Platform.OS === "android" ? "android" : "ios",
4827
+ deviceType: import_react_native12.Platform.OS === "ios" && import_react_native12.Platform.isPad || Math.min(width, height) >= 600 ? "tablet" : "mobile",
4620
4828
  locale: deviceLocale(),
4621
4829
  appVersion
4622
4830
  },
4623
- openExternal: (url) => void import_react_native11.Linking.openURL(url).catch(() => {
4831
+ openExternal: (url) => void import_react_native12.Linking.openURL(url).catch(() => {
4624
4832
  })
4625
4833
  };
4626
4834
  }
4627
- function GuidelyProvider({ children, user, insets, launcherOffset = 72, ...options }) {
4628
- const [client, setClient] = (0, import_react8.useState)(null);
4629
- const optionsRef = (0, import_react8.useRef)(options);
4835
+ function GuidelyProvider({ children, user, insets, launcherOffset = 72, flagsPayload, flagAttributes, ...options }) {
4836
+ const [client, setClient] = (0, import_react9.useState)(null);
4837
+ const optionsRef = (0, import_react9.useRef)(options);
4630
4838
  optionsRef.current = options;
4839
+ const userRef = (0, import_react9.useRef)(user);
4840
+ userRef.current = user;
4841
+ const flagsOpts = options.flags === false ? null : options.flags ?? {};
4842
+ const payload = flagsPayload ?? flagsOpts?.payload ?? null;
4843
+ const payloadRef = (0, import_react9.useRef)(payload);
4844
+ payloadRef.current = payload;
4845
+ const appAttributes = (0, import_react9.useMemo)(() => ({ ...flagsOpts?.attributes ?? {}, ...flagAttributes ?? {} }), [(0, import_flags3.stableStringify)(flagsOpts?.attributes ?? {}), (0, import_flags3.stableStringify)(flagAttributes ?? {})]);
4846
+ const appAttributesRef = (0, import_react9.useRef)(appAttributes);
4847
+ appAttributesRef.current = appAttributes;
4631
4848
  const { projectId, environment, apiUrl, storage } = options;
4632
- (0, import_react8.useEffect)(() => {
4849
+ (0, import_react9.useEffect)(() => {
4633
4850
  if (!projectId) return;
4634
4851
  let c;
4635
4852
  try {
4636
4853
  const latest = optionsRef.current;
4637
4854
  c = new GuidelyClient(
4638
- { ...latest, projectId, environment, apiUrl, storage, onNavigate: (url) => optionsRef.current.onNavigate?.(url), onError: (err) => optionsRef.current.onError?.(err) },
4855
+ {
4856
+ ...latest,
4857
+ projectId,
4858
+ environment,
4859
+ apiUrl,
4860
+ storage,
4861
+ // Known up front so flags evaluate for this user from the first render.
4862
+ user: userRef.current?.id ? userRef.current : void 0,
4863
+ flags: latest.flags === false ? false : { ...latest.flags ?? {}, payload: payloadRef.current ?? void 0, attributes: appAttributesRef.current },
4864
+ onNavigate: (url) => optionsRef.current.onNavigate?.(url),
4865
+ onError: (err) => optionsRef.current.onError?.(err)
4866
+ },
4639
4867
  defaultBridge(latest.appVersion)
4640
4868
  );
4641
4869
  } catch (err) {
@@ -4644,7 +4872,7 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, ...optio
4644
4872
  }
4645
4873
  setClient(c);
4646
4874
  void c.init();
4647
- const sub = import_react_native11.AppState.addEventListener("change", (s) => c.setAppState(s));
4875
+ const sub = import_react_native12.AppState.addEventListener("change", (s) => c.setAppState(s));
4648
4876
  return () => {
4649
4877
  sub.remove();
4650
4878
  c.destroy();
@@ -4652,8 +4880,8 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, ...optio
4652
4880
  };
4653
4881
  }, [projectId, environment, apiUrl, storage]);
4654
4882
  const userKey = user ? JSON.stringify([user.id, user.traits ?? null, user.account ?? null]) : "";
4655
- const hadUser = (0, import_react8.useRef)(false);
4656
- (0, import_react8.useEffect)(() => {
4883
+ const hadUser = (0, import_react9.useRef)(false);
4884
+ (0, import_react9.useEffect)(() => {
4657
4885
  if (!client) return;
4658
4886
  if (user && user.id) {
4659
4887
  hadUser.current = true;
@@ -4663,10 +4891,15 @@ function GuidelyProvider({ children, user, insets, launcherOffset = 72, ...optio
4663
4891
  client.reset();
4664
4892
  }
4665
4893
  }, [client, userKey]);
4666
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(GuidelyContext.Provider, { value: client, children: [
4894
+ (0, import_react9.useEffect)(() => {
4895
+ client?.flags.setAttributes(appAttributes);
4896
+ }, [client, appAttributes]);
4897
+ const bootstrap = useBootstrap(payload, { ...user?.id ? { ...user.traits ?? {}, id: user.id } : {}, ...appAttributes });
4898
+ const flags = (0, import_react9.useMemo)(() => ({ client: client?.flags ?? null, bootstrap }), [client, bootstrap]);
4899
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(GuidelyContext.Provider, { value: client, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(FlagsContext.Provider, { value: flags, children: [
4667
4900
  children,
4668
- client ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(GuidelyOverlay, { client, insets, launcherOffset }) : null
4669
- ] });
4901
+ client ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(GuidelyOverlay, { client, insets, launcherOffset }) : null
4902
+ ] }) });
4670
4903
  }
4671
4904
  var NOOP_RESULTS = {
4672
4905
  init: Promise.resolve(),
@@ -4690,20 +4923,38 @@ var NOOP_RESULTS = {
4690
4923
  },
4691
4924
  getState: { ready: false, destroyed: false }
4692
4925
  };
4926
+ var noopFlags = new Proxy((0, import_flags3.createStaticFlags)(), {
4927
+ get(target, prop, receiver) {
4928
+ if (prop in target) return Reflect.get(target, prop, receiver);
4929
+ if (prop === "ready" || prop === "refresh" || prop === "flush") return () => Promise.resolve();
4930
+ if (prop === "getPayload") return () => null;
4931
+ if (prop === "forUser") return () => target;
4932
+ return typeof prop === "string" ? () => void 0 : void 0;
4933
+ }
4934
+ });
4935
+ var missing = (key, fallback) => ({ key, value: fallback, reason: "FLAG_NOT_FOUND", inExperiment: false, usedFallback: true });
4936
+ var NOOP_FUNCTIONS = {
4937
+ isFeatureEnabled: (_key, fallback = false) => fallback,
4938
+ getFeatureValue: (_key, fallback) => fallback,
4939
+ getFeatureDetails: (key, fallback) => missing(key, fallback),
4940
+ onFlagsChange: () => () => {
4941
+ }
4942
+ };
4693
4943
  var noopClient = new Proxy({}, {
4694
4944
  get(_target, prop) {
4695
4945
  if (prop === "options") return { projectId: "" };
4946
+ if (prop === "flags") return noopFlags;
4696
4947
  if (typeof prop !== "string") return void 0;
4697
- return () => NOOP_RESULTS[prop];
4948
+ return NOOP_FUNCTIONS[prop] ?? (() => NOOP_RESULTS[prop]);
4698
4949
  }
4699
4950
  });
4700
4951
  function useGuidely() {
4701
- return (0, import_react8.useContext)(GuidelyContext) ?? noopClient;
4952
+ return (0, import_react9.useContext)(GuidelyContext) ?? noopClient;
4702
4953
  }
4703
4954
  function useGuidelyReady() {
4704
- const client = (0, import_react8.useContext)(GuidelyContext);
4705
- const [ready, setReady] = (0, import_react8.useState)(() => !!client?.isClientReady());
4706
- (0, import_react8.useEffect)(() => {
4955
+ const client = (0, import_react9.useContext)(GuidelyContext);
4956
+ const [ready, setReady] = (0, import_react9.useState)(() => !!client?.isClientReady());
4957
+ (0, import_react9.useEffect)(() => {
4707
4958
  if (!client) return setReady(false);
4708
4959
  setReady(client.isClientReady());
4709
4960
  const off = client.on("ready", () => setReady(true));
@@ -4713,44 +4964,44 @@ function useGuidelyReady() {
4713
4964
  return ready;
4714
4965
  }
4715
4966
  function useGuidelyIdentify(userId, traits, account) {
4716
- const client = (0, import_react8.useContext)(GuidelyContext);
4967
+ const client = (0, import_react9.useContext)(GuidelyContext);
4717
4968
  const key = JSON.stringify([userId ?? null, traits ?? null, account ?? null]);
4718
- (0, import_react8.useEffect)(() => {
4969
+ (0, import_react9.useEffect)(() => {
4719
4970
  if (client && userId) void client.identify(userId, traits, account);
4720
4971
  }, [client, key]);
4721
4972
  }
4722
4973
  function useGuidelyEvent(event, handler) {
4723
- const client = (0, import_react8.useContext)(GuidelyContext);
4724
- const ref = (0, import_react8.useRef)(handler);
4974
+ const client = (0, import_react9.useContext)(GuidelyContext);
4975
+ const ref = (0, import_react9.useRef)(handler);
4725
4976
  ref.current = handler;
4726
- (0, import_react8.useEffect)(() => {
4977
+ (0, import_react9.useEffect)(() => {
4727
4978
  if (!client) return;
4728
4979
  return client.on(event, ((...args) => ref.current(...args)));
4729
4980
  }, [client, event]);
4730
4981
  }
4731
4982
  function useGuidelyScreen(name, properties) {
4732
- const client = (0, import_react8.useContext)(GuidelyContext);
4733
- (0, import_react8.useEffect)(() => {
4983
+ const client = (0, import_react9.useContext)(GuidelyContext);
4984
+ (0, import_react9.useEffect)(() => {
4734
4985
  if (client && name) client.screen(name, properties);
4735
4986
  }, [client, name]);
4736
4987
  }
4737
4988
  function useGuidelyNavigation(navigationRef, options = {}) {
4738
- const client = (0, import_react8.useContext)(GuidelyContext);
4739
- const nameRef = (0, import_react8.useRef)(options.screenName);
4989
+ const client = (0, import_react9.useContext)(GuidelyContext);
4990
+ const nameRef = (0, import_react9.useRef)(options.screenName);
4740
4991
  nameRef.current = options.screenName;
4741
4992
  const report = () => {
4742
4993
  const route = navigationRef.getCurrentRoute?.();
4743
4994
  if (client && route?.name) client.screen(nameRef.current ? nameRef.current(route) : route.name);
4744
4995
  };
4745
- (0, import_react8.useEffect)(report, [client]);
4996
+ (0, import_react9.useEffect)(report, [client]);
4746
4997
  return { onReady: report, onStateChange: report };
4747
4998
  }
4748
4999
  function useUiSelector(select, fallback) {
4749
- const client = (0, import_react8.useContext)(GuidelyContext);
4750
- const selectRef = (0, import_react8.useRef)(select);
5000
+ const client = (0, import_react9.useContext)(GuidelyContext);
5001
+ const selectRef = (0, import_react9.useRef)(select);
4751
5002
  selectRef.current = select;
4752
- const [value, setValue] = (0, import_react8.useState)(() => client ? select(client) : fallback);
4753
- (0, import_react8.useEffect)(() => {
5003
+ const [value, setValue] = (0, import_react9.useState)(() => client ? select(client) : fallback);
5004
+ (0, import_react9.useEffect)(() => {
4754
5005
  if (!client) return;
4755
5006
  let last = JSON.stringify(selectRef.current(client));
4756
5007
  setValue(selectRef.current(client));
@@ -4785,15 +5036,21 @@ function useActiveGuide() {
4785
5036
  // Annotate the CommonJS export names for ESM import in node:
4786
5037
  0 && (module.exports = {
4787
5038
  DEFAULT_API_URL,
5039
+ Feature,
5040
+ FeatureFlag,
5041
+ FeatureValue,
5042
+ FlagsProvider,
4788
5043
  GuidelyClient,
4789
5044
  GuidelyOverlay,
4790
5045
  GuidelyProvider,
4791
5046
  GuidelyScreenScope,
4792
5047
  GuidelyTarget,
5048
+ InMemoryStickyBucketStore,
4793
5049
  Messenger,
4794
5050
  SDK_NAME,
4795
5051
  SDK_VERSION,
4796
5052
  TargetRegistry,
5053
+ createFlagsClient,
4797
5054
  createSseParser,
4798
5055
  guidelyScreenLayout,
4799
5056
  htmlToText,
@@ -4803,6 +5060,12 @@ function useActiveGuide() {
4803
5060
  targetIdsFor,
4804
5061
  useActiveGuide,
4805
5062
  useChecklistProgress,
5063
+ useFeatureValue,
5064
+ useFlag,
5065
+ useFlagDetails,
5066
+ useFlags,
5067
+ useFlagsClient,
5068
+ useFlagsReady,
4806
5069
  useGuideState,
4807
5070
  useGuidely,
4808
5071
  useGuidelyEvent,