diff --git a/designs/wemail-macos-client/app.jsx b/designs/wemail-macos-client/app.jsx new file mode 100644 index 0000000..ab4646e --- /dev/null +++ b/designs/wemail-macos-client/app.jsx @@ -0,0 +1,164 @@ +const { + Sidebar, + MessageList, + DetailPane, + MenuBar, + QuickPopover, + PreferencesSheet, + ComposeSheet, + Toast, + Icon, + IconButton, + ToolbarButton +} = window; + +function filterMessages(messages, filter, query) { + const normalizedQuery = query.trim().toLowerCase(); + return messages.filter((message) => { + const matchesFilter = + filter === "all" || + (filter === "code" && message.type === "code") || + (filter === "link" && message.type === "link") || + (filter === "unread" && message.unread); + + if (!matchesFilter) return false; + if (!normalizedQuery) return true; + + return [message.sender, message.from, message.subject, message.preview, message.extractionValue] + .join(" ") + .toLowerCase() + .includes(normalizedQuery); + }); +} + +function App() { + const [theme, setTheme] = React.useState("light"); + const [activeMailboxId, setActiveMailboxId] = React.useState(window.mailboxData[0].id); + const [selectedMessageId, setSelectedMessageId] = React.useState(window.messageData[0].id); + const [filter, setFilter] = React.useState("all"); + const [query, setQuery] = React.useState(""); + const [showQuick, setShowQuick] = React.useState(false); + const [showPreferences, setShowPreferences] = React.useState(false); + const [showCompose, setShowCompose] = React.useState(false); + const [showRaw, setShowRaw] = React.useState(false); + const [toast, setToast] = React.useState(null); + + React.useEffect(() => { + document.documentElement.dataset.theme = theme; + }, [theme]); + + React.useEffect(() => { + if (!toast) return undefined; + const timer = window.setTimeout(() => setToast(null), 2400); + return () => window.clearTimeout(timer); + }, [toast]); + + const activeMailbox = window.mailboxData.find((mailbox) => mailbox.id === activeMailboxId) ?? window.mailboxData[0]; + const mailboxMessages = window.messageData.filter((message) => message.mailboxId === activeMailbox.id); + const visibleMessages = filterMessages(mailboxMessages, filter, query); + const selectedMessage = + visibleMessages.find((message) => message.id === selectedMessageId) ?? + mailboxMessages.find((message) => message.id === selectedMessageId) ?? + visibleMessages[0] ?? + null; + + const quickMessages = window.messageData.filter((message) => message.type !== "muted"); + const highValueCount = quickMessages.filter((message) => message.unread).length; + + function handleSelectMailbox(mailboxId) { + const nextMessages = window.messageData.filter((message) => message.mailboxId === mailboxId); + setActiveMailboxId(mailboxId); + setSelectedMessageId(nextMessages[0]?.id ?? null); + setShowRaw(false); + } + + function handleCopy(value) { + const text = value || "未提取"; + if (navigator.clipboard?.writeText) { + navigator.clipboard.writeText(text).catch(() => undefined); + } + setToast({ + title: "已复制到剪贴板", + detail: text + }); + } + + function handleSendTest() { + setShowCompose(false); + setToast({ + title: "测试邮件已加入队列", + detail: "发件箱会显示发送记录和异常状态" + }); + } + + return ( +
+ setShowQuick((value) => !value)} + onToggleTheme={() => setTheme((value) => (value === "dark" ? "light" : "dark"))} + /> + {showQuick ? : null} +
+ setShowPreferences(true)} + onSelectMailbox={handleSelectMailbox} + /> +
+
+
+ +
+

收件处理

+ {activeMailbox.address} +
+
+
+ handleCopy("已刷新当前邮箱")} /> + setShowCompose(true)} /> + handleCopy(selectedMessage?.extractionValue)} /> +
+
+ + setQuery(event.target.value)} + placeholder="搜索发件人、主题或验证码" + value={query} + /> + {query ? setQuery("")} /> : } +
+
+
+ { + setSelectedMessageId(messageId); + setShowRaw(false); + }} + selectedMessageId={selectedMessage?.id ?? null} + /> + setShowRaw((value) => !value)} + showRaw={showRaw} + /> +
+
+
+ {showPreferences ? setShowPreferences(false)} /> : null} + {showCompose ? setShowCompose(false)} onSend={handleSendTest} /> : null} + +
+ ); +} + +const root = ReactDOM.createRoot(document.getElementById("root")); +root.render(); diff --git a/designs/wemail-macos-client/components.jsx b/designs/wemail-macos-client/components.jsx new file mode 100644 index 0000000..4a313da --- /dev/null +++ b/designs/wemail-macos-client/components.jsx @@ -0,0 +1,441 @@ +const { Icon } = window; + +function TrafficLights() { + return ( + + ); +} + +function IconButton({ icon, label, active = false, onClick }) { + return ( + + ); +} + +function ToolbarButton({ icon, label, primary = false, onClick }) { + return ( + + ); +} + +function SegmentedControl({ value, options, onChange }) { + return ( +
+ {options.map((option) => ( + + ))} +
+ ); +} + +function Sidebar({ activeMailboxId, onSelectMailbox, onOpenPreferences }) { + return ( + + ); +} + +function ExtractionChip({ message }) { + const iconName = message.type === "code" ? "key-round" : message.type === "link" ? "link-2" : "minus"; + return ( + + + {message.type === "code" ? message.extractionValue : message.extractionLabel} + + ); +} + +function MessageRow({ message, active, onSelect }) { + return ( + + ); +} + +function MessageList({ mailbox, messages, selectedMessageId, filter, onFilterChange, onSelectMessage }) { + const filterOptions = [ + { id: "all", label: "全部", icon: "list-filter" }, + { id: "code", label: "验证码", icon: "key-round" }, + { id: "link", label: "链接", icon: "link-2" }, + { id: "unread", label: "未读", icon: "circle" } + ]; + + return ( +
+
+
+
+

{mailbox.name}

+

{mailbox.address}

+
+
+ + {mailbox.extractions} + 待提取 + + + {messages.length} + 消息 + +
+
+
+ +
+
+
+ {messages.length ? ( + messages.map((message) => ( + + )) + ) : ( +
当前条件下没有可显示的消息。
+ )} +
+
+ ); +} + +function DetailPane({ message, onCopy, showRaw, onToggleRaw }) { + if (!message) { + return ( +
+
选择一封邮件查看提取结果、正文和调试信息。
+
+ ); + } + + const hasTaskValue = Boolean(message.extractionValue); + const taskLabel = message.type === "code" ? "识别到验证码" : message.type === "link" ? "识别到登录链接" : "未识别高价值结果"; + + return ( +
+
+
+

{message.subject}

+
+ {message.from} + {message.time} + {message.attachments ? `${message.attachments} 个附件` : "无附件"} +
+
+
+ + onCopy("原始邮件链接已准备打开")} /> + onCopy(message.extractionValue || message.subject)} /> +
+
+
+
+
+ + + {taskLabel} + + + {hasTaskValue ? message.extractionValue : "未提取"} + +
+
+ + 置信度 + {message.confidence}% + +
+
+
+
+
+
+
+

正文预览

+ {showRaw ? ( +
{JSON.stringify({ id: message.id, extraction: message.extractionValue, meta: message.meta }, null, 2)}
+ ) : ( + message.body.map((paragraph) =>

{paragraph}

) + )} +
+
+
+

提取上下文

+
+ {Object.entries(message.meta).map(([key, value]) => ( +
+
{key}
+
{value}
+
+ ))} +
+
+
+

处理链路

+
+ {message.timeline.map((item) => ( +
+ + {item} +
+ ))} +
+
+
+

本机行为

+
+ 高价值邮件通知 + +
+
+ 复制后标记已读 + 开启 +
+
+
+
+
+
+ ); +} + +function MenuBar({ theme, onToggleTheme, quickCount, showQuick, onToggleQuick }) { + return ( +
+
+ WeMail + File + Message + Rules +
+
+ + + 09:42 +
+
+ ); +} + +function QuickPopover({ messages, onCopy }) { + return ( +
+
+ 最新可复制结果 + +
+
+ {messages.slice(0, 4).map((message) => ( +
+ + {message.sender} + {message.subject} + + +
+ ))} +
+
+ ); +} + +function PreferencesSheet({ onClose }) { + return ( +
+
+
+

WeMail 偏好设置

+ +
+
+
+
+

通知策略

+

验证码和登录链接触发系统通知,普通邮件仅进入收件处理列表。

+
高价值邮件立即通知
+
通知声音Glass
+
+
+

复制行为

+

从详情页或菜单栏复制后,自动记录操作并可选标记为已读。

+
复制后标记已读开启
+
保留剪贴板记录30 分钟
+
+
+

路由与告警

+

异常或未匹配邮件进入发件箱异常视图,同时保留原始邮件上下文。

+
Webhook已连接
+
Telegram仅失败告警
+
+
+

桌面工作台

+

默认进入收件处理,消息列表优先展示提取结果而不是长主题。

+
默认视图验证码
+
阅读密度紧凑
+
+
+
+
+ +
+
+
+ ); +} + +function ComposeSheet({ onClose, onSend }) { + return ( +
+
+
+

发送测试邮件

+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+
+
+ ); +} + +function Toast({ toast }) { + if (!toast) return null; + return ( +
+ + + {toast.title} + {toast.detail} + +
+ ); +} + +Object.assign(window, { + TrafficLights, + IconButton, + ToolbarButton, + SegmentedControl, + Sidebar, + MessageList, + DetailPane, + MenuBar, + QuickPopover, + PreferencesSheet, + ComposeSheet, + Toast +}); diff --git a/designs/wemail-macos-client/data.jsx b/designs/wemail-macos-client/data.jsx new file mode 100644 index 0000000..6fd5c31 --- /dev/null +++ b/designs/wemail-macos-client/data.jsx @@ -0,0 +1,259 @@ +const mailboxData = [ + { + id: "qa-login", + name: "QA Login Flow", + address: "qa-login@wemail.dev", + unread: 7, + extractions: 12, + attachments: 3 + }, + { + id: "staging", + name: "Staging Accounts", + address: "staging@wemail.dev", + unread: 3, + extractions: 6, + attachments: 1 + }, + { + id: "billing", + name: "Billing Sandbox", + address: "billing@wemail.dev", + unread: 2, + extractions: 2, + attachments: 0 + }, + { + id: "security", + name: "Security Review", + address: "security@wemail.dev", + unread: 0, + extractions: 1, + attachments: 5 + } +]; + +const messageData = [ + { + id: "msg-vercel", + mailboxId: "qa-login", + sender: "Vercel", + from: "login@vercel.com", + subject: "Your Vercel verification code", + preview: "Use this code to continue signing in. The code expires in 10 minutes.", + time: "09:42", + unread: true, + type: "code", + extractionLabel: "验证码", + extractionValue: "482913", + confidence: 96, + attachments: 0, + body: [ + "You requested a sign-in code for the Vercel staging workspace.", + "Enter the code above in the browser to complete login. If this was not you, ignore this message.", + "Workspace: WeOpen QA. Region: Singapore edge." + ], + meta: { + "提取类型": "auth_code", + "来源规则": "six-digit-code", + "处理链路": "inbound-email-routing", + "Headers": "DKIM pass, SPF pass" + }, + timeline: ["收到 Cloudflare Email Routing 事件", "postal-mime 解析正文", "命中验证码提取规则", "已推送 macOS 通知"] + }, + { + id: "msg-github", + mailboxId: "qa-login", + sender: "GitHub", + from: "noreply@github.com", + subject: "Confirm your device for WeMail tests", + preview: "A new device is trying to access your GitHub account. Confirm this sign-in request.", + time: "09:31", + unread: true, + type: "link", + extractionLabel: "LOGIN LINK", + extractionValue: "github.com/login/device/verify", + confidence: 91, + attachments: 0, + body: [ + "A new device wants to access the WeMail QA organization.", + "Use the login link above to confirm the request. This link is only valid for this session.", + "Device: Chrome macOS. IP range: internal QA network." + ], + meta: { + "提取类型": "auth_link", + "来源规则": "login-url-primary", + "处理链路": "inbound-email-routing", + "Headers": "DKIM pass, SPF neutral" + }, + timeline: ["收到邮件", "识别登录按钮 URL", "折叠长链接", "标记为高价值消息"] + }, + { + id: "msg-linear", + mailboxId: "qa-login", + sender: "Linear", + from: "security@linear.app", + subject: "Magic link for QA session", + preview: "Click the link to finish signing in to Linear. The link will expire shortly.", + time: "09:10", + unread: false, + type: "link", + extractionLabel: "MAGIC LINK", + extractionValue: "linear.app/login/magic/QA-7K", + confidence: 88, + attachments: 0, + body: [ + "Here is your magic link for Linear.", + "Use it only if you requested access from the WeMail disposable inbox flow." + ], + meta: { + "提取类型": "service_link", + "来源规则": "magic-link-button", + "处理链路": "inbound-email-routing", + "Headers": "DKIM pass" + }, + timeline: ["收到邮件", "解析 HTML 正文", "命中 magic link", "记录提取 JSON"] + }, + { + id: "msg-notion", + mailboxId: "qa-login", + sender: "Notion", + from: "team@makenotion.com", + subject: "Welcome to the QA workspace", + preview: "Your workspace is ready. No verification code was detected in this message.", + time: "08:48", + unread: false, + type: "muted", + extractionLabel: "未提取", + extractionValue: "", + confidence: 0, + attachments: 1, + body: [ + "Welcome to the QA workspace.", + "This message contains onboarding context but no login code or link. It remains readable in the desktop client for audit trails." + ], + meta: { + "提取类型": "none", + "来源规则": "no-match", + "处理链路": "inbound-email-routing", + "Headers": "DKIM pass, SPF pass" + }, + timeline: ["收到邮件", "正文解析完成", "未命中验证码或链接", "保留完整正文"] + }, + { + id: "msg-stripe", + mailboxId: "staging", + sender: "Stripe", + from: "support@stripe.com", + subject: "Confirm your staging payout account", + preview: "Your Stripe staging account requires a confirmation code before continuing.", + time: "08:18", + unread: true, + type: "code", + extractionLabel: "验证码", + extractionValue: "761228", + confidence: 94, + attachments: 0, + body: [ + "Use this confirmation code to continue setting up the staging payout account.", + "This code expires soon. Do not share it with anyone outside the QA run." + ], + meta: { + "提取类型": "auth_code", + "来源规则": "six-digit-code", + "处理链路": "inbound-email-routing", + "Headers": "DKIM pass" + }, + timeline: ["收到邮件", "解析纯文本正文", "提取验证码", "写入收件箱视图模型"] + }, + { + id: "msg-slack", + mailboxId: "staging", + sender: "Slack", + from: "feedback@slack.com", + subject: "Security alert for staging login", + preview: "We noticed a sign-in from a new browser. Review the session if you do not recognize it.", + time: "07:54", + unread: true, + type: "muted", + extractionLabel: "未提取", + extractionValue: "", + confidence: 0, + attachments: 0, + body: [ + "A sign-in happened from a browser that has not been seen before.", + "No actionable login code or link was present, so WeMail keeps the message in a lower-priority state." + ], + meta: { + "提取类型": "none", + "来源规则": "no-match", + "处理链路": "inbound-email-routing", + "Headers": "DKIM pass" + }, + timeline: ["收到邮件", "安全提示分类", "未提取", "保留给人工确认"] + }, + { + id: "msg-aws", + mailboxId: "billing", + sender: "AWS", + from: "no-reply-aws@amazon.com", + subject: "Root account verification", + preview: "Enter the verification code for the billing sandbox root account.", + time: "昨天", + unread: true, + type: "code", + extractionLabel: "验证码", + extractionValue: "390144", + confidence: 97, + attachments: 0, + body: [ + "Enter this verification code to complete access to the AWS billing sandbox.", + "This is a controlled disposable mailbox for test automation and manual QA only." + ], + meta: { + "提取类型": "auth_code", + "来源规则": "six-digit-code", + "处理链路": "inbound-email-routing", + "Headers": "DKIM pass, SPF pass" + }, + timeline: ["收到邮件", "提取验证码", "命中账单沙箱邮箱", "通知已发送"] + }, + { + id: "msg-security-report", + mailboxId: "security", + sender: "Cloudflare", + from: "notify@cloudflare.com", + subject: "Email Routing weekly digest", + preview: "Weekly routing digest with attachment logs and delivery summary.", + time: "周一", + unread: false, + type: "muted", + extractionLabel: "未提取", + extractionValue: "", + confidence: 0, + attachments: 5, + body: [ + "This digest summarizes inbound routing events for the WeMail project.", + "The macOS client keeps these messages available, but does not promote them above verification tasks." + ], + meta: { + "提取类型": "none", + "来源规则": "digest-message", + "处理链路": "inbound-email-routing", + "Headers": "DKIM pass" + }, + timeline: ["收到 digest", "识别附件", "无验证码", "归档到安全审查邮箱"] + } +]; + +const navItems = [ + { id: "inbox", label: "收件处理", icon: "inbox", count: 12 }, + { id: "outbound", label: "发件箱", icon: "send", count: 3 }, + { id: "settings", label: "邮件规则", icon: "sliders-horizontal", count: 0 } +]; + +Object.assign(window, { + mailboxData, + messageData, + navItems +}); diff --git a/designs/wemail-macos-client/icons.jsx b/designs/wemail-macos-client/icons.jsx new file mode 100644 index 0000000..830f9a2 --- /dev/null +++ b/designs/wemail-macos-client/icons.jsx @@ -0,0 +1,25 @@ +function Icon({ name, label, size = 16, className = "" }) { + React.useEffect(() => { + if (window.lucide) { + window.lucide.createIcons({ + attrs: { + "stroke-width": "1.8", + "absolute-stroke-width": "true" + } + }); + } + }); + + return ( + + ); +} + +Object.assign(window, { Icon }); diff --git a/designs/wemail-macos-client/prototype-desktop.png b/designs/wemail-macos-client/prototype-desktop.png new file mode 100644 index 0000000..0274804 Binary files /dev/null and b/designs/wemail-macos-client/prototype-desktop.png differ diff --git a/designs/wemail-macos-client/prototype-mobile.png b/designs/wemail-macos-client/prototype-mobile.png new file mode 100644 index 0000000..3452266 Binary files /dev/null and b/designs/wemail-macos-client/prototype-mobile.png differ diff --git a/designs/wemail-macos-client/wemail-macos-client.html b/designs/wemail-macos-client/wemail-macos-client.html new file mode 100644 index 0000000..008ec20 --- /dev/null +++ b/designs/wemail-macos-client/wemail-macos-client.html @@ -0,0 +1,1340 @@ + + + + + + WeMail macOS Client Prototype + + + +
+ + + + + + + + + + diff --git a/designs/wemail-macos-client2/WeMail macOS Client.html b/designs/wemail-macos-client2/WeMail macOS Client.html new file mode 100644 index 0000000..2bf30d5 --- /dev/null +++ b/designs/wemail-macos-client2/WeMail macOS Client.html @@ -0,0 +1,1433 @@ + + + + + + WeMail macOS Client - High Fidelity Prototype + + + +
+
+
+ + + + +
+
+
W
+
+

WeMail 识别到验证码

+

来自 Stripe 的邮件包含验证码 482193,可直接复制到当前注册流程。

+
+ + +
+
+
+
+ +
+
+
+
+
+
W
+
+

WeMail

+

刚刚收到 1 封新邮件

+
+
+
+ + +
+
+ +
+
+ 当前临时邮箱 + signup-aqua-27@wemail.dev +
+ +
+ +
+
+
+ + 等待验证码模式 +
+ +
+
+
+ 每 5 秒刷新,剩余 01:42 + 超时后自动降频 +
+
+ +
+
+
+ 验证码 +
482193
+
+ Stripe 登录验证 + 2 秒前 · 已由服务端 extraction 识别 +
+
+ +
+
+ + +
+
+ + + +
+ + + +
+ +
+
+
+
+ 登录凭据 + Token 存储在 macOS Keychain +
+ 安全 +
+
+
+ 刷新策略 + 等待模式 5 秒,后台 90 秒 +
+ +
+
+
+ 隐私边界 + 不读取剪贴板,不扫描浏览器页面 +
+ 本地 +
+
+
+ 连接 + api.wemail.dev · 已认证 +
+ +
+
+
+
+
+ +
已复制到剪贴板
+
+
+ + +
+ + + + diff --git a/designs/wemail-macos-client2/prototype-screenshot.png b/designs/wemail-macos-client2/prototype-screenshot.png new file mode 100644 index 0000000..6519412 Binary files /dev/null and b/designs/wemail-macos-client2/prototype-screenshot.png differ