三件套,参照Slack/Telegram Web/WhatsApp Web等主流IM网页客户端的标准做法: - lib/notifications/tabIndicator.ts:标签页被切到后台且有未读时,标题在 "📨 (N条新消息) <原标题>"上做左向滚动,用户切回来的瞬间(visibilitychange) 立即恢复原标题,不等下一次未读数变化才发现。favicon右上角画一个红色圆点+ 白色数字角标(canvas绘制成PNG data URL,写回现有<link rel="icon">,不是 另加一个可能被浏览器忽略的竞争链接)。两者都由AppShell.tsx里已经算好的 同一个unreadMessages号驱动,不另起一套可能对不上的计数。 - lib/notifications/push.ts:真正的浏览器桌面通知(Notification API)。只 在标签页还开着(哪怕最小化/切到后台)时生效——做不到浏览器整个关掉也能收到, 那需要Service Worker+推送订阅+后端配合,是完全另一个量级的工程。 抑制规则("什么时候不弹"): · 消息来自自己(比如另一个标签页/设备同步过来的回显) · 正在看的就是这个会话本身(会话已打开且标签页确实可见——两者都要满足, 一个被切到后台的标签页哪怕挂载着这个会话也不算"正在看") · 该会话设置了"消息免打扰"(contact.private.mute_notifications) · 浏览器未授予通知权限 同一会话的连续通知用tag合并显示为一条,不会叠加刷屏;点击通知会聚焦标签页 并跳转到对应会话(通过stores/notificationNav.ts中转,push.ts在React之外, 没有useNavigate可用,做法与stores/calls.ts、messageJump.ts是同一个模式)。 判断"是否是真的新消息"用的是防抖后的seq对比(防止刚打开一个从未见过的会话时, 历史消息分批到达被逐条误判成"新消息")——第一次见到某个会话时记录基准而不是 照单全收。**这里踩到一个真实bug并当场修复**:最初版本把"第一次见到这个会话" 一律当成"历史消息,不通知",结果全新好友的第一条消息(对这个会话来说seq=1, 同时也是"第一次见到")被误判成历史消息静默吞掉——用两个全新互加好友的测试 账号实机复现后,改成额外看消息时间戳:一分钟以内的算真正新消息,无论是不是 第一次见到这个会话,否则才当历史记录处理。 实机验证(systest01/systest02两个全新互加好友的测试账号,全部通过mock Notification类捕获+SDK本地状态直接读取的方式验证,因为这个自动化浏览器环境 本身把Notification.permission锁死成denied,没法测试真正弹出的系统通知): - 真实新消息:通知的title/body/tag均正确(对方昵称/消息预览文本/会话id) - 免打扰会话:确认不弹 - 已打开且标签页可见:确认不弹 - 标题栏滚动+favicon角标:实机确认能正确开始/停止滚动、favicon生成的是 真实可解码的64×64 PNG Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules.
Currently, two official plugins are available:
- @vitejs/plugin-react uses Oxc
- @vitejs/plugin-react-swc uses SWC
React Compiler
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see this documentation.
Expanding the Oxlint configuration
If you are developing a production application, we recommend enabling type-aware lint rules by installing oxlint-tsgolint and editing .oxlintrc.json:
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "oxc"],
"options": {
"typeAware": true
},
"rules": {
"react/rules-of-hooks": "error",
"react/only-export-components": ["warn", { "allowConstantExport": true }]
}
}
See the Oxlint rules documentation for the full list of rules and categories.