diff --git a/frontend/src/components/AgAgentPanel.jsx b/frontend/src/components/AgAgentPanel.jsx
index 8927f0b..2833582 100644
--- a/frontend/src/components/AgAgentPanel.jsx
+++ b/frontend/src/components/AgAgentPanel.jsx
@@ -1,7 +1,6 @@
import { useState } from "react";
import {
Calendar,
- Check,
ChevronDown,
ChevronRight,
FileSearch,
@@ -18,12 +17,14 @@ import SenderAvatar from "./SenderAvatar";
import { inboxContext } from "../utils/inboxSignals";
const ICON = { size: 14, strokeWidth: 1.75 };
+const AGENDA_SLOTS = 3;
+const ATTENTION_SLOTS = 2;
const cards = [
- { key: "important", label: "Important emails", captionKey: "importantFromClients", captionSuffix: "from clients", icon: Mail, tone: "rose" },
- { key: "actionItems", label: "Action items", hint: "Need your response", icon: ListChecks, tone: "blue" },
- { key: "meetings", label: "Meetings mentioned", hint: "In your emails", icon: Calendar, tone: "green" },
- { key: "invoices", label: "Invoices & bills", hint: "This week", icon: Receipt, tone: "amber" },
+ { key: "important", label: "Important emails", captionKey: "importantFromClients", captionSuffix: "from clients", emptyCaption: "No priority mail yet", icon: Mail, tone: "rose" },
+ { key: "actionItems", label: "Action items", hint: "Need your response", emptyCaption: "Nothing pending", icon: ListChecks, tone: "blue" },
+ { key: "meetings", label: "Meetings mentioned", hint: "In your emails", emptyCaption: "No meetings found", icon: Calendar, tone: "green" },
+ { key: "invoices", label: "Invoices & bills", hint: "This week", emptyCaption: "No invoices detected", icon: Receipt, tone: "amber" },
];
function greeting(name) {
@@ -32,6 +33,70 @@ function greeting(name) {
return `${period}, ${name}`;
}
+function statCaption(card, stats) {
+ const count = stats[card.key] || 0;
+ if (count === 0) return card.emptyCaption || card.hint;
+ if (card.captionKey && stats[card.captionKey] > 0) {
+ return `${stats[card.captionKey]} ${card.captionSuffix}`;
+ }
+ return card.hint;
+}
+
+function padSlots(items, slots) {
+ const result = items.slice(0, slots).map((item) => ({ ...item, placeholder: false }));
+ while (result.length < slots) {
+ result.push({ placeholder: true, id: `slot-${result.length}` });
+ }
+ return result;
+}
+
+function AgendaPlaceholder({ index }) {
+ return (
+
0}>
+
+
+ {index === 0 ? (
+ <>
+
+ No meetings in your emails
+ Calendar events will appear here
+ >
+ ) : (
+ <>
+
+
+ >
+ )}
+
+
+ );
+}
+
+function AttentionPlaceholder({ index, showEmptyMessage }) {
+ return (
+ 0 && !showEmptyMessage}>
+ {showEmptyMessage && index === 0 ? (
+ <>
+
+
+ Nothing needs attention
+ Threads awaiting reply will appear here
+
+ >
+ ) : (
+ <>
+
+
+
+
+
+
+ >
+ )}
+
+ );
+}
+
export default function AgAgentPanel({
account,
emails,
@@ -45,9 +110,8 @@ export default function AgAgentPanel({
const [agentQuery, setAgentQuery] = useState("");
const [panelOpen, setPanelOpen] = useState(true);
const firstName = account?.first_name || "there";
- const activeCards = cards.filter((card) => (stats[card.key] || 0) > 0);
- const hasContext = needsReply.length > 0 || today.length > 0 || deadlines.length > 0;
- const hasSignals = activeCards.length > 0 || hasContext;
+ const agendaSlots = padSlots(today, AGENDA_SLOTS);
+ const attentionSlots = padSlots(needsReply, ATTENTION_SLOTS);
function submitAgentQuery(event) {
event.preventDefault();
@@ -80,138 +144,145 @@ export default function AgAgentPanel({
{panelOpen && (
- {!insight && hasSignals && (
+ {!insight && (
{greeting(firstName)}
Here's what needs your attention today.
)}
- {!insight && !hasSignals && (
-
-
You're all caught up.
-
No urgent messages or outstanding actions.
-
Inbox is quiet right now.
-
- )}
-
- {!insight && activeCards.length > 0 && (
+ {!insight && (
- {activeCards.map((card) => {
+ {cards.map((card) => {
const Icon = card.icon;
const count = stats[card.key] || 0;
- const caption = card.captionKey && stats[card.captionKey]
- ? `${stats[card.captionKey]} ${card.captionSuffix}`
- : card.hint;
+ const isEmpty = count === 0;
return (
);
})}
)}
- {!insight && focus && hasSignals &&
{focus}
}
+ {!insight && focus &&
{focus}
}
-
Quick actions
-
-
-
-
-
+
Quick actions
+
+
+
+
+
+
-
- {!insight && today.length > 0 && (
-
-
-
Today's agenda
-
-
-
-
+ {!insight && (
+
+
+
Today's agenda
+
+
+
+
)}
- {!insight && needsReply.length > 0 && (
-
- Needs attention
-
- {needsReply.slice(0, 3).map((item) => (
- -
-
-
- ))}
-
-
+ {!insight && (
+
+ Needs attention
+
+ {attentionSlots.map((item, index) => (
+ item.placeholder ? (
+
+ ) : (
+ -
+
+
+ )
+ ))}
+
+
)}
{!insight && deadlines.length > 0 && (
-
- Deadlines
-
- {deadlines.map((item) => (
- -
- {item.label}
- {item.due}
-
- ))}
-
-
+
+ Deadlines
+
+ {deadlines.map((item) => (
+ -
+ {item.label}
+ {item.due}
+
+ ))}
+
+
)}
)}
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index d1e682b..392acf5 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -619,6 +619,10 @@ h1 em { color:var(--coral); font-style:normal; font-weight:500; }
.agent-stat-blue { background:linear-gradient(180deg,#f3f7ff 0%,#eaf1ff 100%); border-color:#d5e2f8; color:#4d74c4; }
.agent-stat-green { background:linear-gradient(180deg,#f1faf4 0%,#e8f5ed 100%); border-color:#cfe8d8; color:#3d8a5d; }
.agent-stat-amber { background:linear-gradient(180deg,#f7f2ff 0%,#efe8ff 100%); border-color:#ddd0f5; color:#8b6fd4; }
+.agent-stat-empty { opacity:.72; cursor:default; }
+.agent-stat-empty:hover { transform:none; box-shadow:none; }
+.agent-stat-empty strong { color:#b5b0ba; }
+.agent-stat-empty small { color:#a39ea8; }
.agent-quick { margin-top:var(--space-1); padding-top:var(--space-3); border-top:1px solid var(--border-subtle); }
.agent-quick h3 { margin:0 0 10px; color:#7a7580; font-size:10px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; }
.agent-quick div { display:grid; grid-template-columns:1fr 1fr; gap:var(--space-2); }
@@ -657,6 +661,22 @@ h1 em { color:var(--coral); font-style:normal; font-weight:500; }
.agent-agenda-row time { display:block; color:#8d8792; font-size:10px; font-weight:600; }
.agent-agenda-row strong { display:block; color:#2a2530; font-size:var(--text-meta); font-weight:600; line-height:1.3; }
.agent-agenda-row small { display:block; color:#8d8792; font-size:10px; }
+.agent-placeholder-row { border-style:dashed; background:#fafafa; pointer-events:none; }
+.agent-placeholder-row .agent-agenda-bar.placeholder { background:#e4e0dc; min-height:36px; }
+.agent-placeholder-row time,.agent-placeholder-row strong,.agent-placeholder-row small { color:#b8b3bc; }
+.agent-placeholder-row strong { font-weight:500; }
+.agent-placeholder-copy { min-width:0; display:grid; gap:5px; align-content:center; }
+.agent-placeholder-line { display:block; height:8px; border-radius:var(--radius-pill); background:linear-gradient(90deg,#ece8e4 0%,#f4f1ee 100%); }
+.agent-placeholder-line.wide { width:78%; }
+.agent-placeholder-line.narrow { width:42%; }
+.agent-attention-placeholder { padding:10px 0; display:grid; grid-template-columns:30px 1fr 24px; align-items:center; gap:var(--space-2); border-bottom:1px solid var(--border-subtle); pointer-events:none; }
+.agent-attention-placeholder:last-child { border-bottom:0; }
+.agent-placeholder-avatar { width:30px; height:30px; border-radius:50%; background:linear-gradient(180deg,#ece8e4 0%,#e2deda 100%); }
+.agent-placeholder-avatar.muted { background:linear-gradient(180deg,#f0ece8 0%,#e8e4e0 100%); }
+.agent-placeholder-label { color:#b8b3bc; font-size:var(--text-meta); font-weight:600; }
+.agent-placeholder-copy small { color:#c5c0ba; font-size:10px; }
+.agent-placeholder-age { width:20px; height:8px; border-radius:var(--radius-pill); background:#ece8e4; }
+.agent-attention-block .agent-attention-list:has(.agent-attention-placeholder) .agent-attention-placeholder:first-child .agent-placeholder-line.wide { width:65%; }
.agent-attention-name { color:#2a2530; font-size:var(--text-meta); font-weight:600; }
.agent-attention-meta { color:#8d8792; font-size:10px; }
.agent-attention-list button:hover .agent-attention-name { color:var(--coral); }