inbox streamlining
This commit is contained in:
parent
c26a8a2094
commit
f969cfed44
4 changed files with 382 additions and 104 deletions
|
|
@ -76,9 +76,26 @@ function metricTotal(stats) {
|
|||
return cards.reduce((sum, card) => sum + (stats[card.key] || 0), 0);
|
||||
}
|
||||
|
||||
function emailContextSummary(email) {
|
||||
const text = (email?.body_text || email?.snippet || "").trim();
|
||||
if (!text) return "Open message loaded — summary will appear here.";
|
||||
return text.length > 160 ? `${text.slice(0, 157)}…` : text;
|
||||
}
|
||||
|
||||
function emailContextActions(email) {
|
||||
const text = (email?.body_text || email?.snippet || "").toLowerCase();
|
||||
const actions = [];
|
||||
if (text.includes("check") && text.includes("inbox")) actions.push("Check inbox");
|
||||
if (text.includes("repl")) actions.push("Review replies");
|
||||
if (text.includes("attach")) actions.push("Review attachments");
|
||||
if (!actions.length) actions.push("Review message", "Draft a reply");
|
||||
return actions.slice(0, 3);
|
||||
}
|
||||
|
||||
export default function AgAgentPanel({
|
||||
account,
|
||||
emails,
|
||||
contextEmail,
|
||||
focus,
|
||||
insight,
|
||||
loading = false,
|
||||
|
|
@ -95,9 +112,10 @@ export default function AgAgentPanel({
|
|||
const firstName = account?.first_name || "there";
|
||||
const allMetricsZero = metricTotal(stats) === 0;
|
||||
const hasQuery = agentQuery.trim().length > 0;
|
||||
const emailContextMode = Boolean(contextEmail) && !insight;
|
||||
const showOnboarding = !insight && !loading && mailboxEmpty && isBrandNewMailbox;
|
||||
const showQuietEmpty = !insight && !loading && mailboxEmpty && !isBrandNewMailbox;
|
||||
const showFullPanel = !insight && !loading && !mailboxEmpty;
|
||||
const showFullPanel = !insight && !loading && !mailboxEmpty && !emailContextMode;
|
||||
|
||||
function submitAgentQuery(event) {
|
||||
event.preventDefault();
|
||||
|
|
@ -108,7 +126,7 @@ export default function AgAgentPanel({
|
|||
}
|
||||
|
||||
return (
|
||||
<section className="agent-panel" aria-label="AG Agent">
|
||||
<section className={`agent-panel${emailContextMode ? " email-context" : ""}`} aria-label="AG Agent">
|
||||
<header className="agent-panel-head">
|
||||
<span className="agent-panel-mark" aria-hidden="true">
|
||||
<Sparkles {...ICON} />
|
||||
|
|
@ -136,7 +154,9 @@ export default function AgAgentPanel({
|
|||
<div className="agent-panel-hello">
|
||||
<strong>{greeting(firstName)}</strong>
|
||||
<span>
|
||||
{showOnboarding
|
||||
{emailContextMode
|
||||
? "About this email"
|
||||
: showOnboarding
|
||||
? "Your new inbox is ready."
|
||||
: showQuietEmpty
|
||||
? "Nothing needs your attention right now."
|
||||
|
|
@ -145,6 +165,22 @@ export default function AgAgentPanel({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{emailContextMode && (
|
||||
<section className="agent-email-context">
|
||||
<h3>Summary</h3>
|
||||
<p>{emailContextSummary(contextEmail)}</p>
|
||||
<h3>Action items</h3>
|
||||
<ul>
|
||||
{emailContextActions(contextEmail).map((item) => (
|
||||
<li key={item}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
<button type="button" className="agent-email-context-reply" onClick={() => onCompose?.()}>
|
||||
<PenLine {...ICON_SM} /> Draft reply
|
||||
</button>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{!insight && loading && (
|
||||
<div className="agent-loading-block" aria-hidden="true">
|
||||
<span className="agent-loading-pulse" />
|
||||
|
|
|
|||
|
|
@ -1,18 +1,26 @@
|
|||
import {
|
||||
Archive,
|
||||
ArrowLeft,
|
||||
Clock,
|
||||
Forward,
|
||||
Mail,
|
||||
MailOpen,
|
||||
MoreHorizontal,
|
||||
Reply,
|
||||
RotateCcw,
|
||||
Star,
|
||||
Tag,
|
||||
FolderInput,
|
||||
ShieldAlert,
|
||||
Trash2,
|
||||
X,
|
||||
ChevronDown,
|
||||
} from "lucide-react";
|
||||
import AgentSpark from "./AgentSpark";
|
||||
import SenderAvatar from "./SenderAvatar";
|
||||
import { senderDisplayName } from "../utils/senderAvatar";
|
||||
import { addressText, senderDisplayName } from "../utils/senderAvatar";
|
||||
import { attachmentCount } from "../utils/inboxSignals";
|
||||
|
||||
const ICON = { size: 18, strokeWidth: 1.75 };
|
||||
const ICON_SM = { size: 16, strokeWidth: 1.75 };
|
||||
const ICON = { size: 17, strokeWidth: 1.7 };
|
||||
|
||||
function joinAddresses(value) {
|
||||
if (!value) return "";
|
||||
|
|
@ -37,16 +45,80 @@ function messageBody(email) {
|
|||
return { mode: "text", value: "This message has no readable content." };
|
||||
}
|
||||
|
||||
function detailDate(email) {
|
||||
const value =
|
||||
email?.received_at || email?.sent_at || email?.created_at || email?.updated_at;
|
||||
if (!value) return "";
|
||||
function plainTextParagraphs(text) {
|
||||
return text
|
||||
.split(/\n{2,}/)
|
||||
.map((block) => block.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function detailTime(email) {
|
||||
const value = email?.received_at || email?.sent_at || email?.created_at || email?.updated_at;
|
||||
if (!value) return { absolute: "", relative: "" };
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return "";
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
if (Number.isNaN(date.getTime())) return { absolute: "", relative: "" };
|
||||
const absolute = new Intl.DateTimeFormat(undefined, {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
}).format(date);
|
||||
const diffMs = Date.now() - date.getTime();
|
||||
const diffHours = Math.max(1, Math.round(diffMs / (1000 * 60 * 60)));
|
||||
const relative = diffHours < 24 ? `${diffHours}h ago` : `${Math.round(diffHours / 24)}d ago`;
|
||||
return { absolute, relative };
|
||||
}
|
||||
|
||||
function senderIdentity(email) {
|
||||
const name = senderDisplayName(email);
|
||||
const address = addressText(email.sender || email.from);
|
||||
if (name && address && !name.includes("@")) {
|
||||
return `${name} <${address}>`;
|
||||
}
|
||||
return address || name || "Unknown sender";
|
||||
}
|
||||
|
||||
function recipientLabel(email, viewerEmail) {
|
||||
const recipients = email.recipients || email.to || [];
|
||||
const list = Array.isArray(recipients) ? recipients : [recipients];
|
||||
const addresses = list
|
||||
.map((item) => (typeof item === "string" ? item : item?.address || item?.email || ""))
|
||||
.filter(Boolean);
|
||||
if (!addresses.length) return "Undisclosed recipients";
|
||||
if (viewerEmail && addresses.length === 1 && addresses[0].toLowerCase() === viewerEmail.toLowerCase()) {
|
||||
return "to me";
|
||||
}
|
||||
if (addresses.length === 1) return `to ${addresses[0]}`;
|
||||
return `to ${addresses.length} recipients`;
|
||||
}
|
||||
|
||||
function formatFileSize(bytes) {
|
||||
if (!bytes) return "";
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
function mailboxLabel(mailbox) {
|
||||
if (mailbox === "inbox") return "Inbox";
|
||||
if (mailbox === "sent") return "Sent";
|
||||
if (mailbox === "trash") return "Trash";
|
||||
return mailbox;
|
||||
}
|
||||
|
||||
function ToolbarButton({ title, label, onClick, disabled, danger, children }) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={danger ? "danger" : ""}
|
||||
title={title}
|
||||
aria-label={label || title}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default function MailDetail({
|
||||
|
|
@ -55,96 +127,216 @@ export default function MailDetail({
|
|||
error,
|
||||
mailbox,
|
||||
busy,
|
||||
viewerEmail,
|
||||
threadSize = 1,
|
||||
onClose,
|
||||
onToggleRead,
|
||||
onToggleStar,
|
||||
onTrash,
|
||||
onRestore,
|
||||
onDelete,
|
||||
onArchive,
|
||||
onSpam,
|
||||
onSnooze,
|
||||
onMove,
|
||||
onReply,
|
||||
onForward,
|
||||
onExtractActions,
|
||||
onSummarizeThread,
|
||||
onAnalyzeAttachment,
|
||||
onDraftReply,
|
||||
}) {
|
||||
const files = email ? attachmentCount(email) : 0;
|
||||
const body = email ? messageBody(email) : null;
|
||||
const time = email ? detailTime(email) : { absolute: "", relative: "" };
|
||||
const attachments = email?.attachments || [];
|
||||
const summarizeLabel = threadSize > 1 ? "Summarize thread" : "Summarize email";
|
||||
|
||||
return (
|
||||
<article className={`mail-detail${email || loading ? " open" : ""}`} aria-live="polite">
|
||||
<header className="mail-detail-toolbar">
|
||||
<button type="button" onClick={onClose} aria-label="Back to message list">
|
||||
<div className="mail-detail-toolbar-group">
|
||||
<ToolbarButton title="Back to inbox" label="Back" onClick={onClose}>
|
||||
<ArrowLeft {...ICON} />
|
||||
</button>
|
||||
<div>
|
||||
</ToolbarButton>
|
||||
</div>
|
||||
{email && (
|
||||
<button
|
||||
type="button"
|
||||
disabled={busy}
|
||||
onClick={() => onToggleRead(!email.is_read)}
|
||||
title={email.is_read ? "Mark unread" : "Mark read"}
|
||||
aria-label={email.is_read ? "Mark unread" : "Mark read"}
|
||||
>
|
||||
{email.is_read ? <Mail {...ICON} /> : <MailOpen {...ICON} />}
|
||||
</button>
|
||||
)}
|
||||
{email && mailbox === "trash" && (
|
||||
<button type="button" disabled={busy} onClick={onRestore} title="Restore" aria-label="Restore message">
|
||||
<RotateCcw {...ICON} />
|
||||
</button>
|
||||
)}
|
||||
{email && (
|
||||
<button
|
||||
className={mailbox === "trash" ? "danger" : ""}
|
||||
type="button"
|
||||
disabled={busy}
|
||||
<>
|
||||
<div className="mail-detail-toolbar-group">
|
||||
<ToolbarButton title="Archive" onClick={onArchive} disabled={busy}>
|
||||
<Archive {...ICON} />
|
||||
</ToolbarButton>
|
||||
<ToolbarButton title="Report spam" onClick={onSpam} disabled={busy}>
|
||||
<ShieldAlert {...ICON} />
|
||||
</ToolbarButton>
|
||||
<ToolbarButton
|
||||
title={mailbox === "trash" ? "Delete permanently" : "Delete"}
|
||||
onClick={mailbox === "trash" ? onDelete : onTrash}
|
||||
title={mailbox === "trash" ? "Delete permanently" : "Move to trash"}
|
||||
aria-label={mailbox === "trash" ? "Delete message permanently" : "Move message to trash"}
|
||||
disabled={busy}
|
||||
danger
|
||||
>
|
||||
<Trash2 {...ICON} />
|
||||
</button>
|
||||
)}
|
||||
<button className="mail-detail-close" type="button" onClick={onClose} aria-label="Close message">
|
||||
<X size={18} strokeWidth={1.75} />
|
||||
</button>
|
||||
</ToolbarButton>
|
||||
</div>
|
||||
<span className="mail-detail-toolbar-sep" aria-hidden="true" />
|
||||
<div className="mail-detail-toolbar-group">
|
||||
<ToolbarButton
|
||||
title={email.is_read ? "Mark unread" : "Mark read"}
|
||||
onClick={() => onToggleRead(!email.is_read)}
|
||||
disabled={busy}
|
||||
>
|
||||
{email.is_read ? <Mail {...ICON} /> : <MailOpen {...ICON} />}
|
||||
</ToolbarButton>
|
||||
<ToolbarButton title="Snooze" onClick={onSnooze} disabled={busy}>
|
||||
<Clock {...ICON} />
|
||||
</ToolbarButton>
|
||||
</div>
|
||||
<span className="mail-detail-toolbar-sep" aria-hidden="true" />
|
||||
<div className="mail-detail-toolbar-group">
|
||||
<ToolbarButton title="Move to" onClick={onMove} disabled={busy}>
|
||||
<FolderInput {...ICON} />
|
||||
</ToolbarButton>
|
||||
<ToolbarButton title="Labels" onClick={onMove} disabled={busy}>
|
||||
<Tag {...ICON} />
|
||||
</ToolbarButton>
|
||||
<ToolbarButton title="More actions" onClick={onMove} disabled={busy}>
|
||||
<MoreHorizontal {...ICON} />
|
||||
</ToolbarButton>
|
||||
</div>
|
||||
{mailbox === "trash" && (
|
||||
<>
|
||||
<span className="mail-detail-toolbar-sep" aria-hidden="true" />
|
||||
<div className="mail-detail-toolbar-group">
|
||||
<ToolbarButton title="Restore" onClick={onRestore} disabled={busy}>
|
||||
<RotateCcw {...ICON} />
|
||||
</ToolbarButton>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{loading && <div className="mail-detail-state">Opening message…</div>}
|
||||
{error && <div className="mail-detail-state error" role="alert">{error}</div>}
|
||||
|
||||
{email && !loading && (
|
||||
<div className="mail-detail-content">
|
||||
<div className="mail-detail-agent-bar">
|
||||
<button type="button" className="agent-action subtle" onClick={onExtractActions}>
|
||||
<div className="mail-detail-subject-row">
|
||||
<div className="mail-detail-subject-main">
|
||||
<h1>{email.subject?.trim() || "(No subject)"}</h1>
|
||||
<div className="mail-detail-labels">
|
||||
<span className="mail-detail-label-pill">{mailboxLabel(mailbox)}</span>
|
||||
{email.is_starred && <span className="mail-detail-label-pill starred">Starred</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mail-detail-ai-chips">
|
||||
<button type="button" className="mail-detail-ai-chip" onClick={onSummarizeThread}>
|
||||
<AgentSpark /> {summarizeLabel}
|
||||
</button>
|
||||
<button type="button" className="mail-detail-ai-chip" onClick={onExtractActions}>
|
||||
<AgentSpark /> Extract actions
|
||||
</button>
|
||||
<button type="button" className="agent-action subtle" onClick={onSummarizeThread}>
|
||||
<AgentSpark /> Summarize thread
|
||||
<button type="button" className="mail-detail-ai-chip" onClick={onDraftReply}>
|
||||
<AgentSpark /> Draft reply
|
||||
</button>
|
||||
{files > 0 && (
|
||||
<button type="button" className="agent-action subtle" onClick={onAnalyzeAttachment}>
|
||||
<button type="button" className="mail-detail-ai-chip" onClick={onAnalyzeAttachment}>
|
||||
<AgentSpark /> Analyze attachment
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<h2>{email.subject?.trim() || "(No subject)"}</h2>
|
||||
<div className="mail-detail-meta">
|
||||
<div>
|
||||
<div className="mail-detail-sender">
|
||||
<SenderAvatar email={email} />
|
||||
<strong>{senderDisplayName(email)}</strong>
|
||||
</div>
|
||||
<small>From: {joinAddresses(email.sender) || "Unknown"}</small>
|
||||
|
||||
<div className="mail-detail-sender-row">
|
||||
<div className="mail-detail-sender-left">
|
||||
<SenderAvatar email={email} />
|
||||
<div className="mail-detail-sender-meta">
|
||||
<div className="mail-detail-sender-line">
|
||||
<strong>{senderIdentity(email)}</strong>
|
||||
</div>
|
||||
<button type="button" className="mail-detail-recipient-toggle">
|
||||
{recipientLabel(email, viewerEmail)} <ChevronDown size={12} strokeWidth={1.75} />
|
||||
</button>
|
||||
<div className="mail-detail-recipient-details">
|
||||
<small>To: {joinAddresses(email.recipients || email.to) || "Undisclosed"}</small>
|
||||
{joinAddresses(email.cc) && <small>Cc: {joinAddresses(email.cc)}</small>}
|
||||
</div>
|
||||
<time>{detailDate(email)}</time>
|
||||
</div>
|
||||
<div className="mail-detail-body">
|
||||
</div>
|
||||
<div className="mail-detail-sender-actions">
|
||||
<time dateTime={email.created_at}>
|
||||
{time.absolute}
|
||||
{time.relative ? ` (${time.relative})` : ""}
|
||||
</time>
|
||||
<button
|
||||
type="button"
|
||||
className={`mail-detail-icon-btn${email.is_starred ? " active" : ""}`}
|
||||
title={email.is_starred ? "Unstar" : "Star"}
|
||||
aria-label={email.is_starred ? "Unstar" : "Star"}
|
||||
onClick={() => onToggleStar?.(!email.is_starred)}
|
||||
>
|
||||
<Star size={16} strokeWidth={1.75} />
|
||||
</button>
|
||||
<button type="button" className="mail-detail-icon-btn" title="Reply" onClick={onReply}>
|
||||
<Reply size={16} strokeWidth={1.75} />
|
||||
</button>
|
||||
<button type="button" className="mail-detail-icon-btn" title="More" onClick={onMove}>
|
||||
<MoreHorizontal size={16} strokeWidth={1.75} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`mail-detail-body${body?.mode === "html" ? " is-html" : " is-text"}`}>
|
||||
{body?.mode === "html" ? (
|
||||
<div className="mail-detail-body-html" dangerouslySetInnerHTML={{ __html: body.value }} />
|
||||
) : (
|
||||
body?.value
|
||||
<div className="mail-detail-body-text">
|
||||
{plainTextParagraphs(body?.value || "").map((paragraph) => (
|
||||
<p key={paragraph.slice(0, 24)}>
|
||||
{paragraph.split("\n").map((line, index, lines) => (
|
||||
<span key={`${line}-${index}`}>
|
||||
{line}
|
||||
{index < lines.length - 1 && <br />}
|
||||
</span>
|
||||
))}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{attachments.length > 0 && (
|
||||
<div className="mail-detail-attachments" aria-label="Attachments">
|
||||
{attachments.map((file) => (
|
||||
<div key={`${file.filename}-${file.size}`} className="mail-detail-attachment">
|
||||
<span className="mail-detail-attachment-icon">📄</span>
|
||||
<div>
|
||||
<strong>{file.filename}</strong>
|
||||
<small>{file.content_type || "File"} · {formatFileSize(file.size)}</small>
|
||||
</div>
|
||||
<div className="mail-detail-attachment-actions">
|
||||
<button type="button">Preview</button>
|
||||
<button type="button">Download</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<footer className="mail-detail-reply-bar">
|
||||
<button type="button" className="mail-detail-reply-btn" onClick={onReply}>
|
||||
<Reply size={15} strokeWidth={1.75} /> Reply
|
||||
</button>
|
||||
{(email.recipients?.length > 1 || email.cc?.length) && (
|
||||
<button type="button" className="mail-detail-reply-btn subtle" onClick={onReply}>
|
||||
Reply all
|
||||
</button>
|
||||
)}
|
||||
<button type="button" className="mail-detail-reply-btn subtle" onClick={onForward}>
|
||||
<Forward size={15} strokeWidth={1.75} /> Forward
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
|
|
|
|||
|
|
@ -698,14 +698,24 @@ export default function MailboxView({
|
|||
error={detailStatus.error}
|
||||
busy={detailStatus.busy}
|
||||
mailbox={mailbox}
|
||||
viewerEmail={account?.email}
|
||||
threadSize={1}
|
||||
onClose={() => { setSelected(null); setDetail(null); }}
|
||||
onToggleRead={toggleRead}
|
||||
onToggleStar={(starred) => detail && toggleStar(detail, starred)}
|
||||
onTrash={() => mutateSelected(trashEmail)}
|
||||
onRestore={() => mutateSelected(restoreEmail)}
|
||||
onDelete={deletePermanently}
|
||||
onArchive={() => detail && hideForSession(detail, "Archived in this view until you refresh.")}
|
||||
onSpam={() => detail && hideForSession(detail, "Marked as spam in this view until you refresh.")}
|
||||
onSnooze={() => detail && hideForSession(detail, "Snoozed in this view until you refresh.")}
|
||||
onMove={() => setAgentFocus("Move and labels — coming soon.")}
|
||||
onReply={() => { onCompose?.(); setAgentFocus(detail?.subject ? `Reply: ${detail.subject}` : "Reply"); }}
|
||||
onForward={() => { onCompose?.(); setAgentFocus(detail?.subject ? `Forward: ${detail.subject}` : "Forward"); }}
|
||||
onExtractActions={() => detail && extractActions(detail)}
|
||||
onSummarizeThread={() => detail && summarizeThread(detail)}
|
||||
onAnalyzeAttachment={() => detail && analyzeAttachment(detail)}
|
||||
onDraftReply={() => detail && extractActions(detail)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -715,6 +725,7 @@ export default function MailboxView({
|
|||
<AgAgentPanel
|
||||
account={account}
|
||||
emails={listedEmails}
|
||||
contextEmail={detail}
|
||||
focus={agentFocus}
|
||||
insight={insight}
|
||||
loading={status.loading}
|
||||
|
|
|
|||
|
|
@ -649,33 +649,73 @@ h1 em { color:var(--coral); font-style:normal; font-weight:500; }
|
|||
.sticky-note-form-actions button[type="submit"] { background:var(--ink); color:white; }
|
||||
.sticky-note-form-actions button:disabled { opacity:.5; cursor:not-allowed; }
|
||||
|
||||
.mail-detail { min-width:0; display:none; position:absolute; inset:0; z-index:4; overflow:hidden; border-left:0; background:#fffefd; }
|
||||
.mail-detail.open { display:flex; flex-direction:column; }
|
||||
.mailbox-shell.detail-visible { border:1px solid rgba(24,20,35,.06); border-radius:10px; box-shadow:none; background:#fff; }
|
||||
.mailbox-shell.detail-visible .mailbox-categories,
|
||||
.mailbox-shell.detail-visible .mailbox-rows,
|
||||
.mailbox-shell.detail-visible .mailbox-pagination { display:none; }
|
||||
.mailbox-shell.detail-visible .mailbox-list-pane { display:flex; flex-direction:column; min-height:min(680px,calc(100vh - 200px)); }
|
||||
.mail-detail-toolbar { flex-shrink:0; min-height:62px; padding:10px 13px; display:flex; align-items:center; justify-content:space-between; border-bottom:1px solid var(--line); }
|
||||
.mail-detail-toolbar>div { display:flex; gap:3px; }
|
||||
.mail-detail-toolbar button { width:36px; padding:0; }
|
||||
.mailbox-shell.detail-visible .mailbox-list-pane { display:block; min-height:0; height:auto; }
|
||||
.mail-detail { min-width:0; display:none; position:relative; z-index:1; background:#fff; }
|
||||
.mail-detail.open { display:block; }
|
||||
.mail-detail-toolbar { height:50px; padding:0 24px; display:flex; align-items:center; gap:6px; border-bottom:1px solid #EEECEF; background:#fff; }
|
||||
.mail-detail-toolbar-group { display:flex; align-items:center; gap:4px; }
|
||||
.mail-detail-toolbar-sep { width:1px; height:18px; margin:0 4px; background:#ECE9EE; flex-shrink:0; }
|
||||
.mail-detail-toolbar button { width:32px; height:32px; padding:0; display:grid; place-items:center; border:0; border-radius:7px; background:transparent; color:#5a5562; }
|
||||
.mail-detail-toolbar button:hover { background:#f5f4f2; color:#241f2b; }
|
||||
.mail-detail-toolbar button.danger { color:#bb4e43; }
|
||||
.mail-detail-close { display:none!important; }
|
||||
.mail-detail-state { min-height:320px; padding:30px; display:grid; place-items:center; color:#8d8891; font-size:11px; text-align:center; }
|
||||
.mail-detail-toolbar button.danger:hover { background:#fdeeed; }
|
||||
.mail-detail-state { min-height:200px; padding:32px; display:grid; place-items:center; color:#8d8891; font-size:12px; text-align:center; }
|
||||
.mail-detail-state.error { color:#a94f40; }
|
||||
.mail-detail-content { flex:1; min-height:0; overflow-y:auto; padding:26px clamp(19px,3vw,34px) 40px; }
|
||||
.mail-detail-content h2 { margin:0 0 22px; overflow-wrap:anywhere; font:600 21px/1.3 "Manrope"; letter-spacing:-.7px; }
|
||||
.mail-detail-meta { padding-bottom:19px; display:flex; align-items:start; justify-content:space-between; gap:14px; border-bottom:1px solid var(--line); }
|
||||
.mail-detail-meta>div { min-width:0; display:grid; gap:3px; }
|
||||
.mail-detail-sender { display:flex; align-items:center; gap:10px; min-width:0; }
|
||||
.mail-detail-sender .sender-avatar { width:40px; height:40px; font-size:12px; flex-shrink:0; }
|
||||
.mail-detail-sender strong { overflow:hidden; text-overflow:ellipsis; font-size:11px; }
|
||||
.mail-detail-meta strong { overflow:hidden; text-overflow:ellipsis; font-size:11px; }
|
||||
.mail-detail-meta small,.mail-detail-meta time { color:#928d96; font-size:8px; line-height:1.5; }
|
||||
.mail-detail-meta time { white-space:nowrap; }
|
||||
.mail-detail-body { padding-top:23px; color:#383440; font-size:12px; line-height:1.75; white-space:pre-wrap; overflow-wrap:anywhere; }
|
||||
.mail-detail-content { padding:24px 32px 32px; }
|
||||
.mail-detail-subject-row { margin-bottom:22px; display:flex; align-items:flex-start; justify-content:space-between; gap:20px; }
|
||||
.mail-detail-subject-main { min-width:0; display:flex; flex-wrap:wrap; align-items:center; gap:10px; }
|
||||
.mail-detail-subject-main h1 { margin:0; overflow-wrap:anywhere; font:600 23px/30px "Manrope"; letter-spacing:-.5px; color:#171424; }
|
||||
.mail-detail-labels { display:flex; flex-wrap:wrap; gap:6px; }
|
||||
.mail-detail-label-pill { height:21px; padding:0 8px; display:inline-flex; align-items:center; border-radius:6px; background:#f5f4f2; color:#77758a; font-size:10px; font-weight:600; }
|
||||
.mail-detail-label-pill.starred { background:#fff8e8; color:#9a7228; }
|
||||
.mail-detail-ai-chips { display:flex; flex-wrap:wrap; justify-content:flex-end; gap:6px; flex-shrink:0; }
|
||||
.mail-detail-ai-chip { height:28px; padding:0 10px; display:inline-flex; align-items:center; gap:5px; border:1px solid #ece7ff; border-radius:7px; background:#fbf9ff; color:#6d5bd0; font-size:10px; font-weight:600; white-space:nowrap; }
|
||||
.mail-detail-ai-chip:hover { background:#f3efff; border-color:#ddd4ff; }
|
||||
.mail-detail-sender-row { margin-bottom:24px; display:flex; align-items:flex-start; justify-content:space-between; gap:16px; }
|
||||
.mail-detail-sender-left { min-width:0; display:flex; align-items:flex-start; gap:12px; }
|
||||
.mail-detail-sender-left .sender-avatar { width:38px; height:38px; font-size:12px; flex-shrink:0; }
|
||||
.mail-detail-sender-meta { min-width:0; display:grid; gap:3px; }
|
||||
.mail-detail-sender-line strong { font:600 14px/1.35 "DM Sans"; color:#171424; overflow-wrap:anywhere; }
|
||||
.mail-detail-recipient-toggle { padding:0; display:inline-flex; align-items:center; gap:3px; border:0; background:transparent; color:#77758a; font-size:11px; font-weight:500; }
|
||||
.mail-detail-recipient-toggle:hover { color:#4a4652; }
|
||||
.mail-detail-recipient-details { display:grid; gap:2px; }
|
||||
.mail-detail-recipient-details small { color:#77758a; font-size:11px; line-height:1.4; }
|
||||
.mail-detail-sender-actions { display:flex; align-items:center; gap:4px; flex-shrink:0; }
|
||||
.mail-detail-sender-actions time { margin-right:6px; color:#77758a; font-size:11px; white-space:nowrap; }
|
||||
.mail-detail-icon-btn { width:32px; height:32px; display:grid; place-items:center; border:0; border-radius:7px; background:transparent; color:#6f6a73; }
|
||||
.mail-detail-icon-btn:hover { background:#f5f4f2; color:#241f2b; }
|
||||
.mail-detail-icon-btn.active { color:#d4a017; }
|
||||
.mail-detail-body { color:#292536; }
|
||||
.mail-detail-body.is-text { max-width:800px; font-size:14px; line-height:1.62; }
|
||||
.mail-detail-body-text p { margin:0 0 10px; }
|
||||
.mail-detail-body-text p:last-child { margin-bottom:0; }
|
||||
.mail-detail-body.is-html { max-width:none; font-size:14px; line-height:1.55; }
|
||||
.mail-detail-body-html { white-space:normal; }
|
||||
.mail-detail-body-html p { margin:0 0 12px; }
|
||||
.mail-detail-body-html a { color:#4d74c4; text-decoration:underline; }
|
||||
.mail-detail-attachments { margin-top:24px; display:flex; flex-wrap:wrap; gap:10px; }
|
||||
.mail-detail-attachment { min-width:220px; max-width:320px; padding:10px 12px; display:grid; grid-template-columns:auto 1fr auto; gap:10px; align-items:center; border:1px solid #ECE9EE; border-radius:8px; background:#fcfbfa; }
|
||||
.mail-detail-attachment-icon { font-size:18px; }
|
||||
.mail-detail-attachment strong { display:block; font-size:12px; font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.mail-detail-attachment small { display:block; margin-top:2px; color:#77758a; font-size:10px; }
|
||||
.mail-detail-attachment-actions { display:flex; flex-direction:column; gap:2px; }
|
||||
.mail-detail-attachment-actions button { padding:2px 4px; border:0; background:transparent; color:#6d5bd0; font-size:9px; font-weight:700; }
|
||||
.mail-detail-reply-bar { margin-top:30px; display:flex; flex-wrap:wrap; gap:8px; }
|
||||
.mail-detail-reply-btn { height:38px; padding:0 16px; display:inline-flex; align-items:center; gap:7px; border:1px solid #ECE9EE; border-radius:8px; background:#fff; color:#241f2b; font-size:12px; font-weight:600; }
|
||||
.mail-detail-reply-btn:hover { background:#f8f7f5; border-color:#ddd8d3; }
|
||||
.mail-detail-reply-btn.subtle { color:#5a5562; background:#fafafa; }
|
||||
.agent-panel.email-context .agent-stat-grid { display:none; }
|
||||
.agent-panel.email-context .agent-context-block { display:none; }
|
||||
.agent-email-context { margin:10px 0 14px; padding:12px; border:1px solid #ece9f2; border-radius:10px; background:#faf9fc; }
|
||||
.agent-email-context h3 { margin:0 0 6px; font-size:10px; font-weight:700; letter-spacing:.4px; text-transform:uppercase; color:#8a8496; }
|
||||
.agent-email-context p { margin:0 0 12px; font-size:12px; line-height:1.5; color:#4a4652; }
|
||||
.agent-email-context ul { margin:0 0 12px; padding-left:16px; color:#4a4652; font-size:12px; line-height:1.5; }
|
||||
.agent-email-context-reply { height:34px; padding:0 12px; display:inline-flex; align-items:center; gap:6px; border:1px solid #ddd4ff; border-radius:8px; background:#f6f2ff; color:#6d4aff; font-size:11px; font-weight:700; }
|
||||
.agent-email-context-reply:hover { background:#ebe4ff; }
|
||||
|
||||
.compose-dock { position:fixed; right:var(--compose-dock-right,16px); bottom:16px; z-index:45; display:flex; flex-direction:column; align-items:flex-end; gap:10px; pointer-events:none; transition:right 160ms ease-out; }
|
||||
.compose-dock-expanded,.compose-dock-chips { display:flex; flex-direction:row-reverse; align-items:flex-end; gap:16px; pointer-events:auto; }
|
||||
|
|
@ -846,9 +886,11 @@ body.compose-blank-open .mailbox-empty-cta { opacity:.42; filter:saturate(.55);
|
|||
.mailbox-shell.detail-visible .mailbox-toolbar,
|
||||
.mailbox-shell.detail-visible .mailbox-filters,
|
||||
.mailbox-shell.detail-visible .mailbox-list-footer { display:none; }
|
||||
.mailbox-shell.detail-visible .mailbox-list-pane { min-height:min(100vh - 120px,720px); }
|
||||
.mail-detail { position:static; flex:1; }
|
||||
.mail-detail-close { display:flex!important; }
|
||||
.mail-detail-content { padding:20px 18px 28px; }
|
||||
.mail-detail-subject-row { flex-direction:column; }
|
||||
.mail-detail-ai-chips { justify-content:flex-start; }
|
||||
.mail-detail-sender-row { flex-direction:column; }
|
||||
.mail-detail-sender-actions { width:100%; justify-content:flex-start; }
|
||||
.compose-dock { right:12px !important; bottom:12px; }
|
||||
.compose-window { width:min(calc(100vw - 24px),560px); min-width:0; max-width:none; height:min(560px,calc(100vh - 24px)); }
|
||||
.compose-window.expanded { width:calc(100vw - 24px); min-width:0; height:min(78vh,calc(100vh - 24px)); }
|
||||
|
|
@ -1164,10 +1206,7 @@ body.compose-blank-open .mailbox-empty-cta { opacity:.42; filter:saturate(.55);
|
|||
.command-palette-foot kbd { margin-right:4px; padding:1px 5px; border:1px solid #e4e0da; border-radius:5px; background:#fff; }
|
||||
.workspace-search-kbd { padding:0; border:0; background:transparent; cursor:pointer; }
|
||||
.workspace-search-kbd kbd { pointer-events:none; }
|
||||
.mail-detail-agent-bar { margin-bottom:16px; display:flex; flex-wrap:wrap; gap:6px; }
|
||||
.mail-detail.open { animation:slideInDetail var(--motion-slow) var(--ease-standard); }
|
||||
.mail-detail-agent-bar .agent-action.subtle { height:36px; padding:0 var(--space-3); font-size:var(--text-meta); border-radius:var(--radius-sm); }
|
||||
.mail-detail-content h2 { margin-top:0; }
|
||||
.mailbox-notes-pane.compact { min-height:0; border-radius:10px; border:1px solid rgba(26,24,38,0.055); background:#FFFFFF; box-shadow:0 1px 2px rgba(20,18,30,0.025),0 4px 12px rgba(20,18,30,0.03); overflow:hidden; }
|
||||
.mailbox-notes-pane.compact.empty { background:#FFFFFF; }
|
||||
.mailbox-notes-pane.compact.empty .sticky-notes-toggle { min-height:38px; color:#77758A; font-size:11px; font-weight:600; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue