@mushi-mushi/react-native 0.22.0 → 0.24.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.d.cts CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, FC, ReactElement } from 'react';
3
- import { MushiAnalyticsConfig, MushiRewardsConfig, MushiPageContext, MushiBreadcrumb, MushiReporterReport, MushiReporterComment, MushiHallOfFameEntry, MushiPropertyValue, MushiTierResult, MushiReputationResult, MushiAssistantReply } from '@mushi-mushi/core';
3
+ import { MushiAnalyticsConfig, MushiRewardsConfig, MushiPageContext, MushiBreadcrumb, MushiReporterReport, MushiReporterComment, MushiApiResponse, MushiHallOfFameEntry, MushiPropertyValue, MushiTierResult, MushiReputationResult, MushiAssistantReply } from '@mushi-mushi/core';
4
4
  export { MushiHallOfFameEntry, MushiReporterComment, MushiReporterReport } from '@mushi-mushi/core';
5
+ import { MushiReporterUpdates, MushiReporterNotificationPrefs, MushiReporterPrefsUpdate, MushiReporterReportDetail } from '@mushi-mushi/core/reporter-channels';
5
6
 
6
7
  interface ConsoleEntry {
7
8
  level: 'log' | 'warn' | 'error';
@@ -74,6 +75,42 @@ declare function getDeviceInfo(): DeviceInfo;
74
75
  /** The subset of the web `analytics` block that applies on native. */
75
76
  type RNAnalyticsConfig = Pick<MushiAnalyticsConfig, 'enabled' | 'consent' | 'flushIntervalMs' | 'propertyAllowlist'>;
76
77
 
78
+ /**
79
+ * FILE: theme.ts
80
+ * PURPOSE: Host-adaptive theme for the RN report sheet (`widget.theme`).
81
+ *
82
+ * OVERVIEW:
83
+ * - Defaults are neutral: the shared washi/sumi surfaces with an INK accent,
84
+ * so the sheet reads as part of the host app rather than a neon Mushi
85
+ * product. The neon palette is only used by `<MushiBanner variant="neon">`.
86
+ * - Hosts override any token. When a host sets `accent` but not `accentFg`,
87
+ * the text colour on the accent is picked for contrast (black or white).
88
+ *
89
+ * Plan 018 (docs/execplans/reporter-loop-v2.md §1.4).
90
+ */
91
+ interface MushiRNTheme {
92
+ /** Sheet background. */
93
+ bg: string;
94
+ /** Primary text. */
95
+ fg: string;
96
+ /** Secondary text. */
97
+ muted: string;
98
+ /** Cards, inputs, chips. */
99
+ surface: string;
100
+ /** Borders and separators. */
101
+ border: string;
102
+ /** Selected chip, primary button, links. */
103
+ accent: string;
104
+ /** Text on `accent`. */
105
+ accentFg: string;
106
+ success: string;
107
+ error: string;
108
+ /** Host font family; undefined keeps the platform font. */
109
+ fontFamily?: string;
110
+ /** Corner radius for cards, inputs and buttons. */
111
+ radius: number;
112
+ }
113
+
77
114
  interface MushiRNConfig {
78
115
  projectId: string;
79
116
  apiKey: string;
@@ -115,6 +152,39 @@ interface MushiRNConfig {
115
152
  };
116
153
  /** Poll interval (ms) for My Reports while the inbox tab is open. 0 = off (default). */
117
154
  inboxPollIntervalMs?: number;
155
+ /**
156
+ * Theme tokens for the report sheet (bg, fg, muted, surface, border,
157
+ * accent, accentFg, success, error, fontFamily, radius). Unset tokens
158
+ * use neutral system-style defaults with an ink accent; when only
159
+ * `accent` is set, the text on it is chosen for contrast.
160
+ */
161
+ theme?: Partial<MushiRNTheme>;
162
+ /**
163
+ * Characters needed before Send enables. Default 8 (Plan 018 §1.1); an
164
+ * attached screenshot lowers it to 0.
165
+ */
166
+ minDescriptionLength?: number;
167
+ };
168
+ /**
169
+ * Optional native modules, passed in instead of loaded. When set, the SDK
170
+ * uses them and never loads that package itself — the safest option for
171
+ * Metro configs that disallow optional requires. Each one is also picked up
172
+ * automatically when installed.
173
+ * - `netInfo`: `@react-native-community/netinfo` — resend queued reports when the network returns.
174
+ * - `viewShot`: `react-native-view-shot` — attach a screenshot.
175
+ * - `expoSensors`: `expo-sensors` — shake to report.
176
+ */
177
+ netInfo?: unknown;
178
+ viewShot?: unknown;
179
+ expoSensors?: unknown;
180
+ /** How reporters hear back outside the sheet (Plan 018 §4). No native push. */
181
+ notifications?: {
182
+ /** One "the developer replied" / "your bug is fixed" toast when the app returns to the foreground. Default true. */
183
+ toast?: boolean;
184
+ /** Offer "Get updates by email" after sending, when the project offers email. Default true. */
185
+ email?: boolean;
186
+ /** Prefill that email box from `setUser({ email })`. Default false; the box is never pre-ticked. */
187
+ emailFromIdentity?: boolean;
118
188
  };
119
189
  assistant?: {
120
190
  enabled?: boolean;
@@ -175,6 +245,8 @@ interface MushiRNInstance {
175
245
  submitReport(data: {
176
246
  description: string;
177
247
  category: string;
248
+ /** Host / chip sub-label, e.g. 'feature' for the Idea chip. */
249
+ userCategory?: string;
178
250
  screenshotDataUrl?: string;
179
251
  }): Promise<{
180
252
  ok: boolean;
@@ -226,8 +298,39 @@ interface MushiRNInstance {
226
298
  }): void;
227
299
  /** List this device's reports ordered by most recent. Returns [] on failure. */
228
300
  listMyReports(): Promise<MushiReporterReport[]>;
229
- /** List admin + reporter comments on a specific report. */
301
+ /** List admin + reporter comments on a specific report. Returns [] on failure. */
230
302
  listMyComments(reportId: string): Promise<MushiReporterComment[]>;
303
+ /** Like `listMyComments`, but resolves `null` when the thread could not be loaded, so a UI can offer Retry. */
304
+ loadMyThread(reportId: string): Promise<MushiReporterComment[] | null>;
305
+ /** Mark this device's unread notifications for a report as read. Resolves the number marked (0 on failure). */
306
+ markReportRead(reportId: string): Promise<number>;
307
+ /** Unread count and the newest unread updates on this device's reports (null on failure). */
308
+ getReporterUpdates(): Promise<MushiReporterUpdates | null>;
309
+ /**
310
+ * Fires with the unread count and newest updates when you subscribe and
311
+ * each time the app comes back to the foreground, so the host can draw its
312
+ * own badge (a feedback band, a tab dot). Returns an unsubscribe function.
313
+ */
314
+ onReporterUpdate(cb: (updates: MushiReporterUpdates) => void): () => void;
315
+ /** This reporter's email choices; the address comes back masked (null on failure). */
316
+ getNotificationPrefs(): Promise<MushiReporterNotificationPrefs | null>;
317
+ /**
318
+ * Ask for email updates (`{ email }` sends a confirmation email first) or
319
+ * change channels. Only when the reporter asked — never pre-tick an opt-in.
320
+ * Resolves the server's answer, including `EMAIL_NOT_AVAILABLE` when this
321
+ * app does not offer email. (No native push on React Native.)
322
+ */
323
+ setNotificationPrefs(update: MushiReporterPrefsUpdate): Promise<MushiApiResponse<MushiReporterNotificationPrefs>>;
324
+ /**
325
+ * One report with its timeline (GET /v1/reporter/reports/:id). Resolves
326
+ * null when it could not be loaded (show Retry) and 'unsupported' when the
327
+ * server predates the route (fall back to `loadMyThread`).
328
+ */
329
+ loadMyReportDetail(reportId: string): Promise<MushiReporterReportDetail | null | 'unsupported'>;
330
+ /** The address to prefill in the email opt-in box, only with `notifications.emailFromIdentity`. */
331
+ emailPrefill(): string | null;
332
+ /** Open the sheet on "Your reports". */
333
+ openMyReports(): void;
231
334
  /** Post a reporter reply on a report thread. Returns the new comment or null on failure. */
232
335
  replyToReport(reportId: string, body: string): Promise<MushiReporterComment | null>;
233
336
  /** Record a reporter feedback signal (e.g. `confirms`, `not_fixed`) on a report. Returns the outcome or null. */
@@ -281,9 +384,13 @@ declare function useMushiWidget(): {
281
384
  *
282
385
  * OVERVIEW:
283
386
  * - Slide-up modal built entirely on RN built-ins (Modal, Animated, PanResponder)
284
- * - Conversational flow: category → description → submit → confirmation
387
+ * - One screen, free text first: optional type chips, Send once there are a
388
+ * few words (8 by default) or a screenshot, then a receipt in place with
389
+ * Track it / Done and an optional, never pre-ticked email opt-in
285
390
  * - Drag-to-dismiss via PanResponder on the handle area
286
- * - Dark/light theme via useColorScheme
391
+ * - Host-adaptive theme (`widget.theme`, see ../theme.ts) over light/dark
392
+ * - "Your reports": statuses come from the shared core table
393
+ * (`@mushi-mushi/core/reporter-ui`), so web and RN say the same thing
287
394
  *
288
395
  * DEPENDENCIES:
289
396
  * - React Native built-in APIs only (no third-party libs)
@@ -296,11 +403,19 @@ declare function useMushiWidget(): {
296
403
  * TECHNICAL DETAILS:
297
404
  * - PanResponder threshold: 80 px downward drag dismisses
298
405
  * - Animated.spring for slide-up, Animated.timing for backdrop
299
- * - Success confirmation auto-closes after 1.4 s
406
+ * - Reduce Motion (AccessibilityInfo) makes open / close instant
407
+ * - The thread is the report's v2 timeline (GET /v1/reporter/reports/:id):
408
+ * pipeline events render from the shared core templates in the reporter's
409
+ * locale, replies as bubbles. Loads show a skeleton, then Retry on failure;
410
+ * a failed reply keeps its text and offers Retry. Opening a thread marks it
411
+ * read. An older server without the route falls back to the comment list.
300
412
  *
301
413
  * NOTES:
302
414
  * - KeyboardAvoidingView wraps the sheet so the text input stays visible
303
- * - Categories match the web SDK: bug, slow, visual, confusing, other
415
+ * - Chips come from core's REPORTER_CATEGORIES (bug, slow, visual, confusing,
416
+ * idea); "Idea" files a feature request
417
+ * - accessibilityViewIsModal is iOS-only; RN's Modal is its own window on
418
+ * Android, which keeps TalkBack focus inside the sheet there.
304
419
  */
305
420
 
306
421
  interface MushiBottomSheetProps {
@@ -324,6 +439,12 @@ interface MushiBottomSheetProps {
324
439
  assistantSuggestions?: string[];
325
440
  /** Poll My Reports while inbox tab is open. 0 disables polling. */
326
441
  inboxPollIntervalMs?: number;
442
+ /** Theme tokens (`widget.theme`); unset tokens use the neutral defaults. */
443
+ theme?: Partial<MushiRNTheme>;
444
+ /** Characters needed before Send enables (`widget.minDescriptionLength`, default 8). */
445
+ minDescriptionLength?: number;
446
+ /** Offer "Get updates by email" on the receipt when the project offers email (`notifications.email`). */
447
+ emailOptIn?: boolean;
327
448
  }
328
449
  declare const MushiBottomSheet: FC<MushiBottomSheetProps>;
329
450
 
@@ -447,4 +568,46 @@ declare class AsyncStorageQueue {
447
568
  private getAsyncStorage;
448
569
  }
449
570
 
450
- export { AsyncStorageQueue, type ConsoleEntry, type DeviceInfo, MUSHI_BANNER_DEFAULT_HEIGHT, MushiBanner, type MushiBannerProps, MushiBottomSheet, type MushiBottomSheetProps, MushiFloatingButton, type MushiFloatingButtonProps, MushiProvider, type MushiRNConfig, type MushiRNInstance, MushiTrigger, type MushiTriggerProps, type NavigationEntry, type NetworkEntry, type RNAnalyticsConfig, getDeviceInfo, setupConsoleCapture, setupNetworkCapture, useMushi, useMushiReport, useMushiWidget, useNavigationCapture };
571
+ /**
572
+ * FILE: optional-modules.ts
573
+ * PURPOSE: The SDK's three optional native peers (network-aware delivery,
574
+ * screenshots, shake-to-report), loaded without crashing the app.
575
+ *
576
+ * Order for each one:
577
+ * 1. A module the host passed in config (`netInfo`, `viewShot`, `expoSensors`).
578
+ * No dynamic loading at all — the safest path.
579
+ * 2. A literal `require('<module>')` inside try/catch. Metro resolves a literal
580
+ * require at bundle time and treats one inside try/catch as optional
581
+ * (`transformer.allowOptionalDependencies`, on in Expo and React Native's
582
+ * default Metro config).
583
+ *
584
+ * Why the build matters: esbuild's ESM output rewrites `require('x')` to its
585
+ * `__require('x')` interop shim. Metro can't resolve a module name handed to
586
+ * the shim, so it called its own require with a string at runtime and crashed
587
+ * the app (reportFatalError) — the try/catch never saw it (0.21–0.23).
588
+ * tsup.config.ts restores literal `require(...)` in dist and fails the build
589
+ * if any `__require(` is left (src/bundling/preserve-optional-requires.ts).
590
+ */
591
+ interface MushiNetInfoModule {
592
+ addEventListener(listener: (state: {
593
+ isConnected: boolean | null;
594
+ isInternetReachable: boolean | null;
595
+ }) => void): () => void;
596
+ }
597
+ interface MushiViewShotModule {
598
+ captureScreen(options: Record<string, unknown>): Promise<string>;
599
+ }
600
+ interface MushiExpoSensorsModule {
601
+ Accelerometer: {
602
+ setUpdateInterval(ms: number): void;
603
+ addListener(listener: (event: {
604
+ x: number;
605
+ y: number;
606
+ z: number;
607
+ }) => void): {
608
+ remove(): void;
609
+ };
610
+ };
611
+ }
612
+
613
+ export { AsyncStorageQueue, type ConsoleEntry, type DeviceInfo, MUSHI_BANNER_DEFAULT_HEIGHT, MushiBanner, type MushiBannerProps, MushiBottomSheet, type MushiBottomSheetProps, type MushiExpoSensorsModule, MushiFloatingButton, type MushiFloatingButtonProps, type MushiNetInfoModule, MushiProvider, type MushiRNConfig, type MushiRNInstance, MushiTrigger, type MushiTriggerProps, type MushiViewShotModule, type NavigationEntry, type NetworkEntry, type RNAnalyticsConfig, getDeviceInfo, setupConsoleCapture, setupNetworkCapture, useMushi, useMushiReport, useMushiWidget, useNavigationCapture };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, FC, ReactElement } from 'react';
3
- import { MushiAnalyticsConfig, MushiRewardsConfig, MushiPageContext, MushiBreadcrumb, MushiReporterReport, MushiReporterComment, MushiHallOfFameEntry, MushiPropertyValue, MushiTierResult, MushiReputationResult, MushiAssistantReply } from '@mushi-mushi/core';
3
+ import { MushiAnalyticsConfig, MushiRewardsConfig, MushiPageContext, MushiBreadcrumb, MushiReporterReport, MushiReporterComment, MushiApiResponse, MushiHallOfFameEntry, MushiPropertyValue, MushiTierResult, MushiReputationResult, MushiAssistantReply } from '@mushi-mushi/core';
4
4
  export { MushiHallOfFameEntry, MushiReporterComment, MushiReporterReport } from '@mushi-mushi/core';
5
+ import { MushiReporterUpdates, MushiReporterNotificationPrefs, MushiReporterPrefsUpdate, MushiReporterReportDetail } from '@mushi-mushi/core/reporter-channels';
5
6
 
6
7
  interface ConsoleEntry {
7
8
  level: 'log' | 'warn' | 'error';
@@ -74,6 +75,42 @@ declare function getDeviceInfo(): DeviceInfo;
74
75
  /** The subset of the web `analytics` block that applies on native. */
75
76
  type RNAnalyticsConfig = Pick<MushiAnalyticsConfig, 'enabled' | 'consent' | 'flushIntervalMs' | 'propertyAllowlist'>;
76
77
 
78
+ /**
79
+ * FILE: theme.ts
80
+ * PURPOSE: Host-adaptive theme for the RN report sheet (`widget.theme`).
81
+ *
82
+ * OVERVIEW:
83
+ * - Defaults are neutral: the shared washi/sumi surfaces with an INK accent,
84
+ * so the sheet reads as part of the host app rather than a neon Mushi
85
+ * product. The neon palette is only used by `<MushiBanner variant="neon">`.
86
+ * - Hosts override any token. When a host sets `accent` but not `accentFg`,
87
+ * the text colour on the accent is picked for contrast (black or white).
88
+ *
89
+ * Plan 018 (docs/execplans/reporter-loop-v2.md §1.4).
90
+ */
91
+ interface MushiRNTheme {
92
+ /** Sheet background. */
93
+ bg: string;
94
+ /** Primary text. */
95
+ fg: string;
96
+ /** Secondary text. */
97
+ muted: string;
98
+ /** Cards, inputs, chips. */
99
+ surface: string;
100
+ /** Borders and separators. */
101
+ border: string;
102
+ /** Selected chip, primary button, links. */
103
+ accent: string;
104
+ /** Text on `accent`. */
105
+ accentFg: string;
106
+ success: string;
107
+ error: string;
108
+ /** Host font family; undefined keeps the platform font. */
109
+ fontFamily?: string;
110
+ /** Corner radius for cards, inputs and buttons. */
111
+ radius: number;
112
+ }
113
+
77
114
  interface MushiRNConfig {
78
115
  projectId: string;
79
116
  apiKey: string;
@@ -115,6 +152,39 @@ interface MushiRNConfig {
115
152
  };
116
153
  /** Poll interval (ms) for My Reports while the inbox tab is open. 0 = off (default). */
117
154
  inboxPollIntervalMs?: number;
155
+ /**
156
+ * Theme tokens for the report sheet (bg, fg, muted, surface, border,
157
+ * accent, accentFg, success, error, fontFamily, radius). Unset tokens
158
+ * use neutral system-style defaults with an ink accent; when only
159
+ * `accent` is set, the text on it is chosen for contrast.
160
+ */
161
+ theme?: Partial<MushiRNTheme>;
162
+ /**
163
+ * Characters needed before Send enables. Default 8 (Plan 018 §1.1); an
164
+ * attached screenshot lowers it to 0.
165
+ */
166
+ minDescriptionLength?: number;
167
+ };
168
+ /**
169
+ * Optional native modules, passed in instead of loaded. When set, the SDK
170
+ * uses them and never loads that package itself — the safest option for
171
+ * Metro configs that disallow optional requires. Each one is also picked up
172
+ * automatically when installed.
173
+ * - `netInfo`: `@react-native-community/netinfo` — resend queued reports when the network returns.
174
+ * - `viewShot`: `react-native-view-shot` — attach a screenshot.
175
+ * - `expoSensors`: `expo-sensors` — shake to report.
176
+ */
177
+ netInfo?: unknown;
178
+ viewShot?: unknown;
179
+ expoSensors?: unknown;
180
+ /** How reporters hear back outside the sheet (Plan 018 §4). No native push. */
181
+ notifications?: {
182
+ /** One "the developer replied" / "your bug is fixed" toast when the app returns to the foreground. Default true. */
183
+ toast?: boolean;
184
+ /** Offer "Get updates by email" after sending, when the project offers email. Default true. */
185
+ email?: boolean;
186
+ /** Prefill that email box from `setUser({ email })`. Default false; the box is never pre-ticked. */
187
+ emailFromIdentity?: boolean;
118
188
  };
119
189
  assistant?: {
120
190
  enabled?: boolean;
@@ -175,6 +245,8 @@ interface MushiRNInstance {
175
245
  submitReport(data: {
176
246
  description: string;
177
247
  category: string;
248
+ /** Host / chip sub-label, e.g. 'feature' for the Idea chip. */
249
+ userCategory?: string;
178
250
  screenshotDataUrl?: string;
179
251
  }): Promise<{
180
252
  ok: boolean;
@@ -226,8 +298,39 @@ interface MushiRNInstance {
226
298
  }): void;
227
299
  /** List this device's reports ordered by most recent. Returns [] on failure. */
228
300
  listMyReports(): Promise<MushiReporterReport[]>;
229
- /** List admin + reporter comments on a specific report. */
301
+ /** List admin + reporter comments on a specific report. Returns [] on failure. */
230
302
  listMyComments(reportId: string): Promise<MushiReporterComment[]>;
303
+ /** Like `listMyComments`, but resolves `null` when the thread could not be loaded, so a UI can offer Retry. */
304
+ loadMyThread(reportId: string): Promise<MushiReporterComment[] | null>;
305
+ /** Mark this device's unread notifications for a report as read. Resolves the number marked (0 on failure). */
306
+ markReportRead(reportId: string): Promise<number>;
307
+ /** Unread count and the newest unread updates on this device's reports (null on failure). */
308
+ getReporterUpdates(): Promise<MushiReporterUpdates | null>;
309
+ /**
310
+ * Fires with the unread count and newest updates when you subscribe and
311
+ * each time the app comes back to the foreground, so the host can draw its
312
+ * own badge (a feedback band, a tab dot). Returns an unsubscribe function.
313
+ */
314
+ onReporterUpdate(cb: (updates: MushiReporterUpdates) => void): () => void;
315
+ /** This reporter's email choices; the address comes back masked (null on failure). */
316
+ getNotificationPrefs(): Promise<MushiReporterNotificationPrefs | null>;
317
+ /**
318
+ * Ask for email updates (`{ email }` sends a confirmation email first) or
319
+ * change channels. Only when the reporter asked — never pre-tick an opt-in.
320
+ * Resolves the server's answer, including `EMAIL_NOT_AVAILABLE` when this
321
+ * app does not offer email. (No native push on React Native.)
322
+ */
323
+ setNotificationPrefs(update: MushiReporterPrefsUpdate): Promise<MushiApiResponse<MushiReporterNotificationPrefs>>;
324
+ /**
325
+ * One report with its timeline (GET /v1/reporter/reports/:id). Resolves
326
+ * null when it could not be loaded (show Retry) and 'unsupported' when the
327
+ * server predates the route (fall back to `loadMyThread`).
328
+ */
329
+ loadMyReportDetail(reportId: string): Promise<MushiReporterReportDetail | null | 'unsupported'>;
330
+ /** The address to prefill in the email opt-in box, only with `notifications.emailFromIdentity`. */
331
+ emailPrefill(): string | null;
332
+ /** Open the sheet on "Your reports". */
333
+ openMyReports(): void;
231
334
  /** Post a reporter reply on a report thread. Returns the new comment or null on failure. */
232
335
  replyToReport(reportId: string, body: string): Promise<MushiReporterComment | null>;
233
336
  /** Record a reporter feedback signal (e.g. `confirms`, `not_fixed`) on a report. Returns the outcome or null. */
@@ -281,9 +384,13 @@ declare function useMushiWidget(): {
281
384
  *
282
385
  * OVERVIEW:
283
386
  * - Slide-up modal built entirely on RN built-ins (Modal, Animated, PanResponder)
284
- * - Conversational flow: category → description → submit → confirmation
387
+ * - One screen, free text first: optional type chips, Send once there are a
388
+ * few words (8 by default) or a screenshot, then a receipt in place with
389
+ * Track it / Done and an optional, never pre-ticked email opt-in
285
390
  * - Drag-to-dismiss via PanResponder on the handle area
286
- * - Dark/light theme via useColorScheme
391
+ * - Host-adaptive theme (`widget.theme`, see ../theme.ts) over light/dark
392
+ * - "Your reports": statuses come from the shared core table
393
+ * (`@mushi-mushi/core/reporter-ui`), so web and RN say the same thing
287
394
  *
288
395
  * DEPENDENCIES:
289
396
  * - React Native built-in APIs only (no third-party libs)
@@ -296,11 +403,19 @@ declare function useMushiWidget(): {
296
403
  * TECHNICAL DETAILS:
297
404
  * - PanResponder threshold: 80 px downward drag dismisses
298
405
  * - Animated.spring for slide-up, Animated.timing for backdrop
299
- * - Success confirmation auto-closes after 1.4 s
406
+ * - Reduce Motion (AccessibilityInfo) makes open / close instant
407
+ * - The thread is the report's v2 timeline (GET /v1/reporter/reports/:id):
408
+ * pipeline events render from the shared core templates in the reporter's
409
+ * locale, replies as bubbles. Loads show a skeleton, then Retry on failure;
410
+ * a failed reply keeps its text and offers Retry. Opening a thread marks it
411
+ * read. An older server without the route falls back to the comment list.
300
412
  *
301
413
  * NOTES:
302
414
  * - KeyboardAvoidingView wraps the sheet so the text input stays visible
303
- * - Categories match the web SDK: bug, slow, visual, confusing, other
415
+ * - Chips come from core's REPORTER_CATEGORIES (bug, slow, visual, confusing,
416
+ * idea); "Idea" files a feature request
417
+ * - accessibilityViewIsModal is iOS-only; RN's Modal is its own window on
418
+ * Android, which keeps TalkBack focus inside the sheet there.
304
419
  */
305
420
 
306
421
  interface MushiBottomSheetProps {
@@ -324,6 +439,12 @@ interface MushiBottomSheetProps {
324
439
  assistantSuggestions?: string[];
325
440
  /** Poll My Reports while inbox tab is open. 0 disables polling. */
326
441
  inboxPollIntervalMs?: number;
442
+ /** Theme tokens (`widget.theme`); unset tokens use the neutral defaults. */
443
+ theme?: Partial<MushiRNTheme>;
444
+ /** Characters needed before Send enables (`widget.minDescriptionLength`, default 8). */
445
+ minDescriptionLength?: number;
446
+ /** Offer "Get updates by email" on the receipt when the project offers email (`notifications.email`). */
447
+ emailOptIn?: boolean;
327
448
  }
328
449
  declare const MushiBottomSheet: FC<MushiBottomSheetProps>;
329
450
 
@@ -447,4 +568,46 @@ declare class AsyncStorageQueue {
447
568
  private getAsyncStorage;
448
569
  }
449
570
 
450
- export { AsyncStorageQueue, type ConsoleEntry, type DeviceInfo, MUSHI_BANNER_DEFAULT_HEIGHT, MushiBanner, type MushiBannerProps, MushiBottomSheet, type MushiBottomSheetProps, MushiFloatingButton, type MushiFloatingButtonProps, MushiProvider, type MushiRNConfig, type MushiRNInstance, MushiTrigger, type MushiTriggerProps, type NavigationEntry, type NetworkEntry, type RNAnalyticsConfig, getDeviceInfo, setupConsoleCapture, setupNetworkCapture, useMushi, useMushiReport, useMushiWidget, useNavigationCapture };
571
+ /**
572
+ * FILE: optional-modules.ts
573
+ * PURPOSE: The SDK's three optional native peers (network-aware delivery,
574
+ * screenshots, shake-to-report), loaded without crashing the app.
575
+ *
576
+ * Order for each one:
577
+ * 1. A module the host passed in config (`netInfo`, `viewShot`, `expoSensors`).
578
+ * No dynamic loading at all — the safest path.
579
+ * 2. A literal `require('<module>')` inside try/catch. Metro resolves a literal
580
+ * require at bundle time and treats one inside try/catch as optional
581
+ * (`transformer.allowOptionalDependencies`, on in Expo and React Native's
582
+ * default Metro config).
583
+ *
584
+ * Why the build matters: esbuild's ESM output rewrites `require('x')` to its
585
+ * `__require('x')` interop shim. Metro can't resolve a module name handed to
586
+ * the shim, so it called its own require with a string at runtime and crashed
587
+ * the app (reportFatalError) — the try/catch never saw it (0.21–0.23).
588
+ * tsup.config.ts restores literal `require(...)` in dist and fails the build
589
+ * if any `__require(` is left (src/bundling/preserve-optional-requires.ts).
590
+ */
591
+ interface MushiNetInfoModule {
592
+ addEventListener(listener: (state: {
593
+ isConnected: boolean | null;
594
+ isInternetReachable: boolean | null;
595
+ }) => void): () => void;
596
+ }
597
+ interface MushiViewShotModule {
598
+ captureScreen(options: Record<string, unknown>): Promise<string>;
599
+ }
600
+ interface MushiExpoSensorsModule {
601
+ Accelerometer: {
602
+ setUpdateInterval(ms: number): void;
603
+ addListener(listener: (event: {
604
+ x: number;
605
+ y: number;
606
+ z: number;
607
+ }) => void): {
608
+ remove(): void;
609
+ };
610
+ };
611
+ }
612
+
613
+ export { AsyncStorageQueue, type ConsoleEntry, type DeviceInfo, MUSHI_BANNER_DEFAULT_HEIGHT, MushiBanner, type MushiBannerProps, MushiBottomSheet, type MushiBottomSheetProps, type MushiExpoSensorsModule, MushiFloatingButton, type MushiFloatingButtonProps, type MushiNetInfoModule, MushiProvider, type MushiRNConfig, type MushiRNInstance, MushiTrigger, type MushiTriggerProps, type MushiViewShotModule, type NavigationEntry, type NetworkEntry, type RNAnalyticsConfig, getDeviceInfo, setupConsoleCapture, setupNetworkCapture, useMushi, useMushiReport, useMushiWidget, useNavigationCapture };