@remit/web-client 0.0.165 → 0.0.167

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 (43) hide show
  1. package/package.json +1 -1
  2. package/src/components/compose/ComposeForm.tsx +77 -25
  3. package/src/components/compose/ComposeProvider.tsx +11 -122
  4. package/src/components/compose/FullCompose.tsx +29 -18
  5. package/src/components/compose/InlineCompose.tsx +21 -11
  6. package/src/components/compose/MobileComposeSheet.tsx +14 -18
  7. package/src/components/compose/compose-send-stops-autosave.render.test.ts +7 -9
  8. package/src/components/compose/compose-starts-a-second-message.render.test.ts +170 -0
  9. package/src/components/compose/compose-title.ts +10 -0
  10. package/src/components/compose/mobile-header-stays-expanded.render.test.ts +5 -13
  11. package/src/components/layout/ComposeFab.tsx +6 -15
  12. package/src/components/layout/MailShell.tsx +9 -1
  13. package/src/components/layout/MailTopBar.tsx +3 -6
  14. package/src/components/mail/BriefPane.tsx +6 -5
  15. package/src/components/mail/DraftsView.tsx +14 -15
  16. package/src/components/mail/FlaggedPane.tsx +6 -5
  17. package/src/components/mail/MailboxPane.tsx +36 -124
  18. package/src/components/mail/MessageActionMenu.tsx +4 -3
  19. package/src/components/mail/MessageList.tsx +4 -3
  20. package/src/components/mail/OutboxPane.tsx +8 -13
  21. package/src/components/mail/SwipeableMessageRow.tsx +4 -3
  22. package/src/components/mail/intelligence-drawer.stories.tsx +208 -0
  23. package/src/hooks/useSaveDraft.ts +11 -0
  24. package/src/hooks/useSearchMirror.ts +12 -1
  25. package/src/lib/mail-route.test.ts +35 -1
  26. package/src/lib/mail-route.ts +2 -1
  27. package/src/routeTree.gen.ts +92 -0
  28. package/src/routes/mail/$mailboxId/compose.{-$outboxMessageId}.tsx +14 -0
  29. package/src/routes/mail/brief/compose.{-$outboxMessageId}.tsx +22 -0
  30. package/src/routes/mail/flagged/compose.{-$outboxMessageId}.tsx +13 -0
  31. package/src/routes/mail/outbox/compose.{-$outboxMessageId}.tsx +17 -0
  32. package/src/routes/mail.tsx +12 -8
  33. package/src/routing/compose-press-opens.render.test.ts +179 -0
  34. package/src/routing/compose.ts +220 -0
  35. package/src/routing/fragment.render.test.ts +98 -0
  36. package/src/routing/fragment.test.ts +54 -9
  37. package/src/routing/fragment.ts +37 -9
  38. package/src/routing/index.ts +10 -1
  39. package/src/routing/nav-link.tsx +5 -3
  40. package/src/components/compose/compose-clears-open-thread.render.test.ts +0 -312
  41. package/src/hooks/useComposeTargetMailbox.ts +0 -75
  42. package/src/lib/compose-routes.test.ts +0 -46
  43. package/src/lib/compose-routes.ts +0 -25
@@ -16,8 +16,6 @@
16
16
  * On phone, use `<MailboxPane.Phone />` instead of the slot sub-views.
17
17
  */
18
18
  import {
19
- outboxDetailOperationsDeleteOutboxMessageMutation,
20
- outboxOperationsListOutboxMessagesQueryKey,
21
19
  threadOperationsListThreadsQueryKey,
22
20
  threadOperationsSearchThreadsQueryKey,
23
21
  } from "@remit/api-http-client/@tanstack/react-query.gen.ts";
@@ -35,11 +33,7 @@ import {
35
33
  type SearchResult,
36
34
  useAppShellLayout,
37
35
  } from "@remit/ui";
38
- import {
39
- useInfiniteQuery,
40
- useMutation,
41
- useQueryClient,
42
- } from "@tanstack/react-query";
36
+ import { useInfiniteQuery } from "@tanstack/react-query";
43
37
  import { useNavigate } from "@tanstack/react-router";
44
38
  import {
45
39
  createContext,
@@ -53,8 +47,6 @@ import {
53
47
  useState,
54
48
  } from "react";
55
49
  import type { ComposeMode } from "@/components/compose/ComposeProvider";
56
- import { useCompose } from "@/components/compose/ComposeProvider";
57
- import { FullCompose } from "@/components/compose/FullCompose";
58
50
  import { Drawer } from "@/components/layout/Drawer";
59
51
  import { ConversationView } from "@/components/mail/ConversationView";
60
52
  import { DraftsView } from "@/components/mail/DraftsView";
@@ -66,8 +58,6 @@ import {
66
58
  import { MessageToolbar } from "@/components/mail/MessageToolbar";
67
59
  import { PullToRefresh } from "@/components/mail/PullToRefresh";
68
60
  import { SpamRescue } from "@/components/mail/SpamRescue";
69
- import { useErrorBanners } from "@/components/ui/ErrorBannerProvider";
70
- import { buildMutationErrorBanner } from "@/components/ui/error-banners";
71
61
  import {
72
62
  useArchiveMailbox,
73
63
  useDraftsMailbox,
@@ -83,7 +73,6 @@ import {
83
73
  } from "@/hooks/useDeleteMessages";
84
74
  import type { EscalationSearchQuery } from "@/hooks/useEscalatedActions";
85
75
  import { useIntelligenceData } from "@/hooks/useIntelligenceData";
86
- import { useKeyboardNavigation } from "@/hooks/useKeyboardNavigation";
87
76
  import { useLayoutTier } from "@/hooks/useLayoutTier";
88
77
  import { useMailboxAccount } from "@/hooks/useMailboxAccount";
89
78
  import { useToggleReadFor } from "@/hooks/useMarkAsRead";
@@ -127,7 +116,9 @@ import {
127
116
  import {
128
117
  type OpenThreadPath,
129
118
  type OpenThreadTarget,
130
- retainOpenPanels,
119
+ useIsComposing,
120
+ useOpenCompose,
121
+ useRetainOpenPanels,
131
122
  } from "@/routing";
132
123
  import { MailViewChrome } from "./MailViewChrome";
133
124
 
@@ -215,11 +206,7 @@ interface MailboxPaneContextValue {
215
206
  onClearComposeRequest: () => void;
216
207
  onToolbarDelete: () => void;
217
208
  onToolbarStar: () => void;
218
- onToolbarDiscardDraft: () => void;
219
209
  onToolbarMove: (destMailboxId: string) => void;
220
- composeState: ReturnType<typeof useCompose>["state"];
221
- closeCompose: () => void;
222
- hasRemitDraftOpen: boolean;
223
210
  // Phone actions
224
211
  onBack: () => void;
225
212
  /** The rows either side of the open one — the phone's swipe gestures. */
@@ -264,6 +251,7 @@ function MailboxPaneProvider({
264
251
  children,
265
252
  }: MailboxPaneProps) {
266
253
  const navigate = useNavigate();
254
+ const retainPanels = useRetainOpenPanels();
267
255
  const threadId = thread?.threadId;
268
256
  const pointedAtMessageId = thread?.messageId;
269
257
  const telemetry = useTelemetry();
@@ -467,9 +455,9 @@ function MailboxPaneProvider({
467
455
  to: "/mail/$mailboxId",
468
456
  params: { mailboxId },
469
457
  search: (prev) => prev,
470
- hash: retainOpenPanels,
458
+ hash: retainPanels,
471
459
  });
472
- }, [mailboxId, navigate]);
460
+ }, [mailboxId, navigate, retainPanels]);
473
461
 
474
462
  const handleOpenThread = useCallback(
475
463
  (target: OpenThreadTarget) => {
@@ -477,10 +465,10 @@ function MailboxPaneProvider({
477
465
  to: "/mail/$mailboxId/$threadId/$messageId",
478
466
  params: { mailboxId, ...target },
479
467
  search: (prev) => prev,
480
- hash: retainOpenPanels,
468
+ hash: retainPanels,
481
469
  });
482
470
  },
483
- [mailboxId, navigate],
471
+ [mailboxId, navigate, retainPanels],
484
472
  );
485
473
 
486
474
  const handleDeselectIfRemoved = useCallback(
@@ -538,9 +526,6 @@ function MailboxPaneProvider({
538
526
  // so there is no fallback: until the mailbox resolves there is no number.
539
527
  const unreadCount = useCurrentMailboxUnseenCount({ accounts }) ?? 0;
540
528
 
541
- const queryClient = useQueryClient();
542
- const { pushError } = useErrorBanners();
543
-
544
529
  const toolbarActions = useThreadActions({
545
530
  thread: selectedThread,
546
531
  mailboxId,
@@ -568,48 +553,8 @@ function MailboxPaneProvider({
568
553
  setToolbarComposeRequest("forward");
569
554
  }, [setToolbarComposeRequest]);
570
555
 
571
- const { state: composeState, openCompose, closeCompose } = useCompose();
572
-
573
- const handleNewCompose = useCallback(() => {
574
- openCompose({ mode: "new" });
575
- }, [openCompose]);
576
-
577
- // A thread opening closes compose. Only a selection arriving counts, so this
578
- // cannot close the compose that just cleared one.
579
- const previousSelectionRef = useRef(selectedMessageId);
580
- useEffect(() => {
581
- const previous = previousSelectionRef.current;
582
- previousSelectionRef.current = selectedMessageId;
583
- if (!selectedMessageId || selectedMessageId === previous) return;
584
- closeCompose();
585
- }, [selectedMessageId, closeCompose]);
586
-
587
- const deleteOutboxMutation = useMutation({
588
- ...outboxDetailOperationsDeleteOutboxMessageMutation(),
589
- onError: (mutationError) => {
590
- pushError(
591
- buildMutationErrorBanner(
592
- "Couldn't discard draft",
593
- "The draft wasn't deleted.",
594
- mutationError,
595
- ),
596
- );
597
- },
598
- });
599
- const handleToolbarDiscardDraft = useCallback(() => {
600
- const outboxMessageId = composeState.outboxMessageId;
601
- if (!outboxMessageId) return;
602
- deleteOutboxMutation.mutate({ path: { outboxMessageId } });
603
- queryClient.invalidateQueries({
604
- queryKey: outboxOperationsListOutboxMessagesQueryKey(),
605
- });
606
- closeCompose();
607
- }, [
608
- composeState.outboxMessageId,
609
- deleteOutboxMutation,
610
- queryClient,
611
- closeCompose,
612
- ]);
556
+ const isComposing = useIsComposing();
557
+ const openCompose = useOpenCompose();
613
558
 
614
559
  const messageIdsForFocusedThread = useCallback(
615
560
  (thread: typeof focusedThread): string[] => {
@@ -783,17 +728,13 @@ function MailboxPaneProvider({
783
728
  }
784
729
  }, [normalizedSearchQuery, mailboxType, telemetry]);
785
730
 
786
- const hasRemitDraftOpen =
787
- isDraftsMailbox &&
788
- composeState.isOpen &&
789
- !!composeState.outboxMessageId &&
790
- !selectedThread;
791
-
792
731
  const { goBack, nextMessageId, previousMessageId } = useTriageLayer({
793
732
  context: triage,
794
733
  orderedIds: threads.map((t) => t.messageId),
795
734
  selectedMessageId,
796
- enabled: !composeState.isOpen,
735
+ // The list stays mounted under the compose surface, so the triage keys
736
+ // would otherwise fire at the message behind whatever is being typed.
737
+ enabled: !isComposing,
797
738
  onClose: closeThread,
798
739
  handlers: {
799
740
  reply: triageReply,
@@ -807,7 +748,7 @@ function MailboxPaneProvider({
807
748
  vipSender: triageVip,
808
749
  markJunk: triageMarkJunk,
809
750
  toggleIntelligence: selectedThread ? onToggleIntelligence : undefined,
810
- compose: handleNewCompose,
751
+ compose: openCompose,
811
752
  goBrief: () => goToRoute("/mail/brief"),
812
753
  goInbox: () => goToRoute("/mail/brief"),
813
754
  goSent: () => goToRoute("/mail/brief"),
@@ -816,11 +757,6 @@ function MailboxPaneProvider({
816
757
  },
817
758
  });
818
759
 
819
- useKeyboardNavigation({
820
- enabled: composeState.isOpen,
821
- bindings: [{ key: "Escape", handler: closeCompose, preventDefault: true }],
822
- });
823
-
824
760
  // The swipe gestures open a whole conversation, so the adjacent row has to
825
761
  // name its thread. This folder's own listing is where that is looked up, so a
826
762
  // row it does not hold offers no gesture rather than a tap that goes nowhere.
@@ -883,11 +819,7 @@ function MailboxPaneProvider({
883
819
  onClearComposeRequest: toolbarActions.clearComposeRequest,
884
820
  onToolbarDelete: toolbarActions.deleteThread,
885
821
  onToolbarStar: toolbarActions.toggleStar,
886
- onToolbarDiscardDraft: handleToolbarDiscardDraft,
887
822
  onToolbarMove: toolbarActions.moveThread,
888
- composeState,
889
- closeCompose,
890
- hasRemitDraftOpen,
891
823
  onBack: goBack,
892
824
  nextThread: adjacentThread(nextMessageId),
893
825
  previousThread: adjacentThread(previousMessageId),
@@ -942,6 +874,7 @@ function MailboxList() {
942
874
  useMailContext();
943
875
  const tier = useLayoutTier();
944
876
  const navigate = useNavigate();
877
+ const retainPanels = useRetainOpenPanels();
945
878
 
946
879
  const listTitle = mailboxName ?? "Inbox";
947
880
  const preset = useMemo(() => inboxFilterConfig(), []);
@@ -1029,10 +962,10 @@ function MailboxList() {
1029
962
  // `searchInput`: a row can be tapped before the debounce settles, when
1030
963
  // the committed query is still empty.
1031
964
  search: (prev) => ({ ...prev, q: searchInput || undefined }),
1032
- hash: retainOpenPanels,
965
+ hash: retainPanels,
1033
966
  });
1034
967
  },
1035
- [mailboxId, navigate, searchInput, threads],
968
+ [mailboxId, navigate, retainPanels, searchInput, threads],
1036
969
  );
1037
970
 
1038
971
  // Drafts keep their own dedicated view (and header); they don't carry the
@@ -1136,7 +1069,6 @@ function MailboxReading() {
1136
1069
  mailboxAccountId,
1137
1070
  selectedThread,
1138
1071
  conversation,
1139
- hasRemitDraftOpen,
1140
1072
  intelligenceOpen,
1141
1073
  onToggleIntelligence,
1142
1074
  toolbarComposeRequest,
@@ -1146,9 +1078,7 @@ function MailboxReading() {
1146
1078
  onClearComposeRequest,
1147
1079
  onToolbarDelete,
1148
1080
  onToolbarStar,
1149
- onToolbarDiscardDraft,
1150
1081
  onToolbarMove,
1151
- composeState,
1152
1082
  handleDeselectIfRemoved,
1153
1083
  } = useMailboxPane();
1154
1084
  // Which surface intelligence has here: the rail between 1280 and up, the
@@ -1195,25 +1125,22 @@ function MailboxReading() {
1195
1125
  const intelligenceShowing =
1196
1126
  hasThread && (railFits ? intelligenceOpen : drawerOpen);
1197
1127
 
1198
- const detailPane =
1199
- composeState.isOpen && !conversation ? (
1200
- <FullCompose />
1201
- ) : conversation ? (
1202
- <ConversationView
1203
- threadId={conversation.threadId}
1204
- mailboxId={conversation.mailboxId}
1205
- subject={conversation.subject}
1206
- selectedMessageId={conversation.messageId}
1207
- authenticity={conversation.authenticity}
1208
- onOpenIntelligence={
1209
- conversation.authenticity?.dkimMismatch ? openIntelligence : undefined
1210
- }
1211
- composeRequest={toolbarComposeRequest}
1212
- onComposeClose={onClearComposeRequest}
1213
- />
1214
- ) : (
1215
- <ReadingPaneEmpty />
1216
- );
1128
+ const detailPane = conversation ? (
1129
+ <ConversationView
1130
+ threadId={conversation.threadId}
1131
+ mailboxId={conversation.mailboxId}
1132
+ subject={conversation.subject}
1133
+ selectedMessageId={conversation.messageId}
1134
+ authenticity={conversation.authenticity}
1135
+ onOpenIntelligence={
1136
+ conversation.authenticity?.dkimMismatch ? openIntelligence : undefined
1137
+ }
1138
+ composeRequest={toolbarComposeRequest}
1139
+ onComposeClose={onClearComposeRequest}
1140
+ />
1141
+ ) : (
1142
+ <ReadingPaneEmpty />
1143
+ );
1217
1144
 
1218
1145
  return (
1219
1146
  <>
@@ -1226,14 +1153,8 @@ function MailboxReading() {
1226
1153
  onReply={hasThread ? onToolbarReply : undefined}
1227
1154
  onReplyAll={hasThread ? onToolbarReplyAll : undefined}
1228
1155
  onForward={hasThread ? onToolbarForward : undefined}
1229
- canDelete={hasThread || hasRemitDraftOpen}
1230
- onDelete={
1231
- hasThread
1232
- ? onToolbarDelete
1233
- : hasRemitDraftOpen
1234
- ? onToolbarDiscardDraft
1235
- : undefined
1236
- }
1156
+ canDelete={hasThread}
1157
+ onDelete={hasThread ? onToolbarDelete : undefined}
1237
1158
  onToggleStar={hasThread ? onToolbarStar : undefined}
1238
1159
  isStarred={selectedThread?.hasStars}
1239
1160
  moveContext={
@@ -1307,7 +1228,6 @@ function MailboxPhone() {
1307
1228
  onBack,
1308
1229
  nextThread,
1309
1230
  previousThread,
1310
- composeState,
1311
1231
  handleDeselectIfRemoved,
1312
1232
  } = useMailboxPane();
1313
1233
 
@@ -1347,14 +1267,6 @@ function MailboxPhone() {
1347
1267
  );
1348
1268
  }
1349
1269
 
1350
- if (composeState.isOpen) {
1351
- return (
1352
- <div className="h-full">
1353
- <FullCompose />
1354
- </div>
1355
- );
1356
- }
1357
-
1358
1270
  return <MailboxList />;
1359
1271
  }
1360
1272
 
@@ -35,7 +35,7 @@ import {
35
35
  type ThreadListSnapshotEntry,
36
36
  threadListCacheKeys,
37
37
  } from "@/lib/thread-list-cache";
38
- import { retainOpenPanels, useOpenThreadPath } from "@/routing";
38
+ import { useOpenThreadPath, useRetainOpenPanels } from "@/routing";
39
39
  import { MoveToTrigger } from "./MoveToTrigger";
40
40
 
41
41
  interface ThreadMessagesData {
@@ -103,6 +103,7 @@ export const MessageActionMenu = ({
103
103
  }: MessageActionMenuProps) => {
104
104
  const queryClient = useQueryClient();
105
105
  const navigate = useNavigate();
106
+ const retainPanels = useRetainOpenPanels();
106
107
  const { pushError } = useErrorBanners();
107
108
  const selectedMessageId = useOpenThreadPath()?.messageId;
108
109
 
@@ -193,10 +194,10 @@ export const MessageActionMenu = ({
193
194
  to: "/mail/$mailboxId",
194
195
  params: { mailboxId },
195
196
  search: (prev) => prev,
196
- hash: retainOpenPanels,
197
+ hash: retainPanels,
197
198
  });
198
199
  },
199
- [selectedMessageId, mailboxId, navigate],
200
+ [selectedMessageId, mailboxId, navigate, retainPanels],
200
201
  );
201
202
 
202
203
  const { deleteMessages, isPending: isDeleting } = useDeleteMessages({
@@ -53,7 +53,7 @@ import { useListHeaderChrome } from "@/lib/list-header-chrome";
53
53
  import { listVerbRequest } from "@/lib/list-verb-request";
54
54
  import { shouldExitSelectionOnNavigate } from "@/lib/selection-mode";
55
55
  import { useSelectionWizard, useWizardStepValue } from "@/lib/wizard-history";
56
- import { retainOpenPanels } from "@/routing";
56
+ import { useRetainOpenPanels } from "@/routing";
57
57
  import { LabelApplyTrigger } from "./LabelApplyTrigger";
58
58
  import {
59
59
  type EscalatedSelection,
@@ -234,6 +234,7 @@ export const MessageList = ({
234
234
  }: MessageListProps) => {
235
235
  const parentRef = useRef<HTMLDivElement>(null);
236
236
  const navigate = useNavigate();
237
+ const retainPanels = useRetainOpenPanels();
237
238
  const isDesktop = useIsDesktop();
238
239
  const wizard = useSelectionWizard();
239
240
  const { verb: wizardVerb, start: startWizard, startFromSearch } = wizard;
@@ -635,11 +636,11 @@ export const MessageList = ({
635
636
  to: "/mail/$mailboxId/$threadId/$messageId",
636
637
  params: { mailboxId, threadId, messageId },
637
638
  search: (prev) => prev,
638
- hash: retainOpenPanels,
639
+ hash: retainPanels,
639
640
  replace: options?.replace,
640
641
  });
641
642
  },
642
- [navigate, mailboxId, threads],
643
+ [navigate, retainPanels, mailboxId, threads],
643
644
  );
644
645
 
645
646
  // Enter: open the focused row in the reading pane. This is the focus→open
@@ -48,7 +48,6 @@ import {
48
48
  useMemo,
49
49
  useRef,
50
50
  } from "react";
51
- import { useCompose } from "@/components/compose/ComposeProvider";
52
51
  import { MessageBody } from "@/components/mail/MessageBody";
53
52
  import { NavMenuButton } from "@/components/mail/NavMenuButton";
54
53
  import { useErrorBanners } from "@/components/ui/ErrorBannerProvider";
@@ -63,7 +62,7 @@ import {
63
62
  import { isOutboxListRow, isUnsendableStatus } from "@/lib/outbox-status";
64
63
  import { normalizeSearchQuery } from "@/lib/search-query";
65
64
  import { parseSearchTokens } from "@/lib/search-tokens";
66
- import { retainOpenPanels } from "@/routing";
65
+ import { useEditDraft, useRetainOpenPanels } from "@/routing";
67
66
 
68
67
  /* ------------------------------------------------------------------ */
69
68
  /* Helpers (shared between List, Reading, Phone sub-views) */
@@ -174,6 +173,7 @@ function OutboxPaneProvider({
174
173
  children,
175
174
  }: OutboxPaneProps) {
176
175
  const navigate = useNavigate();
176
+ const retainPanels = useRetainOpenPanels();
177
177
 
178
178
  const { data: outboxResponse, isLoading } = useQuery(
179
179
  outboxOperationsListOutboxMessagesOptions(),
@@ -219,19 +219,19 @@ function OutboxPaneProvider({
219
219
  to: "/mail/outbox/draft/$outboxMessageId",
220
220
  params: { outboxMessageId },
221
221
  search: (prev) => prev,
222
- hash: retainOpenPanels,
222
+ hash: retainPanels,
223
223
  });
224
224
  },
225
- [navigate],
225
+ [navigate, retainPanels],
226
226
  );
227
227
 
228
228
  const handleCloseMessage = useCallback(() => {
229
229
  navigate({
230
230
  to: "/mail/outbox",
231
231
  search: (prev) => prev,
232
- hash: retainOpenPanels,
232
+ hash: retainPanels,
233
233
  });
234
- }, [navigate]);
234
+ }, [navigate, retainPanels]);
235
235
 
236
236
  const ctx: OutboxPaneContextValue = {
237
237
  messages,
@@ -265,7 +265,7 @@ function OutboxMessageRow({
265
265
  onSelect,
266
266
  }: OutboxMessageRowProps) {
267
267
  const queryClient = useQueryClient();
268
- const { openCompose } = useCompose();
268
+ const editDraft = useEditDraft();
269
269
  const { pushError } = useErrorBanners();
270
270
 
271
271
  const invalidateOutbox = useCallback(() => {
@@ -330,12 +330,7 @@ function OutboxMessageRow({
330
330
  : undefined
331
331
  }
332
332
  retrying={retryMutation.isPending}
333
- onEdit={() =>
334
- openCompose({
335
- mode: "new",
336
- outboxMessageId: message.outboxMessageId,
337
- })
338
- }
333
+ onEdit={() => editDraft(message.outboxMessageId)}
339
334
  onDelete={() =>
340
335
  deleteMutation.mutate({
341
336
  path: { outboxMessageId: message.outboxMessageId },
@@ -10,7 +10,7 @@ import { useNavigate } from "@tanstack/react-router";
10
10
  import { useCallback, useState } from "react";
11
11
  import { toDisplayCategory } from "@/lib/display-category";
12
12
  import { formatEmailDate } from "@/lib/format";
13
- import { retainOpenPanels } from "@/routing";
13
+ import { useRetainOpenPanels } from "@/routing";
14
14
  import { MessageListItem } from "./MessageListItem";
15
15
  import { useModifierSelect } from "./useModifierSelect";
16
16
 
@@ -75,6 +75,7 @@ export const SwipeableMessageRow = ({
75
75
  }: SwipeableMessageRowProps) => {
76
76
  const [peek, setPeek] = useState<SwipePeek>("none");
77
77
  const navigate = useNavigate();
78
+ const retainPanels = useRetainOpenPanels();
78
79
 
79
80
  const handleAct = useCallback(
80
81
  (side: "leading" | "trailing") => {
@@ -106,9 +107,9 @@ export const SwipeableMessageRow = ({
106
107
  messageId: thread.messageId,
107
108
  },
108
109
  search: (prev) => prev,
109
- hash: retainOpenPanels,
110
+ hash: retainPanels,
110
111
  });
111
- }, [navigate, mailboxId, thread.threadId, thread.messageId]);
112
+ }, [navigate, retainPanels, mailboxId, thread.threadId, thread.messageId]);
112
113
 
113
114
  const modifierSelect = useModifierSelect(thread.messageId, onRowSelect);
114
115