@remit/web-client 0.0.322 → 0.0.324
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/better-auth-config.ts +3 -10
- 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 +8 -3
- 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 +3 -1
- 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/self-update.stories.tsx +18 -0
- 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 +5 -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/session-storage.ts +7 -0
- package/src/lib/stale-build.test.ts +52 -0
- package/src/lib/stale-build.ts +36 -0
- 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/__root.tsx +14 -4
- 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 +3 -5
- 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.test.ts +17 -0
- package/src/runtime-config.ts +18 -3
- package/src/shell/index.tsx +3 -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
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* route matched below this one, and the open thread and the message inside it
|
|
7
7
|
* are the segments there rather than a selection read out of the query.
|
|
8
8
|
*/
|
|
9
|
+
import { ErrorState } from "@remit/ui";
|
|
9
10
|
import {
|
|
10
11
|
createFileRoute,
|
|
11
12
|
type ErrorComponentProps,
|
|
@@ -13,7 +14,6 @@ import {
|
|
|
13
14
|
} from "@tanstack/react-router";
|
|
14
15
|
import { MailShell } from "@/components/layout/MailShell";
|
|
15
16
|
import { FlaggedPane } from "@/components/mail/FlaggedPane";
|
|
16
|
-
import { ErrorState } from "@/components/ui/ErrorState";
|
|
17
17
|
import { flaggedSearchSchema } from "@/lib/mail-search";
|
|
18
18
|
import { useOpenThreadPath, useSearchMirror } from "@/routing";
|
|
19
19
|
|
package/src/routes/mail.tsx
CHANGED
|
@@ -2,7 +2,11 @@ import {
|
|
|
2
2
|
configOperationsGetConfigOptions,
|
|
3
3
|
unifiedThreadOperationsListAllThreadsOptions,
|
|
4
4
|
} from "@remit/api-http-client/@tanstack/react-query.gen.ts";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ErrorState,
|
|
7
|
+
KeyboardShortcutsModal,
|
|
8
|
+
useTriageKeyboard,
|
|
9
|
+
} from "@remit/ui";
|
|
6
10
|
import { useQuery } from "@tanstack/react-query";
|
|
7
11
|
import {
|
|
8
12
|
createFileRoute,
|
|
@@ -16,8 +20,6 @@ import { ComposeFab } from "@/components/layout/ComposeFab";
|
|
|
16
20
|
import { MailShellProvider } from "@/components/layout/MailShell";
|
|
17
21
|
import { MailTopBar } from "@/components/layout/MailTopBar";
|
|
18
22
|
import { MailNav } from "@/components/mail/MailNav";
|
|
19
|
-
import { ErrorState } from "@/components/ui/ErrorState";
|
|
20
|
-
import { KeyboardShortcutsModal } from "@/components/ui/KeyboardShortcutsModal";
|
|
21
23
|
import { useKeyboardNavigation } from "@/hooks/useKeyboardNavigation";
|
|
22
24
|
import { isSinglePaneTier, useLayoutTier } from "@/hooks/useLayoutTier";
|
|
23
25
|
import { useMailboxNameIndex } from "@/hooks/useMailboxNameIndex";
|
|
@@ -80,11 +80,6 @@ describe("mapOauthError", () => {
|
|
|
80
80
|
`Expected code in message, got: ${result}`,
|
|
81
81
|
);
|
|
82
82
|
});
|
|
83
|
-
|
|
84
|
-
test("empty string returns generic fallback", () => {
|
|
85
|
-
const result = mapOauthError("");
|
|
86
|
-
assert.ok(typeof result === "string" && result.length > 0);
|
|
87
|
-
});
|
|
88
83
|
});
|
|
89
84
|
|
|
90
85
|
/**
|
|
@@ -66,11 +66,26 @@ export const advancedHelp = (
|
|
|
66
66
|
<strong className="text-fg">Notification rules</strong> and data export
|
|
67
67
|
are coming in a future release.
|
|
68
68
|
</p>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
export const warningsHelp = (
|
|
73
|
+
<div className="space-y-3">
|
|
74
|
+
<p>
|
|
75
|
+
A message that fails to sync three times shows here. A{" "}
|
|
76
|
+
<strong className="text-fg">retrying</strong> one is tried again every
|
|
77
|
+
sync; a body that <strong className="text-fg">gave up</strong> is not.
|
|
78
|
+
Either leaves the list once it syncs.
|
|
79
|
+
</p>
|
|
69
80
|
<p>
|
|
70
81
|
A message Remit cannot read is{" "}
|
|
71
82
|
<strong className="text-fg">set aside</strong> rather than skipped, and
|
|
72
83
|
the folder keeps syncing. Setting one aside is a defect in Remit, so every
|
|
73
84
|
entry can be reported with the diagnostics already attached.
|
|
74
85
|
</p>
|
|
86
|
+
<p>
|
|
87
|
+
A report carries no folder name, subject, address, message content or
|
|
88
|
+
error text.
|
|
89
|
+
</p>
|
|
75
90
|
</div>
|
|
76
91
|
);
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
Banner,
|
|
13
13
|
Button,
|
|
14
14
|
Dialog,
|
|
15
|
+
ErrorState,
|
|
15
16
|
type RowAction,
|
|
16
17
|
RowActions,
|
|
17
18
|
SettingsShell,
|
|
@@ -28,7 +29,6 @@ import {
|
|
|
28
29
|
import { AccountFormPanel } from "@/components/settings/AccountFormPanel";
|
|
29
30
|
import { AccountServices } from "@/components/settings/AccountServices";
|
|
30
31
|
import { DangerZone } from "@/components/settings/DangerZone";
|
|
31
|
-
import { ErrorState } from "@/components/ui/ErrorState";
|
|
32
32
|
import {
|
|
33
33
|
REDIRECT_STALL_MESSAGE,
|
|
34
34
|
useRedirectEnded,
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Advanced settings. Updates live here — and only here — with a full pane in
|
|
3
|
-
* Settings › Advanced
|
|
4
|
-
* rules and export are future scope.
|
|
3
|
+
* Settings › Advanced. Notification rules and export are future scope.
|
|
5
4
|
*/
|
|
6
5
|
import { SettingsShell } from "@remit/ui";
|
|
7
6
|
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
|
8
7
|
import { useState } from "react";
|
|
9
8
|
import { ConfigTransferPanel } from "@/components/settings/ConfigTransferPanel";
|
|
10
|
-
import { QuarantinePanel } from "@/components/settings/QuarantinePanel";
|
|
11
9
|
import { SelfUpdatePanel } from "@/components/settings/SelfUpdatePanel";
|
|
12
10
|
import { TlsRootCaDownload } from "@/components/settings/TlsRootCaDownload";
|
|
13
11
|
import { AppVersion } from "@/components/ui/AppVersion";
|
|
14
12
|
import { SETTINGS_ID_TO_PATH, SETTINGS_NAV_ITEMS } from "@/routes/settings";
|
|
13
|
+
import { getRuntimeConfig } from "@/runtime-config";
|
|
15
14
|
import { advancedHelp } from "./-shared/help-copy";
|
|
16
15
|
|
|
17
16
|
export const Route = createFileRoute("/settings/advanced")({
|
|
@@ -39,8 +38,7 @@ function AdvancedSettings() {
|
|
|
39
38
|
onSelect={handleSelectNav}
|
|
40
39
|
onBackToMail={() => void navigate({ to: "/mail" })}
|
|
41
40
|
>
|
|
42
|
-
<SelfUpdatePanel />
|
|
43
|
-
<QuarantinePanel />
|
|
41
|
+
{getRuntimeConfig().updateMode === "self" && <SelfUpdatePanel />}
|
|
44
42
|
<div className="mt-6 border-t border-line pt-4">
|
|
45
43
|
<ConfigTransferPanel />
|
|
46
44
|
</div>
|
|
@@ -3,14 +3,13 @@ import {
|
|
|
3
3
|
mailboxOperationsListMailboxesOptions,
|
|
4
4
|
} from "@remit/api-http-client/@tanstack/react-query.gen.ts";
|
|
5
5
|
import type { RemitImapAccountResponse } from "@remit/api-http-client/types.gen.ts";
|
|
6
|
-
import { type LabelOption, SettingsShell } from "@remit/ui";
|
|
6
|
+
import { ErrorState, type LabelOption, SettingsShell } from "@remit/ui";
|
|
7
7
|
import { useQuery } from "@tanstack/react-query";
|
|
8
8
|
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
|
9
9
|
import { useCallback, useMemo, useState } from "react";
|
|
10
10
|
import { FilterActionError } from "@/components/settings/FilterActionError";
|
|
11
11
|
import { FilterEditorSurface } from "@/components/settings/FilterEditorSurface";
|
|
12
12
|
import { FiltersList } from "@/components/settings/FiltersList";
|
|
13
|
-
import { ErrorState } from "@/components/ui/ErrorState";
|
|
14
13
|
import {
|
|
15
14
|
useDeleteFilter,
|
|
16
15
|
useFilterList,
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
import {
|
|
14
14
|
Banner,
|
|
15
15
|
type CandidateFolder,
|
|
16
|
+
ErrorState,
|
|
16
17
|
FolderManager,
|
|
17
18
|
FolderRenameDialog,
|
|
18
19
|
type FolderRole,
|
|
@@ -26,7 +27,6 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
26
27
|
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
|
27
28
|
import { useMemo, useState } from "react";
|
|
28
29
|
import { DeleteFolderDialog } from "@/components/settings/DeleteFolderDialog";
|
|
29
|
-
import { ErrorState } from "@/components/ui/ErrorState";
|
|
30
30
|
import { isMailboxNotSettledRefusal } from "@/components/ui/folder-role-refusal";
|
|
31
31
|
import { useCreateMailbox } from "@/hooks/useCreateMailbox";
|
|
32
32
|
import { useFolderLabelTranslator } from "@/hooks/useFolderLabelTranslator";
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
Banner,
|
|
9
9
|
Button,
|
|
10
10
|
ConfirmDialog,
|
|
11
|
+
ErrorState,
|
|
11
12
|
Input,
|
|
12
13
|
labelColorOptions,
|
|
13
14
|
Select,
|
|
@@ -17,7 +18,6 @@ import { useQuery } from "@tanstack/react-query";
|
|
|
17
18
|
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
|
18
19
|
import { useState } from "react";
|
|
19
20
|
import { LabelsList } from "@/components/settings/LabelsList";
|
|
20
|
-
import { ErrorState } from "@/components/ui/ErrorState";
|
|
21
21
|
import {
|
|
22
22
|
useCreateLabel,
|
|
23
23
|
useDeleteLabel,
|
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
} from "@remit/api-http-client/types.gen.ts";
|
|
21
21
|
import {
|
|
22
22
|
Button,
|
|
23
|
+
ErrorState,
|
|
23
24
|
Input,
|
|
24
25
|
Kbd,
|
|
25
26
|
SenderFlagRow,
|
|
@@ -30,7 +31,6 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
30
31
|
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
|
31
32
|
import { Search, Star, X } from "lucide-react";
|
|
32
33
|
import { useCallback, useEffect, useState } from "react";
|
|
33
|
-
import { ErrorState } from "@/components/ui/ErrorState";
|
|
34
34
|
import { useDebouncedValue } from "@/hooks/useDebouncedValue";
|
|
35
35
|
import { formatDate } from "@/lib/format";
|
|
36
36
|
import { SETTINGS_ID_TO_PATH, SETTINGS_NAV_ITEMS } from "@/routes/settings";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { SettingsShell } from "@remit/ui";
|
|
2
|
+
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { WarningsPanel } from "@/components/settings/WarningsPanel";
|
|
5
|
+
import { SETTINGS_ID_TO_PATH, SETTINGS_NAV_ITEMS } from "@/routes/settings";
|
|
6
|
+
import { warningsHelp } from "./-shared/help-copy";
|
|
7
|
+
|
|
8
|
+
export const Route = createFileRoute("/settings/warnings")({
|
|
9
|
+
component: WarningsSettings,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
function WarningsSettings() {
|
|
13
|
+
const navigate = useNavigate();
|
|
14
|
+
const [helpOpen, setHelpOpen] = useState(true);
|
|
15
|
+
|
|
16
|
+
const handleSelectNav = (id: string) => {
|
|
17
|
+
const path = SETTINGS_ID_TO_PATH[id];
|
|
18
|
+
if (path) void navigate({ to: path });
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<SettingsShell
|
|
23
|
+
items={SETTINGS_NAV_ITEMS}
|
|
24
|
+
activeId="warnings"
|
|
25
|
+
title="Warnings"
|
|
26
|
+
description="Messages that failed to sync, and messages set aside."
|
|
27
|
+
help={warningsHelp}
|
|
28
|
+
helpOpen={helpOpen}
|
|
29
|
+
onToggleHelp={() => setHelpOpen((v) => !v)}
|
|
30
|
+
onSelect={handleSelectNav}
|
|
31
|
+
onBackToMail={() => void navigate({ to: "/mail" })}
|
|
32
|
+
>
|
|
33
|
+
<WarningsPanel />
|
|
34
|
+
</SettingsShell>
|
|
35
|
+
);
|
|
36
|
+
}
|
package/src/routes/settings.tsx
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
Users,
|
|
21
21
|
} from "lucide-react";
|
|
22
22
|
import { AdvancedNavIcon } from "@/components/settings/AdvancedNavIcon";
|
|
23
|
+
import { WarningsNavIcon } from "@/components/settings/WarningsNavIcon";
|
|
23
24
|
|
|
24
25
|
/* ------------------------------------------------------------------ */
|
|
25
26
|
/* Nav items — single source of truth shared by all settings pages */
|
|
@@ -57,6 +58,11 @@ export const SETTINGS_NAV_ITEMS: SettingsNavItem[] = [
|
|
|
57
58
|
label: "Appearance",
|
|
58
59
|
icon: <Palette className="size-4" />,
|
|
59
60
|
},
|
|
61
|
+
{
|
|
62
|
+
id: "warnings",
|
|
63
|
+
label: "Warnings",
|
|
64
|
+
icon: <WarningsNavIcon />,
|
|
65
|
+
},
|
|
60
66
|
{ id: "advanced", label: "Advanced", icon: <AdvancedNavIcon /> },
|
|
61
67
|
];
|
|
62
68
|
|
|
@@ -68,6 +74,7 @@ export const SETTINGS_ID_TO_PATH: Record<string, string> = {
|
|
|
68
74
|
labels: "/settings/labels",
|
|
69
75
|
calendars: "/settings/calendars",
|
|
70
76
|
appearance: "/settings/appearance",
|
|
77
|
+
warnings: "/settings/warnings",
|
|
71
78
|
advanced: "/settings/advanced",
|
|
72
79
|
};
|
|
73
80
|
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { dirname, resolve } from "node:path";
|
|
4
2
|
import { describe, it } from "node:test";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
3
|
import {
|
|
7
4
|
backExits,
|
|
8
5
|
type MatchMode,
|
|
@@ -28,7 +25,7 @@ const VERBS: readonly Verb[] = [
|
|
|
28
25
|
"markRead",
|
|
29
26
|
"organize",
|
|
30
27
|
];
|
|
31
|
-
const MODES: readonly MatchMode[] = ["selected", "
|
|
28
|
+
const MODES: readonly MatchMode[] = ["selected", "properties"];
|
|
32
29
|
const SCOPES: readonly (RuleScope | undefined)[] = [
|
|
33
30
|
undefined,
|
|
34
31
|
"once",
|
|
@@ -50,51 +47,10 @@ const answerSets = (): WizardAnswers[] => {
|
|
|
50
47
|
return sets;
|
|
51
48
|
};
|
|
52
49
|
|
|
53
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
54
|
-
const source = readFileSync(resolve(here, "wizard-history.ts"), "utf8");
|
|
55
|
-
|
|
56
50
|
/** The steps up to and including the one an answer is given on. */
|
|
57
51
|
const prefixThrough = (steps: readonly StepId[], step: StepId): StepId[] =>
|
|
58
52
|
steps.slice(0, steps.indexOf(step) + 1);
|
|
59
53
|
|
|
60
|
-
/**
|
|
61
|
-
* The re-root runs inside a router, so the rule is enforced by reading the
|
|
62
|
-
* source — as `../components/mail/MessageList.selection.test.ts` does for its
|
|
63
|
-
* own component-level rules. What it pins is where the decision comes from: a
|
|
64
|
-
* step the app itself pushed must never be re-rooted, or the push that opens
|
|
65
|
-
* the wizard from a verb (#483) duplicates the entry underneath it and the
|
|
66
|
-
* first back after Cancel appears to do nothing.
|
|
67
|
-
*/
|
|
68
|
-
describe("re-rooting a wizard that was loaded into", () => {
|
|
69
|
-
it("decides from what the first render held, not from a flag", () => {
|
|
70
|
-
assert.match(source, /useRef\(step !== undefined\)/);
|
|
71
|
-
assert.match(
|
|
72
|
-
source,
|
|
73
|
-
/if \(!loadedHoldingStep\.current\) return;\s*\n\s*loadedHoldingStep\.current = false;/,
|
|
74
|
-
);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// The root is the wizard-closed state, so it carries neither the step nor
|
|
78
|
-
// the affordance that opened it; otherwise closing the wizard leaves the
|
|
79
|
-
// entry marker behind in the address bar (#484).
|
|
80
|
-
it("puts the wizard back on a root that names no entry", () => {
|
|
81
|
-
assert.match(source, /wizard: undefined,\s*\n\s*wizardFrom: undefined,/);
|
|
82
|
-
assert.match(
|
|
83
|
-
source,
|
|
84
|
-
/wizard: openingStep,\s*\n\s*wizardFrom: openingEntry,/,
|
|
85
|
-
);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("is never armed by a step the app pushed", () => {
|
|
89
|
-
const goToStep = source.slice(source.indexOf("const goToStep"));
|
|
90
|
-
assert.doesNotMatch(goToStep, /loadedHoldingStep/);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("does not re-run when the step changes", () => {
|
|
94
|
-
assert.doesNotMatch(source, /\}, \[step, openingStep, navigate\]\)/);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
54
|
describe("the wizard step in the URL", () => {
|
|
99
55
|
it("round-trips every step the wizard can reach", () => {
|
|
100
56
|
for (const answers of answerSets()) {
|
|
@@ -24,3 +24,20 @@ describe("getRuntimeConfig tlsMode", () => {
|
|
|
24
24
|
assert.equal(getRuntimeConfig().tlsMode, "off");
|
|
25
25
|
});
|
|
26
26
|
});
|
|
27
|
+
|
|
28
|
+
describe("getRuntimeConfig updateMode", () => {
|
|
29
|
+
it("defaults to self when config.js sets nothing", () => {
|
|
30
|
+
globalThis.__REMIT_CONFIG__ = {};
|
|
31
|
+
assert.equal(getRuntimeConfig().updateMode, "self");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("passes through managed", () => {
|
|
35
|
+
globalThis.__REMIT_CONFIG__ = { updateMode: "managed" };
|
|
36
|
+
assert.equal(getRuntimeConfig().updateMode, "managed");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("falls back to self for a value config.js never declares", () => {
|
|
40
|
+
globalThis.__REMIT_CONFIG__ = { updateMode: "auto" };
|
|
41
|
+
assert.equal(getRuntimeConfig().updateMode, "self");
|
|
42
|
+
});
|
|
43
|
+
});
|
package/src/runtime-config.ts
CHANGED
|
@@ -26,10 +26,19 @@ export interface RumConfig {
|
|
|
26
26
|
*/
|
|
27
27
|
export type TlsMode = "internal" | "acme" | "tailscale" | "off";
|
|
28
28
|
|
|
29
|
+
export type UpdateMode = "self" | "managed";
|
|
30
|
+
|
|
31
|
+
const DEFAULT_ISSUES_URL = "https://github.com/remit-mail/reader/issues/new";
|
|
32
|
+
|
|
29
33
|
const TLS_MODES: readonly TlsMode[] = ["internal", "acme", "tailscale", "off"];
|
|
30
34
|
|
|
31
|
-
const
|
|
32
|
-
|
|
35
|
+
const UPDATE_MODES: readonly UpdateMode[] = ["self", "managed"];
|
|
36
|
+
|
|
37
|
+
const oneOf = <T extends string>(
|
|
38
|
+
values: readonly T[],
|
|
39
|
+
raw: unknown,
|
|
40
|
+
fallback: T,
|
|
41
|
+
): T => values.find((value) => value === raw) ?? fallback;
|
|
33
42
|
|
|
34
43
|
export interface RemitRuntimeConfig {
|
|
35
44
|
apiUrl: string;
|
|
@@ -40,6 +49,8 @@ export interface RemitRuntimeConfig {
|
|
|
40
49
|
mailboxPollIntervalSeconds?: string;
|
|
41
50
|
disableDevtools: boolean;
|
|
42
51
|
tlsMode: TlsMode;
|
|
52
|
+
issuesUrl: string;
|
|
53
|
+
updateMode: UpdateMode;
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
interface RawRuntimeConfig {
|
|
@@ -51,6 +62,8 @@ interface RawRuntimeConfig {
|
|
|
51
62
|
mailboxPollIntervalSeconds?: string | number;
|
|
52
63
|
disableDevtools?: boolean;
|
|
53
64
|
tlsMode?: string;
|
|
65
|
+
issuesUrl?: string;
|
|
66
|
+
updateMode?: string;
|
|
54
67
|
}
|
|
55
68
|
|
|
56
69
|
declare global {
|
|
@@ -84,6 +97,8 @@ export const getRuntimeConfig = (): RemitRuntimeConfig => {
|
|
|
84
97
|
? String(raw.mailboxPollIntervalSeconds)
|
|
85
98
|
: undefined,
|
|
86
99
|
disableDevtools: raw.disableDevtools ?? false,
|
|
87
|
-
tlsMode:
|
|
100
|
+
tlsMode: oneOf(TLS_MODES, raw.tlsMode, "off"),
|
|
101
|
+
issuesUrl: raw.issuesUrl ?? DEFAULT_ISSUES_URL,
|
|
102
|
+
updateMode: oneOf(UPDATE_MODES, raw.updateMode, "self"),
|
|
88
103
|
};
|
|
89
104
|
};
|
package/src/shell/index.tsx
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
handleQueryCacheError,
|
|
18
18
|
} from "@/lib/query-error-handler";
|
|
19
19
|
import { initRum } from "@/lib/rum-adapter";
|
|
20
|
+
import { installStaleBuildReload } from "@/lib/stale-build";
|
|
20
21
|
import { TelemetryContext } from "@/lib/telemetry-context";
|
|
21
22
|
import { installThemeSync } from "@/lib/theme";
|
|
22
23
|
import { createAppRouter } from "@/router";
|
|
@@ -69,6 +70,8 @@ export const mountApp = ({
|
|
|
69
70
|
},
|
|
70
71
|
});
|
|
71
72
|
|
|
73
|
+
installStaleBuildReload();
|
|
74
|
+
|
|
72
75
|
const router = createAppRouter(queryClient, telemetry);
|
|
73
76
|
telemetry.recordPageView(window.location.pathname);
|
|
74
77
|
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Draft-discard must surface a banner on a soft failure.
|
|
3
|
-
*
|
|
4
|
-
* `ComposeForm.handleDiscard` closes the compose dialog optimistically, then
|
|
5
|
-
* fires the delete mutation. Before #741 the delete had no `onError`, so a soft
|
|
6
|
-
* 4xx (409/404 "the draft is already gone") closed the dialog as if it
|
|
7
|
-
* succeeded — silent discard. The fix adds an `onError` that pushes a banner via
|
|
8
|
-
* `buildMutationErrorBanner("Couldn't discard draft", …)`, mirroring the
|
|
9
|
-
* outbox-delete pattern. A fatal 5xx still escalates globally; this pins the
|
|
10
|
-
* non-fatal branch.
|
|
11
|
-
*
|
|
12
|
-
* Exercising the real mutation `onError` headlessly (no DOM) via the same
|
|
13
|
-
* `MutationCache` wiring `ComposeForm` uses through `QueryClient`.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import assert from "node:assert/strict";
|
|
17
|
-
import { describe, it } from "node:test";
|
|
18
|
-
import { MutationCache, QueryClient } from "@tanstack/react-query";
|
|
19
|
-
import { ApiError } from "../../lib/api.js";
|
|
20
|
-
import {
|
|
21
|
-
buildMutationErrorBanner,
|
|
22
|
-
type PushErrorInput,
|
|
23
|
-
} from "../ui/error-banners.js";
|
|
24
|
-
|
|
25
|
-
describe("draft discard surfaces a banner on a soft failure (#741)", () => {
|
|
26
|
-
const buildDiscardMutation = (
|
|
27
|
-
pushError: (input: PushErrorInput) => void,
|
|
28
|
-
fail: () => Promise<never>,
|
|
29
|
-
) => {
|
|
30
|
-
const client = new QueryClient({
|
|
31
|
-
mutationCache: new MutationCache(),
|
|
32
|
-
});
|
|
33
|
-
return client.getMutationCache().build(client, {
|
|
34
|
-
mutationFn: fail,
|
|
35
|
-
onError: (error: unknown) => {
|
|
36
|
-
pushError(
|
|
37
|
-
buildMutationErrorBanner(
|
|
38
|
-
"Couldn't discard draft",
|
|
39
|
-
"The draft wasn't discarded.",
|
|
40
|
-
error,
|
|
41
|
-
),
|
|
42
|
-
);
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
it("pushes a 'Couldn't discard draft' banner on a 409 already-gone", async () => {
|
|
48
|
-
const pushed: PushErrorInput[] = [];
|
|
49
|
-
const mutation = buildDiscardMutation(
|
|
50
|
-
(input) => pushed.push(input),
|
|
51
|
-
async () => {
|
|
52
|
-
throw new ApiError("Draft already deleted", 409);
|
|
53
|
-
},
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
await mutation.execute(undefined).catch(() => {});
|
|
57
|
-
|
|
58
|
-
assert.equal(pushed.length, 1);
|
|
59
|
-
assert.equal(pushed[0].title, "Couldn't discard draft");
|
|
60
|
-
assert.equal(pushed[0].detail, "Draft already deleted");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("falls back to a human detail when the error carries no message", async () => {
|
|
64
|
-
const pushed: PushErrorInput[] = [];
|
|
65
|
-
const mutation = buildDiscardMutation(
|
|
66
|
-
(input) => pushed.push(input),
|
|
67
|
-
async () => {
|
|
68
|
-
throw {};
|
|
69
|
-
},
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
await mutation.execute(undefined).catch(() => {});
|
|
73
|
-
|
|
74
|
-
assert.equal(pushed[0].detail, "The draft wasn't discarded.");
|
|
75
|
-
});
|
|
76
|
-
});
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Render invariants for the shared reading-pane zero-state and the keyboard
|
|
3
|
-
* hint bar (#785). Both ship as remit-ui components consumed by the live mail
|
|
4
|
-
* routes. Uses `react-dom/server` so no jsdom is needed.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import assert from "node:assert/strict";
|
|
8
|
-
import { describe, it } from "node:test";
|
|
9
|
-
import { KeyboardHintBar, ReadingPaneEmpty } from "@remit/ui";
|
|
10
|
-
import React, { createElement } from "react";
|
|
11
|
-
import { renderToString } from "react-dom/server";
|
|
12
|
-
|
|
13
|
-
// See MessageToolbar.render.test.ts: the SSR test loader transpiles remit-ui
|
|
14
|
-
// `.tsx` with the classic JSX runtime, which needs a global `React`.
|
|
15
|
-
(globalThis as { React?: typeof React }).React = React;
|
|
16
|
-
|
|
17
|
-
describe("ReadingPaneEmpty (#785)", () => {
|
|
18
|
-
it("renders the prompt and the j/k/Enter Kbd hints by default", () => {
|
|
19
|
-
const html = renderToString(createElement(ReadingPaneEmpty) as never);
|
|
20
|
-
assert.match(html, /Select a thread to read/);
|
|
21
|
-
assert.match(html, /<kbd[^>]*>j<\/kbd>/);
|
|
22
|
-
assert.match(html, /<kbd[^>]*>k<\/kbd>/);
|
|
23
|
-
assert.match(html, /<kbd[^>]*>Enter<\/kbd>/);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("can hide the hint line on touch surfaces", () => {
|
|
27
|
-
const html = renderToString(
|
|
28
|
-
createElement(ReadingPaneEmpty, { showHints: false }) as never,
|
|
29
|
-
);
|
|
30
|
-
assert.equal(/<kbd/.test(html), false);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe("KeyboardHintBar (#785)", () => {
|
|
35
|
-
it("renders the default hint set", () => {
|
|
36
|
-
const html = renderToString(createElement(KeyboardHintBar) as never);
|
|
37
|
-
assert.match(html, /navigate/);
|
|
38
|
-
assert.match(html, /mute/);
|
|
39
|
-
assert.match(html, /all shortcuts/);
|
|
40
|
-
assert.match(html, /<kbd[^>]*>j<\/kbd>/);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The rescue banner is rendered by SpamRescue above the spam list. These tests
|
|
3
|
-
* cover the banner's rendering with a count sourced from the backend (via
|
|
4
|
-
* useRescueCandidates), exercising the component's output in isolation without
|
|
5
|
-
* app providers.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import assert from "node:assert/strict";
|
|
9
|
-
import { describe, it } from "node:test";
|
|
10
|
-
import { RescueBanner } from "@remit/ui";
|
|
11
|
-
import React, { createElement } from "react";
|
|
12
|
-
import { renderToString } from "react-dom/server";
|
|
13
|
-
|
|
14
|
-
(globalThis as { React?: typeof React }).React = React;
|
|
15
|
-
|
|
16
|
-
const noop = () => {};
|
|
17
|
-
|
|
18
|
-
const renderBanner = (count: number): string =>
|
|
19
|
-
renderToString(createElement(RescueBanner, { count, onReview: noop }));
|
|
20
|
-
|
|
21
|
-
describe("RescueBanner rendering", () => {
|
|
22
|
-
it("shows the candidate count for multiple messages", () => {
|
|
23
|
-
const html = renderBanner(2);
|
|
24
|
-
assert.match(html, /2 messages here/);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("shows singular phrasing for a single message", () => {
|
|
28
|
-
const html = renderBanner(1);
|
|
29
|
-
assert.ok(html.includes("1"), "count should appear");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("renders without throwing for zero count", () => {
|
|
33
|
-
assert.doesNotThrow(() => renderBanner(0));
|
|
34
|
-
});
|
|
35
|
-
});
|