From fb3311cb7f2e4b630d34b9493b93ee289c17392b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 28 Mar 2026 19:55:11 +0000
Subject: [PATCH 1/5] Initial plan
From 105d6dcd6ecad199491a6d67d13c7dcdf098c1c3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 28 Mar 2026 19:56:23 +0000
Subject: [PATCH 2/5] Initial plan
From 6afd7c871e7c694a93f119aa66f1b798c9b5b2bd Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 28 Mar 2026 20:02:12 +0000
Subject: [PATCH 3/5] feat(dashboard): replace per-run table with per-account
Mailbox Status view
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Remove noisy "Recent Processing Runs" table from dashboard
- Add per-account AccountStatusRow showing: name, email, OK/Error/Pending
badge, relative last-check time, inline error message, lifetime counters
- Stats cards: "Emails Forwarded Today" → "Emails Processed" (all-time),
"Errors" → "Accounts with Errors" (turns gray when 0)
- Remove unused processingRunsApi import (saves one API call on page load)
- "View activity & history →" link to /logs for full drill-down
- Update CHANGELOG.md and docs/TODO.md
Agent-Logs-Url: https://github.com/christianlouis/InboxConverge/sessions/0d97741e-e999-49dd-a8f2-dceac2c1da1f
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
---
CHANGELOG.md | 7 +
docs/TODO.md | 1 +
frontend/src/app/dashboard/page.tsx | 232 ++++++++++++++--------------
3 files changed, 124 insertions(+), 116 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a222f96..449c721 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
+## [Unreleased]
+
+### Changed
+
+- Dashboard "Recent Processing Runs" table replaced with a per-account **Mailbox Status** view: each account now shows its last-check status (OK / Error / Pending), relative last-check time, any error message, and lifetime processed/failed counters. The noisy per-run table is gone; full activity history remains available on the Logs page.
+- Stats cards updated: "Emails Forwarded Today" → "Emails Processed" (all-time total from account records); "Errors" → "Accounts with Errors" (count of accounts currently showing an error).
+
## v0.3.2 (2026-03-28)
### Bug Fixes
diff --git a/docs/TODO.md b/docs/TODO.md
index 82bca22..2a77b4e 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -4,6 +4,7 @@ Comprehensive task breakdown for repository improvements and production readines
## ✅ Recently Completed
+- [x] **Dashboard redesign**: Replaced noisy "Recent Processing Runs" table with a per-account "Mailbox Status" view showing last-check status (OK/Error/Pending), relative timestamp, error messages, and lifetime counters. Stats cards updated to show all-time processed count and accounts-with-errors count.
- [x] **Pull Now**: Added "Pull Now" button on Accounts page that immediately queues a `process_mail_account` Celery task via `POST /mail-accounts/{id}/pull-now`. Button shows spinner while in flight and is disabled for inactive accounts.
- [x] Fixed 21 mypy type errors: `Column[T]` vs native type mismatches in `notification_service.py`, `mail_processor.py`, `auth.py`, `tasks.py`, `providers.py`, `mail_accounts.py`, and `main.py` (`lifespan` parameter rename).
- [x] **Provider logos rework**: Logos now displayed as full-width banner strips at the top of each account card using `next/image fill + object-contain`. Handles all aspect ratios (1:1 square to 6:1 wordmark) without distortion. Proton Mail added.
diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx
index 07b77a4..5e5f74e 100644
--- a/frontend/src/app/dashboard/page.tsx
+++ b/frontend/src/app/dashboard/page.tsx
@@ -3,38 +3,44 @@
import { AuthGuard } from '@/components/AuthGuard';
import { DashboardLayout } from '@/components/DashboardLayout';
import { useQuery } from '@tanstack/react-query';
-import { mailAccountsApi, processingRunsApi } from '@/lib/api';
+import { mailAccountsApi, MailAccount } from '@/lib/api';
import Link from 'next/link';
-import {
- Mail,
- Send,
- CheckCircle,
+import {
+ Mail,
+ Send,
+ CheckCircle,
AlertCircle,
- TrendingUp,
- Clock
+ Clock,
+ XCircle,
+ AlertTriangle,
+ Inbox,
} from 'lucide-react';
+function formatRelative(iso?: string | null): string {
+ if (!iso) return 'Never';
+ const diff = Date.now() - new Date(iso).getTime();
+ const minutes = Math.floor(diff / 60000);
+ if (minutes < 1) return 'Just now';
+ if (minutes < 60) return `${minutes}m ago`;
+ const hours = Math.floor(minutes / 60);
+ if (hours < 24) return `${hours}h ago`;
+ return `${Math.floor(hours / 24)}d ago`;
+}
+
interface StatCardProps {
title: string;
value: string | number;
icon: React.ComponentType<{ className?: string }>;
iconColor: string;
- trend?: string;
}
-function StatCard({ title, value, icon: Icon, iconColor, trend }: StatCardProps) {
+function StatCard({ title, value, icon: Icon, iconColor }: StatCardProps) {
return (
{title}
{value}
- {trend && (
-
-
- {trend}
-
- )}
@@ -44,27 +50,78 @@ function StatCard({ title, value, icon: Icon, iconColor, trend }: StatCardProps)
);
}
+function AccountStatusRow({ account }: { account: MailAccount }) {
+ const hasError = !!account.last_error_message;
+ const lastChecked = account.last_check_at;
+
+ return (
+
+
+ {/* Left: name + email */}
+
+
+
+
{account.name}
+
{account.email_address}
+
+
+
+ {/* Right: status badge + last check */}
+
+ {hasError ? (
+
+
+ Error
+
+ ) : lastChecked ? (
+
+
+ OK
+
+ ) : (
+
+
+ Pending
+
+ )}
+
+ {formatRelative(lastChecked)}
+
+
+
+
+ {/* Error message */}
+ {hasError && (
+
+
+
{account.last_error_message}
+
+ )}
+
+ {/* Lifetime counters (only when there's activity) */}
+ {(account.total_emails_processed > 0 || account.total_emails_failed > 0) && (
+
+ {account.total_emails_processed.toLocaleString()} processed
+ {account.total_emails_failed > 0 && (
+ {account.total_emails_failed.toLocaleString()} failed
+ )}
+
+ )}
+
+ );
+}
+
export default function DashboardPage() {
- const { data: accounts } = useQuery({
+ const { data: accounts, isLoading: accountsLoading } = useQuery({
queryKey: ['mail-accounts'],
queryFn: mailAccountsApi.list,
});
- const { data: runs, isLoading: runsLoading } = useQuery({
- queryKey: ['processing-runs'],
- queryFn: () => processingRunsApi.list({ page: 1, page_size: 10 }),
- });
-
const stats = {
totalAccounts: accounts?.length || 0,
activeAccounts: accounts?.filter((a) => a.is_enabled).length || 0,
- emailsToday: runs?.items
- ?.filter((r) => {
- const today = new Date().toDateString();
- return new Date(r.started_at).toDateString() === today;
- })
- .reduce((sum, r) => sum + r.emails_forwarded, 0) || 0,
- errors: runs?.items?.filter((r) => r.emails_failed > 0).length || 0,
+ totalProcessed: accounts?.reduce((sum, a) => sum + a.total_emails_processed, 0) || 0,
+ accountsWithErrors: accounts?.filter((a) => !!a.last_error_message).length || 0,
};
return (
@@ -80,8 +137,8 @@ export default function DashboardPage() {
iconColor="bg-blue-500"
/>
@@ -92,101 +149,44 @@ export default function DashboardPage() {
iconColor="bg-purple-500"
/>
0 ? 'bg-red-500' : 'bg-gray-400'}
/>
- {/* Recent Processing Runs */}
+ {/* Mailbox Status Overview */}
-
Recent Processing Runs
+ Mailbox Status
- View all logs →
+ View activity & history →
-
- {runsLoading ? (
-
- ) : runs && runs.items && runs.items.length > 0 ? (
-
-
-
- |
- Account
- |
-
- Started At
- |
-
- Status
- |
-
- Fetched
- |
-
- Forwarded
- |
-
- Errors
- |
-
-
-
- {runs.items.map((run) => {
- const account = accounts?.find((a) => a.id === run.mail_account_id);
- return (
-
- |
- {run.account_name || account?.name || `Account ${run.mail_account_id}`}
- |
-
-
-
- {new Date(run.started_at).toLocaleString()}
-
- |
-
-
- {run.status}
-
- |
-
- {run.emails_fetched}
- |
-
- {run.emails_forwarded}
- |
-
- {run.emails_failed > 0 ? (
- {run.emails_failed}
- ) : (
- 0
- )}
- |
-
- );
- })}
-
-
- ) : (
-
-
No processing runs yet
-
- )}
-
+
+ {accountsLoading ? (
+
+ ) : accounts && accounts.length > 0 ? (
+
+ {accounts.map((account) => (
+
+ ))}
+
+ ) : (
+
+
+
No mail accounts configured yet.
+
+ Add your first account →
+
+
+ )}
From f7caa05c8ff7b1a4a39792bbe28c5eaf18195b45 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 28 Mar 2026 20:03:19 +0000
Subject: [PATCH 4/5] Fix provider logos, improve Fetch button UX on mail
accounts page
Agent-Logs-Url: https://github.com/christianlouis/InboxConverge/sessions/058b6481-f8eb-43ed-8f5f-715c19c4a377
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
---
CHANGELOG.md | 6 +++++
backend/app/api/v1/endpoints/mail_accounts.py | 1 +
backend/app/models/schemas.py | 2 ++
docs/TODO.md | 3 +++
frontend/src/app/accounts/page.tsx | 26 ++++++++++++++++---
.../src/components/AddMailAccountModal.tsx | 4 ++-
frontend/src/components/ProviderWizard.tsx | 2 ++
frontend/src/lib/api.ts | 2 ++
8 files changed, 42 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a222f96..97e6a42 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
+## [Unreleased]
+
+### Fixed
+- Provider logos now appear on the Mail Accounts page: `provider_name` is correctly saved when creating accounts via the provider wizard and propagated through backend/frontend schemas.
+- Fetch-emails button now shows a text label ("Fetch"), a descriptive tooltip, a "Fetching…" loading state, and a brief green "Queued!" confirmation after the action completes.
+
## v0.3.2 (2026-03-28)
### Bug Fixes
diff --git a/backend/app/api/v1/endpoints/mail_accounts.py b/backend/app/api/v1/endpoints/mail_accounts.py
index 3d870f4..34174a2 100644
--- a/backend/app/api/v1/endpoints/mail_accounts.py
+++ b/backend/app/api/v1/endpoints/mail_accounts.py
@@ -104,6 +104,7 @@ async def create_mail_account(
check_interval_minutes=account_in.check_interval_minutes,
max_emails_per_check=account_in.max_emails_per_check,
delete_after_forward=account_in.delete_after_forward,
+ provider_name=account_in.provider_name,
)
db.add(account)
diff --git a/backend/app/models/schemas.py b/backend/app/models/schemas.py
index 51d6fff..f630f60 100644
--- a/backend/app/models/schemas.py
+++ b/backend/app/models/schemas.py
@@ -113,6 +113,7 @@ class MailAccountBase(BaseModel):
check_interval_minutes: int = Field(default=5, gt=0, le=1440)
max_emails_per_check: int = Field(default=50, gt=0, le=1000)
delete_after_forward: bool = True
+ provider_name: Optional[str] = Field(None, max_length=100)
class MailAccountCreate(MailAccountBase):
@@ -135,6 +136,7 @@ class MailAccountUpdate(BaseModel):
check_interval_minutes: Optional[int] = Field(None, gt=0, le=1440)
max_emails_per_check: Optional[int] = Field(None, gt=0, le=1000)
delete_after_forward: Optional[bool] = None
+ provider_name: Optional[str] = Field(None, max_length=100)
class MailAccountResponse(MailAccountBase):
diff --git a/docs/TODO.md b/docs/TODO.md
index 82bca22..a8862f8 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -4,6 +4,9 @@ Comprehensive task breakdown for repository improvements and production readines
## ✅ Recently Completed
+- [x] **Provider logos now saved on account creation**: `provider_name` field added to `MailAccountCreate` and `MailAccountUpdate` schemas (backend and frontend). `ProviderWizard` now passes `provider_name` in its `onSelect` callback; `AddMailAccountModal` stores it so logos are displayed correctly on the accounts page.
+- [x] **Fetch button UX improvements**: The "fetch emails" button on the accounts page now shows a "Fetch" text label for clarity, a tooltip explaining its purpose, a spinning "Fetching…" state during the API call, and a brief green "Queued!" confirmation after success.
+
- [x] **Pull Now**: Added "Pull Now" button on Accounts page that immediately queues a `process_mail_account` Celery task via `POST /mail-accounts/{id}/pull-now`. Button shows spinner while in flight and is disabled for inactive accounts.
- [x] Fixed 21 mypy type errors: `Column[T]` vs native type mismatches in `notification_service.py`, `mail_processor.py`, `auth.py`, `tasks.py`, `providers.py`, `mail_accounts.py`, and `main.py` (`lifespan` parameter rename).
- [x] **Provider logos rework**: Logos now displayed as full-width banner strips at the top of each account card using `next/image fill + object-contain`. Handles all aspect ratios (1:1 square to 6:1 wordmark) without distortion. Proton Mail added.
diff --git a/frontend/src/app/accounts/page.tsx b/frontend/src/app/accounts/page.tsx
index 6e6aae5..714aa91 100644
--- a/frontend/src/app/accounts/page.tsx
+++ b/frontend/src/app/accounts/page.tsx
@@ -53,6 +53,7 @@ export default function AccountsPage() {
const [isModalOpen, setIsModalOpen] = useState(false);
const [editingAccount, setEditingAccount] = useState
(null);
const [pullingIds, setPullingIds] = useState>(new Set());
+ const [successIds, setSuccessIds] = useState>(new Set());
const queryClient = useQueryClient();
const { data: accounts, isLoading } = useQuery({
@@ -101,6 +102,14 @@ export default function AccountsPage() {
setPullingIds((prev) => new Set(prev).add(id));
try {
await mailAccountsApi.pullNow(id);
+ setSuccessIds((prev) => new Set(prev).add(id));
+ setTimeout(() => {
+ setSuccessIds((prev) => {
+ const next = new Set(prev);
+ next.delete(id);
+ return next;
+ });
+ }, 2000);
} catch {
alert('Failed to queue pull');
} finally {
@@ -227,11 +236,22 @@ export default function AccountsPage() {