152 lines
5.7 KiB
Markdown
152 lines
5.7 KiB
Markdown
# 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` 验证标题、品牌、登录页删减及双账号新消息提醒路径。
|