Skip to content

Commit cf9084c

Browse files
committed
Add industrial read workflow and alias readiness checks
1 parent dc3ddbc commit cf9084c

16 files changed

Lines changed: 1561 additions & 131 deletions

File tree

frontend/src/hooks/use-ops.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
runMailboxClassification,
88
runMailboxBackfill,
99
runAutoLabel,
10+
runIndustrialReadWorkflow,
1011
runLabelCleanup,
1112
runFullAnchorWorkflow,
1213
runOpsScan,
@@ -150,6 +151,20 @@ export function useRunSafeCleanupWorkflow() {
150151
});
151152
}
152153

154+
export function useRunIndustrialReadWorkflow() {
155+
const invalidate = useInvalidateWorkspace();
156+
return useMutation({
157+
mutationFn: (timeRange: MailboxTimeRange) => runIndustrialReadWorkflow(timeRange),
158+
onSuccess: (result) => {
159+
invalidate();
160+
toast.success("Unread mail marked as read", {
161+
description: `${result.count || 0} cached unread emails were marked as read in the live inbox.`,
162+
});
163+
},
164+
onError: (err) => toast.error(`Failed to mark unread mail as read: ${err.message}`),
165+
});
166+
}
167+
153168
export function useRunFullAnchorWorkflow() {
154169
const invalidate = useInvalidateWorkspace();
155170
return useMutation({

frontend/src/lib/api-client.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,17 @@ export interface OpsOverview {
513513
hydratedEmailsCount: number;
514514
oldestCachedAt: string | null;
515515
newestCachedAt: string | null;
516+
mailboxMemory?: {
517+
targetCount: number;
518+
processedTotal: number;
519+
resumeOffset: number;
520+
remainingCount: number;
521+
completed: boolean;
522+
fullMailboxMode?: boolean;
523+
includeBody: boolean;
524+
unreadOnly: boolean;
525+
lastBackfillAt: string | null;
526+
};
516527
categoryCounts: Record<string, number>;
517528
summary: string;
518529
liveConnected: boolean;
@@ -527,6 +538,10 @@ export interface WorkflowMutationResult {
527538
cleanupApplied?: number;
528539
cachedCount?: number;
529540
hydratedCount?: number;
541+
processedTotal?: number;
542+
resumeOffset?: number;
543+
remainingCount?: number;
544+
completed?: boolean;
530545
deletedLabelCount?: number;
531546
deletedLabels?: string[];
532547
overview: OpsOverview;
@@ -588,7 +603,7 @@ export async function runMailboxBackfill(timeRange?: MailboxTimeRange): Promise<
588603
method: "POST",
589604
body: JSON.stringify({
590605
force_refresh: false,
591-
limit: 20000,
606+
limit: null,
592607
batch_size: 250,
593608
include_body: false,
594609
unread_only: false,
@@ -629,6 +644,15 @@ export async function runSafeCleanupWorkflow(
629644
});
630645
}
631646

647+
export async function runIndustrialReadWorkflow(
648+
timeRange?: MailboxTimeRange,
649+
): Promise<WorkflowMutationResult> {
650+
return apiFetch<WorkflowMutationResult>("/ops/industrial-read", {
651+
method: "POST",
652+
body: JSON.stringify({ force_refresh: false, time_range: timeRange }),
653+
});
654+
}
655+
632656
export async function runFullAnchorWorkflow(
633657
timeRange?: MailboxTimeRange,
634658
): Promise<WorkflowMutationResult> {
@@ -659,6 +683,10 @@ export interface EmailAliasListResponse {
659683
mode?: "plus" | "managed";
660684
domain?: string | null;
661685
managed_enabled?: boolean;
686+
managed_ready?: boolean;
687+
managed_resolver_configured?: boolean;
688+
managed_inbound_ready?: boolean;
689+
managed_blockers?: string[];
662690
plus_fallback_enabled?: boolean;
663691
}
664692

frontend/src/routes/index.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
useRunLabelCleanup,
1414
useRunMailboxBackfill,
1515
useRunFullAnchorWorkflow,
16+
useRunIndustrialReadWorkflow,
1617
useRunOpsScan,
1718
useRunSafeCleanupWorkflow,
1819
} from "@/hooks/use-ops";
@@ -27,6 +28,7 @@ import {
2728
Inbox,
2829
Layers3,
2930
LogOut,
31+
MailOpen,
3032
Palette,
3133
Settings,
3234
ShieldCheck,
@@ -77,6 +79,7 @@ function CommandCenter() {
7779
const autoLabelMutation = useRunAutoLabel();
7880
const cleanLabelsMutation = useRunLabelCleanup();
7981
const cleanupMutation = useRunSafeCleanupWorkflow();
82+
const industrialReadMutation = useRunIndustrialReadWorkflow();
8083
const fullAnchorMutation = useRunFullAnchorWorkflow();
8184
const overviewError = error instanceof Error ? error.message : "InboxAnchor could not load the live mailbox overview yet.";
8285

@@ -87,6 +90,7 @@ function CommandCenter() {
8790
autoLabelMutation.isPending ||
8891
cleanLabelsMutation.isPending ||
8992
cleanupMutation.isPending ||
93+
industrialReadMutation.isPending ||
9094
fullAnchorMutation.isPending;
9195
const shouldPollProgress =
9296
isLoading ||
@@ -329,7 +333,9 @@ function CommandCenter() {
329333
</div>
330334
<div className="flex flex-wrap gap-2">
331335
<Badge variant="outline">
332-
{progress.processed_count} / {progress.target_count} cached
336+
{progress.target_count > 0
337+
? `${progress.processed_count} / ${progress.target_count} cached`
338+
: `${progress.processed_count} cached`}
333339
</Badge>
334340
<Button
335341
variant="outline"
@@ -483,6 +489,15 @@ function CommandCenter() {
483489
loading={cleanLabelsMutation.isPending}
484490
onClick={() => cleanLabelsMutation.mutate(timeRange)}
485491
/>
492+
<WorkflowCard
493+
title="Industrial mark as read"
494+
icon={<MailOpen className="h-4 w-4" />}
495+
description="Mark the full cached unread working set as read in batched provider calls, no matter what category the emails fall into."
496+
impact={overview?.workflows.find((item) => item.slug === "industrial-read")?.impact}
497+
cta="Mark all read"
498+
loading={industrialReadMutation.isPending}
499+
onClick={() => industrialReadMutation.mutate(timeRange)}
500+
/>
486501
<WorkflowCard
487502
title="Safe cleanup"
488503
icon={<ShieldCheck className="h-4 w-4" />}

frontend/src/routes/settings.tsx

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ function SettingsPage() {
4949
const [aliasMode, setAliasMode] = useState<"plus" | "managed">("plus");
5050
const [aliasDomain, setAliasDomain] = useState("");
5151
const [managedAliasEnabled, setManagedAliasEnabled] = useState(false);
52+
const [managedAliasReady, setManagedAliasReady] = useState(false);
53+
const [managedAliasResolverConfigured, setManagedAliasResolverConfigured] = useState(false);
54+
const [managedAliasInboundReady, setManagedAliasInboundReady] = useState(false);
55+
const [managedAliasBlockers, setManagedAliasBlockers] = useState<string[]>([]);
5256
const [plusFallbackEnabled, setPlusFallbackEnabled] = useState(false);
5357
const [aliasItems, setAliasItems] = useState<Array<{
5458
id: number;
@@ -122,6 +126,10 @@ function SettingsPage() {
122126
setAliasMode(response.mode === "managed" ? "managed" : "plus");
123127
setAliasDomain(response.domain || "");
124128
setManagedAliasEnabled(Boolean(response.managed_enabled));
129+
setManagedAliasReady(Boolean(response.managed_ready));
130+
setManagedAliasResolverConfigured(Boolean(response.managed_resolver_configured));
131+
setManagedAliasInboundReady(Boolean(response.managed_inbound_ready));
132+
setManagedAliasBlockers(response.managed_blockers || []);
125133
setPlusFallbackEnabled(Boolean(response.plus_fallback_enabled));
126134
setAliasError("");
127135
} catch (err) {
@@ -440,7 +448,7 @@ function SettingsPage() {
440448
InboxAnchor can also auto-label alias mail and keep it out of the primary inbox.
441449
</p>
442450
<div className="rounded-md border border-border bg-secondary/30 p-3 text-xs leading-5 text-muted-foreground">
443-
{managedAliasEnabled ? (
451+
{managedAliasEnabled && managedAliasReady ? (
444452
<>
445453
InboxAnchor is using its managed alias domain
446454
{" "}
@@ -452,6 +460,20 @@ function SettingsPage() {
452460
travel1234567@{aliasDomain}
453461
</span>.
454462
</>
463+
) : managedAliasEnabled ? (
464+
<>
465+
InboxAnchor sees the managed alias domain
466+
{" "}
467+
<span className="font-medium text-foreground">{aliasDomain}</span>
468+
{" "}
469+
but the inbound path is not fully live yet, so product-owned aliases like
470+
{" "}
471+
<span className="font-medium text-foreground">
472+
travel1234567@{aliasDomain}
473+
</span>
474+
{" "}
475+
will not forward until the remaining setup is finished.
476+
</>
455477
) : (
456478
<>
457479
InboxAnchor alias domain is not configured yet.
@@ -476,6 +498,21 @@ function SettingsPage() {
476498
</>
477499
)}
478500
</div>
501+
{managedAliasEnabled && !managedAliasReady ? (
502+
<div className="rounded-md border border-warning/30 bg-warning/5 p-3 text-[11px] leading-5 text-warning space-y-2">
503+
<p>
504+
Managed aliases are not live yet.
505+
{" "}
506+
{!managedAliasResolverConfigured ? "The backend resolver secret is missing. " : ""}
507+
{!managedAliasInboundReady ? "Cloudflare inbound routing still needs to be marked ready. " : ""}
508+
</p>
509+
{managedAliasBlockers.map((item) => (
510+
<p key={item}>
511+
{item}
512+
</p>
513+
))}
514+
</div>
515+
) : null}
479516
{!managedAliasEnabled ? (
480517
<div className="rounded-md border border-warning/30 bg-warning/5 p-3 text-[11px] leading-5 text-warning">
481518
To get product-owned aliases like
@@ -513,12 +550,18 @@ function SettingsPage() {
513550
</div>
514551
<Button
515552
onClick={handleGenerateAlias}
516-
disabled={aliasLoading || (!managedAliasEnabled && !plusFallbackEnabled)}
553+
disabled={
554+
aliasLoading ||
555+
(managedAliasEnabled && !managedAliasReady) ||
556+
(!managedAliasEnabled && !plusFallbackEnabled)
557+
}
517558
>
518559
{aliasLoading
519560
? "Generating..."
520-
: managedAliasEnabled
561+
: managedAliasEnabled && managedAliasReady
521562
? "Generate InboxAnchor alias"
563+
: managedAliasEnabled
564+
? "Managed alias setup incomplete"
522565
: plusFallbackEnabled
523566
? "Generate Gmail fallback alias"
524567
: "Managed alias domain required"}

0 commit comments

Comments
 (0)