Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
527 changes: 527 additions & 0 deletions docs/prd/clawfeed-prd-1.2.md

Large diffs are not rendered by default.

512 changes: 512 additions & 0 deletions docs/prd/clawfeed-prd-1.3.md

Large diffs are not rendered by default.

204 changes: 204 additions & 0 deletions docs/prd/clawfeed-prd-1.4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# PRD 1.4: Sources 页面重构

> ClawFeed v1.0 · Phase 1 · 优先级 P0
> 作者: Lucy · 日期: 2026-02-25

---

## 背景

ClawFeed 的核心理念是"AI 编辑部"——Source 越多,筛选池越大,输出质量越高,但 Digest 篇幅不变(15-20 条/期)。用户不看原始内容,只看 AI 精选。

当前 Sources 页面(v0.8.1)存在以下问题:

1. **概念混淆**:页面混合了三个不同概念——"我的 Sources"(我创建的)、"公共 Sources"(所有公开的,与前者重叠)、"Source Packs"。用户进入页面后不知道该做什么。
2. **创建 ≠ 订阅 未清晰传达**:创建 Source 和订阅 Source 是两个独立操作,但当前 UI 没有明确区分。
3. **无探索入口**:用户只能看到已有的 Sources,缺少发现新 Source 的途径。
4. **空状态体验差**:新注册用户看到空白页面,不知从何开始。

PR #15(raw_items 采集管道)已 merge,Source 级采集与 Digest 生成解耦完成。

### 现有实现参考
- 前端:web/index.html 单文件 SPA(~1300 行),Sources 渲染在 renderSources() 函数中
- 后端 API:/api/sources、/api/subscriptions、/api/packs 已完整
- 数据库:sources、user_subscriptions、source_packs 表已就绪
- 已有功能:Smart Add(URL 自动检测)、订阅/退订、Pack 安装、软删除

---

## 目标

1. **信息架构清晰化**:让用户一眼看懂"我订阅了什么"、"我能发现什么"、"我创建了什么"
2. **订阅操作零摩擦**:订阅/退订一键完成,无需理解背后的数据模型
3. **建立探索基础**:为 Phase 3 Source Market 铺路,当前阶段提供基础的 Source 发现能力
4. **空状态引导**:新用户 3 步内完成首次订阅,进入个性化 Digest 体验

---

## 用户故事

### US-1: 新用户首次订阅
> 作为新注册用户,我希望看到推荐的 Source 包并一键安装,这样我能快速获得个性化 Digest。

验收场景:注册后进入 Sources 页面看到欢迎引导 + 推荐 Pack 列表;点击 Pack 查看包含的 Sources 预览;一键安装后"已订阅"分区立即显示这些 Sources;自动提示去查看 Digest。

### US-2: 管理已订阅 Sources
> 作为已有订阅的用户,我希望在页面顶部看到所有已订阅的 Sources 及其状态,能快速退订不想要的。

验收场景:"已订阅"分区显示所有活跃订阅;每张卡片显示名称、类型图标、简要描述;一键退订(无需确认弹窗,支持撤销);已删除的 Source 灰色显示。

### US-3: 发现并订阅新 Sources
> 作为现有用户,我希望能浏览其他人创建的公开 Sources 并订阅感兴趣的。

验收场景:"探索"分区展示所有公开 Sources(排除已订阅的);支持按类型筛选;每张卡片显示订阅人数;一键订阅后 Source 移入"已订阅"分区。

### US-4: 管理自己创建的 Sources
> 作为 Source 创建者,我希望在独立分区管理自己创建的 Sources。

验收场景:"我创建的"分区默认折叠;可编辑名称/配置/公开状态;可软删除(有订阅者时提醒);区分已订阅和未订阅自己的 Source。

### US-5: 添加新 Source
> 作为用户,我希望通过粘贴 URL 快速添加 Source,系统自动识别类型。

验收场景:"添加 Source"入口始终可见;粘贴 URL 后自动检测并预览;确认后创建+自动订阅;URL 已存在时提示并提供直接订阅选项。

---

## 功能需求

### F-1: 页面信息架构重构

页面分区(从上到下):已订阅 → 探索 Sources → 推荐 Packs → 我创建的(折叠)

空状态(新用户):欢迎引导 + 推荐 Pack 选择 + 添加自定义 Source 入口

### F-2: Source 卡片升级

每张卡片包含:名称、类型图标、类型标签、订阅人数(新增: COUNT from user_subscriptions)、操作按钮(根据上下文显示订阅/退订/编辑/删除)。

不在此版本:Source 健康指标(采集成功率、最后更新时间)——留给后续迭代。

### F-3: 探索分区

- 展示所有 is_public=1 且 is_deleted=0 的 Sources,排除已订阅的
- 类型筛选 Tab:全部 | Twitter | RSS | Hacker News | Reddit | GitHub | 其他
- 按订阅人数降序排列
- 一键订阅,订阅后从探索列表移至已订阅列表(无需刷新)

### F-4: 订阅/退订操作优化

- 订阅:点击后立即生效,卡片移动到已订阅分区,显示 toast
- 退订:点击后立即移除,显示 toast + 5秒内可撤销(Undo)
- 无确认弹窗(低成本操作)
- 乐观更新:UI 立即响应,API 后台完成,失败时回滚

### F-5: 添加 Source 入口优化

- 页面顶部右侧常驻 [+] 添加按钮
- 增加重复检测:同类型+同 config 已存在时,提示并提供直接订阅选项
- Manual Add 保留为 Smart Add 面板内的手动添加链接

### F-6: 匿名用户体验

- 匿名用户看到所有公开 Sources 列表(只读)
- 操作按钮替换为"登录以订阅" CTA
- 作为转化入口展示 Source 丰富度

---

## 技术方案

### 后端变更

**API-1: GET /api/sources 增强**
- 新增查询参数:?type=twitter_feed(按类型筛选)、?explore=true(探索模式:公开 Sources 排除已订阅)
- 响应增加 subscriber_count 和 is_subscribed 字段
- 实现:LEFT JOIN user_subscriptions 做 COUNT + EXISTS 子查询

**API-2: POST /api/sources 重复检测**
- 创建前检查同 type+config 是否已存在(getSourceByTypeConfig() 已有)
- 已存在返回 409 Conflict,body 含已存在 Source 信息
- 前端展示"此 Source 已存在,是否直接订阅?"

**API-3: DELETE /api/subscriptions/source/:sourceId**
- 新增按 source_id 退订接口(当前用 subscription ID,不够直觉)
- 保留原接口向后兼容

### 前端变更

**FE-1: renderSources() 重构**
拆分为:renderSubscribedSection() / renderExploreSection() / renderPacksSection() / renderCreatedByMeSection() / renderEmptyState()

**FE-2: 类型筛选 Tab**
新增 exploreFilter 状态变量,前端过滤(一次 API 请求获取所有公开 Sources)

**FE-3: 乐观更新 + Toast**
订阅/退订立即更新本地状态+重新渲染;toast 3秒消失;退订 toast 含撤销链接(5秒);API 失败回滚+错误提示

**FE-4: Source 卡片组件**
增强 renderSourceCard(),根据分区显示不同内容

### 数据库变更
无 schema 变更。subscriber_count 通过查询时计算。

---

## 验收标准

### 页面结构
- AC-1: 已登录用户看到四个分区:已订阅 → 探索 → 推荐 Packs → 我创建的
- AC-2: "我创建的" 默认折叠,点击可展开
- AC-3: 每个分区标题显示数量(如 "已订阅 (12)")
- AC-4: 页面顶部有常驻 [+] 添加按钮

### 订阅操作
- AC-5: 点击订阅后 Source 立即出现在已订阅分区(无需刷新)
- AC-6: 点击退订后 Source 立即从已订阅分区移除
- AC-7: 退订后显示 toast,5秒内可撤销
- AC-8: API 请求失败时 UI 回滚并显示错误提示

### 探索功能
- AC-9: 探索分区显示所有公开 Sources,排除已订阅的
- AC-10: 类型筛选 Tab 正常工作(全部/Twitter/RSS/HN 等)
- AC-11: Sources 按订阅人数降序排列
- AC-12: 每张卡片显示订阅人数

### 空状态
- AC-13: 新用户(无订阅)看到推荐 Pack 引导页
- AC-14: 安装 Pack 后自动切换到正常页面布局
- AC-15: 引导页有"添加自定义 Source"的次要入口

### 添加 Source
- AC-16: Smart Add 检测到已存在的 Source 时,提示"已存在"并提供订阅选项
- AC-17: 创建 Source 后自动订阅并显示在已订阅分区

### 匿名用户
- AC-18: 未登录用户看到公开 Sources 列表(只读)
- AC-19: 操作按钮替换为"登录以订阅"提示

### 兼容性
- AC-20: 移动端响应式布局正常(卡片单列)
- AC-21: Dark/Light 主题切换正常
- AC-22: 中英文 i18n 切换正常

---

## 依赖关系

### 前置依赖
- PR #15 raw_items 采集管道: ✅ 已 merge
- PR #32 Phase 1 release: ✅ 已部署 v0.8.1
- 用户认证系统: ✅ 已完成 (Google OAuth)
- Sources/Subscriptions API: ✅ 已完成 (CRUD + 订阅管理)

### 被依赖(下游)
- 1.2 个性化 Digest 生成: 依赖用户有清晰的订阅管理入口
- 3.2 Source Market: 本 PRD 的探索分区是 Market 的基础版本
- 2.1 多渠道推送: 推送内容基于用户订阅的 Sources

### 外部依赖
无。不引入新的第三方库或外部服务。

---

*PRD by Lucy · ClawFeed Phase 1*
123 changes: 123 additions & 0 deletions docs/prd/clawfeed-prd-2.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# ClawFeed PRD 2.1 — 多渠道推送

作者: Lucy · 日期: 2026-02-25

## 背景

用户必须主动打开网页看 Digest,没有推送。Phase 2 交付标准:用户通过 Telegram 收到个性化 Digest,能即时追问。

## 目标

G1: Telegram Bot 推送 — ≥10 用户绑定
G2: Email 推送 — ≥5 用户配置邮件
G3: 用户自主配置渠道偏好
G4: 新增渠道 < 200 行改动
G5: 推送失败率 < 1%

## 用户故事(6 个)

US-1 Telegram 绑定: 网页端点击"连接 Telegram" → t.me/ClawFeedBot?start=<token> → Bot 回复"绑定成功" → 网页显示已连接
US-2 定时推送: Digest 生成 5 分钟内推送,含标题+前 3-5 条+查看完整链接
US-3 按需查询: /digest 获取最新,/digest daily 获取 daily,/settings 查看配置
US-4 Email 推送: 启用 Email → 选频率(per-digest/daily/weekly) → HTML 邮件+退订链接
US-5 推送频率配置: 三种频率,不同渠道可设不同频率
US-6 飞书推送(P1): 群机器人 Webhook 或 Bot DM

## 功能需求

### 渠道优先级
Telegram Bot (P0,双向) → Email (P1,单向HTML) → 飞书 (P1,Webhook) → Slack (P2) → Discord (P2)

### Telegram Bot 设计
7 个命令: /start, /start <token>(绑定), /digest, /digest daily|weekly, /settings(inline keyboard), /unlink, /help
Webhook 模式(非 Long Polling): POST /api/telegram/webhook + secret_token 验证
MarkdownV2 消息格式,4096 字符限制截断+链接
绑定流程: 网页生成 link_token(32字符hex, 10分钟有效, 一次性) → 用户 Telegram /start <token> → 验证+关联 user_id+chat_id

### Email 设计
Nodemailer + SMTP,.env 配置 SMTP_HOST/PORT/USER/PASS/FROM
HTML 模板: templates/email-digest.html,内联 CSS,移动端自适应(600px)
退订: HMAC(user_id+"email", SESSION_SECRET) token,无需数据库验证
频率: per-digest(每次) / daily summary(每日 20:00 UTC+8) / weekly only(每周日 20:00)

### 飞书 P1: 群 Webhook(最简,单向),Bot DM 延后
### Slack/Discord P2: Incoming Webhook

### 推送引擎
事件驱动+队列: createDigest() → push_queue 表(pending) → Worker 30秒轮询 → 发送 → sent/failed
失败重试: 3 次,指数退避(2min/4min/8min)
频率聚合 Cron: daily summary 每日 20:00 UTC+8 合并当日 Digest,weekly 每周日
渠道适配器接口: PushChannel.send(config, digest, user) → { success, error }

## 技术方案

### Migration 010 (4 张表)
1. push_channels: user_id, channel, is_enabled, config(JSON), frequency, UNIQUE(user_id,channel)
2. push_queue: user_id, digest_id, channel, status(pending/sent/failed), retry_count, scheduled_at, error
3. push_log: user_id, digest_id, channel, status, error (审计+统计)
4. push_link_tokens: user_id, token(UNIQUE), channel, expires_at (绑定令牌,临时)

config JSON per channel:
- telegram: {chat_id, username}
- email: {address}
- lark/slack/discord: {webhook_url}

### API 8 个新接口
GET/POST /api/push/channels — 列表/添加更新渠道
DELETE/PATCH /api/push/channels/:channel — 删除/更新渠道
POST /api/push/link-token — 生成绑定令牌
GET /api/push/unsubscribe — 邮件一键退订(token验证)
POST /api/telegram/webhook — Telegram 回调(secret验证)
GET /api/push/stats — 推送统计(管理员)

### 文件结构
src/push/engine.mjs (队列+worker), formatter.mjs (格式化)
src/push/channels/{telegram,email,lark,slack,discord}.mjs
src/telegram-bot.mjs (webhook handler+命令路由)
templates/email-digest.html

### db.mjs 新增 ~15 个导出函数
Push Channels: list/get/upsert/delete/update/listByFrequency
Push Queue: enqueue/getPending/updateStatus/incrementRetry
Link Tokens: create/consume/cleanExpired
Push Log: log/getStats

### Telegram Bot 注册
.env: TELEGRAM_BOT_TOKEN, TELEGRAM_WEBHOOK_SECRET
服务启动时自动 setWebhook + setMyCommands

### Worker
setInterval 30秒,每次取 50 pending jobs,串行处理
仅新增 nodemailer 1 个 npm 依赖,其余用原生 https

## 验收标准(29 条)

Telegram Bot (P0): AC-1~10
- Bot 创建+Webhook 自动注册、绑定流程、5 分钟内推送、/digest /settings /unlink 命令、未绑定引导、4096 截断、secret 验证

Email (P1): AC-11~17
- 启用用 OAuth 邮箱、per-digest 10 分钟内、daily 20:00、weekly 周日 20:00、HTML 移动端可读、退订链接可用、SMTP 缺失时自动禁用

推送引擎: AC-18~21
- Worker 30 秒轮询、失败 3 次重试(指数退避)、push_log 完整、频率正确

用户设置: AC-22~25
- 设置页显示所有渠道、不同渠道不同频率、开关即时生效、断开连接删除记录

数据完整性: AC-26~29
- Migration 幂等、CASCADE 清理、token 过期失效、UNIQUE 约束

## 依赖

上游: Digest 生成管线(已有,需加 hook)、Google OAuth(已有)、HTTPS(已有)、BotFather 注册(需操作)、SMTP 配置(需配置)
下游: 新增 Cron(daily/weekly 聚合、token 清理)、.env 新增 4 配置项、push_queue 定期清理(7天sent/failed)

### 实施顺序
2.1a 基础设施(1周): Migration + db.mjs + engine + telegram channel + bot handler
2.1b Telegram 集成(1周): BotFather + server 路由 + 前端设置页 + 绑定流程 + E2E
2.1c Email+飞书(1周): email channel + HTML 模板 + lark channel + SMTP + 频率 Cron
2.1d P2 渠道(按需): slack + discord + 监控告警

---

*PRD by Lucy · ClawFeed Phase 2*
Loading
Loading