@remit/web-client 0.0.321 → 0.0.323
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/package.json +4 -4
- package/src/auth/SignInAgain.tsx +18 -0
- package/src/auth/amplify-config.test.ts +1 -107
- package/src/auth/auth-interceptor.test.ts +0 -6
- package/src/auth/cognito/cognito-token.test.ts +2 -4
- package/src/components/calendar/AgendaStrip.tsx +1 -1
- package/src/components/calendar/CalendarWorkspace.tsx +1 -1
- package/src/components/calendar/ChangeCalendarSuggestion.tsx +1 -1
- package/src/components/calendar/OpenCalendarEvent.tsx +1 -18
- package/src/components/compose/ComposeForm.tsx +11 -6
- package/src/components/compose/FullCompose.tsx +1 -1
- package/src/components/compose/MobileComposeSheet.tsx +1 -2
- package/src/components/compose/compose.stories.tsx +18 -0
- package/src/components/compose/draft-discard-failure.render.test.ts +138 -0
- package/src/components/config-import/config-import.fixtures.ts +8 -9
- package/src/components/config-import/config-import.stories.tsx +2 -2
- package/src/components/layout/MailTopBar.tsx +3 -0
- package/src/components/mail/BriefPane.tsx +0 -3
- package/src/components/mail/ConversationView.tsx +1 -3
- package/src/components/mail/DailyBrief.tsx +5 -28
- package/src/components/mail/FlaggedList.tsx +3 -4
- package/src/components/mail/FlaggedPane.tsx +0 -3
- package/src/components/mail/IntelligenceDrawer.tsx +0 -3
- package/src/components/mail/IntelligencePane.render.test.ts +0 -1
- package/src/components/mail/IntelligencePane.test.ts +1 -95
- package/src/components/mail/IntelligencePane.tsx +3 -93
- package/src/components/mail/MailListHeader.tsx +12 -43
- package/src/components/mail/MailNav.tsx +6 -2
- package/src/components/mail/MailViewChrome.tsx +1 -11
- package/src/components/mail/MailboxPane.tsx +4 -34
- package/src/components/mail/MessageActionMenu.tsx +6 -6
- package/src/components/mail/MessageAttachments.tsx +35 -0
- package/src/components/mail/MessageBody.tsx +1 -0
- package/src/components/mail/MessageBodyErrorBanner.test.ts +0 -30
- package/src/components/mail/MessageCard.tsx +16 -4
- package/src/components/mail/MessageList.run-reporting.test.ts +2 -46
- package/src/components/mail/MessageList.tsx +23 -11
- package/src/components/mail/MessageRow.render.test.ts +0 -7
- package/src/components/mail/MoveToTrigger.tsx +1 -1
- package/src/components/mail/RawMessageView.tsx +1 -1
- package/src/components/mail/SelectionWizardHost.search-entry.test.ts +0 -1
- package/src/components/mail/SelectionWizardHost.tsx +16 -102
- package/src/components/mail/authenticity-warning.stories.tsx +0 -21
- package/src/components/mail/daily-brief.stories.tsx +31 -17
- package/src/components/mail/enter-cursor-late-open.render.test.ts +197 -0
- package/src/components/mail/intelligence-drawer.stories.tsx +0 -2
- package/src/components/mail/message-gone.stories.tsx +44 -0
- package/src/components/mail/offer-times-reply.render.test.ts +25 -1
- package/src/components/mail/reclassify-remove-override.render.test.ts +2 -6
- package/src/components/overlay-escape.render.test.ts +7 -3
- package/src/components/settings/AccountFormPanel.render.test.ts +26 -3
- package/src/components/settings/AccountFormPanel.test.ts +1 -36
- package/src/components/settings/AccountFormPanel.tsx +5 -25
- package/src/components/settings/AccountServices.tsx +1 -1
- package/src/components/settings/AdvancedNavIcon.tsx +3 -3
- package/src/components/settings/CalendarFeedCard.tsx +2 -1
- package/src/components/settings/CalendarSubscribeCard.tsx +1 -1
- package/src/components/settings/CalendarSubscriptionStatus.tsx +1 -2
- package/src/components/settings/FilterActionError.tsx +1 -1
- package/src/components/settings/FilterEditor.render.test.ts +3 -27
- package/src/components/settings/FilterEditor.tsx +4 -24
- package/src/components/settings/FilterEditorSurface.tsx +0 -3
- package/src/components/settings/FiltersList.render.test.ts +5 -24
- package/src/components/settings/FiltersList.stories.tsx +0 -1
- package/src/components/settings/FiltersList.tsx +10 -35
- package/src/components/settings/WarningsNavIcon.tsx +12 -0
- package/src/components/settings/{QuarantinePanel.delimiter.test.ts → WarningsPanel.dom.test.ts} +64 -5
- package/src/components/settings/WarningsPanel.render.test.ts +56 -0
- package/src/components/settings/WarningsPanel.tsx +244 -0
- package/src/components/settings/accounts.stories.tsx +18 -0
- package/src/components/settings/advanced.stories.tsx +3 -125
- package/src/components/settings/settings-filter.stories.tsx +2 -42
- package/src/components/settings/warnings.stories.tsx +290 -0
- package/src/components/ui/ErrorBannerProvider.tsx +5 -3
- package/src/components/ui/error-banners.test.ts +1 -22
- package/src/components/ui/error-banners.ts +9 -20
- package/src/hooks/calendar/useCalendarCollectionWrites.ts +2 -1
- package/src/hooks/calendar/useCalendarSuggestions.ts +2 -1
- package/src/hooks/calendar/useCalendarWrites.ts +2 -1
- package/src/hooks/intelligence-calendar.render.test.ts +0 -1
- package/src/hooks/useDeleteMessages.test.ts +1 -42
- package/src/hooks/useFilters.test.ts +0 -8
- package/src/hooks/useFollowFocusOpen.render.test.ts +1 -11
- package/src/hooks/useIntelligenceData.test.ts +9 -47
- package/src/hooks/useIntelligenceData.ts +7 -100
- package/src/hooks/useLayoutTier.test.ts +0 -48
- package/src/hooks/useReportSpam.ts +1 -1
- package/src/hooks/useRescueCandidates.test.ts +0 -23
- package/src/hooks/useRulePreview.ts +8 -7
- package/src/hooks/useSearchTokenContext.ts +2 -3
- package/src/hooks/useStaleAccountSync.test.ts +0 -17
- package/src/hooks/useStarredThreads.test.ts +0 -10
- package/src/hooks/useTriggerSync.test.ts +0 -6
- package/src/hooks/useVisualViewport.render.test.ts +63 -0
- package/src/hooks/useWarningsAttention.ts +37 -0
- package/src/lib/account-order.test.ts +0 -10
- package/src/lib/adjacent-message.test.ts +0 -9
- package/src/lib/app-info.ts +0 -3
- package/src/lib/autodiscovery.test.ts +0 -47
- package/src/lib/bug-report.test.ts +38 -0
- package/src/lib/bug-report.ts +10 -7
- package/src/lib/bulk-action-copy.ts +1 -1
- package/src/lib/calendar-route.test.ts +0 -5
- package/src/lib/config-import.test.ts +3 -39
- package/src/lib/dedupe-thread-messages.test.ts +0 -4
- package/src/lib/desktop-breakpoint.test.ts +0 -8
- package/src/lib/drafts.test.ts +0 -23
- package/src/lib/fatal-error.test.ts +0 -19
- package/src/lib/folder-roles.test.ts +0 -4
- package/src/lib/hot-sync-window.test.ts +0 -5
- package/src/lib/list-header-chrome.ts +1 -1
- package/src/lib/mail-context.ts +1 -1
- package/src/lib/message-attachments.test.ts +77 -0
- package/src/lib/message-attachments.ts +31 -15
- package/src/lib/message-body-source.test.ts +21 -4
- package/src/lib/message-body-source.ts +5 -2
- package/src/lib/onboarding-completion.test.ts +0 -16
- package/src/lib/organize/filter-edit-model.test.ts +2 -32
- package/src/lib/organize/filter-edit-model.ts +6 -16
- package/src/lib/organize/organize-model.test.ts +7 -40
- package/src/lib/organize/organize-model.ts +16 -18
- package/src/lib/organize/rule-model.test.ts +24 -202
- package/src/lib/organize/rule-model.ts +19 -166
- package/src/lib/organize/search-to-rule.test.ts +2 -36
- package/src/lib/organize/search-to-rule.ts +1 -16
- package/src/lib/provider-presets.test.ts +1 -77
- package/src/lib/query-error-handler.test.ts +0 -61
- package/src/lib/query-escalation.integration.test.ts +4 -4
- package/src/lib/result-count.ts +1 -1
- package/src/lib/result-folder.ts +1 -1
- package/src/lib/rum-adapter.test.ts +0 -104
- package/src/lib/search-query.test.ts +0 -11
- package/src/lib/search-result.test.ts +2 -142
- package/src/lib/search-result.ts +3 -73
- package/src/lib/search-scope.test.ts +1 -92
- package/src/lib/search-scope.ts +0 -29
- package/src/lib/search-surface.ts +2 -2
- package/src/lib/search-token-index.test.ts +0 -8
- package/src/lib/search-tokens.ts +15 -16
- package/src/lib/starred-rows.test.ts +0 -4
- package/src/lib/sync-warning-entries.ts +33 -0
- package/src/lib/wizard-selection.ts +1 -1
- package/src/mocks/story-frame/mail-handlers.ts +12 -8
- package/src/mocks/story-frame/mail-world.ts +5 -0
- package/src/routeTree.gen.ts +21 -0
- package/src/routes/calendar/$view.$date/index.tsx +1 -1
- package/src/routes/calendar.tsx +2 -1
- package/src/routes/mail/brief.tsx +1 -1
- package/src/routes/mail/flagged.tsx +1 -1
- package/src/routes/mail.tsx +5 -3
- package/src/routes/settings/-accounts.test.ts +0 -5
- package/src/routes/settings/-filters.test.ts +0 -1
- package/src/routes/settings/-shared/help-copy.tsx +15 -0
- package/src/routes/settings/accounts.tsx +1 -1
- package/src/routes/settings/advanced.tsx +1 -4
- package/src/routes/settings/filters.tsx +1 -2
- package/src/routes/settings/folders.tsx +1 -1
- package/src/routes/settings/labels.tsx +1 -1
- package/src/routes/settings/senders.tsx +1 -1
- package/src/routes/settings/warnings.tsx +36 -0
- package/src/routes/settings.tsx +7 -0
- package/src/routing/wizard-history.test.ts +1 -45
- package/src/runtime-config.ts +5 -0
- package/src/test-support/fixtures.ts +1 -1
- package/src/components/compose/draft-discard-banner.test.ts +0 -76
- package/src/components/mail/ReadingPaneEmpty.render.test.ts +0 -42
- package/src/components/mail/SpamRescue.render.test.ts +0 -35
- package/src/components/mail/intelligence-semantic-off.render.test.ts +0 -111
- package/src/components/mail/selection-surface-rollcall.test.ts +0 -270
- package/src/components/settings/QuarantinePanel.render.test.ts +0 -71
- package/src/components/settings/QuarantinePanel.tsx +0 -178
- package/src/components/ui/DropdownMenu.tsx +0 -99
- package/src/components/ui/EmptyState.tsx +0 -11
- package/src/components/ui/ErrorBanner.render.test.ts +0 -104
- package/src/components/ui/ErrorBanner.stories.tsx +0 -132
- package/src/components/ui/ErrorBanner.tsx +0 -106
- package/src/components/ui/ErrorBannerStack.tsx +0 -35
- package/src/components/ui/ErrorState.render.test.ts +0 -82
- package/src/components/ui/ErrorState.tsx +0 -80
- package/src/components/ui/KeyboardShortcutsModal.tsx +0 -113
- package/src/hooks/useMoveMessages.test.ts +0 -63
- package/src/hooks/useOrganizeWiden.test.ts +0 -217
- package/src/hooks/useOrganizeWiden.ts +0 -112
- package/src/hooks/useSemanticSearch.ts +0 -132
- package/src/hooks/useSemanticSearchEnabled.ts +0 -23
- package/src/hooks/useVisualViewport.test.ts +0 -126
- package/src/lib/organize/sender-fallback.test.ts +0 -52
- package/src/lib/organize/sender-fallback.ts +0 -38
- package/src/lib/rescue-telemetry.test.ts +0 -75
- package/src/router.test.ts +0 -81
|
@@ -109,7 +109,6 @@ import { useLayoutTier } from "@/hooks/useLayoutTier";
|
|
|
109
109
|
import { useIsDesktop } from "@/hooks/useMediaQuery";
|
|
110
110
|
import { useRefreshControl } from "@/hooks/useRefreshControl";
|
|
111
111
|
import { useSearchTokenContext } from "@/hooks/useSearchTokenContext";
|
|
112
|
-
import { useSemanticSearch } from "@/hooks/useSemanticSearch";
|
|
113
112
|
import { useSpamMatchCounts } from "@/hooks/useSpamMatchCounts";
|
|
114
113
|
import type { TriageContextUpdate } from "@/hooks/useTriageLayer";
|
|
115
114
|
import { sortAccountsByCreatedAt } from "@/lib/account-order";
|
|
@@ -132,7 +131,7 @@ import { useMailContext } from "@/lib/mail-context";
|
|
|
132
131
|
import { useMailFreshness } from "@/lib/mail-freshness";
|
|
133
132
|
import { shouldRequestResultCount } from "@/lib/result-count";
|
|
134
133
|
import { junkMailboxIds } from "@/lib/result-folder";
|
|
135
|
-
import {
|
|
134
|
+
import { rowToSearchResult } from "@/lib/search-result";
|
|
136
135
|
import { showInlineSearchResults } from "@/lib/search-surface";
|
|
137
136
|
import { parseSearchTokens } from "@/lib/search-tokens";
|
|
138
137
|
import { resolveSelectionAccountScope } from "@/lib/selection-account-scope";
|
|
@@ -320,7 +319,7 @@ function BriefSelectionChrome({
|
|
|
320
319
|
[selectedIds],
|
|
321
320
|
);
|
|
322
321
|
// The ticked rows as the wizard reads them — the sample under every screen
|
|
323
|
-
// that names a match, and the senders its
|
|
322
|
+
// that names a match, and the senders its properties prefill reads.
|
|
324
323
|
const wizardSelection = useMemo(
|
|
325
324
|
() => wizardSelectionFrom(rows, selectedIds),
|
|
326
325
|
[rows, selectedIds],
|
|
@@ -820,8 +819,7 @@ export function DailyBrief({
|
|
|
820
819
|
[filteredRows, onOpenThread],
|
|
821
820
|
);
|
|
822
821
|
|
|
823
|
-
// The
|
|
824
|
-
// each one carries the thread it belongs to.
|
|
822
|
+
// The results panel; each result carries the thread it belongs to.
|
|
825
823
|
const openResult = useCallback(
|
|
826
824
|
(result: SearchResult) => {
|
|
827
825
|
const threadId =
|
|
@@ -894,25 +892,6 @@ export function DailyBrief({
|
|
|
894
892
|
[filteredRows, resultFolderIndex],
|
|
895
893
|
);
|
|
896
894
|
|
|
897
|
-
// "Related" (semantic) spans every account here — the brief is the
|
|
898
|
-
// cross-account view, so no mailbox scope. Dedupe against the literal "Top
|
|
899
|
-
// matches" by thread; the brief rows key on messageId, so resolve each back to
|
|
900
|
-
// its thread via the raw threads.
|
|
901
|
-
const { hits: semanticHits, isLoading: relatedLoading } = useSemanticSearch();
|
|
902
|
-
const relatedResults = useMemo<SearchResult[]>(() => {
|
|
903
|
-
const threadByMessageId = new Map<string, string | undefined>(
|
|
904
|
-
filteredRows.map((row) => [row.id, row.threadId]),
|
|
905
|
-
);
|
|
906
|
-
const literalThreadIds = searchResults
|
|
907
|
-
.map((result) => threadByMessageId.get(result.id))
|
|
908
|
-
.filter((id): id is string => id != null);
|
|
909
|
-
return relatedSearchResults(
|
|
910
|
-
semanticHits,
|
|
911
|
-
literalThreadIds,
|
|
912
|
-
resultFolderIndex,
|
|
913
|
-
);
|
|
914
|
-
}, [semanticHits, searchResults, filteredRows, resultFolderIndex]);
|
|
915
|
-
|
|
916
895
|
const filterConfig = useMemo<Omit<FilterSheetProps, "children">>(() => {
|
|
917
896
|
const preset = briefFilterConfig(
|
|
918
897
|
accountSources.map((s) => ({
|
|
@@ -991,7 +970,7 @@ export function DailyBrief({
|
|
|
991
970
|
|
|
992
971
|
// A search does not take the panel down — the chips compose into the query
|
|
993
972
|
// rather than competing with it. The one window where the brief's own body is
|
|
994
|
-
// not on screen is the
|
|
973
|
+
// not on screen is the results panel, which owns the pane while a
|
|
995
974
|
// first query is still being typed; the caret stands down for exactly that,
|
|
996
975
|
// on the same answer the header swaps the body on.
|
|
997
976
|
const resultsPanelOwnsBody = showInlineSearchResults({
|
|
@@ -1083,8 +1062,6 @@ export function DailyBrief({
|
|
|
1083
1062
|
searchFilter: filterConfig,
|
|
1084
1063
|
searchResults,
|
|
1085
1064
|
searchLoading: isLoading,
|
|
1086
|
-
relatedResults,
|
|
1087
|
-
relatedLoading,
|
|
1088
1065
|
onSelectSearchResult: openResult,
|
|
1089
1066
|
// The read-only results panel holds spam out of the same
|
|
1090
1067
|
// committed search this body does, so it states the same
|
|
@@ -1093,7 +1070,7 @@ export function DailyBrief({
|
|
|
1093
1070
|
// The body already narrows to the committed query (the server
|
|
1094
1071
|
// `query` on every section request, plus `matchesSearchTokens` for
|
|
1095
1072
|
// the residue), so a committed search is a selectable list here
|
|
1096
|
-
// exactly as it is on the mailbox route (#212) — the
|
|
1073
|
+
// exactly as it is on the mailbox route (#212) — the results
|
|
1097
1074
|
// panel stays for the typing/uncommitted state only.
|
|
1098
1075
|
searchResultsInBody: true,
|
|
1099
1076
|
refreshControl,
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import {
|
|
30
30
|
flaggedFilterConfig,
|
|
31
|
+
formatErrorMessage,
|
|
31
32
|
type MessageListFilter,
|
|
32
33
|
MessageListPane,
|
|
33
34
|
type SearchResult,
|
|
@@ -35,7 +36,6 @@ import {
|
|
|
35
36
|
type Verb,
|
|
36
37
|
} from "@remit/ui";
|
|
37
38
|
import { type RefObject, useCallback, useMemo, useState } from "react";
|
|
38
|
-
import { formatErrorMessage } from "@/components/ui/ErrorState";
|
|
39
39
|
import { useIsDesktop } from "@/hooks/useMediaQuery";
|
|
40
40
|
import { useSearchTokenContext } from "@/hooks/useSearchTokenContext";
|
|
41
41
|
import {
|
|
@@ -217,8 +217,7 @@ export function FlaggedList({
|
|
|
217
217
|
[rows, onOpenThread],
|
|
218
218
|
);
|
|
219
219
|
|
|
220
|
-
// The
|
|
221
|
-
// each one carries the thread it belongs to.
|
|
220
|
+
// The results panel; each result carries the thread it belongs to.
|
|
222
221
|
const openResult = useCallback(
|
|
223
222
|
(result: SearchResult) => {
|
|
224
223
|
const threadId =
|
|
@@ -316,7 +315,7 @@ export function FlaggedList({
|
|
|
316
315
|
// A committed search renders in this view's own rows (`rows` already
|
|
317
316
|
// narrows to the query above), so the multi-select toolbar stays
|
|
318
317
|
// reachable exactly as it does on the mailbox route (#212). The
|
|
319
|
-
//
|
|
318
|
+
// results panel stays for the typing/uncommitted state only.
|
|
320
319
|
searchResultsInBody
|
|
321
320
|
>
|
|
322
321
|
<ThreadListInteraction
|
|
@@ -377,7 +377,6 @@ function FlaggedReading() {
|
|
|
377
377
|
isOpen={intelligence.drawerOpen}
|
|
378
378
|
onClose={intelligence.closeDrawer}
|
|
379
379
|
thread={selectedThread}
|
|
380
|
-
mailboxId={selectedThread?.mailboxId}
|
|
381
380
|
accountId={selectedThread?.accountId}
|
|
382
381
|
onAfterOptimisticRemove={handleDeselectIfRemoved}
|
|
383
382
|
onReplyWithText={replyWithText}
|
|
@@ -401,7 +400,6 @@ function FlaggedIntelligence() {
|
|
|
401
400
|
<IntelligencePane
|
|
402
401
|
onClose={onToggleIntelligence}
|
|
403
402
|
thread={selectedThread}
|
|
404
|
-
mailboxId={selectedThread?.mailboxId}
|
|
405
403
|
accountId={selectedThread?.accountId}
|
|
406
404
|
onAfterOptimisticRemove={handleDeselectIfRemoved}
|
|
407
405
|
onReplyWithText={replyWithText}
|
|
@@ -445,7 +443,6 @@ function FlaggedPhone() {
|
|
|
445
443
|
isOpen={drawer.isOpen}
|
|
446
444
|
onClose={drawer.close}
|
|
447
445
|
thread={selectedThread}
|
|
448
|
-
mailboxId={selectedThread?.mailboxId}
|
|
449
446
|
accountId={selectedThread?.accountId}
|
|
450
447
|
onAfterOptimisticRemove={handleDeselectIfRemoved}
|
|
451
448
|
onReplyWithText={replyWithText}
|
|
@@ -15,7 +15,6 @@ export function IntelligenceDrawer({
|
|
|
15
15
|
isOpen,
|
|
16
16
|
onClose,
|
|
17
17
|
thread,
|
|
18
|
-
mailboxId,
|
|
19
18
|
accountId,
|
|
20
19
|
onAfterOptimisticRemove,
|
|
21
20
|
onReplyWithText,
|
|
@@ -24,7 +23,6 @@ export function IntelligenceDrawer({
|
|
|
24
23
|
isOpen: boolean;
|
|
25
24
|
onClose: () => void;
|
|
26
25
|
thread?: RemitImapThreadMessageResponse;
|
|
27
|
-
mailboxId?: string;
|
|
28
26
|
accountId?: string;
|
|
29
27
|
onAfterOptimisticRemove?: (messageIds: string[]) => void;
|
|
30
28
|
onReplyWithText?: ReplyWithText;
|
|
@@ -43,7 +41,6 @@ export function IntelligenceDrawer({
|
|
|
43
41
|
<IntelligencePane
|
|
44
42
|
onClose={onClose}
|
|
45
43
|
thread={thread}
|
|
46
|
-
mailboxId={mailboxId}
|
|
47
44
|
accountId={accountId}
|
|
48
45
|
hideCloseButton
|
|
49
46
|
onAfterOptimisticRemove={onAfterOptimisticRemove}
|
|
@@ -1,110 +1,16 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveSpamAction } from "./IntelligencePane";
|
|
4
4
|
|
|
5
5
|
describe("resolveSpamAction (#648)", () => {
|
|
6
6
|
test("offers Report spam when the message carries no spam report", () => {
|
|
7
7
|
assert.equal(resolveSpamAction({}), "reportSpam");
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
-
test("offers Report spam regardless of which mailbox the message is in", () => {
|
|
11
|
-
// A report is never derived from placement — reporting a message the
|
|
12
|
-
// provider's own filter already put in Junk is a real, no-op-move case.
|
|
13
|
-
assert.equal(resolveSpamAction({ spamReport: undefined }), "reportSpam");
|
|
14
|
-
});
|
|
15
|
-
|
|
16
10
|
test("offers Not spam once the message carries a spam report", () => {
|
|
17
11
|
assert.equal(
|
|
18
12
|
resolveSpamAction({ spamReport: { reportedAt: Date.now() } }),
|
|
19
13
|
"notSpam",
|
|
20
14
|
);
|
|
21
15
|
});
|
|
22
|
-
|
|
23
|
-
test("the two actions are mutually exclusive", () => {
|
|
24
|
-
const reportable = resolveSpamAction({});
|
|
25
|
-
const reported = resolveSpamAction({
|
|
26
|
-
spamReport: { reportedAt: Date.now() },
|
|
27
|
-
});
|
|
28
|
-
assert.notEqual(reportable, reported);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe("resolveSimilarState — fail-fast on similar-messages search", () => {
|
|
33
|
-
test("a fatal 5xx does NOT show the grey label — it escalates instead (ready)", () => {
|
|
34
|
-
assert.equal(
|
|
35
|
-
resolveSimilarState({
|
|
36
|
-
similarError: new Error("500"),
|
|
37
|
-
similarErrorIsFatal: true,
|
|
38
|
-
isSimilarLoading: false,
|
|
39
|
-
semanticEnabled: true,
|
|
40
|
-
}),
|
|
41
|
-
"ready",
|
|
42
|
-
);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test("a soft (non-fatal) error keeps the muted error state", () => {
|
|
46
|
-
assert.equal(
|
|
47
|
-
resolveSimilarState({
|
|
48
|
-
similarError: new Error("404"),
|
|
49
|
-
similarErrorIsFatal: false,
|
|
50
|
-
isSimilarLoading: false,
|
|
51
|
-
semanticEnabled: true,
|
|
52
|
-
}),
|
|
53
|
-
"error",
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("an empty result (no error) is a soft ready state", () => {
|
|
58
|
-
assert.equal(
|
|
59
|
-
resolveSimilarState({
|
|
60
|
-
similarError: null,
|
|
61
|
-
similarErrorIsFatal: false,
|
|
62
|
-
isSimilarLoading: false,
|
|
63
|
-
semanticEnabled: true,
|
|
64
|
-
}),
|
|
65
|
-
"ready",
|
|
66
|
-
);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("loading wins when there is no error", () => {
|
|
70
|
-
assert.equal(
|
|
71
|
-
resolveSimilarState({
|
|
72
|
-
similarError: null,
|
|
73
|
-
similarErrorIsFatal: false,
|
|
74
|
-
isSimilarLoading: true,
|
|
75
|
-
semanticEnabled: true,
|
|
76
|
-
}),
|
|
77
|
-
"loading",
|
|
78
|
-
);
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// Off is a property of the deployment, and it leads: nothing was embedded, so
|
|
83
|
-
// there was no query, and the section states the setting instead of an empty
|
|
84
|
-
// list (#1068). `undefined` is the config read still in flight, which is not
|
|
85
|
-
// an answer.
|
|
86
|
-
describe("resolveSimilarState — semantic search off on this instance", () => {
|
|
87
|
-
test("off wins over every runtime state", () => {
|
|
88
|
-
assert.equal(
|
|
89
|
-
resolveSimilarState({
|
|
90
|
-
similarError: new Error("404"),
|
|
91
|
-
similarErrorIsFatal: false,
|
|
92
|
-
isSimilarLoading: true,
|
|
93
|
-
semanticEnabled: false,
|
|
94
|
-
}),
|
|
95
|
-
"off",
|
|
96
|
-
);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("a config read still in flight is not an off state", () => {
|
|
100
|
-
assert.equal(
|
|
101
|
-
resolveSimilarState({
|
|
102
|
-
similarError: null,
|
|
103
|
-
similarErrorIsFatal: false,
|
|
104
|
-
isSimilarLoading: false,
|
|
105
|
-
semanticEnabled: undefined,
|
|
106
|
-
}),
|
|
107
|
-
"ready",
|
|
108
|
-
);
|
|
109
|
-
});
|
|
110
16
|
});
|
|
@@ -5,8 +5,6 @@ import {
|
|
|
5
5
|
DialogBackdrop,
|
|
6
6
|
IntelligencePanel,
|
|
7
7
|
type IntelligenceQuickActions,
|
|
8
|
-
type SimilarMessageLinkComponent,
|
|
9
|
-
type SimilarState,
|
|
10
8
|
type ThreadCategory,
|
|
11
9
|
useModalFocus,
|
|
12
10
|
} from "@remit/ui";
|
|
@@ -19,7 +17,7 @@ import { useUpdateAddressFlags } from "@/hooks/useUpdateAddressFlags";
|
|
|
19
17
|
import { isRescueCandidate } from "@/lib/rescue-candidates";
|
|
20
18
|
import { recordRescueSentToJunk } from "@/lib/rescue-telemetry";
|
|
21
19
|
import { useTelemetry } from "@/lib/telemetry-context";
|
|
22
|
-
import {
|
|
20
|
+
import type { OpenThreadTarget } from "@/routing";
|
|
23
21
|
import type { ReplyWithText } from "../compose/reply-with-times";
|
|
24
22
|
|
|
25
23
|
export interface IntelligencePaneProps {
|
|
@@ -30,8 +28,6 @@ export interface IntelligencePaneProps {
|
|
|
30
28
|
* the empty-state placeholder.
|
|
31
29
|
*/
|
|
32
30
|
thread?: RemitImapThreadMessageResponse;
|
|
33
|
-
/** The mailbox the message list is currently showing. */
|
|
34
|
-
mailboxId?: string;
|
|
35
31
|
/** Account that owns the open message; scopes mailbox-list cache invalidation after a spam-report mutation. */
|
|
36
32
|
accountId?: string;
|
|
37
33
|
/**
|
|
@@ -89,39 +85,6 @@ export const resolveSpamAction = (thread: {
|
|
|
89
85
|
spamReport?: unknown;
|
|
90
86
|
}): "notSpam" | "reportSpam" => (thread.spamReport ? "notSpam" : "reportSpam");
|
|
91
87
|
|
|
92
|
-
/**
|
|
93
|
-
* Decide the "Similar messages" section state from the semantic-search query.
|
|
94
|
-
*
|
|
95
|
-
* An instance with semantic search off leads, before any error or in-flight
|
|
96
|
-
* state: nothing is embedded, no query was sent, and the section states the
|
|
97
|
-
* setting (#1068). Only `false` counts — the config read has not answered yet
|
|
98
|
-
* while it is `undefined`, and an off state shown then would be a guess.
|
|
99
|
-
*
|
|
100
|
-
* The fail-fast rule: a fatal first-party 5xx must NEVER degrade to the benign
|
|
101
|
-
* grey "Similarity search unavailable" label — it escalates to the global red
|
|
102
|
-
* overlay instead, so this returns `"ready"` for it (the section then renders
|
|
103
|
-
* nothing rather than a misleading soft state). A soft (non-fatal) failure
|
|
104
|
-
* keeps the muted `"error"` state; an empty result is `"ready"` (no error).
|
|
105
|
-
* Pure so the classification can be unit-tested without rendering.
|
|
106
|
-
*/
|
|
107
|
-
export const resolveSimilarState = (input: {
|
|
108
|
-
similarError: unknown;
|
|
109
|
-
similarErrorIsFatal: boolean;
|
|
110
|
-
isSimilarLoading: boolean;
|
|
111
|
-
semanticEnabled: boolean | undefined;
|
|
112
|
-
}): SimilarState => {
|
|
113
|
-
const {
|
|
114
|
-
similarError,
|
|
115
|
-
similarErrorIsFatal,
|
|
116
|
-
isSimilarLoading,
|
|
117
|
-
semanticEnabled,
|
|
118
|
-
} = input;
|
|
119
|
-
if (semanticEnabled === false) return "off";
|
|
120
|
-
if (similarError && !similarErrorIsFatal) return "error";
|
|
121
|
-
if (isSimilarLoading) return "loading";
|
|
122
|
-
return "ready";
|
|
123
|
-
};
|
|
124
|
-
|
|
125
88
|
function IntelligenceSkeleton() {
|
|
126
89
|
return (
|
|
127
90
|
<aside className="flex h-full w-full flex-col bg-surface-sunken">
|
|
@@ -250,7 +213,6 @@ function ReclassifyDialog({
|
|
|
250
213
|
interface WiredPanelProps {
|
|
251
214
|
thread: RemitImapThreadMessageResponse;
|
|
252
215
|
onClose: () => void;
|
|
253
|
-
mailboxId?: string;
|
|
254
216
|
accountId?: string;
|
|
255
217
|
hideCloseButton?: boolean;
|
|
256
218
|
onAfterOptimisticRemove?: (messageIds: string[]) => void;
|
|
@@ -264,22 +226,13 @@ interface WiredPanelProps {
|
|
|
264
226
|
function WiredPanel({
|
|
265
227
|
thread,
|
|
266
228
|
onClose,
|
|
267
|
-
mailboxId,
|
|
268
229
|
accountId,
|
|
269
230
|
hideCloseButton,
|
|
270
231
|
onAfterOptimisticRemove,
|
|
271
232
|
onReplyWithText,
|
|
272
233
|
onOpenThread,
|
|
273
234
|
}: WiredPanelProps) {
|
|
274
|
-
const {
|
|
275
|
-
data,
|
|
276
|
-
address,
|
|
277
|
-
addressId,
|
|
278
|
-
isSimilarLoading,
|
|
279
|
-
similarError,
|
|
280
|
-
similarErrorIsFatal,
|
|
281
|
-
semanticEnabled,
|
|
282
|
-
} = useIntelligenceData(thread, mailboxId);
|
|
235
|
+
const { data, address, addressId } = useIntelligenceData(thread);
|
|
283
236
|
const calendar = useIntelligenceCalendar(
|
|
284
237
|
thread,
|
|
285
238
|
onReplyWithText,
|
|
@@ -317,13 +270,6 @@ function WiredPanel({
|
|
|
317
270
|
reportSpam([thread.messageId]);
|
|
318
271
|
}, [reportSpam, thread, telemetry]);
|
|
319
272
|
|
|
320
|
-
const handleShowSimilar = useCallback(() => {
|
|
321
|
-
// The similar-messages section scrolls into view automatically when
|
|
322
|
-
// data.similar is populated. This handler is for the "N similar" button
|
|
323
|
-
// inside the authenticity panel; a scroll-to implementation can be added
|
|
324
|
-
// in a future pass without breaking the contract.
|
|
325
|
-
}, []);
|
|
326
|
-
|
|
327
273
|
const handleToggleVip = useCallback(() => {
|
|
328
274
|
const next = !(data?.flags?.vip === true);
|
|
329
275
|
updateFlags({ vip: { value: next } });
|
|
@@ -360,40 +306,10 @@ function WiredPanel({
|
|
|
360
306
|
onReportSpam: spamAction === "reportSpam" ? handleReportSpam : undefined,
|
|
361
307
|
};
|
|
362
308
|
|
|
363
|
-
// Each similar-message row is a real router anchor — same link shape as a
|
|
364
|
-
// message-list row — so it opens the message in the reading pane while
|
|
365
|
-
// preserving deep-linking, middle-click, and existing search params.
|
|
366
|
-
const similarLinkComponent: SimilarMessageLinkComponent = ({
|
|
367
|
-
mailboxId: rowMailboxId,
|
|
368
|
-
threadId,
|
|
369
|
-
messageId,
|
|
370
|
-
className,
|
|
371
|
-
ariaLabel,
|
|
372
|
-
children,
|
|
373
|
-
}) => (
|
|
374
|
-
<NavLink
|
|
375
|
-
to="/mail/$mailboxId/$threadId/$messageId"
|
|
376
|
-
params={{ mailboxId: rowMailboxId, threadId, messageId }}
|
|
377
|
-
search={(prev) => prev}
|
|
378
|
-
variant="row"
|
|
379
|
-
className={className}
|
|
380
|
-
aria-label={ariaLabel}
|
|
381
|
-
>
|
|
382
|
-
{children}
|
|
383
|
-
</NavLink>
|
|
384
|
-
);
|
|
385
|
-
|
|
386
309
|
if (!data) {
|
|
387
310
|
return <IntelligenceSkeleton />;
|
|
388
311
|
}
|
|
389
312
|
|
|
390
|
-
const similarState = resolveSimilarState({
|
|
391
|
-
similarError,
|
|
392
|
-
similarErrorIsFatal,
|
|
393
|
-
isSimilarLoading,
|
|
394
|
-
semanticEnabled,
|
|
395
|
-
});
|
|
396
|
-
|
|
397
313
|
return (
|
|
398
314
|
<>
|
|
399
315
|
<IntelligencePanel
|
|
@@ -403,10 +319,7 @@ function WiredPanel({
|
|
|
403
319
|
onTabChange={calendar.onTabChange}
|
|
404
320
|
onClose={onClose}
|
|
405
321
|
hideCloseButton={hideCloseButton}
|
|
406
|
-
onShowSimilar={handleShowSimilar}
|
|
407
322
|
actions={actions}
|
|
408
|
-
similarState={similarState}
|
|
409
|
-
similarLinkComponent={similarLinkComponent}
|
|
410
323
|
reportSpamPending={isReporting}
|
|
411
324
|
notSpamPending={isRestoring}
|
|
412
325
|
// No left border: the ResizableHandle to our left already draws the
|
|
@@ -434,12 +347,10 @@ function WiredPanel({
|
|
|
434
347
|
* - Sender basics + trust + authenticity + category: from the thread row (instant,
|
|
435
348
|
* already loaded by the message list).
|
|
436
349
|
* - Address flags (VIP/muted/blocked/etc.) + first-seen: `GET /addresses/search`.
|
|
437
|
-
* - Similar messages: `GET /search/semantic` (last; panel still renders on failure).
|
|
438
350
|
*/
|
|
439
351
|
export const IntelligencePane = ({
|
|
440
352
|
onClose,
|
|
441
353
|
thread,
|
|
442
|
-
mailboxId,
|
|
443
354
|
accountId,
|
|
444
355
|
hideCloseButton,
|
|
445
356
|
onAfterOptimisticRemove,
|
|
@@ -459,7 +370,7 @@ export const IntelligencePane = ({
|
|
|
459
370
|
<Sparkles className="size-8 text-fg-subtle" />
|
|
460
371
|
<p className="mt-3 text-sm text-fg-muted">Intelligence</p>
|
|
461
372
|
<p className="mt-1 text-2xs text-fg-subtle">
|
|
462
|
-
Sender trust
|
|
373
|
+
Sender trust and authenticity will appear here.
|
|
463
374
|
</p>
|
|
464
375
|
</div>
|
|
465
376
|
</aside>
|
|
@@ -470,7 +381,6 @@ export const IntelligencePane = ({
|
|
|
470
381
|
<WiredPanel
|
|
471
382
|
thread={thread}
|
|
472
383
|
onClose={onClose}
|
|
473
|
-
mailboxId={mailboxId}
|
|
474
384
|
accountId={accountId}
|
|
475
385
|
hideCloseButton={hideCloseButton}
|
|
476
386
|
onAfterOptimisticRemove={onAfterOptimisticRemove}
|
|
@@ -25,13 +25,8 @@
|
|
|
25
25
|
* itself so it is there for every search on every view, whichever body is showing
|
|
26
26
|
* the results.
|
|
27
27
|
*
|
|
28
|
-
* Results
|
|
29
|
-
*
|
|
30
|
-
* section — and names them, so a view whose engines reach different mail says so
|
|
31
|
-
* ("In Archive" / "Everywhere"). Each loads independently; an empty section
|
|
32
|
-
* drops out kit-side, so a semantic-only result still shows when the literal
|
|
33
|
-
* search finds nothing. The hamburger opens the nav drawer via the enclosing
|
|
34
|
-
* `AppShellSlotted`.
|
|
28
|
+
* Results render as one section; an empty section drops out kit-side. The
|
|
29
|
+
* hamburger opens the nav drawer via the enclosing `AppShellSlotted`.
|
|
35
30
|
*
|
|
36
31
|
* This is also the one place the route's scope reaches the results list, so both
|
|
37
32
|
* tiers agree on it: a global search labels every row with the folder it came
|
|
@@ -130,24 +125,16 @@ export interface MailListHeaderProps {
|
|
|
130
125
|
/** Literal/instant results — the "Top matches" section. */
|
|
131
126
|
searchResults?: SearchResult[];
|
|
132
127
|
searchLoading?: boolean;
|
|
133
|
-
/** Semantic results — the "Related" section, deduped against "Top matches". */
|
|
134
|
-
relatedResults?: SearchResult[];
|
|
135
|
-
relatedLoading?: boolean;
|
|
136
128
|
onSelectSearchResult?: (result: SearchResult) => void;
|
|
137
|
-
/**
|
|
138
|
-
* Section headings. A view whose two engines cover different ground names
|
|
139
|
-
* them by that ground ("In Archive" / "Everywhere") so the reach of a result
|
|
140
|
-
* is on screen rather than inferred.
|
|
141
|
-
*/
|
|
129
|
+
/** The results section heading. */
|
|
142
130
|
searchResultsLabel?: string;
|
|
143
|
-
relatedResultsLabel?: string;
|
|
144
131
|
/**
|
|
145
132
|
* The body renders the committed search itself as a selectable list — the
|
|
146
133
|
* mailbox route's `MessageList` and the brief and Starred's own rows filter
|
|
147
134
|
* to the search and host the multi-select toolbar and (on the mailbox route)
|
|
148
135
|
* the "Select all N matching" escalation (#212). When set, a committed query
|
|
149
136
|
* keeps the body on tablet/desktop instead of swapping in the read-only
|
|
150
|
-
*
|
|
137
|
+
* `SearchResults` panel; the panel still shows while the query is
|
|
151
138
|
* being typed (uncommitted). A view whose body is not a search-result list
|
|
152
139
|
* leaves this unset and keeps the panel for every query.
|
|
153
140
|
*/
|
|
@@ -231,11 +218,8 @@ export function MailListHeader({
|
|
|
231
218
|
searchFilter,
|
|
232
219
|
searchResults,
|
|
233
220
|
searchLoading,
|
|
234
|
-
relatedResults,
|
|
235
|
-
relatedLoading,
|
|
236
221
|
onSelectSearchResult,
|
|
237
222
|
searchResultsLabel = "Top matches",
|
|
238
|
-
relatedResultsLabel = "Related",
|
|
239
223
|
searchResultsInBody = false,
|
|
240
224
|
spamOffer,
|
|
241
225
|
refreshControl,
|
|
@@ -368,25 +352,17 @@ export function MailListHeader({
|
|
|
368
352
|
[parsed, searchInput, onSearchChange],
|
|
369
353
|
);
|
|
370
354
|
const topMatches = useMemo(() => searchResults ?? [], [searchResults]);
|
|
371
|
-
|
|
372
|
-
//
|
|
373
|
-
// ones, so a "Related"-only hit still shows and two empties fall to its empty
|
|
374
|
-
// state. The empty-query case (recent searches) is the kit's job.
|
|
355
|
+
// Offer the section while a query is present; the kit drops it when empty and
|
|
356
|
+
// shows its empty state. The empty-query case (recent searches) is the kit's job.
|
|
375
357
|
const sections = useMemo<SearchResultSection[]>(
|
|
376
358
|
() =>
|
|
377
359
|
hasQuery
|
|
378
|
-
? [
|
|
379
|
-
{ id: "top", label: searchResultsLabel, results: topMatches },
|
|
380
|
-
{ id: "related", label: relatedResultsLabel, results: related },
|
|
381
|
-
]
|
|
360
|
+
? [{ id: "top", label: searchResultsLabel, results: topMatches }]
|
|
382
361
|
: [],
|
|
383
|
-
[hasQuery, searchResultsLabel,
|
|
362
|
+
[hasQuery, searchResultsLabel, topMatches],
|
|
384
363
|
);
|
|
385
|
-
// Skeleton only while nothing is in yet
|
|
386
|
-
|
|
387
|
-
const hasAnyResult = topMatches.length + related.length > 0;
|
|
388
|
-
const resultsLoading =
|
|
389
|
-
!hasAnyResult && (searchLoading === true || relatedLoading === true);
|
|
364
|
+
// Skeleton only while nothing is in yet.
|
|
365
|
+
const resultsLoading = topMatches.length === 0 && searchLoading === true;
|
|
390
366
|
|
|
391
367
|
// The mailbox's appointed role is what lets a search scoped to Spam show its
|
|
392
368
|
// rows rather than drop them.
|
|
@@ -426,10 +402,7 @@ export function MailListHeader({
|
|
|
426
402
|
// could not be turned into. The conversion is computed once, here, and reaches
|
|
427
403
|
// the wizard through its own context — the reason the affordance gives and the
|
|
428
404
|
// clauses the wizard seeds from are the same answer, without the per-keystroke
|
|
429
|
-
// object riding on the chrome the list reads.
|
|
430
|
-
// reproduce the search's semantic reach, so the conversion states it whenever
|
|
431
|
-
// the search surfaced a "Related" section — a direct signal, read here from
|
|
432
|
-
// the semantic results, never a capability probe.
|
|
405
|
+
// object riding on the chrome the list reads.
|
|
433
406
|
//
|
|
434
407
|
// It belongs to the search, not to any one way of showing it. The affordance
|
|
435
408
|
// therefore sits in the pane, above whichever body is up — the read-only
|
|
@@ -440,11 +413,7 @@ export function MailListHeader({
|
|
|
440
413
|
// to the URL, taking the affordance down with it a few hundred milliseconds
|
|
441
414
|
// after it appeared. Mounting it here instead keeps it up through that
|
|
442
415
|
// hand-back on every view.
|
|
443
|
-
const
|
|
444
|
-
const conversion = useMemo(
|
|
445
|
-
() => convertSearchToRule(parsed, { searchHadSemanticReach }),
|
|
446
|
-
[parsed, searchHadSemanticReach],
|
|
447
|
-
);
|
|
416
|
+
const conversion = useMemo(() => convertSearchToRule(parsed), [parsed]);
|
|
448
417
|
const searchConversion = hasQuery ? conversion : undefined;
|
|
449
418
|
// The wizard is a full-screen surface, so the phone takeover it was pressed
|
|
450
419
|
// from stands down rather than sitting under it — and the list underneath,
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
* to host them.
|
|
10
10
|
*/
|
|
11
11
|
import type { RemitImapAccountResponse } from "@remit/api-http-client/types.gen.ts";
|
|
12
|
-
import { useAppShellLayout } from "@remit/ui";
|
|
12
|
+
import { AttentionDot, useAppShellLayout } from "@remit/ui";
|
|
13
13
|
import { LogOut, Settings } from "lucide-react";
|
|
14
14
|
import { AccountSession } from "@/auth/AccountSession";
|
|
15
15
|
import { MailSidebarAdapter } from "@/components/mail/MailSidebarAdapter";
|
|
16
16
|
import { BugReportButton } from "@/components/ui/BugReportButton";
|
|
17
|
+
import { useWarningsAttention } from "@/hooks/useWarningsAttention";
|
|
17
18
|
import { NavLink } from "@/routing";
|
|
18
19
|
|
|
19
20
|
interface MailNavProps {
|
|
@@ -24,6 +25,7 @@ interface MailNavProps {
|
|
|
24
25
|
export function MailNav({ accounts, onMailboxSelect }: MailNavProps) {
|
|
25
26
|
const layout = useAppShellLayout();
|
|
26
27
|
const isPane = layout?.showNavPane ?? true;
|
|
28
|
+
const settingsAttention = useWarningsAttention();
|
|
27
29
|
|
|
28
30
|
if (isPane) {
|
|
29
31
|
return <MailSidebarAdapter accounts={accounts} />;
|
|
@@ -40,7 +42,9 @@ export function MailNav({ accounts, onMailboxSelect }: MailNavProps) {
|
|
|
40
42
|
</div>
|
|
41
43
|
<div className="border-t border-line px-2 py-2">
|
|
42
44
|
<NavLink to="/settings/accounts" onClick={onMailboxSelect}>
|
|
43
|
-
<
|
|
45
|
+
<AttentionDot label={settingsAttention}>
|
|
46
|
+
<Settings className="size-4 shrink-0" />
|
|
47
|
+
</AttentionDot>
|
|
44
48
|
<span className="flex-1 truncate text-left">Settings</span>
|
|
45
49
|
</NavLink>
|
|
46
50
|
<BugReportButton />
|
|
@@ -46,13 +46,9 @@ interface MailViewChromeProps {
|
|
|
46
46
|
/** Literal/instant results — the "Top matches" section. */
|
|
47
47
|
searchResults?: SearchResult[];
|
|
48
48
|
searchLoading?: boolean;
|
|
49
|
-
/** Semantic results — the "Related" section. */
|
|
50
|
-
relatedResults?: SearchResult[];
|
|
51
|
-
relatedLoading?: boolean;
|
|
52
49
|
onSelectSearchResult?: (result: SearchResult) => void;
|
|
53
|
-
/** Section
|
|
50
|
+
/** Section heading; see `MailListHeader`. */
|
|
54
51
|
searchResultsLabel?: string;
|
|
55
|
-
relatedResultsLabel?: string;
|
|
56
52
|
/**
|
|
57
53
|
* The body renders the committed search as a selectable list itself; see
|
|
58
54
|
* `MailListHeader`. The mailbox route sets this so a committed search yields
|
|
@@ -78,11 +74,8 @@ export function MailViewChrome({
|
|
|
78
74
|
footer,
|
|
79
75
|
searchResults,
|
|
80
76
|
searchLoading,
|
|
81
|
-
relatedResults,
|
|
82
|
-
relatedLoading,
|
|
83
77
|
onSelectSearchResult,
|
|
84
78
|
searchResultsLabel,
|
|
85
|
-
relatedResultsLabel,
|
|
86
79
|
searchResultsInBody,
|
|
87
80
|
refreshControl,
|
|
88
81
|
}: MailViewChromeProps) {
|
|
@@ -114,11 +107,8 @@ export function MailViewChrome({
|
|
|
114
107
|
searchFilter={filterConfig}
|
|
115
108
|
searchResults={searchResults}
|
|
116
109
|
searchLoading={searchLoading}
|
|
117
|
-
relatedResults={relatedResults}
|
|
118
|
-
relatedLoading={relatedLoading}
|
|
119
110
|
onSelectSearchResult={onSelectSearchResult}
|
|
120
111
|
searchResultsLabel={searchResultsLabel}
|
|
121
|
-
relatedResultsLabel={relatedResultsLabel}
|
|
122
112
|
searchResultsInBody={searchResultsInBody}
|
|
123
113
|
refreshControl={refreshControl}
|
|
124
114
|
>
|