Files
im_webapp/docs/superpowers/plans/2026-09-30-web-brand-and-message-sound.md
T

152 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Web Brand And Message Sound Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 将生产 Web 品牌统一为“迅工捷使”,精简登录页,并为符合提醒条件的新消息播放用户提供的提示音。
**Architecture:** 继续以 `web/src/lib/config.ts` 作为显示品牌名来源,认证页和系统消息只消费该常量;品牌图标和 MP3 作为 Vite `public/` 静态资源。消息声音接入现有 `push.ts` 新消息扫描链路,在浏览器通知权限判断之前复用“本人消息、当前可见会话、会话免打扰”的过滤结果。
**Tech Stack:** React 19、TypeScript、Vite、Node test runner、Jiti。
---
### Task 1: 写品牌和提示音红测
**Files:**
- Create: `web/tests/brand-and-notification-sound.test.mjs`
- [ ] **Step 1: 写失败测试**
创建 Node 测试,核心断言如下;可按实际导出补齐 fake `Audio` 的类型字段,但不得降低断言:
```js
const webRoot = new URL('../', import.meta.url);
assert.match(await readFile(new URL('index.html', webRoot), 'utf8'), /<title>迅工捷使<\/title>/);
assert.doesNotMatch(await readFile(new URL('src/features/auth/LoginPage.tsx', webRoot), 'utf8'), /桌面端|下载桌面客户端|iOS \/ Android|帮助中心/);
assert.doesNotMatch(await readFile(new URL('src/features/auth/TenantEntryPage.tsx', webRoot), 'utf8'), /MeetingIM/);
assert.doesNotMatch(await readFile(new URL('src/features/system/SystemMessagesView.tsx', webRoot), 'utf8'), /MeetingIM 安全团队/);
assert.equal(audioSource, '/message-notification.mp3');
assert.equal(playCount, 1);
```
- [ ] **Step 2: 运行测试确认失败**
Run: `node --experimental-strip-types --test tests/brand-and-notification-sound.test.mjs`
Expected: FAIL,因为品牌仍是 `MeetingIM`,提示音模块和静态资源尚不存在。
### Task 2: 更新品牌和登录页
**Files:**
- Modify: `web/index.html`
- Modify: `web/src/lib/config.ts`
- Modify: `web/src/features/auth/LoginPage.tsx`
- Modify: `web/src/features/auth/TenantEntryPage.tsx`
- Modify: `web/src/features/system/SystemMessagesView.tsx`
- Create: `web/public/app-icon.png`
- Delete: `web/public/favicon.svg`
- Modify: `web/src/lib/notifications/tabIndicator.ts`
- Modify: `web/src/lib/notifications/push.ts`
- [ ] **Step 1: 复制官方品牌图标**
将 `../im-ios-website/dist/assets/app-icon.png` 原样复制为 `web/public/app-icon.png`,所有 Web 图标引用统一指向它。
- [ ] **Step 2: 最小化更新可见品牌**
把浏览器标题、认证页和系统消息署名改为“迅工捷使”;`APP_NAME` 保留 `MeetingIM/<version>` 作为现有 Tinode 客户端技术标识,避免把显示品牌替换扩大成协议改名。
```ts
export const BASE_APP_NAME = '迅工捷使';
export const APP_NAME = `MeetingIM/${__APP_VERSION__}`;
```
认证页统一使用:
```tsx
<img src="/app-icon.png" alt="" className="h-full w-full rounded-[inherit]" />
<strong>{BASE_APP_NAME}</strong>
```
- [ ] **Step 3: 精简登录页**
说明文案改为“单聊、群聊、通讯录、文件与收藏、音视频通话,消息与回执在手机端保持一致。”,删除底部三个入口。
```tsx
<p className="mt-4 max-w-md text-base text-white/80">
单聊、群聊、通讯录、文件与收藏、音视频通话,消息与回执在手机端保持一致。
</p>
```
### Task 3: 接入消息提示音
**Files:**
- Create: `web/src/lib/notifications/sound.ts`
- Modify: `web/src/lib/notifications/push.ts`
- Create: `web/public/message-notification.mp3`
- [ ] **Step 1: 复制用户提供的提示音**
将 `/Users/aa/Downloads/universfield-new-notification-012-363675.mp3` 原样复制为 `web/public/message-notification.mp3`。
- [ ] **Step 2: 实现安全播放器**
首次需要时创建 `Audio('/message-notification.mp3')`,每次播放前设置 `currentTime = 0`;对同步异常和 `play()` Promise 拒绝均静默处理。
```ts
let messageAudio: HTMLAudioElement | null = null;
export function playMessageSound(): void {
try {
messageAudio ||= new Audio('/message-notification.mp3');
messageAudio.currentTime = 0;
void messageAudio.play().catch(() => {});
} catch {
// Optional audio must never interrupt message delivery.
}
}
```
- [ ] **Step 3: 接入新消息过滤链路**
把“本人消息、当前可见会话、会话免打扰”抽成通知与声音共享的判断。通过判断后先播放声音;仅当 Notification API 可用且权限为 `granted` 时再显示系统通知。
```ts
if (!shouldAlert(topicName, latest)) return;
playMessageSound();
if (typeof Notification !== 'undefined' && Notification.permission === 'granted') {
fireNotification(topicName, latest);
}
```
- [ ] **Step 4: 跑绿测**
Run: `node --experimental-strip-types --test tests/*.test.ts tests/*.test.mjs`
Expected: 现有 6 项和新增测试全部 PASS。
### Task 4: 验证、提交与部署
**Files:**
- Verify only
- [ ] **Step 1: 静态验证**
Run: `npm run build && npm run lint`
Expected: 构建退出码 0;lint 无 error,允许记录既有 warning。
- [ ] **Step 2: 完整性验证**
Run: `git diff --check`
Expected: 无输出,退出码 0;MP3 和应用图标的 SHA-256 与来源文件一致。
- [ ] **Step 3: 提交并推送**
提交精确修改文件到 `dev`,推送前先 fetch 并确认无远端分叉。
- [ ] **Step 4: 部署和公网回归**
触发 Gitea `deploy.yml`,等待成功,确认 K3s Deployment 2/2;访问 `https://web.118im.com` 验证标题、品牌、登录页删减及双账号新消息提醒路径。