文档版本:v1.0 创建日期:2026-04-18 文档状态:Draft 适用范围:LearnFlow本地MVP
本文件用于把原始需求文档转成可执行、可验收、可追踪的工程规范。本文采用四条Spec原则。第一条是边界清晰,每个能力都声明范围和排除项。第二条是行为可测试,每条关键需求都能落到输入、处理、输出。第三条是约束可量化,性能、稳定性、可用性都有明确指标。第四条是变更可追踪,需求编号、接口、数据结构、验收标准互相可映射。
LearnFlow是Mac优先的本地学习任务管理应用。核心问题是用户保存了很多文章,但很少完成阅读。产品通过任务化、截止时间、优先级和提醒机制,把保存动作转成可执行学习流程。
MVP只服务单机单用户,不做云同步,不做账号系统,不做团队协作。MVP目标是让用户从添加链接到标记完成形成闭环,并且闭环可重复。
目标1:把收藏内容转成学习任务,且创建成本低。
目标2:提升已保存内容的再打开率和完成率。
目标3:确保用户数据可导出、可迁移、可备份。
M1任务创建成功率不低于99.0%。
M2提醒触达成功率不低于98.0%。
M3用户在7天内至少完成1篇文章的比例不低于35%。
M4从添加URL到任务落库的P95耗时不超过1200ms。网页抓取失败时走兜底流程,兜底落库P95不超过500ms。
M5应用异常退出后重启,任务数据不丢失,最近一次成功事务可恢复。
MVP包含以下能力。
- 菜单栏常驻入口。
- 主窗口任务列表与详情阅读。
- 手动添加URL。
- 自动提取标题与网页正文,失败可兜底。
- 本地SQLite存储。
- 任务状态、优先级、截止时间管理。
- 本地通知提醒。
- 搜索与筛选。
- 数据导出为Markdown、JSON、CSV。
MVP不包含以下能力。
- 微信、邮件、浏览器扩展接入。
- 移动端与多端同步。
- 云端账号与权限体系。
- AI摘要、AI问答、复杂知识库。
- 复杂统计报表与团队协作。
系统当前只有一种角色,单用户本机使用者。用户主要场景有五类。第一类是快速捕获,用户看到有价值链接后几秒内完成保存。第二类是任务排程,用户为待学内容设置优先级和截止时间。第三类是被动提醒,系统在关键时点拉回用户。第四类是沉浸阅读,用户在应用内完成阅读并标记状态。第五类是数据迁移,用户随时导出全部学习记录。
核心实体包括Item、Tag、ReminderEvent、ActivityEvent、Settings。
Item代表一篇文章及其学习任务。Tag用于轻量分类。ReminderEvent用于提醒调度与去重。ActivityEvent用于行为审计和后续统计。Settings用于本地偏好设置。
Item是主实体。Tag和ReminderEvent都从属于Item。ActivityEvent可关联Item,也可记录系统级事件。
| 用例ID | 名称 | 前置条件 | 主成功路径 | 失败路径 |
|---|---|---|---|---|
| UC-01 | 添加链接并创建任务 | 应用已启动 | 输入URL并保存,系统写入Item并返回详情 | URL不合法,返回校验错误 |
| UC-02 | 自动抓取正文 | UC-01提交后 | 系统抓取页面并提取标题与正文 | 抓取失败,保留URL与标题并标记content_status=failed |
| UC-03 | 设置截止和优先级 | Item已存在 | 更新due_at与priority并持久化 |
时间非法或状态冲突,更新失败 |
| UC-04 | 到时提醒 | Item存在有效提醒时间 | 触发本地通知并记录reminded_at |
通知插件失败,记录重试事件 |
| UC-05 | 阅读并标记完成 | Item状态非done |
打开阅读页并点击完成,状态切换为done |
写库失败,提示重试 |
| UC-06 | 搜索和筛选 | 至少有1条Item | 按标题、URL、状态、优先级、日期过滤 | 条件无结果,返回空列表 |
| UC-07 | 导出数据 | 本地数据库可读 | 选择格式并导出文件 | 磁盘写入失败,返回导出错误 |
| 需求ID | 描述 | 优先级 | 验收方式 |
|---|---|---|---|
| FR-01 | 系统必须支持手动输入URL创建任务 | P0 | 接口测试+UI测试 |
| FR-02 | 系统必须校验URL格式并阻断非法输入 | P0 | 单元测试 |
| FR-03 | 系统必须保存任务基础字段与时间字段 | P0 | 数据库集成测试 |
| FR-04 | 系统应尝试抓取标题与正文文本 | P1 | 集成测试 |
| FR-05 | 抓取失败时系统必须保留可用任务 | P0 | 异常流程测试 |
| FR-06 | 系统必须支持四种状态unread/reading/done/archived |
P0 | 状态机测试 |
| FR-07 | 系统必须支持三档优先级high/medium/low |
P0 | 单元测试 |
| FR-08 | 系统必须支持截止时间与提醒时间设置 | P0 | 集成测试 |
| FR-09 | 系统必须在提醒触发后记录触达时间 | P0 | 调度测试 |
| FR-10 | 系统必须支持任务列表排序与筛选 | P0 | UI自动化测试 |
| FR-11 | 系统必须支持阅读页并记录阅读进度 | P1 | UI测试 |
| FR-12 | 系统必须支持标记完成并停止后续提醒 | P0 | 状态+调度联测 |
| FR-13 | 系统必须支持标签增删与筛选 | P1 | 集成测试 |
| FR-14 | 系统必须支持数据导出为Markdown、JSON、CSV | P1 | 文件对比测试 |
| FR-15 | 系统必须支持关键词搜索标题与URL | P1 | 查询测试 |
允许迁移如下。
unread -> reading
unread -> done
unread -> archived
reading -> done
reading -> archived
done -> archived
archived -> unread
以下迁移禁止。
done -> reading
archived -> done
禁止迁移收到请求时,接口返回409_CONFLICT_STATUS_TRANSITION。
用户在菜单栏或主窗口输入URL并点击保存。前端先做同步校验,校验通过后调用add_item。后端在一个事务内写入基础Item,随后异步触发抓取任务。抓取成功则更新内容字段,抓取失败则把content_status置为failed,并写入活动事件。
提醒分成单任务提醒和默认提醒两种。若用户显式设置remind_at,系统按该时间触发。若只设置due_at,系统自动生成同一天09:00提醒。若两者都缺失,不自动提醒。调度器每60秒扫描一次待触发事件,并且在应用启动时执行一次补偿扫描。
用户打开阅读页后,滚动行为按区间更新reading_progress。点击完成时系统把状态改为done并写入completed_at。一旦状态变为done或archived,未触发提醒事件全部置为canceled。
所有时间字段采用ISO8601字符串并保存本地时区偏移。所有主键使用UUIDv7字符串。关键查询字段必须建索引。状态与优先级字段使用CHECK约束保证取值合法。
CREATE TABLE items (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
url TEXT NOT NULL,
url_hash TEXT NOT NULL,
source TEXT,
author TEXT,
published_at TEXT,
content_text TEXT,
content_html TEXT,
cover_image TEXT,
status TEXT NOT NULL DEFAULT 'unread' CHECK (status IN ('unread','reading','done','archived')),
priority TEXT NOT NULL DEFAULT 'medium' CHECK (priority IN ('high','medium','low')),
due_at TEXT,
remind_at TEXT,
reminded_at TEXT,
reading_progress REAL NOT NULL DEFAULT 0 CHECK (reading_progress >= 0 AND reading_progress <= 1),
content_status TEXT NOT NULL DEFAULT 'pending' CHECK (content_status IN ('pending','ready','failed')),
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
completed_at TEXT,
archived_at TEXT
);
CREATE UNIQUE INDEX idx_items_url_hash_active
ON items(url_hash)
WHERE status != 'archived';
CREATE INDEX idx_items_status_due_priority
ON items(status, due_at, priority, created_at DESC);
CREATE INDEX idx_items_remind_at
ON items(remind_at)
WHERE reminded_at IS NULL;
CREATE TABLE tags (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
created_at TEXT NOT NULL
);
CREATE TABLE item_tags (
item_id TEXT NOT NULL,
tag_id TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (item_id, tag_id),
FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
);
CREATE TABLE reminder_events (
id TEXT PRIMARY KEY,
item_id TEXT NOT NULL,
remind_at TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','triggered','canceled','failed')),
triggered_at TEXT,
retry_count INTEGER NOT NULL DEFAULT 0,
error_message TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE
);
CREATE INDEX idx_reminder_events_due
ON reminder_events(status, remind_at);
CREATE TABLE activity_events (
id TEXT PRIMARY KEY,
item_id TEXT,
event_type TEXT NOT NULL,
payload_json TEXT,
created_at TEXT NOT NULL,
FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE SET NULL
);
CREATE INDEX idx_activity_item_created
ON activity_events(item_id, created_at DESC);
CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TEXT NOT NULL
);系统对标准化URL计算url_hash。若存在未归档任务拥有同一url_hash,默认返回该任务并提示已存在,不重复创建。用户明确选择重复创建时,系统在URL后追加参数lf_dup=<timestamp>参与哈希,保留独立任务。
| 命令 | 输入 | 输出 | 错误码 |
|---|---|---|---|
add_item |
url,title?,priority?,due_at?,remind_at? |
ItemDTO |
400_INVALID_URL,409_DUPLICATED_ITEM,500_DB_ERROR |
get_items |
filter?,sort? |
ItemListDTO |
500_DB_ERROR |
get_item |
id |
ItemDTO |
404_ITEM_NOT_FOUND |
update_item |
id,input |
ItemDTO |
400_BAD_INPUT,409_CONFLICT_STATUS_TRANSITION |
delete_item |
id |
DeleteResultDTO |
404_ITEM_NOT_FOUND |
archive_item |
id |
ItemDTO |
404_ITEM_NOT_FOUND |
mark_item_done |
id |
ItemDTO |
409_CONFLICT_STATUS_TRANSITION |
fetch_article |
url |
ArticleContentDTO |
422_PARSE_FAILED,503_FETCH_FAILED |
set_reminder |
id,remind_at |
ReminderDTO |
400_BAD_TIME,404_ITEM_NOT_FOUND |
export_items |
format,path |
ExportResultDTO |
400_BAD_FORMAT,507_EXPORT_WRITE_FAILED |
export type ItemStatus = 'unread' | 'reading' | 'done' | 'archived';
export type ItemPriority = 'high' | 'medium' | 'low';
export interface ItemDTO {
id: string;
title: string;
url: string;
source?: string;
author?: string;
published_at?: string;
content_text?: string;
content_html?: string;
content_status: 'pending' | 'ready' | 'failed';
status: ItemStatus;
priority: ItemPriority;
due_at?: string;
remind_at?: string;
reminded_at?: string;
reading_progress: number;
created_at: string;
updated_at: string;
completed_at?: string;
archived_at?: string;
tags: string[];
}
export interface ItemFilterDTO {
keyword?: string;
statuses?: ItemStatus[];
priorities?: ItemPriority[];
due_from?: string;
due_to?: string;
tags?: string[];
only_overdue?: boolean;
}统一返回结构。
{
"code": "409_CONFLICT_STATUS_TRANSITION",
"message": "状态迁移不允许",
"request_id": "a8f6c5c1-5f31-4c90-84f8-59dbab6d48f1",
"retryable": false
}TaskList负责展示、筛选、排序和批量操作入口。
ArticleReader负责正文展示、进度更新和完成动作。
AddLinkDialog负责输入校验和提交流程。
FilterBar负责条件组合与快速筛选。
SettingsPage负责提醒、默认优先级和导出配置。
状态管理采用Zustand,分成itemStore和settingsStore两个仓库。网络层通过tauriCommands.ts统一调用。
db.rs负责连接池、迁移、事务边界。
commands.rs负责参数校验和命令路由。
article_fetcher.rs负责下载、正文提取、内容清洗。
reminders.rs负责提醒事件调度和通知触发。
export.rs负责多格式导出。
tray.rs负责菜单栏交互与窗口控制。
抓取流程分四步。第一步获取原始HTML,超时上限8秒。第二步提取<title>和站点信息。第三步执行正文抽取,优先Readability规则。第四步清洗脚本和样式标签,生成content_text和content_html。
抓取失败分级处理。网络失败记为503_FETCH_FAILED。解析失败记为422_PARSE_FAILED。任意失败都不阻断任务创建。系统至少保留URL和可得标题。
调度器采用单进程内循环,每60秒执行一次。每轮读取reminder_events中status=pending且remind_at<=now的记录,按时间升序触发通知。触发成功写triggered与triggered_at。触发失败写failed并增加retry_count。重试上限3次,指数回退间隔为1分钟、3分钟、9分钟。
应用启动时执行一次补偿任务,处理离线期间错过的提醒。若提醒对应Item状态已经是done或archived,直接把事件改为canceled。
默认排序使用四级权重。第一层按是否逾期降序。第二层按due_at升序。第三层按优先级high > medium > low。第四层按created_at降序。
关键词搜索首版只覆盖标题和URL,使用不区分大小写匹配。全文搜索不进入MVP。
导出支持三种格式。Markdown用于阅读与归档。JSON用于程序化迁移。CSV用于表格分析。
导出文件命名规则为learnflow_export_YYYYMMDD_HHMMSS.<ext>。导出范围支持全部数据和筛选后数据两种。导出内容必须包含任务元数据与标签,正文字段在用户勾选后写入,默认包含正文。
| 编号 | 项目 | 目标 |
|---|---|---|
| NFR-01 | 启动时长 | 冷启动到可操作界面P95不超过2秒 |
| NFR-02 | 列表性能 | 5000条任务下滚动FPS不低于50 |
| NFR-03 | 存储可靠性 | 关键写操作使用事务,崩溃后数据库完整 |
| NFR-04 | 可观测性 | 关键路径写入结构化日志并带request_id |
| NFR-05 | 隐私安全 | 全部数据本地存储,默认不出网同步 |
| NFR-06 | 可维护性 | 后端模块单元测试覆盖率不低于70% |
应用不上传用户正文到云端。抓取行为仅在用户主动添加URL后发生。导出文件写入前弹出目标路径确认。删除操作需要二次确认。提供本地数据库路径展示,方便用户备份。
测试分四层执行。
第一层是单元测试,覆盖URL校验、状态迁移、排序规则、提醒计算。
第二层是数据库集成测试,覆盖迁移脚本、索引、事务回滚。
第三层是命令接口测试,覆盖主要命令的成功与异常分支。
第四层是端到端测试,覆盖添加、提醒、阅读完成、导出主流程。
关键回归用例如下。
- 添加非法URL应直接失败。
- 解析失败仍可创建任务。
- 已完成任务不会再次提醒。
- 状态迁移违规返回409。
- 导出三种格式字段一致。
交付项包括Tauri2工程、SQLite迁移、基础命令可调用、任务列表可渲染。完成标准是可本地运行并完成最小增删改查。
交付项包括添加链接、状态管理、优先级、截止时间、排序筛选。完成标准是UC-01、UC-03、UC-06通过。
交付项包括正文抓取、失败兜底、阅读页展示、进度记录。完成标准是UC-02、UC-05通过。
交付项包括提醒创建、调度循环、通知点击回跳、补偿扫描。完成标准是UC-04通过。
交付项包括Markdown、JSON、CSV导出和验收测试报告。完成标准是全部P0需求通过,且NFR核心指标达标。
| 验收ID | 对应需求 | 用例 | 通过标准 |
|---|---|---|---|
| AC-01 | FR-01 FR-02 FR-03 | UC-01 | 输入合法URL后任务成功入库并可查询 |
| AC-02 | FR-04 FR-05 | UC-02 | 抓取成功写正文,失败不阻断任务创建 |
| AC-03 | FR-08 FR-09 | UC-04 | 到时通知触发并记录triggered_at |
| AC-04 | FR-06 FR-12 | UC-05 | 标记完成后状态正确且提醒取消 |
| AC-05 | FR-10 FR-15 | UC-06 | 筛选排序结果与规则一致 |
| AC-06 | FR-14 | UC-07 | 三种格式导出成功且字段完整 |
风险1是网页解析稳定性不足。应对策略是分级失败兜底和可重试抓取。
风险2是需求膨胀拖慢MVP。应对策略是只接受能直接提升读完率的新增需求。
风险3是提醒漏触达。应对策略是启动补偿扫描和失败重试。
风险4是误删数据。应对策略是二次确认、导出入口前置、删除行为审计。
当前仍有四个待确认点。第一,默认是否允许重复任务。第二,阅读进度是否需要手动校正。第三,导出是否默认包含正文HTML。第四,菜单栏窗口是否支持直接编辑任务。
若以上事项在实现前未明确,按本Spec默认值执行并在变更日志记录。
任何需求变更都需要补充三项内容。第一项是影响范围。第二项是是否影响P0。第三项是是否影响里程碑时间。变更审批通过后更新文档版本号并记录日期。