@agent-native/core 0.186.0 → 0.187.0-nightly-20260923195356
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/corpus/README.md +1 -1
- package/corpus/templates/clips/app/components/library/page-header.tsx +5 -37
- package/corpus/templates/clips/app/i18n/en-US.ts +0 -1
- package/corpus/templates/mail/AGENTS.md +1 -1
- package/corpus/templates/mail/actions/get-ai-priority.ts +26 -9
- package/corpus/templates/mail/actions/get-jev-availability.ts +23 -0
- package/corpus/templates/mail/actions/navigate.ts +19 -2
- package/corpus/templates/mail/app/components/email/EmailList.tsx +13 -4
- package/corpus/templates/mail/app/pages/InboxPage.tsx +47 -6
- package/corpus/templates/mail/changelog/2026-09-22-jev-powered-mail-rules-and-priority-sort-work-with-an-enable.md +6 -0
- package/corpus/templates/mail/changelog/2026-09-23-preserve-priority-sort-on-availability-error.md +6 -0
- package/corpus/templates/mail/server/lib/automation-engine.ts +147 -132
- package/corpus/templates/mail/server/lib/automation-model.ts +32 -1
- package/dist/agent/jev-tool-prefetch.d.ts +27 -3
- package/dist/agent/jev-tool-prefetch.js +103 -23
- package/dist/agent/production-agent.d.ts +9 -0
- package/dist/agent/production-agent.js +55 -23
- package/dist/collab/routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-chat/prompt-resources.d.ts +1 -0
- package/dist/server/agent-chat/prompt-resources.js +5 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/dist/server/credential-provider.d.ts +1 -1
- package/dist/server/credential-provider.js +4 -3
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +2 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
-
import { IconArrowLeft } from "@tabler/icons-react";
|
|
3
1
|
import {
|
|
4
2
|
type ComponentProps,
|
|
5
3
|
createContext,
|
|
@@ -108,9 +106,8 @@ export interface PageBreadcrumbItem {
|
|
|
108
106
|
/**
|
|
109
107
|
* Collapses breadcrumb segments only as far as the available header width
|
|
110
108
|
* actually requires. Segments closest to the root are hidden first, so the
|
|
111
|
-
* current page and
|
|
112
|
-
*
|
|
113
|
-
* "Root / … / Parent / Current" rather than hiding where "back" leads.
|
|
109
|
+
* current page and its immediate parent stay visible: a long path degrades
|
|
110
|
+
* to "Root / … / Parent / Current" rather than hiding the nearest ancestor.
|
|
114
111
|
* Re-measures on resize so widening the window (or collapsing the sidebar)
|
|
115
112
|
* brings hidden segments back.
|
|
116
113
|
*/
|
|
@@ -118,7 +115,7 @@ function useBreadcrumbOverflow(itemCount: number) {
|
|
|
118
115
|
const listRef = useRef<HTMLOListElement>(null);
|
|
119
116
|
const [hiddenCount, setHiddenCount] = useState(0);
|
|
120
117
|
// Middle segments are everything strictly between the root and the
|
|
121
|
-
// current page; the parent
|
|
118
|
+
// current page; the immediate parent is never hidden.
|
|
122
119
|
const maxHidden = Math.max(0, itemCount - 2 - 1);
|
|
123
120
|
|
|
124
121
|
// The path changed — start fully expanded and let the measurement below
|
|
@@ -152,18 +149,14 @@ export function PageBreadcrumb({
|
|
|
152
149
|
}: {
|
|
153
150
|
items: readonly PageBreadcrumbItem[];
|
|
154
151
|
}) {
|
|
155
|
-
const t = useT();
|
|
156
152
|
const { listRef, hiddenCount } = useBreadcrumbOverflow(items.length);
|
|
157
153
|
|
|
158
154
|
if (items.length === 0) return null;
|
|
159
155
|
|
|
160
|
-
// The item right before the current one is one step back in the path; it
|
|
161
|
-
// only exists on a nested path, so root-level trails render no back button.
|
|
162
|
-
const parent = items[items.length - 2];
|
|
163
156
|
const fullPath = items.map((item) => item.label).join(" / ");
|
|
164
157
|
const collapsed = hiddenCount > 0;
|
|
165
158
|
// Keep the root as an anchor, drop the segments right after it, and keep the
|
|
166
|
-
// whole tail (parent + current)
|
|
159
|
+
// whole tail (parent + current) visible.
|
|
167
160
|
const visibleItems = collapsed
|
|
168
161
|
? [items[0], ...items.slice(1 + hiddenCount)]
|
|
169
162
|
: items;
|
|
@@ -211,7 +204,7 @@ export function PageBreadcrumb({
|
|
|
211
204
|
);
|
|
212
205
|
|
|
213
206
|
// The tooltip only adds value once ancestors are actually hidden.
|
|
214
|
-
|
|
207
|
+
return collapsed ? (
|
|
215
208
|
<Tooltip>
|
|
216
209
|
<TooltipTrigger asChild>{breadcrumb}</TooltipTrigger>
|
|
217
210
|
<TooltipContent>{fullPath}</TooltipContent>
|
|
@@ -219,29 +212,4 @@ export function PageBreadcrumb({
|
|
|
219
212
|
) : (
|
|
220
213
|
breadcrumb
|
|
221
214
|
);
|
|
222
|
-
|
|
223
|
-
if (!parent?.to) return trail;
|
|
224
|
-
|
|
225
|
-
const backLabel = t("navigation.breadcrumbBack", { label: parent.label });
|
|
226
|
-
|
|
227
|
-
return (
|
|
228
|
-
<div className="flex min-w-0 items-center gap-1">
|
|
229
|
-
<Tooltip>
|
|
230
|
-
<TooltipTrigger asChild>
|
|
231
|
-
<Button
|
|
232
|
-
asChild
|
|
233
|
-
variant="ghost"
|
|
234
|
-
size="icon"
|
|
235
|
-
className="size-7 shrink-0 text-muted-foreground"
|
|
236
|
-
>
|
|
237
|
-
<NavLink to={parent.to} aria-label={backLabel}>
|
|
238
|
-
<IconArrowLeft className="size-4" />
|
|
239
|
-
</NavLink>
|
|
240
|
-
</Button>
|
|
241
|
-
</TooltipTrigger>
|
|
242
|
-
<TooltipContent>{backLabel}</TooltipContent>
|
|
243
|
-
</Tooltip>
|
|
244
|
-
{trail}
|
|
245
|
-
</div>
|
|
246
|
-
);
|
|
247
215
|
}
|
|
@@ -76,7 +76,7 @@ Read the relevant skill before deeper work:
|
|
|
76
76
|
| `manage-automations` | Recurring and event-triggered automations shown in Settings. |
|
|
77
77
|
| `manage-email-rules` / `trigger-automations` | Inbox automation rules. |
|
|
78
78
|
| `get-ai-filter` / `apply-ai-filter` | Reversible AI filtering, feedback, and learned instructions. |
|
|
79
|
-
| `get-ai-priority` |
|
|
79
|
+
| `get-ai-priority` | Optional Jev Priority sort; requires Jev access. |
|
|
80
80
|
| `respond-calendar-invite` | Accept/decline/tentative an invite. |
|
|
81
81
|
| `get-mail-settings` / `update-mail-settings` / `import-gmail-signature` | Drafting preferences, including Send + Mark Done. |
|
|
82
82
|
| `manage-snippets` | Saved reply snippets. |
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
|
|
3
|
-
import { defineAction } from "@agent-native/core/action";
|
|
4
|
-
import {
|
|
3
|
+
import { defineAction, fail } from "@agent-native/core/action";
|
|
4
|
+
import {
|
|
5
|
+
getJevContextCredentials,
|
|
6
|
+
getRequestUserEmail,
|
|
7
|
+
isJevEnabled,
|
|
8
|
+
} from "@agent-native/core/server";
|
|
5
9
|
import { z } from "zod";
|
|
6
10
|
|
|
7
11
|
import {
|
|
@@ -11,10 +15,12 @@ import {
|
|
|
11
15
|
saveAiPriorityCache,
|
|
12
16
|
type AiPriorityCacheEntry,
|
|
13
17
|
} from "../server/lib/ai-priority.js";
|
|
18
|
+
import { previewAutomationPriority } from "../server/lib/automation-engine.js";
|
|
14
19
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
TYPESAFE_AUTOMATION_ENGINE,
|
|
21
|
+
TYPESAFE_AUTOMATION_MODEL,
|
|
22
|
+
type AutomationModelSettings,
|
|
23
|
+
} from "../server/lib/automation-model.js";
|
|
18
24
|
import { listAutomationRules } from "../server/lib/automations.js";
|
|
19
25
|
import {
|
|
20
26
|
AI_IMPORTANT_LABEL,
|
|
@@ -64,20 +70,30 @@ function importantRules(
|
|
|
64
70
|
|
|
65
71
|
export default defineAction({
|
|
66
72
|
description:
|
|
67
|
-
"Score up to 500 newest, non-archived Inbox emails for a cached Priority sort using
|
|
73
|
+
"Score up to 500 newest, non-archived Inbox emails with Jev for a cached Priority sort using Mail Important rules. Requires Builder Jev access or a direct Jev API key.",
|
|
68
74
|
schema: z.object({
|
|
69
75
|
emails: z.array(aiPriorityEmailSchema).max(AI_PRIORITY_MAX_EMAILS),
|
|
70
76
|
}),
|
|
71
77
|
agentTool: false,
|
|
72
78
|
run: async ({ emails }) => {
|
|
73
79
|
const ownerEmail = getRequestUserEmail();
|
|
74
|
-
if (!ownerEmail)
|
|
80
|
+
if (!ownerEmail) fail("Unauthenticated", { errorCode: "unauthenticated" });
|
|
81
|
+
const jevCredentials = await getJevContextCredentials(ownerEmail);
|
|
82
|
+
if (!(await isJevEnabled(jevCredentials))) {
|
|
83
|
+
fail("Jev is not enabled for this account.", {
|
|
84
|
+
errorCode: "jev_not_enabled",
|
|
85
|
+
statusCode: 403,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
75
88
|
|
|
76
89
|
const rules = importantRules(await listAutomationRules(ownerEmail));
|
|
77
90
|
const instruction = rules.length
|
|
78
91
|
? rules.map((rule) => rule.condition.trim()).join("\n")
|
|
79
92
|
: AI_PRIORITY_DEFAULT_INSTRUCTION;
|
|
80
|
-
const modelSettings =
|
|
93
|
+
const modelSettings = {
|
|
94
|
+
engine: TYPESAFE_AUTOMATION_ENGINE,
|
|
95
|
+
model: TYPESAFE_AUTOMATION_MODEL,
|
|
96
|
+
};
|
|
81
97
|
const instructionKey = hash(
|
|
82
98
|
JSON.stringify({
|
|
83
99
|
model: modelSettings,
|
|
@@ -108,13 +124,14 @@ export default defineAction({
|
|
|
108
124
|
}));
|
|
109
125
|
const scores = getCachedPriorityScores(cache, fingerprints, instructionKey);
|
|
110
126
|
const pending = eligibleEmails.filter((email) => !scores.has(email.id));
|
|
111
|
-
let model = modelSettings;
|
|
127
|
+
let model: AutomationModelSettings = modelSettings;
|
|
112
128
|
|
|
113
129
|
if (pending.length > 0) {
|
|
114
130
|
const result = await previewAutomationPriority(
|
|
115
131
|
pending,
|
|
116
132
|
ownerEmail,
|
|
117
133
|
instruction,
|
|
134
|
+
jevCredentials,
|
|
118
135
|
AbortSignal.timeout(25_000),
|
|
119
136
|
);
|
|
120
137
|
model = result.model;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core/action";
|
|
2
|
+
import {
|
|
3
|
+
getJevContextCredentials,
|
|
4
|
+
getRequestUserEmail,
|
|
5
|
+
isJevEnabled,
|
|
6
|
+
} from "@agent-native/core/server";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
export default defineAction({
|
|
10
|
+
description:
|
|
11
|
+
"Check whether Jev is configured for this account through Builder or a direct Jev API key.",
|
|
12
|
+
schema: z.object({}),
|
|
13
|
+
outputSchema: z.object({ configured: z.boolean() }),
|
|
14
|
+
http: { method: "GET" },
|
|
15
|
+
readOnly: true,
|
|
16
|
+
agentTool: false,
|
|
17
|
+
run: async () => {
|
|
18
|
+
const ownerEmail = getRequestUserEmail();
|
|
19
|
+
if (!ownerEmail) return { configured: false };
|
|
20
|
+
const credentials = await getJevContextCredentials(ownerEmail);
|
|
21
|
+
return { configured: await isJevEnabled(credentials) };
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { defineAction } from "@agent-native/core/action";
|
|
1
|
+
import { defineAction, fail } from "@agent-native/core/action";
|
|
2
2
|
import { writeAppStateForCurrentTab } from "@agent-native/core/application-state";
|
|
3
|
+
import {
|
|
4
|
+
getJevContextCredentials,
|
|
5
|
+
getRequestUserEmail,
|
|
6
|
+
isJevEnabled,
|
|
7
|
+
} from "@agent-native/core/server";
|
|
3
8
|
import { z } from "zod";
|
|
4
9
|
|
|
5
10
|
export default defineAction({
|
|
6
11
|
description:
|
|
7
|
-
"Navigate the UI to a specific view or email thread. Writes a navigate command to application state which the UI reads and auto-deletes.",
|
|
12
|
+
"Navigate the UI to a specific view, inbox sort, or email thread. Priority sort requires Jev access. Writes a navigate command to application state which the UI reads and auto-deletes.",
|
|
8
13
|
schema: z.object({
|
|
9
14
|
view: z
|
|
10
15
|
.string()
|
|
@@ -59,6 +64,18 @@ export default defineAction({
|
|
|
59
64
|
}),
|
|
60
65
|
http: false,
|
|
61
66
|
run: async (args) => {
|
|
67
|
+
if (args.sort === "priority") {
|
|
68
|
+
const ownerEmail = getRequestUserEmail();
|
|
69
|
+
const credentials = ownerEmail
|
|
70
|
+
? await getJevContextCredentials(ownerEmail)
|
|
71
|
+
: null;
|
|
72
|
+
if (!credentials || !(await isJevEnabled(credentials))) {
|
|
73
|
+
fail("Priority sort requires Jev to be enabled for this account.", {
|
|
74
|
+
errorCode: "jev_not_enabled",
|
|
75
|
+
statusCode: 403,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
62
79
|
const tab = args.tab || args.label || args.filter;
|
|
63
80
|
if (
|
|
64
81
|
!args.view &&
|
|
@@ -146,6 +146,7 @@ interface EmailListProps {
|
|
|
146
146
|
onArchived?: (id: string) => void;
|
|
147
147
|
onDraftOpen?: (email: EmailMessage) => void;
|
|
148
148
|
onNavigateThread?: (threadId: string) => void;
|
|
149
|
+
showPrioritySort?: boolean;
|
|
149
150
|
sortMode?: MailSortMode;
|
|
150
151
|
onSortModeChange?: (mode: MailSortMode) => void;
|
|
151
152
|
}
|
|
@@ -467,6 +468,7 @@ export function EmailList({
|
|
|
467
468
|
onArchived,
|
|
468
469
|
onDraftOpen,
|
|
469
470
|
onNavigateThread,
|
|
471
|
+
showPrioritySort = false,
|
|
470
472
|
sortMode = "newest",
|
|
471
473
|
onSortModeChange,
|
|
472
474
|
}: EmailListProps) {
|
|
@@ -484,7 +486,9 @@ export function EmailList({
|
|
|
484
486
|
const searchQuery = searchParams.get("q") ?? undefined;
|
|
485
487
|
const labelParam = searchParams.get("label");
|
|
486
488
|
const currentSortMode: MailSortMode =
|
|
487
|
-
view === "inbox" && !searchQuery && !labelParam
|
|
489
|
+
showPrioritySort && view === "inbox" && !searchQuery && !labelParam
|
|
490
|
+
? sortMode
|
|
491
|
+
: "newest";
|
|
488
492
|
const routeSearchSuffix = searchParams.toString()
|
|
489
493
|
? `?${searchParams.toString()}`
|
|
490
494
|
: "";
|
|
@@ -1916,7 +1920,7 @@ export function EmailList({
|
|
|
1916
1920
|
() =>
|
|
1917
1921
|
view === "inbox" && !searchQuery && !labelParam && threads.length > 0 ? (
|
|
1918
1922
|
<Select
|
|
1919
|
-
value={
|
|
1923
|
+
value={currentSortMode}
|
|
1920
1924
|
onValueChange={(value) => onSortModeChange?.(value as MailSortMode)}
|
|
1921
1925
|
>
|
|
1922
1926
|
<SelectTrigger
|
|
@@ -1929,16 +1933,21 @@ export function EmailList({
|
|
|
1929
1933
|
</SelectTrigger>
|
|
1930
1934
|
<SelectContent align="end">
|
|
1931
1935
|
<SelectItem value="newest">{t("mail.sort.newest")}</SelectItem>
|
|
1932
|
-
|
|
1936
|
+
{showPrioritySort && (
|
|
1937
|
+
<SelectItem value="priority">
|
|
1938
|
+
{t("mail.sort.priority")}
|
|
1939
|
+
</SelectItem>
|
|
1940
|
+
)}
|
|
1933
1941
|
</SelectContent>
|
|
1934
1942
|
</Select>
|
|
1935
1943
|
) : null,
|
|
1936
1944
|
[
|
|
1937
1945
|
isPriorityPending,
|
|
1938
1946
|
labelParam,
|
|
1947
|
+
showPrioritySort,
|
|
1948
|
+
currentSortMode,
|
|
1939
1949
|
onSortModeChange,
|
|
1940
1950
|
searchQuery,
|
|
1941
|
-
sortMode,
|
|
1942
1951
|
t,
|
|
1943
1952
|
threads.length,
|
|
1944
1953
|
view,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
1
2
|
import { useT } from "@agent-native/core/client/i18n";
|
|
2
3
|
import { normalizeDocumentTitle } from "@agent-native/core/shared";
|
|
3
4
|
import { AI_PRIORITY_MAX_EMAILS, type MailSortMode } from "@shared/ai-priority";
|
|
@@ -10,6 +11,7 @@ import { inboxTabHref } from "@shared/inbox-threads";
|
|
|
10
11
|
import type { EmailMessage } from "@shared/types";
|
|
11
12
|
import { useState, useCallback, useMemo, useEffect, useRef } from "react";
|
|
12
13
|
import { useParams, useNavigate, useSearchParams } from "react-router";
|
|
14
|
+
import { toast } from "sonner";
|
|
13
15
|
|
|
14
16
|
import { EmailList, InboxZero } from "@/components/email/EmailList";
|
|
15
17
|
import { EmailThread } from "@/components/email/EmailThread";
|
|
@@ -350,6 +352,18 @@ export function InboxPage() {
|
|
|
350
352
|
isLoading: settingsLoading,
|
|
351
353
|
isError: settingsError,
|
|
352
354
|
} = useSettings();
|
|
355
|
+
const jevAvailability = useActionQuery(
|
|
356
|
+
"get-jev-availability",
|
|
357
|
+
{},
|
|
358
|
+
{
|
|
359
|
+
enabled: view === "inbox" || navState.command.data?.sort === "priority",
|
|
360
|
+
staleTime: 60_000,
|
|
361
|
+
retry: 2,
|
|
362
|
+
},
|
|
363
|
+
);
|
|
364
|
+
const jevConfigured = jevAvailability.data?.configured === true;
|
|
365
|
+
const showPrioritySort =
|
|
366
|
+
jevConfigured || (jevAvailability.isError && sortMode === "priority");
|
|
353
367
|
const [searchParams] = useSearchParams();
|
|
354
368
|
const activeLabel = searchParams.get("label");
|
|
355
369
|
const activeInboxTab = searchParams.get("tab");
|
|
@@ -442,6 +456,15 @@ export function InboxPage() {
|
|
|
442
456
|
// `useEmails` search path non-inbox views use — the store path is only
|
|
443
457
|
// for a plain /inbox with no `q`.
|
|
444
458
|
const isInboxView = view === "inbox" && !searchParams.get("q");
|
|
459
|
+
useEffect(() => {
|
|
460
|
+
if (!jevAvailability.isSuccess) return;
|
|
461
|
+
if (!jevConfigured && sortMode === "priority") setSortMode("newest");
|
|
462
|
+
}, [jevAvailability.isSuccess, jevConfigured, sortMode]);
|
|
463
|
+
useEffect(() => {
|
|
464
|
+
if (jevAvailability.isError && sortMode === "priority") {
|
|
465
|
+
toast.error(t("mail.sort.priorityFailed"));
|
|
466
|
+
}
|
|
467
|
+
}, [jevAvailability.isError, sortMode, t]);
|
|
445
468
|
useEffect(() => {
|
|
446
469
|
if (!isInboxView || activeLabel || searchQuery) setSortMode("newest");
|
|
447
470
|
}, [activeLabel, isInboxView, searchQuery]);
|
|
@@ -470,9 +493,17 @@ export function InboxPage() {
|
|
|
470
493
|
Math.ceil(AI_PRIORITY_MAX_EMAILS / INBOX_PAGE_SIZE) - 1,
|
|
471
494
|
);
|
|
472
495
|
setInboxExtraPageCount(
|
|
473
|
-
isInboxView && sortMode === "priority"
|
|
496
|
+
showPrioritySort && isInboxView && sortMode === "priority"
|
|
497
|
+
? priorityExtraPages
|
|
498
|
+
: 0,
|
|
474
499
|
);
|
|
475
|
-
}, [
|
|
500
|
+
}, [
|
|
501
|
+
activeAccounts,
|
|
502
|
+
isInboxView,
|
|
503
|
+
showPrioritySort,
|
|
504
|
+
resolvedInboxTab,
|
|
505
|
+
sortMode,
|
|
506
|
+
]);
|
|
476
507
|
const inboxExtraOffsets = useMemo(
|
|
477
508
|
() =>
|
|
478
509
|
Array.from(
|
|
@@ -828,7 +859,7 @@ export function InboxPage() {
|
|
|
828
859
|
activeAccounts.size > 0 ? Array.from(activeAccounts) : undefined,
|
|
829
860
|
selectedThreadIds:
|
|
830
861
|
selectedThreadIds.length > 0 ? selectedThreadIds : undefined,
|
|
831
|
-
sort: sortMode === "priority" ? sortMode : undefined,
|
|
862
|
+
sort: sortMode === "priority" && showPrioritySort ? sortMode : undefined,
|
|
832
863
|
});
|
|
833
864
|
}, [
|
|
834
865
|
view,
|
|
@@ -843,6 +874,8 @@ export function InboxPage() {
|
|
|
843
874
|
activeInboxTab,
|
|
844
875
|
activeAccounts,
|
|
845
876
|
selectedThreadIds,
|
|
877
|
+
showPrioritySort,
|
|
878
|
+
jevAvailability.isError,
|
|
846
879
|
sortMode,
|
|
847
880
|
]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
848
881
|
|
|
@@ -851,6 +884,9 @@ export function InboxPage() {
|
|
|
851
884
|
const lastCommandRef = useRef<string>("");
|
|
852
885
|
useEffect(() => {
|
|
853
886
|
if (!navCommand) return;
|
|
887
|
+
if (navCommand.sort === "priority" && jevAvailability.isLoading) {
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
854
890
|
const key = JSON.stringify(navCommand);
|
|
855
891
|
if (key === lastCommandRef.current) return;
|
|
856
892
|
lastCommandRef.current = key;
|
|
@@ -859,8 +895,12 @@ export function InboxPage() {
|
|
|
859
895
|
const targetFilter = navCommand.filter;
|
|
860
896
|
const targetThread = navCommand.threadId;
|
|
861
897
|
|
|
862
|
-
if (navCommand.sort === "
|
|
863
|
-
setSortMode(
|
|
898
|
+
if (navCommand.sort === "newest") {
|
|
899
|
+
setSortMode("newest");
|
|
900
|
+
} else if (navCommand.sort === "priority") {
|
|
901
|
+
setSortMode(
|
|
902
|
+
jevAvailability.isError || jevConfigured ? "priority" : "newest",
|
|
903
|
+
);
|
|
864
904
|
}
|
|
865
905
|
|
|
866
906
|
if (navCommand.composeDraftId && !targetThread) {
|
|
@@ -898,7 +938,7 @@ export function InboxPage() {
|
|
|
898
938
|
|
|
899
939
|
// Delete the command file so it doesn't re-trigger
|
|
900
940
|
void navState.clearCommand();
|
|
901
|
-
}, [navCommand, view, navigate]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
941
|
+
}, [navCommand, view, navigate, jevAvailability.isLoading, jevConfigured]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
902
942
|
// Stable-identity pattern: keep the previous array reference when the
|
|
903
943
|
// content hasn't meaningfully changed. Without this, markThreadRead's
|
|
904
944
|
// optimistic update (which rebuilds the emails array for a single isRead
|
|
@@ -1117,6 +1157,7 @@ export function InboxPage() {
|
|
|
1117
1157
|
isFetchingNextPage={isFetchingNextPage}
|
|
1118
1158
|
isFetchNextPageError={isFetchNextPageError}
|
|
1119
1159
|
sortMode={sortMode}
|
|
1160
|
+
showPrioritySort={showPrioritySort}
|
|
1120
1161
|
onSortModeChange={setSortMode}
|
|
1121
1162
|
/>
|
|
1122
1163
|
)}
|