@marktiderman/genesis-native 0.1.0 → 0.3.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.
Files changed (87) hide show
  1. package/README.md +354 -10
  2. package/dist/block-C5-noMhB.d.ts +223 -0
  3. package/dist/blocks-a1YQ1Elf.d.ts +1540 -0
  4. package/dist/chunk-65P7RU4V.js +49 -0
  5. package/dist/chunk-65P7RU4V.js.map +1 -0
  6. package/dist/chunk-6BT4PTGH.js +37 -0
  7. package/dist/chunk-6BT4PTGH.js.map +1 -0
  8. package/dist/chunk-75YP72G6.js +374 -0
  9. package/dist/chunk-75YP72G6.js.map +1 -0
  10. package/dist/chunk-NGYNYJPL.js +90 -0
  11. package/dist/chunk-NGYNYJPL.js.map +1 -0
  12. package/dist/chunk-VKTIHNAV.js +95 -0
  13. package/dist/chunk-VKTIHNAV.js.map +1 -0
  14. package/dist/{chunk-MMTPWUWI.js → chunk-VTJDIKFC.js} +9 -6
  15. package/dist/chunk-VTJDIKFC.js.map +1 -0
  16. package/dist/chunk-XE7LC7K6.js +29 -0
  17. package/dist/chunk-XE7LC7K6.js.map +1 -0
  18. package/dist/dev-warnings.d.ts +34 -0
  19. package/dist/dev-warnings.js +15 -0
  20. package/dist/dev-warnings.js.map +1 -0
  21. package/dist/devtools.d.ts +144 -0
  22. package/dist/devtools.js +390 -0
  23. package/dist/devtools.js.map +1 -0
  24. package/dist/index.d.ts +12 -368
  25. package/dist/index.js +88 -191
  26. package/dist/index.js.map +1 -1
  27. package/dist/observability.d.ts +170 -0
  28. package/dist/observability.js +123 -0
  29. package/dist/observability.js.map +1 -0
  30. package/dist/persist.d.ts +1 -1
  31. package/dist/persist.js +1 -1
  32. package/dist/push.d.ts +169 -0
  33. package/dist/push.js +996 -0
  34. package/dist/push.js.map +1 -0
  35. package/dist/{query-DhH3lLFa.d.ts → query-DUmVAfsF.d.ts} +11 -6
  36. package/dist/updates.d.ts +8 -1
  37. package/dist/updates.js +44 -69
  38. package/dist/updates.js.map +1 -1
  39. package/package.json +222 -5
  40. package/src/app.tsx +491 -0
  41. package/src/auth.tsx +176 -0
  42. package/src/blocks.tsx +144 -0
  43. package/src/boot.tsx +188 -0
  44. package/src/dev-warnings.ts +43 -0
  45. package/src/devtools/block.tsx +488 -0
  46. package/src/devtools/gestures.ts +107 -0
  47. package/src/devtools/index.ts +22 -0
  48. package/src/devtools/info.ts +45 -0
  49. package/src/devtools/slots.ts +21 -0
  50. package/src/errors.tsx +195 -0
  51. package/src/held-link.ts +90 -0
  52. package/src/hydration-gate.ts +88 -0
  53. package/src/index.ts +83 -0
  54. package/src/links.ts +140 -0
  55. package/src/nativewind.d.ts +41 -0
  56. package/src/observability/block.tsx +300 -0
  57. package/src/observability/events.ts +89 -0
  58. package/src/observability/index.ts +44 -0
  59. package/src/observability/scrub.ts +166 -0
  60. package/src/observability/sentry.ts +222 -0
  61. package/src/observability/sink.ts +147 -0
  62. package/src/persist.ts +83 -0
  63. package/src/push/backend.ts +129 -0
  64. package/src/push/block.tsx +545 -0
  65. package/src/push/index.ts +102 -0
  66. package/src/push/modules.ts +111 -0
  67. package/src/push/permission.ts +191 -0
  68. package/src/push/preferences.ts +136 -0
  69. package/src/push/quiet-hours.ts +71 -0
  70. package/src/push/registration.ts +189 -0
  71. package/src/push/routing.ts +27 -0
  72. package/src/push/screens.tsx +46 -0
  73. package/src/push/settings.tsx +378 -0
  74. package/src/query.ts +100 -0
  75. package/src/updates/block.tsx +442 -0
  76. package/src/updates/channels.ts +131 -0
  77. package/src/updates/checker.ts +190 -0
  78. package/src/updates/index.ts +87 -0
  79. package/src/updates/modules.ts +64 -0
  80. package/src/updates/policy.ts +314 -0
  81. package/src/updates/screens.tsx +159 -0
  82. package/src/updates/signature.ts +80 -0
  83. package/src/updates/store.ts +33 -0
  84. package/src/updates/version.ts +39 -0
  85. package/src/updates/whats-new.ts +122 -0
  86. package/dist/block-Dh5ny06W.d.ts +0 -535
  87. package/dist/chunk-MMTPWUWI.js.map +0 -1
@@ -0,0 +1,390 @@
1
+ import { useBlocks } from './chunk-65P7RU4V.js';
2
+ import { versionSignature } from './chunk-6BT4PTGH.js';
3
+ import { scrubString, useRecentActivity, scrubUrl } from './chunk-75YP72G6.js';
4
+ import { useSession } from './chunk-NGYNYJPL.js';
5
+ import { useSyncExternalStore, useState, useEffect, useRef, useMemo } from 'react';
6
+ import { View, Modal, ScrollView, Alert, Share } from 'react-native';
7
+ import { useQueryClient } from '@tanstack/react-query';
8
+ import { renderSlot } from '@marktiderman/genesis-core/slots';
9
+ import { NativeText, NativeButton, NativeInput } from '@marktiderman/genesis-ui-native/components';
10
+ import { useThemeRoles } from '@marktiderman/genesis-ui-native/hooks';
11
+ import { defineBlock } from '@marktiderman/genesis-features/blocks';
12
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
13
+
14
+ // src/devtools/gestures.ts
15
+ function createHoldDetector(options) {
16
+ const fingers = Math.max(2, options.fingers ?? 3);
17
+ const holdMs = options.holdMs ?? 800;
18
+ const setTimer = options.setTimer ?? ((run, ms) => setTimeout(run, ms));
19
+ const clearTimer = options.clearTimer ?? ((handle) => clearTimeout(handle));
20
+ let timer = null;
21
+ let fired = false;
22
+ const cancel = () => {
23
+ if (timer !== null) clearTimer(timer);
24
+ timer = null;
25
+ };
26
+ return {
27
+ touches(count) {
28
+ if (count < fingers) {
29
+ cancel();
30
+ fired = false;
31
+ return;
32
+ }
33
+ if (timer !== null || fired) return;
34
+ timer = setTimer(() => {
35
+ timer = null;
36
+ fired = true;
37
+ options.onTrigger();
38
+ }, holdMs);
39
+ },
40
+ cancel() {
41
+ cancel();
42
+ fired = false;
43
+ }
44
+ };
45
+ }
46
+ function createShakeDetector(options) {
47
+ const threshold = options.threshold ?? 1.5;
48
+ const required = options.required ?? 3;
49
+ const windowMs = options.windowMs ?? 800;
50
+ const cooldownMs = options.cooldownMs ?? 2e3;
51
+ const now = options.now ?? Date.now;
52
+ let hits = [];
53
+ let lastFired = -Infinity;
54
+ return {
55
+ sample({ x, y, z }) {
56
+ const at = now();
57
+ if (at - lastFired < cooldownMs) return;
58
+ if (Math.sqrt(x * x + y * y + z * z) - 1 <= threshold) return;
59
+ hits = hits.filter((t) => at - t <= windowMs);
60
+ hits.push(at);
61
+ if (hits.length >= required) {
62
+ hits = [];
63
+ lastFired = at;
64
+ options.onShake();
65
+ }
66
+ }
67
+ };
68
+ }
69
+
70
+ // src/devtools/info.ts
71
+ var UNKNOWN = "unknown";
72
+ function flagValue(value) {
73
+ if (typeof value === "boolean" || typeof value === "number") return String(value);
74
+ if (typeof value === "string") return scrubString(value);
75
+ return value === null || value === void 0 ? "unset" : "(object)";
76
+ }
77
+ function buildDebugInfo(input) {
78
+ const lines = [`Debug info ${new Date(input.at ?? Date.now()).toISOString()}`];
79
+ lines.push(...input.signature ? input.signature.text.split("\n") : ["Version unknown (no updates block)"]);
80
+ lines.push(`User ${input.userId ?? "signed out"}`);
81
+ lines.push(`Server ${input.baseUrl ? scrubString(input.baseUrl.replace(/[?#].*$/, "")) : UNKNOWN}`);
82
+ lines.push(`Blocks ${input.blocks && input.blocks.length > 0 ? input.blocks.join(", ") : "none"}`);
83
+ const flags = Object.entries(input.flags ?? {});
84
+ lines.push(`Flags ${flags.length > 0 ? flags.map(([k, v]) => `${k}=${flagValue(v)}`).join(", ") : "none"}`);
85
+ const errors = input.recent?.errors ?? [];
86
+ lines.push(`Errors (${errors.length})`);
87
+ for (const e of errors.slice(-10)) lines.push(` ${new Date(e.at).toISOString()} ${e.fatal ? "fatal " : ""}${e.source} ${e.name}: ${e.message}`);
88
+ const crumbs = input.recent?.breadcrumbs ?? [];
89
+ lines.push(`Breadcrumbs (${crumbs.length})`);
90
+ for (const c of crumbs.slice(-20)) lines.push(` ${c.timestamp ? new Date(c.timestamp).toISOString() : UNKNOWN} ${c.category} ${c.message ?? ""}`.trimEnd());
91
+ return lines.join("\n");
92
+ }
93
+ var DEVTOOLS_BLOCK_ID = "devtools-overlay";
94
+ function createDevtools(options) {
95
+ const enabled = options.enabled === true;
96
+ let state = { open: false };
97
+ const listeners = /* @__PURE__ */ new Set();
98
+ const set = (next) => {
99
+ if (next.open === state.open) return;
100
+ state = next;
101
+ listeners.forEach((l) => l());
102
+ };
103
+ const subscribe = (listener) => {
104
+ listeners.add(listener);
105
+ return () => listeners.delete(listener);
106
+ };
107
+ const getState = () => state;
108
+ let sheetShown = false;
109
+ let dismissed = [];
110
+ const resolveWaiting = () => {
111
+ const waiting = dismissed;
112
+ dismissed = [];
113
+ waiting.forEach((resolve) => resolve());
114
+ };
115
+ const settleDismissed = () => {
116
+ sheetShown = false;
117
+ resolveWaiting();
118
+ };
119
+ const close = () => {
120
+ set({ open: false });
121
+ if (!sheetShown) return Promise.resolve();
122
+ return new Promise((resolve) => dismissed.push(resolve));
123
+ };
124
+ const declaration = defineBlock({
125
+ id: DEVTOOLS_BLOCK_ID,
126
+ platforms: ["native"],
127
+ contributes: {
128
+ privacy: { notes: ["Development, preview and internal builds only; shows on the device what the app knows about itself and sends nothing."] }
129
+ }
130
+ });
131
+ function CrashTest() {
132
+ throw new Error("Devtools crash test: a render error on purpose.");
133
+ }
134
+ let menuAdded = false;
135
+ function DevtoolsRoot({ slots, slotProps, children }) {
136
+ const { open } = useSyncExternalStore(subscribe, getState, getState);
137
+ const [crash, setCrash] = useState(false);
138
+ const [mounted, setMounted] = useState(open);
139
+ const shown = open || mounted;
140
+ useEffect(() => () => settleDismissed(), []);
141
+ useEffect(() => {
142
+ if (open) {
143
+ resolveWaiting();
144
+ setMounted(true);
145
+ sheetShown = true;
146
+ return;
147
+ }
148
+ if (!mounted) return;
149
+ const fallback = setTimeout(() => {
150
+ setMounted(false);
151
+ settleDismissed();
152
+ }, DISMISS_FALLBACK_MS);
153
+ return () => clearTimeout(fallback);
154
+ }, [open, mounted]);
155
+ const onDismissed = () => {
156
+ if (getState().open) return;
157
+ setMounted(false);
158
+ settleDismissed();
159
+ };
160
+ useEffect(() => {
161
+ if (!options.devSettings || menuAdded) return;
162
+ menuAdded = true;
163
+ options.devSettings.addMenuItem("Open devtools", () => set({ open: true }));
164
+ }, []);
165
+ useEffect(() => {
166
+ const accelerometer = options.accelerometer;
167
+ if (!accelerometer) return;
168
+ let subscription = null;
169
+ let stopped = false;
170
+ const detector = createShakeDetector({ onShake: () => set({ open: true }) });
171
+ void (async () => {
172
+ try {
173
+ if (accelerometer.isAvailableAsync && !await accelerometer.isAvailableAsync()) return;
174
+ if (stopped) return;
175
+ accelerometer.setUpdateInterval?.(100);
176
+ subscription = accelerometer.addListener((reading) => detector.sample(reading));
177
+ } catch {
178
+ }
179
+ })();
180
+ return () => {
181
+ stopped = true;
182
+ subscription?.remove();
183
+ };
184
+ }, []);
185
+ const hold = useRef(options.hold === false ? null : createHoldDetector({ ...options.hold || {}, onTrigger: () => set({ open: true }) }));
186
+ useEffect(() => () => hold.current?.cancel(), []);
187
+ const onTouches = hold.current ? (event) => hold.current?.touches(event.nativeEvent.touches.length) : void 0;
188
+ return /* @__PURE__ */ jsxs(View, { style: { flex: 1 }, onTouchStart: onTouches, onTouchMove: onTouches, onTouchEnd: onTouches, onTouchCancel: onTouches, children: [
189
+ children,
190
+ crash ? /* @__PURE__ */ jsx(CrashTest, {}) : null,
191
+ shown ? /* @__PURE__ */ jsx(
192
+ DevtoolsSheet,
193
+ {
194
+ visible: open,
195
+ onDismissed,
196
+ block,
197
+ Feedback: slots?.devtoolsFeedback,
198
+ feedbackProps: slotProps?.devtoolsFeedback,
199
+ onCrash: () => {
200
+ set({ open: false });
201
+ setCrash(true);
202
+ }
203
+ }
204
+ ) : null
205
+ ] });
206
+ }
207
+ const block = {
208
+ ...declaration,
209
+ enabled,
210
+ options,
211
+ open: () => enabled && set({ open: true }),
212
+ close,
213
+ getState,
214
+ subscribe,
215
+ ...enabled ? { Root: DevtoolsRoot } : {}
216
+ };
217
+ return block;
218
+ }
219
+ function NoFeedback(_props) {
220
+ return null;
221
+ }
222
+ function useSignature(options) {
223
+ const updates = options.updates;
224
+ const [requested, setRequested] = useState(null);
225
+ useEffect(() => {
226
+ if (!updates) return;
227
+ let alive = true;
228
+ void updates.channels.requested().then((value) => alive && setRequested(value));
229
+ return () => {
230
+ alive = false;
231
+ };
232
+ }, [updates]);
233
+ return useMemo(() => {
234
+ if (!updates) return options.signature ?? null;
235
+ if (!requested) return updates.signature;
236
+ const { application, updates: modules, platform } = updates.options;
237
+ return versionSignature({ application, updates: modules, platform, requestedChannel: requested });
238
+ }, [updates, requested, options.signature]);
239
+ }
240
+ var RESULT_TEXT = {
241
+ reloading: "Reloading\u2026",
242
+ blocked: "Set; it applies on the next launch (something is mid-edit).",
243
+ unavailable: "Not available in this build.",
244
+ error: "Set, but checking or reloading failed."
245
+ };
246
+ var DISMISS_FALLBACK_MS = 800;
247
+ function Row({ label, value, testID }) {
248
+ return /* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", justifyContent: "space-between", gap: 12, paddingVertical: 4 }, testID, children: [
249
+ /* @__PURE__ */ jsx(NativeText, { preset: "label", children: label }),
250
+ /* @__PURE__ */ jsx(NativeText, { preset: "body-sm", className: "text-right text-muted-foreground", style: { flex: 1 }, selectable: true, children: value })
251
+ ] });
252
+ }
253
+ function Section({ title, children }) {
254
+ const roles = useThemeRoles();
255
+ return /* @__PURE__ */ jsxs(View, { style: { gap: 4, borderTopWidth: 1, borderTopColor: roles.separator, paddingTop: 12 }, children: [
256
+ /* @__PURE__ */ jsx(NativeText, { preset: "label", accessibilityRole: "header", children: title }),
257
+ children
258
+ ] });
259
+ }
260
+ function DevtoolsSheet({ visible, onDismissed, block, Feedback, feedbackProps, onCrash }) {
261
+ const { options } = block;
262
+ const roles = useThemeRoles();
263
+ const { userId } = useSession();
264
+ const blocks = useBlocks();
265
+ const recent = useRecentActivity(options.observability ?? null);
266
+ const signature = useSignature(options);
267
+ const queryClient = useQueryClient();
268
+ const [channel, setChannel] = useState("");
269
+ const [status, setStatus] = useState(null);
270
+ const channels = options.updates?.channels;
271
+ const close = () => block.close();
272
+ const info = () => buildDebugInfo({ signature, userId, blocks: [...blocks.ids], flags: options.flags, baseUrl: options.baseUrl ?? null, recent });
273
+ const run = (label, action) => {
274
+ Promise.resolve().then(action).then(
275
+ () => setStatus(`${label}: done`),
276
+ (error) => setStatus(`${label}: ${error instanceof Error ? error.message : "failed"}`)
277
+ );
278
+ };
279
+ const copy = () => run("Copy debug info", () => options.copy ? options.copy(info()) : Share.share({ message: info() }));
280
+ const clearCache = () => run("Clear cache", async () => {
281
+ queryClient.clear();
282
+ await options.clearCache?.();
283
+ });
284
+ const confirm = (title, body, action, onConfirm) => Alert.alert(title, body, [
285
+ { text: "Cancel", style: "cancel" },
286
+ { text: action, style: "destructive", onPress: onConfirm }
287
+ ]);
288
+ const switchChannel = () => {
289
+ if (!channels) return;
290
+ run("Switch channel", async () => {
291
+ const result = await channels.switchTo(channel);
292
+ setStatus(RESULT_TEXT[result.kind]);
293
+ });
294
+ };
295
+ const resetChannel = () => {
296
+ if (!channels) return;
297
+ run("Build channel", async () => {
298
+ const result = await channels.reset();
299
+ setStatus(RESULT_TEXT[result.kind]);
300
+ });
301
+ };
302
+ const flags = Object.entries(options.flags ?? {});
303
+ return (
304
+ // React Native's own modal, as a page sheet on iOS: it needs no provider above it (a bottom-sheet
305
+ // library's modal does), so the overlay opens whatever the app has mounted.
306
+ /* @__PURE__ */ jsx(
307
+ Modal,
308
+ {
309
+ visible,
310
+ animationType: "slide",
311
+ presentationStyle: "pageSheet",
312
+ onRequestClose: () => void close(),
313
+ onDismiss: onDismissed,
314
+ testID: "genesis-devtools",
315
+ children: /* @__PURE__ */ jsxs(ScrollView, { style: { flex: 1, backgroundColor: roles.background }, contentContainerStyle: { gap: 12, padding: 16, paddingBottom: 48 }, children: [
316
+ /* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "space-between" }, children: [
317
+ /* @__PURE__ */ jsx(NativeText, { preset: "h3", accessibilityRole: "header", children: "Devtools" }),
318
+ /* @__PURE__ */ jsx(NativeButton, { variant: "ghost", size: "sm", onPress: () => void close(), testID: "genesis-devtools-close", children: "Close" })
319
+ ] }),
320
+ status ? /* @__PURE__ */ jsx(NativeText, { preset: "body-sm", testID: "genesis-devtools-status", children: status }) : null,
321
+ /* @__PURE__ */ jsx(Section, { title: "Build", children: /* @__PURE__ */ jsx(NativeText, { preset: "body-sm", selectable: true, testID: "genesis-devtools-signature", children: signature?.text ?? "Version unknown (no updates block)" }) }),
322
+ channels ? /* @__PURE__ */ jsxs(Section, { title: "Update channel", children: [
323
+ /* @__PURE__ */ jsx(Row, { label: "Channel", value: signature?.requestedChannel ?? signature?.channel ?? "none", testID: "genesis-devtools-channel" }),
324
+ channels.enabled ? /* @__PURE__ */ jsxs(Fragment, { children: [
325
+ /* @__PURE__ */ jsx(
326
+ NativeInput,
327
+ {
328
+ value: channel,
329
+ onChangeText: setChannel,
330
+ placeholder: "pr-42",
331
+ autoCapitalize: "none",
332
+ autoCorrect: false,
333
+ accessibilityLabel: "Channel to switch to",
334
+ testID: "genesis-devtools-channel-input"
335
+ }
336
+ ),
337
+ /* @__PURE__ */ jsx(NativeButton, { size: "sm", onPress: switchChannel, disabled: !channel.trim(), testID: "genesis-devtools-channel-switch", children: "Switch and reload" })
338
+ ] }) : /* @__PURE__ */ jsx(NativeText, { preset: "body-sm", className: "text-muted-foreground", children: "Switching is off in this build." }),
339
+ /* @__PURE__ */ jsx(NativeButton, { size: "sm", variant: "ghost", onPress: resetChannel, testID: "genesis-devtools-channel-reset", children: "Back to the build's channel" })
340
+ ] }) : null,
341
+ /* @__PURE__ */ jsxs(Section, { title: "Session", children: [
342
+ /* @__PURE__ */ jsx(Row, { label: "User id", value: userId ?? "signed out", testID: "genesis-devtools-user" }),
343
+ /* @__PURE__ */ jsx(Row, { label: "Server", value: options.baseUrl ? scrubUrl(options.baseUrl) : "unknown", testID: "genesis-devtools-server" })
344
+ ] }),
345
+ /* @__PURE__ */ jsxs(Section, { title: "Blocks and flags", children: [
346
+ /* @__PURE__ */ jsx(Row, { label: "Blocks", value: blocks.ids.size > 0 ? [...blocks.ids].join(", ") : "none", testID: "genesis-devtools-blocks" }),
347
+ flags.map(([name, value]) => /* @__PURE__ */ jsx(Row, { label: name, value: typeof value === "string" ? scrubString(value) : String(value), testID: `genesis-devtools-flag-${name}` }, name))
348
+ ] }),
349
+ /* @__PURE__ */ jsxs(Section, { title: `Errors (${recent.errors.length})`, children: [
350
+ recent.errors.length === 0 ? /* @__PURE__ */ jsx(NativeText, { preset: "body-sm", children: "None" }) : null,
351
+ recent.errors.slice(-10).reverse().map((e) => /* @__PURE__ */ jsx(NativeText, { preset: "body-sm", testID: "genesis-devtools-error", children: `${e.fatal ? "fatal " : ""}${e.source} \xB7 ${e.name}: ${e.message}` }, `${e.at}-${e.message}`))
352
+ ] }),
353
+ /* @__PURE__ */ jsxs(Section, { title: `Breadcrumbs (${recent.breadcrumbs.length})`, children: [
354
+ recent.breadcrumbs.length === 0 ? /* @__PURE__ */ jsx(NativeText, { preset: "body-sm", children: "None" }) : null,
355
+ recent.breadcrumbs.slice(-15).reverse().map((c, i) => /* @__PURE__ */ jsx(NativeText, { preset: "body-sm", testID: "genesis-devtools-breadcrumb", children: `${c.category} \xB7 ${c.message ?? ""}` }, `${c.timestamp ?? 0}-${i}`))
356
+ ] }),
357
+ /* @__PURE__ */ jsx(Section, { title: "Actions", children: /* @__PURE__ */ jsxs(View, { style: { gap: 8 }, children: [
358
+ /* @__PURE__ */ jsx(NativeButton, { size: "sm", variant: "outline", onPress: copy, testID: "genesis-devtools-copy", children: "Copy debug info" }),
359
+ renderSlot(NoFeedback, Feedback, { debugInfo: info(), close }, feedbackProps),
360
+ /* @__PURE__ */ jsx(NativeButton, { size: "sm", variant: "outline", onPress: clearCache, testID: "genesis-devtools-clear-cache", children: "Clear cache" }),
361
+ /* @__PURE__ */ jsx(
362
+ NativeButton,
363
+ {
364
+ size: "sm",
365
+ variant: "outline",
366
+ onPress: () => confirm("Crash test", "This throws a render error: the fatal screen shows and the error is reported.", "Crash", onCrash),
367
+ testID: "genesis-devtools-crash",
368
+ children: "Crash test"
369
+ }
370
+ ),
371
+ options.signOut ? /* @__PURE__ */ jsx(
372
+ NativeButton,
373
+ {
374
+ size: "sm",
375
+ variant: "destructive",
376
+ onPress: () => confirm("Sign out", "Sign out of this device?", "Sign out", () => run("Sign out", options.signOut)),
377
+ testID: "genesis-devtools-sign-out",
378
+ children: "Sign out"
379
+ }
380
+ ) : null
381
+ ] }) })
382
+ ] })
383
+ }
384
+ )
385
+ );
386
+ }
387
+
388
+ export { DEVTOOLS_BLOCK_ID, buildDebugInfo, createDevtools, createHoldDetector, createShakeDetector };
389
+ //# sourceMappingURL=devtools.js.map
390
+ //# sourceMappingURL=devtools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/devtools/gestures.ts","../src/devtools/info.ts","../src/devtools/block.tsx"],"names":[],"mappings":"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","file":"devtools.js","sourcesContent":["/**\n * The overlay's two ways in, as plain state machines (the root feeds them\n * touches and accelerometer samples; tests feed them numbers).\n *\n * - A hold with three fingers (`fingers`) for `holdMs`. Works in every build\n * with no native module.\n * - A shake, from an accelerometer (`Accelerometer` from expo-sensors, in g):\n * `required` samples above `threshold` g (gravity removed) within\n * `windowMs`, then nothing for `cooldownMs`.\n */\n\nexport interface HoldDetectorOptions {\n /** Default 3. */\n fingers?: number;\n /** Default 800 ms. */\n holdMs?: number;\n onTrigger: () => void;\n setTimer?: (run: () => void, ms: number) => unknown;\n clearTimer?: (handle: unknown) => void;\n}\n\nexport interface HoldDetector {\n /** The number of fingers down now (`event.nativeEvent.touches.length`). */\n touches(count: number): void;\n cancel(): void;\n}\n\nexport function createHoldDetector(options: HoldDetectorOptions): HoldDetector {\n const fingers = Math.max(2, options.fingers ?? 3);\n const holdMs = options.holdMs ?? 800;\n const setTimer = options.setTimer ?? ((run, ms) => setTimeout(run, ms));\n const clearTimer = options.clearTimer ?? ((handle) => clearTimeout(handle as ReturnType<typeof setTimeout>));\n let timer: unknown = null;\n // One trigger per hold: lifting a finger re-arms it.\n let fired = false;\n const cancel = () => {\n if (timer !== null) clearTimer(timer);\n timer = null;\n };\n return {\n touches(count) {\n if (count < fingers) {\n cancel();\n fired = false;\n return;\n }\n if (timer !== null || fired) return;\n timer = setTimer(() => {\n timer = null;\n fired = true;\n options.onTrigger();\n }, holdMs);\n },\n cancel() {\n cancel();\n fired = false;\n },\n };\n}\n\nexport interface ShakeDetectorOptions {\n /** g above gravity a sample must reach. Default 1.5. */\n threshold?: number;\n /** Samples over the threshold needed. Default 3. */\n required?: number;\n /** Default 800 ms. */\n windowMs?: number;\n /** Default 2000 ms. */\n cooldownMs?: number;\n onShake: () => void;\n now?: () => number;\n}\n\nexport interface ShakeDetector {\n sample(reading: { x: number; y: number; z: number }): void;\n}\n\nexport function createShakeDetector(options: ShakeDetectorOptions): ShakeDetector {\n const threshold = options.threshold ?? 1.5;\n const required = options.required ?? 3;\n const windowMs = options.windowMs ?? 800;\n const cooldownMs = options.cooldownMs ?? 2000;\n const now = options.now ?? Date.now;\n let hits: number[] = [];\n let lastFired = -Infinity;\n return {\n sample({ x, y, z }) {\n const at = now();\n if (at - lastFired < cooldownMs) return;\n if (Math.sqrt(x * x + y * y + z * z) - 1 <= threshold) return;\n hits = hits.filter((t) => at - t <= windowMs);\n hits.push(at);\n if (hits.length >= required) {\n hits = [];\n lastFired = at;\n options.onShake();\n }\n },\n };\n}\n\n/** `Accelerometer` from expo-sensors, or a stand-in. */\nexport interface AccelerometerLike {\n isAvailableAsync?(): Promise<boolean>;\n setUpdateInterval?(ms: number): void;\n addListener(listener: (reading: { x: number; y: number; z: number }) => void): { remove(): void };\n}\n","/**\n * The overlay's report, as text for \"Copy debug info\" and the feedback slot.\n * Built from what the overlay shows, already scrubbed where it came from the\n * observability block; the user is an id, never an email.\n */\nimport type { RecentActivity } from \"../observability/block\";\nimport { scrubString } from \"../observability/scrub\";\nimport type { VersionSignature } from \"../updates/signature\";\n\nexport interface DebugInfoInput {\n signature?: VersionSignature | null;\n userId?: string | null;\n blocks?: readonly string[];\n flags?: Readonly<Record<string, unknown>>;\n baseUrl?: string | null;\n recent?: RecentActivity;\n /** Default now. */\n at?: number;\n}\n\nconst UNKNOWN = \"unknown\";\n\nfunction flagValue(value: unknown): string {\n if (typeof value === \"boolean\" || typeof value === \"number\") return String(value);\n if (typeof value === \"string\") return scrubString(value);\n return value === null || value === undefined ? \"unset\" : \"(object)\";\n}\n\n/** Several lines a person can paste into a bug report. Unknown values are written `unknown`, never left out. */\nexport function buildDebugInfo(input: DebugInfoInput): string {\n const lines: string[] = [`Debug info ${new Date(input.at ?? Date.now()).toISOString()}`];\n lines.push(...(input.signature ? input.signature.text.split(\"\\n\") : [\"Version unknown (no updates block)\"]));\n lines.push(`User ${input.userId ?? \"signed out\"}`);\n lines.push(`Server ${input.baseUrl ? scrubString(input.baseUrl.replace(/[?#].*$/, \"\")) : UNKNOWN}`);\n lines.push(`Blocks ${input.blocks && input.blocks.length > 0 ? input.blocks.join(\", \") : \"none\"}`);\n const flags = Object.entries(input.flags ?? {});\n lines.push(`Flags ${flags.length > 0 ? flags.map(([k, v]) => `${k}=${flagValue(v)}`).join(\", \") : \"none\"}`);\n const errors = input.recent?.errors ?? [];\n lines.push(`Errors (${errors.length})`);\n for (const e of errors.slice(-10)) lines.push(` ${new Date(e.at).toISOString()} ${e.fatal ? \"fatal \" : \"\"}${e.source} ${e.name}: ${e.message}`);\n const crumbs = input.recent?.breadcrumbs ?? [];\n lines.push(`Breadcrumbs (${crumbs.length})`);\n for (const c of crumbs.slice(-20)) lines.push(` ${c.timestamp ? new Date(c.timestamp).toISOString() : UNKNOWN} ${c.category} ${c.message ?? \"\"}`.trimEnd());\n return lines.join(\"\\n\");\n}\n","/**\n * `createDevtools(options)`: the devtools overlay, for development, preview\n * and internal builds only. A hold with three fingers (in every build), a\n * shake (with an accelerometer) or \"Open devtools\" in React Native's dev menu\n * opens a sheet with:\n *\n * - the version signature, the update channel and a channel switch (the\n * updates block's guarded `channels`: on only where it allows it);\n * - the signed-in user's id, the blocks in effect (`useBlocks`) and the\n * app's flags;\n * - the recent breadcrumbs and errors (the observability block's, scrubbed);\n * - the server's base URL;\n * - \"Copy debug info\", \"Crash test\", \"Clear cache\" and \"Sign out\";\n * - the `devtoolsFeedback` slot, which the support block fills with\n * \"Send feedback\".\n *\n * Keep it out of production bundles: import this entry only behind a\n * condition Metro can fold at build time (see the README), and pass\n * `enabled` from a fact of the binary as a second lock. With `enabled`\n * false the block mounts nothing and listens to nothing.\n */\nimport { useEffect, useMemo, useRef, useState, useSyncExternalStore, type ComponentType, type ReactNode } from \"react\";\nimport { Alert, Modal, ScrollView, Share, View, type GestureResponderEvent } from \"react-native\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { renderSlot } from \"@marktiderman/genesis-core/slots\";\nimport { NativeButton, NativeInput, NativeText } from \"@marktiderman/genesis-ui-native/components\";\nimport { useThemeRoles } from \"@marktiderman/genesis-ui-native/hooks\";\nimport { defineBlock } from \"@marktiderman/genesis-features/blocks\";\nimport { useSession } from \"../auth\";\nimport { useBlocks, type NativeBlock, type NativeBlockRootProps } from \"../blocks\";\nimport { useRecentActivity, type ObservabilityBlock } from \"../observability/block\";\nimport { scrubString, scrubUrl } from \"../observability/scrub\";\nimport type { UpdatesBlock } from \"../updates/block\";\nimport type { ChannelSwitchResult } from \"../updates/channels\";\nimport { versionSignature, type VersionSignature } from \"../updates/signature\";\nimport { createHoldDetector, createShakeDetector, type AccelerometerLike } from \"./gestures\";\nimport { buildDebugInfo } from \"./info\";\nimport type { DevtoolsFeedbackProps } from \"./slots\";\n\n/** The FeatureId of the devtools overlay. */\nexport const DEVTOOLS_BLOCK_ID = \"devtools-overlay\";\n\n/** React Native's `DevSettings` (development builds only), or a stand-in. */\nexport interface DevSettingsLike {\n addMenuItem(title: string, handler: () => void): void;\n}\n\nexport interface DevtoolsOptions {\n /**\n * Must be explicitly true: `__DEV__` or a fact of the binary (a `.dev` or\n * `.preview` bundle id). False mounts nothing.\n */\n enabled: boolean;\n /** The updates block: the signature, the channel and the channel switch. */\n updates?: UpdatesBlock;\n /** Without the updates block: the signature to show (`versionSignature(…)`). */\n signature?: VersionSignature;\n /** The observability block: recent breadcrumbs and errors. */\n observability?: ObservabilityBlock | null;\n /** The server the app talks to (the Supabase URL). Shown without its query string. */\n baseUrl?: string;\n /** The app's switches and flags, shown by name and value. */\n flags?: Readonly<Record<string, unknown>>;\n /** `Accelerometer` from expo-sensors: shake to open. Without it, the hold and the dev menu still work. */\n accelerometer?: AccelerometerLike;\n /** `DevSettings` from react-native: \"Open devtools\" in the dev menu (development builds). */\n devSettings?: DevSettingsLike;\n /** The hold that opens the overlay, or `false` for none. Default three fingers for 800 ms. */\n hold?: false | { fingers?: number; holdMs?: number };\n /** Copy text (`Clipboard.setStringAsync` from expo-clipboard). Default the share sheet, which has Copy. */\n copy?: (text: string) => unknown;\n /** More to clear after the query cache (the persisted cache, MMKV). */\n clearCache?: () => unknown;\n /** Sign out (`supabase.auth.signOut()`). Without it, the action is hidden. */\n signOut?: () => unknown;\n}\n\nexport interface DevtoolsState {\n open: boolean;\n}\n\nexport interface DevtoolsBlock extends NativeBlock<typeof DEVTOOLS_BLOCK_ID> {\n readonly enabled: boolean;\n readonly options: DevtoolsOptions;\n open(): void;\n /** Closes the overlay. Resolves once its sheet has finished sliding away (or at once if it isn't up). */\n close(): Promise<void>;\n getState(): DevtoolsState;\n subscribe(listener: () => void): () => void;\n}\n\n/** Build the overlay once, at module scope. @stability Beta */\nexport function createDevtools(options: DevtoolsOptions): DevtoolsBlock {\n const enabled = options.enabled === true;\n let state: DevtoolsState = { open: false };\n const listeners = new Set<() => void>();\n const set = (next: DevtoolsState) => {\n if (next.open === state.open) return;\n state = next;\n listeners.forEach((l) => l());\n };\n const subscribe = (listener: () => void) => {\n listeners.add(listener);\n return () => listeners.delete(listener);\n };\n const getState = () => state;\n\n // Waiting for the sheet to be gone: its dismissal is animated, and a screenshot taken before it\n // ends catches the sheet on its way down. Resolved by the modal's `onDismiss`, or after\n // DISMISS_FALLBACK_MS where a platform doesn't report it.\n let sheetShown = false;\n let dismissed: Array<() => void> = [];\n const resolveWaiting = () => {\n const waiting = dismissed;\n dismissed = [];\n waiting.forEach((resolve) => resolve());\n };\n const settleDismissed = () => {\n sheetShown = false;\n resolveWaiting();\n };\n const close = (): Promise<void> => {\n set({ open: false });\n if (!sheetShown) return Promise.resolve();\n return new Promise<void>((resolve) => dismissed.push(resolve));\n };\n\n const declaration = defineBlock<typeof DEVTOOLS_BLOCK_ID, ComponentType<never>>({\n id: DEVTOOLS_BLOCK_ID,\n platforms: [\"native\"],\n contributes: {\n privacy: { notes: [\"Development, preview and internal builds only; shows on the device what the app knows about itself and sends nothing.\"] },\n },\n });\n\n // Thrown while rendering, so it takes the path a real render error takes. The shell's\n // \"Try again\" remounts the root, whose state starts without the crash.\n function CrashTest(): null {\n throw new Error(\"Devtools crash test: a render error on purpose.\");\n }\n\n let menuAdded = false;\n function DevtoolsRoot({ slots, slotProps, children }: NativeBlockRootProps) {\n const { open } = useSyncExternalStore(subscribe, getState, getState);\n const [crash, setCrash] = useState(false);\n // The sheet stays mounted after `open` turns false, so the modal animates out and reports\n // when it's gone (`onDismiss`); then it unmounts.\n const [mounted, setMounted] = useState(open);\n const shown = open || mounted;\n // Unmounted (the crash test, a shell remount): nothing will report a dismissal, so nobody waits.\n useEffect(() => () => settleDismissed(), []);\n useEffect(() => {\n if (open) {\n // Opened again before the last close finished: that wait is over, the sheet stays.\n resolveWaiting();\n setMounted(true);\n sheetShown = true;\n return;\n }\n if (!mounted) return;\n const fallback = setTimeout(() => {\n setMounted(false);\n settleDismissed();\n }, DISMISS_FALLBACK_MS);\n return () => clearTimeout(fallback);\n }, [open, mounted]);\n const onDismissed = () => {\n if (getState().open) return;\n setMounted(false);\n settleDismissed();\n };\n\n useEffect(() => {\n // React Native has no way to remove a dev menu item: add it once per process.\n if (!options.devSettings || menuAdded) return;\n menuAdded = true;\n options.devSettings.addMenuItem(\"Open devtools\", () => set({ open: true }));\n }, []);\n\n useEffect(() => {\n const accelerometer = options.accelerometer;\n if (!accelerometer) return;\n let subscription: { remove(): void } | null = null;\n let stopped = false;\n const detector = createShakeDetector({ onShake: () => set({ open: true }) });\n void (async () => {\n try {\n if (accelerometer.isAvailableAsync && !(await accelerometer.isAvailableAsync())) return;\n if (stopped) return;\n accelerometer.setUpdateInterval?.(100);\n subscription = accelerometer.addListener((reading) => detector.sample(reading));\n } catch {\n // No accelerometer here (a simulator): the hold and the dev menu still open it.\n }\n })();\n return () => {\n stopped = true;\n subscription?.remove();\n };\n }, []);\n\n const hold = useRef(options.hold === false ? null : createHoldDetector({ ...(options.hold || {}), onTrigger: () => set({ open: true }) }));\n useEffect(() => () => hold.current?.cancel(), []);\n const onTouches = hold.current ? (event: GestureResponderEvent) => hold.current?.touches(event.nativeEvent.touches.length) : undefined;\n\n return (\n <View style={{ flex: 1 }} onTouchStart={onTouches} onTouchMove={onTouches} onTouchEnd={onTouches} onTouchCancel={onTouches}>\n {children}\n {crash ? <CrashTest /> : null}\n {shown ? (\n <DevtoolsSheet\n visible={open}\n onDismissed={onDismissed}\n block={block}\n Feedback={slots?.devtoolsFeedback}\n feedbackProps={slotProps?.devtoolsFeedback}\n onCrash={() => {\n set({ open: false });\n setCrash(true);\n }}\n />\n ) : null}\n </View>\n );\n }\n\n const block: DevtoolsBlock = {\n ...declaration,\n enabled,\n options,\n open: () => enabled && set({ open: true }),\n close,\n getState,\n subscribe,\n ...(enabled ? { Root: DevtoolsRoot } : {}),\n };\n return block;\n}\n\nfunction NoFeedback(_props: DevtoolsFeedbackProps) {\n return null;\n}\n\nfunction useSignature(options: DevtoolsOptions): VersionSignature | null {\n const updates = options.updates;\n const [requested, setRequested] = useState<string | null>(null);\n useEffect(() => {\n if (!updates) return;\n let alive = true;\n void updates.channels.requested().then((value) => alive && setRequested(value));\n return () => {\n alive = false;\n };\n }, [updates]);\n return useMemo(() => {\n if (!updates) return options.signature ?? null;\n if (!requested) return updates.signature;\n const { application, updates: modules, platform } = updates.options;\n return versionSignature({ application, updates: modules, platform, requestedChannel: requested });\n }, [updates, requested, options.signature]);\n}\n\nconst RESULT_TEXT: Record<ChannelSwitchResult[\"kind\"], string> = {\n reloading: \"Reloading…\",\n blocked: \"Set; it applies on the next launch (something is mid-edit).\",\n unavailable: \"Not available in this build.\",\n error: \"Set, but checking or reloading failed.\",\n};\n\n/** Longest wait for the sheet's `onDismiss` before treating it as gone. */\nconst DISMISS_FALLBACK_MS = 800;\n\ninterface SheetProps {\n visible: boolean;\n onDismissed: () => void;\n block: DevtoolsBlock;\n Feedback: ComponentType<DevtoolsFeedbackProps> | null | undefined;\n feedbackProps: Partial<DevtoolsFeedbackProps> | undefined;\n onCrash: () => void;\n}\n\n// Layout by `style`, not `className`: this package is compiled before the app's NativeWind\n// transform runs, so a class on React Native's own View would do nothing.\nfunction Row({ label, value, testID }: { label: string; value: string; testID?: string }) {\n return (\n <View style={{ flexDirection: \"row\", justifyContent: \"space-between\", gap: 12, paddingVertical: 4 }} testID={testID}>\n <NativeText preset=\"label\">{label}</NativeText>\n <NativeText preset=\"body-sm\" className=\"text-right text-muted-foreground\" style={{ flex: 1 }} selectable>\n {value}\n </NativeText>\n </View>\n );\n}\n\nfunction Section({ title, children }: { title: string; children: ReactNode }) {\n const roles = useThemeRoles();\n return (\n <View style={{ gap: 4, borderTopWidth: 1, borderTopColor: roles.separator, paddingTop: 12 }}>\n <NativeText preset=\"label\" accessibilityRole=\"header\">\n {title}\n </NativeText>\n {children}\n </View>\n );\n}\n\nfunction DevtoolsSheet({ visible, onDismissed, block, Feedback, feedbackProps, onCrash }: SheetProps) {\n const { options } = block;\n const roles = useThemeRoles();\n const { userId } = useSession();\n const blocks = useBlocks();\n const recent = useRecentActivity(options.observability ?? null);\n const signature = useSignature(options);\n const queryClient = useQueryClient();\n const [channel, setChannel] = useState(\"\");\n const [status, setStatus] = useState<string | null>(null);\n const channels = options.updates?.channels;\n const close = () => block.close();\n\n const info = () =>\n buildDebugInfo({ signature, userId, blocks: [...blocks.ids], flags: options.flags, baseUrl: options.baseUrl ?? null, recent });\n\n const run = (label: string, action: () => unknown) => {\n Promise.resolve()\n .then(action)\n .then(\n () => setStatus(`${label}: done`),\n (error: unknown) => setStatus(`${label}: ${error instanceof Error ? error.message : \"failed\"}`),\n );\n };\n\n const copy = () => run(\"Copy debug info\", () => (options.copy ? options.copy(info()) : Share.share({ message: info() })));\n const clearCache = () =>\n run(\"Clear cache\", async () => {\n queryClient.clear();\n await options.clearCache?.();\n });\n const confirm = (title: string, body: string, action: string, onConfirm: () => void) =>\n Alert.alert(title, body, [\n { text: \"Cancel\", style: \"cancel\" },\n { text: action, style: \"destructive\", onPress: onConfirm },\n ]);\n const switchChannel = () => {\n if (!channels) return;\n run(\"Switch channel\", async () => {\n const result = await channels.switchTo(channel);\n setStatus(RESULT_TEXT[result.kind]);\n });\n };\n const resetChannel = () => {\n if (!channels) return;\n run(\"Build channel\", async () => {\n const result = await channels.reset();\n setStatus(RESULT_TEXT[result.kind]);\n });\n };\n\n const flags = Object.entries(options.flags ?? {});\n return (\n // React Native's own modal, as a page sheet on iOS: it needs no provider above it (a bottom-sheet\n // library's modal does), so the overlay opens whatever the app has mounted.\n <Modal\n visible={visible}\n animationType=\"slide\"\n presentationStyle=\"pageSheet\"\n onRequestClose={() => void close()}\n onDismiss={onDismissed}\n testID=\"genesis-devtools\"\n >\n <ScrollView style={{ flex: 1, backgroundColor: roles.background }} contentContainerStyle={{ gap: 12, padding: 16, paddingBottom: 48 }}>\n <View style={{ flexDirection: \"row\", alignItems: \"center\", justifyContent: \"space-between\" }}>\n <NativeText preset=\"h3\" accessibilityRole=\"header\">\n Devtools\n </NativeText>\n <NativeButton variant=\"ghost\" size=\"sm\" onPress={() => void close()} testID=\"genesis-devtools-close\">\n Close\n </NativeButton>\n </View>\n {status ? (\n <NativeText preset=\"body-sm\" testID=\"genesis-devtools-status\">\n {status}\n </NativeText>\n ) : null}\n\n <Section title=\"Build\">\n <NativeText preset=\"body-sm\" selectable testID=\"genesis-devtools-signature\">\n {signature?.text ?? \"Version unknown (no updates block)\"}\n </NativeText>\n </Section>\n\n {channels ? (\n <Section title=\"Update channel\">\n <Row label=\"Channel\" value={signature?.requestedChannel ?? signature?.channel ?? \"none\"} testID=\"genesis-devtools-channel\" />\n {channels.enabled ? (\n <>\n <NativeInput\n value={channel}\n onChangeText={setChannel}\n placeholder=\"pr-42\"\n autoCapitalize=\"none\"\n autoCorrect={false}\n accessibilityLabel=\"Channel to switch to\"\n testID=\"genesis-devtools-channel-input\"\n />\n <NativeButton size=\"sm\" onPress={switchChannel} disabled={!channel.trim()} testID=\"genesis-devtools-channel-switch\">\n Switch and reload\n </NativeButton>\n </>\n ) : (\n <NativeText preset=\"body-sm\" className=\"text-muted-foreground\">\n Switching is off in this build.\n </NativeText>\n )}\n <NativeButton size=\"sm\" variant=\"ghost\" onPress={resetChannel} testID=\"genesis-devtools-channel-reset\">\n Back to the build's channel\n </NativeButton>\n </Section>\n ) : null}\n\n <Section title=\"Session\">\n <Row label=\"User id\" value={userId ?? \"signed out\"} testID=\"genesis-devtools-user\" />\n <Row label=\"Server\" value={options.baseUrl ? scrubUrl(options.baseUrl) : \"unknown\"} testID=\"genesis-devtools-server\" />\n </Section>\n\n <Section title=\"Blocks and flags\">\n <Row label=\"Blocks\" value={blocks.ids.size > 0 ? [...blocks.ids].join(\", \") : \"none\"} testID=\"genesis-devtools-blocks\" />\n {flags.map(([name, value]) => (\n <Row key={name} label={name} value={typeof value === \"string\" ? scrubString(value) : String(value)} testID={`genesis-devtools-flag-${name}`} />\n ))}\n </Section>\n\n <Section title={`Errors (${recent.errors.length})`}>\n {recent.errors.length === 0 ? <NativeText preset=\"body-sm\">None</NativeText> : null}\n {recent.errors\n .slice(-10)\n .reverse()\n .map((e) => (\n <NativeText key={`${e.at}-${e.message}`} preset=\"body-sm\" testID=\"genesis-devtools-error\">\n {`${e.fatal ? \"fatal \" : \"\"}${e.source} · ${e.name}: ${e.message}`}\n </NativeText>\n ))}\n </Section>\n\n <Section title={`Breadcrumbs (${recent.breadcrumbs.length})`}>\n {recent.breadcrumbs.length === 0 ? <NativeText preset=\"body-sm\">None</NativeText> : null}\n {recent.breadcrumbs\n .slice(-15)\n .reverse()\n .map((c, i) => (\n <NativeText key={`${c.timestamp ?? 0}-${i}`} preset=\"body-sm\" testID=\"genesis-devtools-breadcrumb\">\n {`${c.category} · ${c.message ?? \"\"}`}\n </NativeText>\n ))}\n </Section>\n\n <Section title=\"Actions\">\n <View style={{ gap: 8 }}>\n <NativeButton size=\"sm\" variant=\"outline\" onPress={copy} testID=\"genesis-devtools-copy\">\n Copy debug info\n </NativeButton>\n {renderSlot(NoFeedback, Feedback, { debugInfo: info(), close }, feedbackProps)}\n <NativeButton size=\"sm\" variant=\"outline\" onPress={clearCache} testID=\"genesis-devtools-clear-cache\">\n Clear cache\n </NativeButton>\n <NativeButton\n size=\"sm\"\n variant=\"outline\"\n onPress={() => confirm(\"Crash test\", \"This throws a render error: the fatal screen shows and the error is reported.\", \"Crash\", onCrash)}\n testID=\"genesis-devtools-crash\"\n >\n Crash test\n </NativeButton>\n {options.signOut ? (\n <NativeButton\n size=\"sm\"\n variant=\"destructive\"\n onPress={() => confirm(\"Sign out\", \"Sign out of this device?\", \"Sign out\", () => run(\"Sign out\", options.signOut as () => unknown))}\n testID=\"genesis-devtools-sign-out\"\n >\n Sign out\n </NativeButton>\n ) : null}\n </View>\n </Section>\n </ScrollView>\n </Modal>\n );\n}\n"]}