spaps-issue-reporting-react 0.4.1 → 0.4.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ - Added the reporter-visible operator-to-reporter conversation thread (`IssueReportMessageThread`), rendered inside the issue detail modal when the client implements `issueReporting.listMessages`/`submitMessage`. It shows only `active`, reporter-visible projection rows (retracted/superseded/non-visible excluded), distinguishes operator vs reporter authorship and message kind, exposes a `needs_response` affordance plus a floating-entrypoint badge, and includes a clarification-response composer that submits with a client-generated `idempotency_key` and handles `409 ISSUE_REPORT_MESSAGE_CONFLICT`.
6
+
7
+ ## [0.4.1] - 2026-05-16
8
+
5
9
  - Added optional screenshot attachment UI that uploads private PNG/JPEG/WebP evidence through `issueReporting.uploadAttachment`, validates size/count locally, and submits only backend attachment IDs.
6
10
 
7
11
  ## [0.4.0] - 2026-05-11
package/README.md CHANGED
@@ -73,6 +73,11 @@ export function AppShell() {
73
73
  - A client with `issueReporting.getStatus`, `list`, `get`, `create`, `update`, and `reply`.
74
74
  - A client with `issueReporting.createVoiceToken` when `inputModes` includes `voice`.
75
75
  - A client with `issueReporting.uploadAttachment(file, { filename })` when you want screenshot uploads.
76
+ - Optionally, a client with `issueReporting.listMessages(issueReportId)` and
77
+ `issueReporting.submitMessage(issueReportId, { body, idempotency_key })` to wire the
78
+ operator-to-reporter message thread (and its `needs_response` badge). When these are present,
79
+ the bundled `IssueReportMessageThread` renders the conversation inside the issue detail modal
80
+ (see [Conversation Thread](#conversation-thread)).
76
81
  - Any auth and token refresh behavior needed by that client.
77
82
  - Eligibility rules such as feature flags, account state, or role checks.
78
83
  - The current principal ID and optional role hint passed into the provider.
@@ -148,18 +153,57 @@ The picker accepts PNG, JPEG, and WebP files, enforces the 10 MiB per-file limit
148
153
 
149
154
  This package does not store screenshots, generate public URLs, or redact image content. SPAPS stores screenshots through its shared hosted-asset boundary as private objects, and access URLs are minted by the backend after authorization. Host apps should warn users before upload when a screenshot might include PHI, credentials, payment data, or other sensitive content.
150
155
 
156
+ ## Conversation Thread
157
+
158
+ Support operators can ask a reporter for clarification and post a final resolution message. Those messages flow into an allowlisted, reporter-visible projection that the reporter reads back as a conversation. This package renders that surface when the client implements the two optional message methods:
159
+
160
+ ```tsx
161
+ const client = {
162
+ issueReporting: {
163
+ ...spaps.issueReporting,
164
+ listMessages: (issueReportId: string) =>
165
+ spaps.issueReporting.listMessages(issueReportId),
166
+ submitMessage: (issueReportId, payload) =>
167
+ spaps.issueReporting.submitMessage(issueReportId, payload),
168
+ },
169
+ };
170
+ ```
171
+
172
+ When `listMessages` is present, `FloatingIssueReportButton` mounts the exported `IssueReportMessageThread` inside the issue detail modal (the Edit/Reply views). The thread:
173
+
174
+ - Renders only `active`, `reporter_visible` projection rows in chronological order. Retracted and superseded operator messages, non-visible rows, and raw `support_case_events` payloads are never displayed. The backend filters this; `selectReporterVisibleMessages` re-asserts the same rule in the UI.
175
+ - Distinguishes operator (`Support`) vs reporter (`You`) authorship, labels each message kind (`clarification_request`, `reporter_response`, `final_response`), and shows a relative timestamp.
176
+ - Shows a **needs-response** affordance when the API's `needs_response` flag is set (an open clarification request awaiting an answer), and surfaces a matching dot badge on the floating entrypoint while that thread is open.
177
+ - Renders a reporter clarification-response composer (shown only when the client implements `submitMessage`). It calls `submitMessage(issueReportId, { body, idempotency_key })` with a client-generated `idempotency_key`. A key reused with a different body returns `409 ISSUE_REPORT_MESSAGE_CONFLICT`; the composer detects this (`isReporterMessageConflict`), shows the conflict copy, and rotates the key for retry.
178
+ - Exposes loading, empty, and error (with retry) states.
179
+
180
+ The composer does not reopen a closed case; reopen semantics stay with the Reply flow. You can also mount `IssueReportMessageThread` directly (outside the modal) inside your own issue detail view:
181
+
182
+ ```tsx
183
+ import { IssueReportMessageThread } from "spaps-issue-reporting-react";
184
+
185
+ <IssueReportMessageThread issueReportId={issue.id} />;
186
+ ```
187
+
188
+ All thread strings are overridable through the provider `copy` prop. The thread copy keys are: `threadTitle`, `threadDescription`, `threadLoading`, `threadLoadFailed`, `threadEmpty`, `threadNeedsResponseBadge`, `threadAuthorOperator`, `threadAuthorReporter`, `threadKindClarificationRequest`, `threadKindReporterResponse`, `threadKindFinalResponse`, `threadResponseLabel`, `threadResponsePlaceholder`, `threadResponseSubmitAction`, `threadResponseSubmittingAction`, `threadResponseConflict`, and `threadResponseFailed`.
189
+
151
190
  ## Exported Surface
152
191
 
153
192
  | Export | Purpose |
154
193
  | --- | --- |
155
194
  | `IssueReportingProvider` | Owns queries, modal state, copy, scope, and report-mode behavior |
156
195
  | `IssueReportingPageConfig` | Per-page override for `general_page`, `surface_required`, or `surface_preferred` create policy |
157
- | `FloatingIssueReportButton` | Renders the floating entry point, popover, and modal |
196
+ | `FloatingIssueReportButton` | Renders the floating entry point, popover, modal, and (when the client supports messages) the conversation thread |
197
+ | `IssueReportMessageThread` | Renders the reporter-visible operator-to-reporter conversation for one issue report, with a clarification-response composer |
158
198
  | `ReportableSection` | Marks a region as selectable when report mode is active |
159
199
  | `useIssueReporting` | Full provider state, including `startNewIssue()`, `openPageIssueModal()`, and `enterReportMode()` |
160
200
  | `useIssueReportingStatus` | Query helper for summary state |
161
201
  | `useIssueReportingHistory` | Query helper for filtered history lists |
162
202
  | `useIssueReportingMutations` | Mutation helpers for create, update, and reply flows |
203
+ | `useIssueReportingMessages` | Query helper for one issue report's reporter-visible message thread |
204
+ | `useIssueReportingMessageMutation` | Mutation helper for submitting a reporter clarification response |
205
+ | `selectReporterVisibleMessages` | Filters a message list to `active`, reporter-visible rows in chronological order |
206
+ | `isReporterMessageConflict` | Detects a `409 ISSUE_REPORT_MESSAGE_CONFLICT` from the client transport |
163
207
  | `useReportMode` | Low-level selection state for custom wrappers |
164
208
 
165
209
  ## Styling Notes
@@ -217,6 +261,10 @@ Confirm that the SPAPS server has `ELEVENLABS_API_KEY` set and that the current
217
261
 
218
262
  Expose `issueReporting.uploadAttachment` on the client passed to `IssueReportingProvider`. The package intentionally hides the picker when the upload helper is absent.
219
263
 
264
+ ### The conversation thread does not appear
265
+
266
+ Expose `issueReporting.listMessages` on the client passed to `IssueReportingProvider`, then open an existing issue's detail (Edit/Reply). The thread is intentionally hidden when the client cannot list messages. The response composer additionally requires `issueReporting.submitMessage`; without it the thread renders read-only.
267
+
220
268
  ## Limitations
221
269
 
222
270
  - This package assumes React Query is already part of the host app.
@@ -253,10 +301,19 @@ No. It only renders scope choices that your app explicitly allows.
253
301
 
254
302
  No. This package is UI only. If your browser app calls SPAPS directly with a publishable key, the relevant SPAPS application row must include that browser origin in `allowed_origins`.
255
303
 
304
+ ## Design system
305
+
306
+ This package is intentionally theme-neutral and uses accessible Radix behavior
307
+ primitives plus a restrained slate palette so it blends into the host app. The
308
+ Design System Registry (DSR) adoption decision for this surface is an **explicit
309
+ deferral** because no suitable theme-neutral registry item exists yet. The
310
+ repository-level `docs/DSR-DECISION.md` captures the reasoning, file-level audit,
311
+ and revisit trigger.
312
+
256
313
  ## Metadata
257
314
 
258
315
  - `package_name`: `spaps-issue-reporting-react`
259
- - `latest_version`: `0.4.0`
316
+ - `latest_version`: `0.4.1`
260
317
  - `minimum_runtime`: `Node.js >=18.0.0`
261
318
  - `api_base_url`: `https://api.sweetpotato.dev`
262
319
 
package/dist/index.d.mts CHANGED
@@ -2,8 +2,8 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
  import React__default, { ReactNode } from 'react';
4
4
  import * as spaps_types from 'spaps-types';
5
- import { IssueReportScope, IssueReportStatusResult, IssueReportStatus, IssueReportListResult, IssueReport, CreateIssueReportRequest, UpdateIssueReportRequest, ReplyIssueReportRequest, IssueReportingVoiceTokenResult, IssueReportAttachmentOut, IssueReportingInputMode, IssueReportingVoiceProvider } from 'spaps-types';
6
- export { CreateIssueReportRequest, IssueReport, IssueReportAttachmentOut, IssueReportListResult, IssueReportScope, IssueReportStatus, IssueReportStatusResult, ReplyIssueReportRequest, UpdateIssueReportRequest } from 'spaps-types';
5
+ import { IssueReportScope, IssueReportStatusResult, IssueReportStatus, IssueReportListResult, IssueReport, CreateIssueReportRequest, UpdateIssueReportRequest, ReplyIssueReportRequest, IssueReportingVoiceTokenResult, IssueReportAttachmentOut, ListIssueReportMessagesResponse, CreateReporterMessageRequest, IssueReportMessage, IssueReportingInputMode, IssueReportingVoiceProvider } from 'spaps-types';
6
+ export { CreateIssueReportRequest, CreateReporterMessageRequest, IssueReport, IssueReportAttachmentOut, IssueReportListResult, IssueReportMessage, IssueReportScope, IssueReportStatus, IssueReportStatusResult, ListIssueReportMessagesResponse, ReplyIssueReportRequest, UpdateIssueReportRequest } from 'spaps-types';
7
7
  import * as _tanstack_query_core from '@tanstack/query-core';
8
8
  import * as _tanstack_react_query from '@tanstack/react-query';
9
9
 
@@ -39,6 +39,8 @@ interface IssueReportingClient {
39
39
  uploadAttachment?: (file: Blob, options?: {
40
40
  filename?: string;
41
41
  }) => Promise<IssueReportAttachmentOut>;
42
+ listMessages?: (issueReportId: string) => Promise<ListIssueReportMessagesResponse>;
43
+ submitMessage?: (issueReportId: string, payload: CreateReporterMessageRequest) => Promise<IssueReportMessage>;
42
44
  };
43
45
  }
44
46
  interface ReportableTargetDescriptor {
@@ -96,6 +98,23 @@ interface IssueReportingCopy {
96
98
  originHumanLabel: string;
97
99
  originMachineLabel: string;
98
100
  machineOriginFallback: string;
101
+ threadTitle: string;
102
+ threadDescription: string;
103
+ threadLoading: string;
104
+ threadLoadFailed: string;
105
+ threadEmpty: string;
106
+ threadNeedsResponseBadge: string;
107
+ threadAuthorOperator: string;
108
+ threadAuthorReporter: string;
109
+ threadKindClarificationRequest: string;
110
+ threadKindReporterResponse: string;
111
+ threadKindFinalResponse: string;
112
+ threadResponsePlaceholder: string;
113
+ threadResponseLabel: string;
114
+ threadResponseSubmitAction: string;
115
+ threadResponseSubmittingAction: string;
116
+ threadResponseConflict: string;
117
+ threadResponseFailed: string;
99
118
  }
100
119
  interface IssueReportingProviderProps {
101
120
  client: IssueReportingClient;
@@ -120,6 +139,9 @@ interface IssueReportingPageConfigProps {
120
139
  createMode: IssueReportingCreateMode;
121
140
  }
122
141
 
142
+ declare function IssueReportMessageThread({ issueReportId, }: {
143
+ issueReportId: string;
144
+ }): react_jsx_runtime.JSX.Element | null;
123
145
  declare function FloatingIssueReportButton({ className, positionClassName, }: FloatingIssueReportButtonProps): react_jsx_runtime.JSX.Element | null;
124
146
  declare function ReportableSection({ reportableName, children, className, as: Component, }: {
125
147
  reportableName: ReportableInput;
@@ -174,6 +196,8 @@ type IssueReportingContextValue = {
174
196
  voice: Required<Pick<IssueReportingVoiceConfig, "provider" | "modelId" | "requireMicrophonePermission">> & {
175
197
  microphone?: IssueReportingVoiceConfig["microphone"];
176
198
  };
199
+ needsResponseIssueIds: string[];
200
+ setNeedsResponse: (issueReportId: string, needsResponse: boolean) => void;
177
201
  };
178
202
  declare const defaultIssueReportingCopy: IssueReportingCopy;
179
203
  declare const issueReportingKeys: {
@@ -181,6 +205,7 @@ declare const issueReportingKeys: {
181
205
  status: (scope: IssueReportScope) => readonly ["spaps-issue-reporting", "status", IssueReportScope];
182
206
  history: (scope: IssueReportScope) => readonly ["spaps-issue-reporting", "history", IssueReportScope];
183
207
  detail: (issueReportId: string) => readonly ["spaps-issue-reporting", "detail", string];
208
+ messages: (issueReportId: string) => readonly ["spaps-issue-reporting", "messages", string];
184
209
  };
185
210
  declare function isOpenIssueStatus(status: IssueReportStatus): boolean;
186
211
  declare function isClosedIssueStatus(status: IssueReportStatus): boolean;
@@ -192,6 +217,25 @@ declare function getEntryPointState(status?: {
192
217
  has_recent_resolved: boolean;
193
218
  } | null): "open" | "recent_resolved" | "neutral";
194
219
  declare function getEntryPointClassName(state: "open" | "recent_resolved" | "neutral"): string;
220
+ /**
221
+ * Defensive reporter-visibility filter for the conversation surface.
222
+ *
223
+ * The reporter `GET /messages` endpoint already returns only `active`,
224
+ * reporter-visible projection rows (retracted/superseded operator messages and
225
+ * raw `support_case_events` payloads are excluded server-side). This filter
226
+ * re-asserts that contract in the UI so a non-allowlisted row can never be
227
+ * rendered to a reporter even if a client or fixture passes one through. Rows
228
+ * are returned in chronological (`created_at`) order.
229
+ */
230
+ declare function selectReporterVisibleMessages(messages: IssueReportMessage[]): IssueReportMessage[];
231
+ /**
232
+ * Detect the idempotency-key conflict surfaced by the reporter message API as
233
+ * `409 ISSUE_REPORT_MESSAGE_CONFLICT`. The provider only depends on the
234
+ * abstract `IssueReportingClient` contract, so this inspects whatever shape the
235
+ * host client throws (Error message, `code`, or HTTP `status`) without binding
236
+ * to a specific transport.
237
+ */
238
+ declare function isReporterMessageConflict(error: unknown): boolean;
195
239
  declare function getIssueNoteLengthMessage(note: string, copy: IssueReportingCopy): string;
196
240
  declare function useIssueReporting(): IssueReportingContextValue;
197
241
  declare function IssueReportingPageConfig({ createMode, }: IssueReportingPageConfigProps): react_jsx_runtime.JSX.Element;
@@ -391,6 +435,30 @@ declare function useIssueReportingMutations(): {
391
435
  attachment_ids?: string[];
392
436
  }, unknown>;
393
437
  };
438
+ /**
439
+ * Query helper for the reporter-visible message thread of one issue report.
440
+ *
441
+ * Keyed by `issue_report_id`. Enabled only when the caller is eligible, an
442
+ * issue id is provided, and the host client implements the optional
443
+ * `listMessages` contract method. The query returns the raw server response so
444
+ * consumers can read `needs_response` for the badge; rendering code should pass
445
+ * `data.items` through {@link selectReporterVisibleMessages} before display.
446
+ */
447
+ declare function useIssueReportingMessages(issueReportId: string | null): _tanstack_react_query.UseQueryResult<ListIssueReportMessagesResponse, Error>;
448
+ /**
449
+ * Mutation helper for submitting a reporter clarification response.
450
+ *
451
+ * Calls the host client's `submitMessage(issueReportId, { body, idempotency_key })`
452
+ * and, on success, invalidates the thread query so the new `reporter_response`
453
+ * row and refreshed `needs_response` flag are re-fetched. The caller supplies a
454
+ * client-generated `idempotency_key` for safe retries; a key reused with a
455
+ * different body surfaces as a `409` conflict the UI can detect with
456
+ * {@link isReporterMessageConflict}.
457
+ */
458
+ declare function useIssueReportingMessageMutation(issueReportId: string | null): _tanstack_react_query.UseMutationResult<IssueReportMessage, unknown, {
459
+ body: string;
460
+ idempotency_key?: string;
461
+ }, unknown>;
394
462
  declare function IssueReportingProvider({ client, isEligible, reporterRoleHint, principalId, getPageUrl, defaultScope, allowTenantScope, defaultCreateMode, inputModes, defaultInputMode, voice, copy, children, }: IssueReportingProviderProps): react_jsx_runtime.JSX.Element;
395
463
 
396
464
  interface ReportModeContextValue {
@@ -404,4 +472,4 @@ interface ReportModeContextValue {
404
472
  declare const ReportModeContext: React.Context<ReportModeContextValue | null>;
405
473
  declare function useReportMode(): ReportModeContextValue | null;
406
474
 
407
- export { FloatingIssueReportButton, type FloatingIssueReportButtonProps, type IssueHistoryFilter, type IssueReportingClient, type IssueReportingCopy, type IssueReportingCreateMode, IssueReportingPageConfig, type IssueReportingPageConfigProps, IssueReportingProvider, type IssueReportingProviderProps, ReportModeContext, type ReportModeContextValue, type ReportableInput, ReportableSection, type ReportableTargetDescriptor, defaultIssueReportingCopy, filterIssueReports, getEntryPointClassName, getEntryPointState, getIssueNoteLengthMessage, getIssueStatusBadgeLabel, getIssueStatusClassName, isClosedIssueStatus, isOpenIssueStatus, issueReportingKeys, useIssueReporting, useIssueReportingHistory, useIssueReportingMutations, useIssueReportingStatus, useReportMode };
475
+ export { FloatingIssueReportButton, type FloatingIssueReportButtonProps, type IssueHistoryFilter, IssueReportMessageThread, type IssueReportingClient, type IssueReportingCopy, type IssueReportingCreateMode, IssueReportingPageConfig, type IssueReportingPageConfigProps, IssueReportingProvider, type IssueReportingProviderProps, ReportModeContext, type ReportModeContextValue, type ReportableInput, ReportableSection, type ReportableTargetDescriptor, defaultIssueReportingCopy, filterIssueReports, getEntryPointClassName, getEntryPointState, getIssueNoteLengthMessage, getIssueStatusBadgeLabel, getIssueStatusClassName, isClosedIssueStatus, isOpenIssueStatus, isReporterMessageConflict, issueReportingKeys, selectReporterVisibleMessages, useIssueReporting, useIssueReportingHistory, useIssueReportingMessageMutation, useIssueReportingMessages, useIssueReportingMutations, useIssueReportingStatus, useReportMode };
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
  import React__default, { ReactNode } from 'react';
4
4
  import * as spaps_types from 'spaps-types';
5
- import { IssueReportScope, IssueReportStatusResult, IssueReportStatus, IssueReportListResult, IssueReport, CreateIssueReportRequest, UpdateIssueReportRequest, ReplyIssueReportRequest, IssueReportingVoiceTokenResult, IssueReportAttachmentOut, IssueReportingInputMode, IssueReportingVoiceProvider } from 'spaps-types';
6
- export { CreateIssueReportRequest, IssueReport, IssueReportAttachmentOut, IssueReportListResult, IssueReportScope, IssueReportStatus, IssueReportStatusResult, ReplyIssueReportRequest, UpdateIssueReportRequest } from 'spaps-types';
5
+ import { IssueReportScope, IssueReportStatusResult, IssueReportStatus, IssueReportListResult, IssueReport, CreateIssueReportRequest, UpdateIssueReportRequest, ReplyIssueReportRequest, IssueReportingVoiceTokenResult, IssueReportAttachmentOut, ListIssueReportMessagesResponse, CreateReporterMessageRequest, IssueReportMessage, IssueReportingInputMode, IssueReportingVoiceProvider } from 'spaps-types';
6
+ export { CreateIssueReportRequest, CreateReporterMessageRequest, IssueReport, IssueReportAttachmentOut, IssueReportListResult, IssueReportMessage, IssueReportScope, IssueReportStatus, IssueReportStatusResult, ListIssueReportMessagesResponse, ReplyIssueReportRequest, UpdateIssueReportRequest } from 'spaps-types';
7
7
  import * as _tanstack_query_core from '@tanstack/query-core';
8
8
  import * as _tanstack_react_query from '@tanstack/react-query';
9
9
 
@@ -39,6 +39,8 @@ interface IssueReportingClient {
39
39
  uploadAttachment?: (file: Blob, options?: {
40
40
  filename?: string;
41
41
  }) => Promise<IssueReportAttachmentOut>;
42
+ listMessages?: (issueReportId: string) => Promise<ListIssueReportMessagesResponse>;
43
+ submitMessage?: (issueReportId: string, payload: CreateReporterMessageRequest) => Promise<IssueReportMessage>;
42
44
  };
43
45
  }
44
46
  interface ReportableTargetDescriptor {
@@ -96,6 +98,23 @@ interface IssueReportingCopy {
96
98
  originHumanLabel: string;
97
99
  originMachineLabel: string;
98
100
  machineOriginFallback: string;
101
+ threadTitle: string;
102
+ threadDescription: string;
103
+ threadLoading: string;
104
+ threadLoadFailed: string;
105
+ threadEmpty: string;
106
+ threadNeedsResponseBadge: string;
107
+ threadAuthorOperator: string;
108
+ threadAuthorReporter: string;
109
+ threadKindClarificationRequest: string;
110
+ threadKindReporterResponse: string;
111
+ threadKindFinalResponse: string;
112
+ threadResponsePlaceholder: string;
113
+ threadResponseLabel: string;
114
+ threadResponseSubmitAction: string;
115
+ threadResponseSubmittingAction: string;
116
+ threadResponseConflict: string;
117
+ threadResponseFailed: string;
99
118
  }
100
119
  interface IssueReportingProviderProps {
101
120
  client: IssueReportingClient;
@@ -120,6 +139,9 @@ interface IssueReportingPageConfigProps {
120
139
  createMode: IssueReportingCreateMode;
121
140
  }
122
141
 
142
+ declare function IssueReportMessageThread({ issueReportId, }: {
143
+ issueReportId: string;
144
+ }): react_jsx_runtime.JSX.Element | null;
123
145
  declare function FloatingIssueReportButton({ className, positionClassName, }: FloatingIssueReportButtonProps): react_jsx_runtime.JSX.Element | null;
124
146
  declare function ReportableSection({ reportableName, children, className, as: Component, }: {
125
147
  reportableName: ReportableInput;
@@ -174,6 +196,8 @@ type IssueReportingContextValue = {
174
196
  voice: Required<Pick<IssueReportingVoiceConfig, "provider" | "modelId" | "requireMicrophonePermission">> & {
175
197
  microphone?: IssueReportingVoiceConfig["microphone"];
176
198
  };
199
+ needsResponseIssueIds: string[];
200
+ setNeedsResponse: (issueReportId: string, needsResponse: boolean) => void;
177
201
  };
178
202
  declare const defaultIssueReportingCopy: IssueReportingCopy;
179
203
  declare const issueReportingKeys: {
@@ -181,6 +205,7 @@ declare const issueReportingKeys: {
181
205
  status: (scope: IssueReportScope) => readonly ["spaps-issue-reporting", "status", IssueReportScope];
182
206
  history: (scope: IssueReportScope) => readonly ["spaps-issue-reporting", "history", IssueReportScope];
183
207
  detail: (issueReportId: string) => readonly ["spaps-issue-reporting", "detail", string];
208
+ messages: (issueReportId: string) => readonly ["spaps-issue-reporting", "messages", string];
184
209
  };
185
210
  declare function isOpenIssueStatus(status: IssueReportStatus): boolean;
186
211
  declare function isClosedIssueStatus(status: IssueReportStatus): boolean;
@@ -192,6 +217,25 @@ declare function getEntryPointState(status?: {
192
217
  has_recent_resolved: boolean;
193
218
  } | null): "open" | "recent_resolved" | "neutral";
194
219
  declare function getEntryPointClassName(state: "open" | "recent_resolved" | "neutral"): string;
220
+ /**
221
+ * Defensive reporter-visibility filter for the conversation surface.
222
+ *
223
+ * The reporter `GET /messages` endpoint already returns only `active`,
224
+ * reporter-visible projection rows (retracted/superseded operator messages and
225
+ * raw `support_case_events` payloads are excluded server-side). This filter
226
+ * re-asserts that contract in the UI so a non-allowlisted row can never be
227
+ * rendered to a reporter even if a client or fixture passes one through. Rows
228
+ * are returned in chronological (`created_at`) order.
229
+ */
230
+ declare function selectReporterVisibleMessages(messages: IssueReportMessage[]): IssueReportMessage[];
231
+ /**
232
+ * Detect the idempotency-key conflict surfaced by the reporter message API as
233
+ * `409 ISSUE_REPORT_MESSAGE_CONFLICT`. The provider only depends on the
234
+ * abstract `IssueReportingClient` contract, so this inspects whatever shape the
235
+ * host client throws (Error message, `code`, or HTTP `status`) without binding
236
+ * to a specific transport.
237
+ */
238
+ declare function isReporterMessageConflict(error: unknown): boolean;
195
239
  declare function getIssueNoteLengthMessage(note: string, copy: IssueReportingCopy): string;
196
240
  declare function useIssueReporting(): IssueReportingContextValue;
197
241
  declare function IssueReportingPageConfig({ createMode, }: IssueReportingPageConfigProps): react_jsx_runtime.JSX.Element;
@@ -391,6 +435,30 @@ declare function useIssueReportingMutations(): {
391
435
  attachment_ids?: string[];
392
436
  }, unknown>;
393
437
  };
438
+ /**
439
+ * Query helper for the reporter-visible message thread of one issue report.
440
+ *
441
+ * Keyed by `issue_report_id`. Enabled only when the caller is eligible, an
442
+ * issue id is provided, and the host client implements the optional
443
+ * `listMessages` contract method. The query returns the raw server response so
444
+ * consumers can read `needs_response` for the badge; rendering code should pass
445
+ * `data.items` through {@link selectReporterVisibleMessages} before display.
446
+ */
447
+ declare function useIssueReportingMessages(issueReportId: string | null): _tanstack_react_query.UseQueryResult<ListIssueReportMessagesResponse, Error>;
448
+ /**
449
+ * Mutation helper for submitting a reporter clarification response.
450
+ *
451
+ * Calls the host client's `submitMessage(issueReportId, { body, idempotency_key })`
452
+ * and, on success, invalidates the thread query so the new `reporter_response`
453
+ * row and refreshed `needs_response` flag are re-fetched. The caller supplies a
454
+ * client-generated `idempotency_key` for safe retries; a key reused with a
455
+ * different body surfaces as a `409` conflict the UI can detect with
456
+ * {@link isReporterMessageConflict}.
457
+ */
458
+ declare function useIssueReportingMessageMutation(issueReportId: string | null): _tanstack_react_query.UseMutationResult<IssueReportMessage, unknown, {
459
+ body: string;
460
+ idempotency_key?: string;
461
+ }, unknown>;
394
462
  declare function IssueReportingProvider({ client, isEligible, reporterRoleHint, principalId, getPageUrl, defaultScope, allowTenantScope, defaultCreateMode, inputModes, defaultInputMode, voice, copy, children, }: IssueReportingProviderProps): react_jsx_runtime.JSX.Element;
395
463
 
396
464
  interface ReportModeContextValue {
@@ -404,4 +472,4 @@ interface ReportModeContextValue {
404
472
  declare const ReportModeContext: React.Context<ReportModeContextValue | null>;
405
473
  declare function useReportMode(): ReportModeContextValue | null;
406
474
 
407
- export { FloatingIssueReportButton, type FloatingIssueReportButtonProps, type IssueHistoryFilter, type IssueReportingClient, type IssueReportingCopy, type IssueReportingCreateMode, IssueReportingPageConfig, type IssueReportingPageConfigProps, IssueReportingProvider, type IssueReportingProviderProps, ReportModeContext, type ReportModeContextValue, type ReportableInput, ReportableSection, type ReportableTargetDescriptor, defaultIssueReportingCopy, filterIssueReports, getEntryPointClassName, getEntryPointState, getIssueNoteLengthMessage, getIssueStatusBadgeLabel, getIssueStatusClassName, isClosedIssueStatus, isOpenIssueStatus, issueReportingKeys, useIssueReporting, useIssueReportingHistory, useIssueReportingMutations, useIssueReportingStatus, useReportMode };
475
+ export { FloatingIssueReportButton, type FloatingIssueReportButtonProps, type IssueHistoryFilter, IssueReportMessageThread, type IssueReportingClient, type IssueReportingCopy, type IssueReportingCreateMode, IssueReportingPageConfig, type IssueReportingPageConfigProps, IssueReportingProvider, type IssueReportingProviderProps, ReportModeContext, type ReportModeContextValue, type ReportableInput, ReportableSection, type ReportableTargetDescriptor, defaultIssueReportingCopy, filterIssueReports, getEntryPointClassName, getEntryPointState, getIssueNoteLengthMessage, getIssueStatusBadgeLabel, getIssueStatusClassName, isClosedIssueStatus, isOpenIssueStatus, isReporterMessageConflict, issueReportingKeys, selectReporterVisibleMessages, useIssueReporting, useIssueReportingHistory, useIssueReportingMessageMutation, useIssueReportingMessages, useIssueReportingMutations, useIssueReportingStatus, useReportMode };