feat: show failed or partial Accounts mailbox inventory as unavailable instead of measured zero counts or an empty list

This commit is contained in:
Matthew Meszaros
2026-10-09 16:27:20 +00:00
committed by Devin AI
parent a8ddfc65ec
commit 5a542e1222
3 changed files with 37 additions and 6 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ Browsing filters, searches and view choices are [remembered in your browser tab]
A mailbox is a sender account you connect to Warmbly. Every warmup message and campaign email goes out through one you own.
The Accounts list checks mailbox health and, on linked self-hosted instances, Cloud warmup standing separately from the mailbox inventory. While those checks are loading or refreshing, it shows **Checking…** rather than treating missing results as Idle, Healthy, or zero sent. A failed or incomplete check shows **Unavailable**, not a measured zero or a claim that the mailbox recovered. A previously reported issue may remain marked **Last observed** for up to five minutes during a refresh; this is historical evidence, not a claim that it is still failing. A newly measured healthy result replaces it. A displayed health score is an account-health assessment, not proof that the provider confirmed a send; check send records and the mailbox's error details when investigating a stopped mailbox.
The Accounts list checks mailbox health and, on linked self-hosted instances, Cloud warmup standing separately from the mailbox inventory. While those checks are loading or refreshing, it shows **Checking…** rather than treating missing results as Idle, Healthy, or zero sent. A failed or incomplete check shows **Unavailable**, not a measured zero or a claim that the mailbox recovered. If the mailbox list itself cannot be loaded, or only part of it loaded, the summary counts show **Unavailable** too, rather than an empty account list. A previously reported issue may remain marked **Last observed** for up to five minutes during a refresh; this is historical evidence, not a claim that it is still failing. A newly measured healthy result replaces it. A displayed health score is an account-health assessment, not proof that the provider confirmed a send; check send records and the mailbox's error details when investigating a stopped mailbox.
### Diagnostic participation and stopping
+20 -2
View File
@@ -9,7 +9,7 @@ import type AccountStatus from "@/lib/api/models/app/analytics/AccountStatus";
import AddressesPage from "./page";
const { tags, rows, statusState } = vi.hoisted(() => ({
statusState: { data: [] as AccountStatus[], observations: [] as { status: AccountStatus; observedAt: number; current: boolean }[], isLoading: false, isFetching: false, isError: false, cloudLoading: false, cloudUnavailable: false, emailsLoading: false },
statusState: { data: [] as AccountStatus[], observations: [] as { status: AccountStatus; observedAt: number; current: boolean }[], isLoading: false, isFetching: false, isError: false, cloudLoading: false, cloudUnavailable: false, emailsLoading: false, emailsError: false, emailsIncomplete: false },
tags: [
{ id: "sending", title: "Sending account", color: "#0088cc", position: 0 },
{ id: "serveblink", title: "Serveblink.com", color: "#008800", position: 1 },
@@ -30,7 +30,7 @@ vi.mock("@/hooks/context/user", async (original) => ({
vi.mock("@/hooks/context/confirm", () => ({ useConfirm: () => ({ show: vi.fn() }) }));
vi.mock("@/hooks/usePermission", () => ({ usePermission: () => true }));
vi.mock("@/lib/api/hooks/app/emails/useEmails", () => ({
default: ({ tag }: { tag: string }) => ({ emails: statusState.emailsLoading ? [] : tag ? rows.filter((row) => row.tags.includes(tag)) : rows, isLoading: statusState.emailsLoading, isPending: statusState.emailsLoading }),
default: ({ tag }: { tag: string }) => ({ emails: statusState.emailsLoading || statusState.emailsError ? [] : tag ? rows.filter((row) => row.tags.includes(tag)) : rows, isLoading: statusState.emailsLoading, isPending: statusState.emailsLoading, isError: statusState.emailsError || statusState.emailsIncomplete, isIncomplete: statusState.emailsIncomplete, refetch: vi.fn() }),
}));
vi.mock("@/lib/api/hooks/app/analytics/useAccountStatuses", () => ({ default: () => statusState }));
vi.mock("@/lib/api/hooks/app/subscription/useFeatureStatus", () => ({ default: () => ({ data: { can_use_warmup: true } }) }));
@@ -149,6 +149,8 @@ describe("mailbox status while account and Cloud checks settle", () => {
statusState.cloudLoading = false;
statusState.cloudUnavailable = false;
statusState.emailsLoading = false;
statusState.emailsError = false;
statusState.emailsIncomplete = false;
});
afterEach(() => { cleanup(); vi.restoreAllMocks(); });
@@ -159,6 +161,22 @@ describe("mailbox status while account and Cloud checks settle", () => {
expect(screen.queryByText("Healthy 100")).not.toBeInTheDocument();
});
it("shows failed or partial mailbox inventory as unavailable rather than measured zeros", async () => {
statusState.emailsError = true;
await renderPage();
expect(screen.getAllByText("Unavailable").length).toBeGreaterThanOrEqual(4);
expect(screen.getAllByText("Mailboxes unavailable").length).toBeGreaterThan(0);
expect(screen.queryByText("No email accounts yet")).not.toBeInTheDocument();
expect(screen.queryByText("0 mailboxes")).not.toBeInTheDocument();
cleanup();
statusState.emailsError = false;
statusState.emailsIncomplete = true;
statusState.data = rows.map(measuredStatus);
await renderPage();
expect(screen.getAllByText("Unavailable").length).toBeGreaterThanOrEqual(4);
});
it("does not show Idle or Healthy while Cloud and account status are loading, then shows the real error", async () => {
statusState.cloudLoading = true;
statusState.data = rows.map(measuredStatus);
+16 -3
View File
@@ -206,7 +206,8 @@ export default function AddressesPage() {
const row = cloud.rowFor(box.id);
return row?.enrolled && !row.cloud?.health;
});
const statusUnavailable = cloud.unavailable || cloudStandingUnavailable || statuses.isError || (!statusChecking && visibleEmailIds.some((id) => !statusById.has(id)));
const inventoryUnavailable = !emailsData.isLoading && (emailsData.isError || emailsData.isIncomplete);
const statusUnavailable = inventoryUnavailable || cloud.unavailable || cloudStandingUnavailable || statuses.isError || (!statusChecking && visibleEmailIds.some((id) => !statusById.has(id)));
const [lastReportedIssues, setLastReportedIssues] = React.useState<Map<string, { status: AccountStatus; observedAt: number }>>(new Map());
useEffect(() => {
setLastReportedIssues((previous) => {
@@ -443,7 +444,9 @@ export default function AddressesPage() {
subtitle={
emailsData.isPending
? "Loading…"
: `${stats.total.toLocaleString()}${emailsData.isLoadingRest || emailsData.isIncomplete ? "+" : ""} mailbox${stats.total === 1 ? "" : "es"}`
: emailsData.isError && emailsData.emails.length === 0
? "Mailboxes unavailable"
: `${stats.total.toLocaleString()}${emailsData.isLoadingRest || emailsData.isIncomplete ? "+" : ""} mailbox${stats.total === 1 ? "" : "es"}`
}
>
<MailboxImportsMenu />
@@ -459,7 +462,7 @@ export default function AddressesPage() {
</PageTopbar>
<StatStrip cols={4}>
<Stat label="Total" value={emailsData.isLoading ? "Checking…" : <AnimatedNumber value={stats.total} />} sub="connected" />
<Stat label="Total" value={emailsData.isLoading ? "Checking…" : inventoryUnavailable ? "Unavailable" : <AnimatedNumber value={stats.total} />} sub="connected" />
<Stat label="Healthy" value={statusChecking ? "Checking…" : statusUnavailable ? "Unavailable" : <AnimatedNumber value={stats.healthy} />} sub="no reported health issues" accent={!statusChecking && !statusUnavailable && stats.healthy > 0} />
<Stat label="Warming" value={statusChecking ? "Checking…" : statusUnavailable ? "Unavailable" : <AnimatedNumber value={stats.warming} />} sub="ramping up" />
<Stat label="Needs attention" value={statusChecking ? "Checking…" : statusUnavailable ? "Unavailable" : <AnimatedNumber value={stats.issues} />} sub="paused or failing" last />
@@ -540,6 +543,16 @@ export default function AddressesPage() {
</div>
))}
</div>
) : emailsData.isError && emailsData.emails.length === 0 ? (
<EmptyBlock
title="Mailboxes unavailable"
body="The mailbox list could not be loaded, so no counts are shown."
cta={
<TopbarAction onClick={() => void emailsData.refetch()}>
Try again
</TopbarAction>
}
/>
) : !emailsData.emails || emailsData.emails.length === 0 ? (
cloud.selfHosted || authConfigLoading ? (
<EmptyBlock