@remit/web-client 0.0.313 → 0.0.314

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.
@@ -0,0 +1,299 @@
1
+ import assert from "node:assert/strict";
2
+ import { after, afterEach, before, describe, it } from "node:test";
3
+ import type {
4
+ RemitImapCalendarResponse,
5
+ RemitImapCalendarSuggestionResponse,
6
+ } from "@remit/api-http-client/types.gen.ts";
7
+ import {
8
+ type AnyRouter,
9
+ createMemoryHistory,
10
+ createRootRoute,
11
+ createRoute,
12
+ createRouter,
13
+ Outlet,
14
+ type RouteComponent,
15
+ RouterProvider,
16
+ } from "@tanstack/react-router";
17
+ import { createElement } from "react";
18
+ import { calendarSearchSchema } from "@/lib/calendar-route";
19
+ import { stringifySearch } from "@/lib/search-params";
20
+ import { Route as SuggestionRoute } from "../../routes/calendar/$view.$date/suggestion.$suggestionId.js";
21
+ import { createDomHarness, type DomHarness } from "../../test-support/dom";
22
+ import {
23
+ type HttpCall,
24
+ type HttpMock,
25
+ httpError,
26
+ mockFetch,
27
+ } from "../../test-support/http";
28
+
29
+ (globalThis as { self?: typeof globalThis }).self ??= globalThis;
30
+
31
+ const WORK = "11111111-1111-4111-8111-111111111111";
32
+ const SUGGESTION = "22222222-2222-4222-8222-222222222222";
33
+ const WEEK = "/calendar/week/2026-06-10";
34
+ const ENTRY = `${WEEK}/suggestion/${SUGGESTION}`;
35
+
36
+ const calendars = [
37
+ {
38
+ calendarId: WORK,
39
+ accountConfigId: "cfg-1",
40
+ urlSegment: "work",
41
+ displayName: "Work",
42
+ color: "Cal1",
43
+ componentSet: "VeventOnly",
44
+ source: "Default",
45
+ timezone: "Europe/Amsterdam",
46
+ syncSequence: 1,
47
+ subscriptionUrl: "",
48
+ subscriptionEnabled: false,
49
+ subscriptionCheckedAt: 0,
50
+ subscriptionFetchedAt: 0,
51
+ subscriptionError: "",
52
+ createdAt: 0,
53
+ updatedAt: 0,
54
+ },
55
+ ] satisfies RemitImapCalendarResponse[];
56
+
57
+ const reading = (
58
+ overrides: Partial<RemitImapCalendarSuggestionResponse> = {},
59
+ ): RemitImapCalendarSuggestionResponse => ({
60
+ suggestionId: SUGGESTION,
61
+ accountConfigId: "cfg-1",
62
+ messageId: "msg-1",
63
+ bodyPartId: "part-1",
64
+ icalUid: "uid-1",
65
+ sequence: 0,
66
+ method: "Request",
67
+ source: "IcalendarPart",
68
+ state: "Pending",
69
+ summary: "Billing migration kickoff",
70
+ dtStart: "2026-06-11T12:00:00+00:00",
71
+ dtEnd: "2026-06-11T13:00:00+00:00",
72
+ allDay: false,
73
+ location: "Room Noord",
74
+ organizer: "priya@example.test",
75
+ zoneCertainty: "Explicit",
76
+ acceptedCalendarObjectId: "",
77
+ createdAt: 0,
78
+ updatedAt: 0,
79
+ ...overrides,
80
+ });
81
+
82
+ let harness: DomHarness | undefined;
83
+ let http: HttpMock | undefined;
84
+ const runnerZone = process.env.TZ;
85
+
86
+ before(() => {
87
+ process.env.TZ = "Europe/Amsterdam";
88
+ });
89
+
90
+ after(() => {
91
+ if (runnerZone === undefined) Reflect.deleteProperty(process.env, "TZ");
92
+ else process.env.TZ = runnerZone;
93
+ });
94
+
95
+ afterEach(() => {
96
+ harness?.close();
97
+ harness = undefined;
98
+ http?.restore();
99
+ http = undefined;
100
+ });
101
+
102
+ const componentOf = (route: unknown): RouteComponent => {
103
+ const { component } = (route as { options: { component?: RouteComponent } })
104
+ .options;
105
+ if (!component) throw new Error("the route mounts no component");
106
+ return component;
107
+ };
108
+
109
+ const testRouter = (): AnyRouter => {
110
+ const rootRoute = createRootRoute({ component: Outlet });
111
+ const viewRoute = createRoute({
112
+ getParentRoute: () => rootRoute,
113
+ path: "/calendar/$view/$date",
114
+ validateSearch: calendarSearchSchema,
115
+ component: Outlet,
116
+ });
117
+ const routeTree = rootRoute.addChildren([
118
+ viewRoute.addChildren([
119
+ createRoute({
120
+ getParentRoute: () => viewRoute,
121
+ path: "/",
122
+ component: () => null,
123
+ }),
124
+ createRoute({
125
+ getParentRoute: () => viewRoute,
126
+ path: "suggestion/$suggestionId",
127
+ component: componentOf(SuggestionRoute),
128
+ }),
129
+ ]),
130
+ ]);
131
+ return createRouter({
132
+ routeTree,
133
+ stringifySearch,
134
+ history: createMemoryHistory({ initialEntries: [ENTRY] }),
135
+ }) as unknown as AnyRouter;
136
+ };
137
+
138
+ const server = (
139
+ suggestion: RemitImapCalendarSuggestionResponse,
140
+ answer: (call: HttpCall) => Response | undefined = () => undefined,
141
+ ) => {
142
+ let pending = [suggestion];
143
+ return (call: HttpCall): unknown => {
144
+ if (call.path.endsWith("/calendars")) return { items: calendars };
145
+ if (call.path.endsWith("/calendar-suggestions")) return { items: pending };
146
+ if (call.path.endsWith("/accept")) {
147
+ const refusal = answer(call);
148
+ if (refusal) return refusal;
149
+ pending = [];
150
+ return { ...suggestion, state: "Accepted" };
151
+ }
152
+ return { items: [] };
153
+ };
154
+ };
155
+
156
+ const mount = async (respond: (call: HttpCall) => unknown) => {
157
+ http = mockFetch(respond);
158
+ const router = testRouter();
159
+ await router.load();
160
+ harness = createDomHarness();
161
+ harness.renderApp(createElement(RouterProvider, { router }));
162
+ await harness.waitFor(
163
+ () =>
164
+ (harness?.queryAll<HTMLInputElement>("input") ?? []).some(
165
+ (input) => input.value === "Billing migration kickoff",
166
+ ),
167
+ "the editor to open seeded from the suggestion",
168
+ );
169
+ await harness.waitFor(
170
+ () => harness?.query("input[type=radio]:checked") !== null,
171
+ "the default calendar to be picked",
172
+ );
173
+ return router;
174
+ };
175
+
176
+ const fieldValue = (label: string): string => {
177
+ if (!harness) throw new Error("not mounted");
178
+ return (harness.byLabel(label) as HTMLInputElement).value;
179
+ };
180
+
181
+ const acceptedEdits = (): HttpCall[] =>
182
+ http?.to(`/calendar-suggestions/${SUGGESTION}/accept`) ?? [];
183
+
184
+ describe("changing a suggestion before adding it", () => {
185
+ it("opens the editor on what the mail said, on this device's clock", async () => {
186
+ await mount(server(reading()));
187
+
188
+ assert.equal(fieldValue("Title"), "Billing migration kickoff");
189
+ assert.equal(fieldValue("Location"), "Room Noord");
190
+ assert.equal(fieldValue("Date"), "2026-06-11");
191
+ assert.equal(fieldValue("Start time"), "14:00");
192
+ assert.equal(fieldValue("End time"), "15:00");
193
+ });
194
+
195
+ it("adds the corrected event, settles the suggestion and closes the editor", async () => {
196
+ const router = await mount(server(reading()));
197
+
198
+ harness?.type(harness.byLabel("Title"), "Billing migration kickoff, moved");
199
+ harness?.type(harness.byLabel("Start time"), "16:00");
200
+ harness?.type(harness.byLabel("End time"), "17:30");
201
+ await harness?.flush();
202
+ harness?.click(harness.byText("button", "Add"));
203
+
204
+ await harness?.waitFor(
205
+ () => router.state.location.pathname === WEEK,
206
+ "the editor to close once the server settled the suggestion",
207
+ );
208
+ const sent = acceptedEdits();
209
+ assert.equal(sent.length, 1);
210
+ assert.deepEqual(sent[0].body, {
211
+ calendarId: WORK,
212
+ event: {
213
+ summary: "Billing migration kickoff, moved",
214
+ start: "2026-06-11T16:00:00+02:00",
215
+ end: "2026-06-11T17:30:00+02:00",
216
+ allDay: false,
217
+ timeZone: "Europe/Amsterdam",
218
+ },
219
+ });
220
+ const listings = http?.to("/calendar-suggestions") ?? [];
221
+ assert.ok(
222
+ listings.length > 1,
223
+ "the waiting list is read again once the suggestion is settled",
224
+ );
225
+ });
226
+
227
+ it("places a reading nobody could put on a clock, even when the time is left alone", async () => {
228
+ await mount(
229
+ server(
230
+ reading({
231
+ dtStart: "2026-06-11T09:00:00+00:00",
232
+ dtEnd: "2026-06-11T10:00:00+00:00",
233
+ zoneCertainty: "Ambiguous",
234
+ }),
235
+ ),
236
+ );
237
+
238
+ assert.equal(fieldValue("Start time"), "09:00");
239
+ assert.match(harness?.text() ?? "", /time zone nothing could resolve/);
240
+ harness?.click(harness.byText("button", "Add"));
241
+ await harness?.waitFor(
242
+ () => acceptedEdits().length === 1,
243
+ "the correction to be sent",
244
+ );
245
+
246
+ assert.deepEqual(acceptedEdits()[0].body?.event, {
247
+ start: "2026-06-11T09:00:00+02:00",
248
+ end: "2026-06-11T10:00:00+02:00",
249
+ allDay: false,
250
+ timeZone: "Europe/Amsterdam",
251
+ });
252
+ });
253
+
254
+ it("states a refusal and keeps the editor open with what was typed", async () => {
255
+ const router = await mount(
256
+ server(reading(), () => httpError(400, "Already declined.")),
257
+ );
258
+
259
+ harness?.type(harness.byLabel("Title"), "Kickoff, renamed");
260
+ await harness?.flush();
261
+ harness?.click(harness.byText("button", "Add"));
262
+
263
+ await harness?.waitFor(
264
+ () => harness?.query('[role="alert"]') !== null,
265
+ "the refusal to be stated",
266
+ );
267
+ assert.match(harness?.text() ?? "", /Couldn't add this to your calendar/);
268
+ assert.equal(fieldValue("Title"), "Kickoff, renamed");
269
+ assert.equal(router.state.location.pathname, ENTRY);
270
+ });
271
+
272
+ it("finds a suggestion that is not on the first page of what waits", async () => {
273
+ http = mockFetch((call: HttpCall): unknown => {
274
+ if (call.path.endsWith("/calendars")) return { items: calendars };
275
+ if (call.path.endsWith("/calendar-suggestions"))
276
+ return call.url.includes("continuationToken=page-2")
277
+ ? { items: [reading()] }
278
+ : {
279
+ items: [{ ...reading(), suggestionId: "other" }],
280
+ continuationToken: "page-2",
281
+ };
282
+ return { items: [] };
283
+ });
284
+ const router = testRouter();
285
+ await router.load();
286
+ harness = createDomHarness();
287
+ harness.renderApp(createElement(RouterProvider, { router }));
288
+
289
+ await harness.waitFor(
290
+ () =>
291
+ harness
292
+ ?.queryAll<HTMLInputElement>("input")
293
+ .some((input) => input.value === "Billing migration kickoff") ===
294
+ true,
295
+ "the editor to open on the suggestion from the second page",
296
+ );
297
+ assert.doesNotMatch(harness.text(), /no longer waiting/);
298
+ });
299
+ });
@@ -69,16 +69,21 @@ const pending: RemitImapCalendarSuggestionResponse = {
69
69
 
70
70
  let harness: DomHarness | undefined;
71
71
  let http: HttpMock | undefined;
72
+ let changedFirst: { suggestionId: string; date: string }[] = [];
72
73
 
73
74
  afterEach(() => {
74
75
  harness?.close();
75
76
  harness = undefined;
76
77
  http?.restore();
77
78
  http = undefined;
79
+ changedFirst = [];
78
80
  });
79
81
 
80
- const server = (accept: () => Response | undefined) => {
81
- let waiting = [pending];
82
+ const server = (
83
+ accept: () => Response | undefined,
84
+ reading: RemitImapCalendarSuggestionResponse = pending,
85
+ ) => {
86
+ let waiting = [reading];
82
87
  return (call: HttpCall): unknown => {
83
88
  if (call.path.endsWith("/calendars")) return { items: calendars };
84
89
  if (call.path.endsWith("/calendar-suggestions")) return { items: waiting };
@@ -95,7 +100,12 @@ const server = (accept: () => Response | undefined) => {
95
100
  const mount = async (respond: (call: HttpCall) => unknown) => {
96
101
  http = mockFetch(respond);
97
102
  harness = createDomHarness();
98
- harness.renderApp(createElement(PendingSuggestions));
103
+ harness.renderApp(
104
+ createElement(PendingSuggestions, {
105
+ onChangeFirst: (suggestionId: string, date: string) =>
106
+ changedFirst.push({ suggestionId, date }),
107
+ }),
108
+ );
99
109
  await harness.waitFor(
100
110
  () => harness?.text().includes("Quarterly review") === true,
101
111
  "the waiting reading to be drawn",
@@ -134,4 +144,59 @@ describe("what the mail is waiting on, beside the calendar", () => {
134
144
  assert.match(harness?.text() ?? "", /Couldn't add this to your calendar/);
135
145
  assert.match(harness?.text() ?? "", /Quarterly review/);
136
146
  });
147
+
148
+ it("hands the top reading to the editor on the day it falls", async () => {
149
+ await mount(server(() => undefined));
150
+ harness?.click(harness.byText("button", "Change first"));
151
+
152
+ assert.deepEqual(changedFirst, [
153
+ { suggestionId: SUGGESTION, date: "2026-09-01" },
154
+ ]);
155
+ assert.equal(
156
+ http?.to(`/calendar-suggestions/${SUGGESTION}/accept`).length,
157
+ 0,
158
+ );
159
+ });
160
+
161
+ it("sends a reading nobody could put on a clock to the editor instead of adding it", async () => {
162
+ await mount(
163
+ server(() => undefined, { ...pending, zoneCertainty: "Ambiguous" }),
164
+ );
165
+ harness?.click(harness.byText("button", "Add"));
166
+
167
+ assert.deepEqual(
168
+ changedFirst.map((entry) => entry.suggestionId),
169
+ [SUGGESTION],
170
+ );
171
+ assert.equal(
172
+ http?.to(`/calendar-suggestions/${SUGGESTION}/accept`).length,
173
+ 0,
174
+ );
175
+ assert.match(harness?.text() ?? "", /Quarterly review/);
176
+ });
177
+
178
+ it("applies a cancellation nobody could put on a clock, and offers no editor for it", async () => {
179
+ await mount(
180
+ server(() => undefined, {
181
+ ...pending,
182
+ method: "Cancel",
183
+ zoneCertainty: "Ambiguous",
184
+ }),
185
+ );
186
+ assert.equal(
187
+ harness
188
+ ?.queryAll("button")
189
+ .some((button) => button.textContent?.includes("Change first")),
190
+ false,
191
+ );
192
+
193
+ harness?.click(harness.byText("button", "Add"));
194
+ await harness?.waitFor(
195
+ () =>
196
+ (http?.to(`/calendar-suggestions/${SUGGESTION}/accept`).length ?? 0) >
197
+ 0,
198
+ "the cancellation to be applied",
199
+ );
200
+ assert.deepEqual(changedFirst, []);
201
+ });
137
202
  });
@@ -85,7 +85,7 @@ export function draftFromEvent(
85
85
  * Stamping any other zone's offset onto those digits moves the event by the
86
86
  * difference between the two — silently, and only for whoever is travelling.
87
87
  */
88
- function timesFor(
88
+ export function timesFor(
89
89
  draft: EventDraft,
90
90
  clock: string,
91
91
  ): { start: string; end: string } {
@@ -63,6 +63,11 @@ export {
63
63
  useCalendarSelection,
64
64
  useDrawnEvents,
65
65
  } from "./selection";
66
+ export {
67
+ type AcceptEditedInput,
68
+ acceptEditedInputFromDrafts,
69
+ draftFromSuggestion,
70
+ } from "./suggestion-draft";
66
71
  export {
67
72
  type CalendarCollectionOutcome,
68
73
  type CalendarCollectionWrites,
@@ -89,9 +94,11 @@ export { type CalendarJumpSearch, useCalendarJump } from "./useCalendarJump";
89
94
  export {
90
95
  type CalendarSuggestionAnswers,
91
96
  type CalendarSuggestionsResult,
97
+ type PendingCalendarSuggestionResult,
92
98
  type SuggestionAnswer,
93
99
  useCalendarSuggestionAnswers,
94
100
  useMessageCalendarSuggestions,
101
+ usePendingCalendarSuggestion,
95
102
  usePendingCalendarSuggestions,
96
103
  } from "./useCalendarSuggestions";
97
104
  export { type CalendarsResult, useCalendars } from "./useCalendars";
@@ -0,0 +1,77 @@
1
+ import type {
2
+ RemitImapAcceptCalendarSuggestionInput,
3
+ RemitImapCalendarSuggestionResponse,
4
+ } from "@remit/api-http-client/types.gen.ts";
5
+ import type { EventDraft } from "@remit/ui";
6
+ import {
7
+ type DraftRefusal,
8
+ type DraftZones,
9
+ emptyDraft,
10
+ patchFromDrafts,
11
+ timesFor,
12
+ } from "./draft";
13
+ import { addDays, isoOnClock } from "./window";
14
+
15
+ export type AcceptEditedInput =
16
+ | { ok: true; input: RemitImapAcceptCalendarSuggestionInput }
17
+ | DraftRefusal;
18
+
19
+ type Suggestion = Pick<
20
+ RemitImapCalendarSuggestionResponse,
21
+ "summary" | "location" | "dtStart" | "dtEnd" | "allDay" | "zoneCertainty"
22
+ >;
23
+
24
+ export function draftFromSuggestion(
25
+ suggestion: Suggestion,
26
+ calendarId: string,
27
+ clock: string,
28
+ ): EventDraft {
29
+ const base = {
30
+ title: suggestion.summary,
31
+ location: suggestion.location,
32
+ };
33
+ if (suggestion.allDay) {
34
+ const date = suggestion.dtStart.slice(0, 10);
35
+ const end = suggestion.dtEnd.slice(0, 10);
36
+ return {
37
+ ...emptyDraft(date, calendarId),
38
+ ...base,
39
+ allDay: true,
40
+ startTime: "",
41
+ endDate: end > date ? addDays(end, -1) : date,
42
+ endTime: "",
43
+ };
44
+ }
45
+ const wall = (iso: string): string =>
46
+ suggestion.zoneCertainty === "Ambiguous" ? iso : isoOnClock(iso, clock);
47
+ const start = wall(suggestion.dtStart);
48
+ const end = wall(suggestion.dtEnd);
49
+ return {
50
+ ...emptyDraft(start.slice(0, 10), calendarId),
51
+ ...base,
52
+ startTime: start.slice(11, 16),
53
+ endDate: end.slice(0, 10),
54
+ endTime: end.slice(11, 16),
55
+ };
56
+ }
57
+
58
+ export function acceptEditedInputFromDrafts(
59
+ suggestion: Suggestion,
60
+ seed: EventDraft,
61
+ draft: EventDraft,
62
+ zones: DraftZones,
63
+ ): AcceptEditedInput {
64
+ const built = patchFromDrafts(seed, draft, zones);
65
+ if (!built.ok) return built;
66
+ const placed =
67
+ suggestion.zoneCertainty === "Ambiguous" || built.patch.start !== undefined;
68
+ const event = placed
69
+ ? {
70
+ ...built.patch,
71
+ ...timesFor(draft, zones.clock),
72
+ allDay: draft.allDay,
73
+ timeZone: zones.anchor,
74
+ }
75
+ : built.patch;
76
+ return { ok: true, input: { calendarId: draft.calendarId, event } };
77
+ }
@@ -18,7 +18,11 @@ import {
18
18
  calendarSuggestionOperationsListCalendarSuggestionsOptions,
19
19
  messageCalendarSuggestionOperationsListMessageCalendarSuggestionsOptions,
20
20
  } from "@remit/api-http-client/@tanstack/react-query.gen.ts";
21
- import type { RemitImapCalendarSuggestionResponse } from "@remit/api-http-client/types.gen.ts";
21
+ import { calendarSuggestionOperationsListCalendarSuggestions } from "@remit/api-http-client/sdk.gen.ts";
22
+ import type {
23
+ RemitImapAcceptCalendarSuggestionInput,
24
+ RemitImapCalendarSuggestionResponse,
25
+ } from "@remit/api-http-client/types.gen.ts";
22
26
  import {
23
27
  type QueryClient,
24
28
  useMutation,
@@ -89,6 +93,46 @@ export function usePendingCalendarSuggestions(): CalendarSuggestionsResult {
89
93
  };
90
94
  }
91
95
 
96
+ const findPending = async (
97
+ suggestionId: string,
98
+ signal: AbortSignal,
99
+ ): Promise<RemitImapCalendarSuggestionResponse | null> => {
100
+ let continuationToken: string | undefined;
101
+ do {
102
+ const { data } = await calendarSuggestionOperationsListCalendarSuggestions({
103
+ query: { state: "Pending", continuationToken },
104
+ signal,
105
+ throwOnError: true,
106
+ });
107
+ const found = data.items.find((item) => item.suggestionId === suggestionId);
108
+ if (found) return found;
109
+ continuationToken = data.continuationToken;
110
+ } while (continuationToken);
111
+ return null;
112
+ };
113
+
114
+ export interface PendingCalendarSuggestionResult {
115
+ suggestion: RemitImapCalendarSuggestionResponse | null | undefined;
116
+ isLoading: boolean;
117
+ error: unknown;
118
+ }
119
+
120
+ export function usePendingCalendarSuggestion(
121
+ suggestionId: string,
122
+ ): PendingCalendarSuggestionResult {
123
+ const { data, isLoading, error } = useQuery({
124
+ queryKey: [
125
+ {
126
+ _id: "calendarSuggestionOperationsListCalendarSuggestions",
127
+ suggestionId,
128
+ },
129
+ ],
130
+ queryFn: ({ signal }) => findPending(suggestionId, signal),
131
+ meta: SUGGESTION_META,
132
+ });
133
+ return { suggestion: data, isLoading, error: error ?? null };
134
+ }
135
+
92
136
  export type SuggestionAnswer =
93
137
  | { kind: "answered" }
94
138
  | { kind: "refused"; message: string };
@@ -109,6 +153,10 @@ export interface CalendarSuggestionAnswers {
109
153
  suggestionId: string,
110
154
  calendarId: string,
111
155
  ) => Promise<SuggestionAnswer>;
156
+ acceptEdited: (
157
+ suggestionId: string,
158
+ input: RemitImapAcceptCalendarSuggestionInput,
159
+ ) => Promise<SuggestionAnswer>;
112
160
  decline: (suggestionId: string) => Promise<SuggestionAnswer>;
113
161
  dismiss: (
114
162
  suggestionId: string,
@@ -159,6 +207,14 @@ export function useCalendarSuggestionAnswers(): CalendarSuggestionAnswers {
159
207
  .catch(refused),
160
208
  [accept],
161
209
  );
210
+ const acceptEditedSuggestion = useCallback(
211
+ (suggestionId: string, input: RemitImapAcceptCalendarSuggestionInput) =>
212
+ accept
213
+ .mutateAsync({ path: { suggestionId }, body: input })
214
+ .then(() => ANSWERED)
215
+ .catch(refused),
216
+ [accept],
217
+ );
162
218
  const declineSuggestion = useCallback(
163
219
  (suggestionId: string) =>
164
220
  decline
@@ -180,6 +236,7 @@ export function useCalendarSuggestionAnswers(): CalendarSuggestionAnswers {
180
236
  () => ({
181
237
  accept: acceptSuggestion,
182
238
  tentative: tentativeSuggestion,
239
+ acceptEdited: acceptEditedSuggestion,
183
240
  decline: declineSuggestion,
184
241
  dismiss: dismissSuggestion,
185
242
  isAnswering: accept.isPending || decline.isPending || dismiss.isPending,
@@ -187,6 +244,7 @@ export function useCalendarSuggestionAnswers(): CalendarSuggestionAnswers {
187
244
  [
188
245
  acceptSuggestion,
189
246
  tentativeSuggestion,
247
+ acceptEditedSuggestion,
190
248
  declineSuggestion,
191
249
  dismissSuggestion,
192
250
  accept.isPending,