Files
Kanban/docs/tasks.json
T
chenshoulin 4e064e82ea feat: 个人轻量看板工具 MVP
从零实现一个 local-first 的个人任务看板,数据只存在浏览器本地,不依赖后端。

功能
- 固定三列看板(待办 / 进行中 / 已完成),支持拖拽改状态与列内排序
- 快速新增、详情编辑、删除撤销、已完成归档
- 搜索与筛选(关键词 / 优先级 / 标签 / 今日到期 / 逾期)
- 今日视图:按紧急度分组(逾期 > 今天到期 > 进行中)+ 统计卡片
- 导出导入 JSON;启动时自动留快照,保留最近 3 份
- 键盘快捷键(N / 「/」/ ? / Esc)
- PWA:可安装、离线可用、更新提示

技术要点
- 持久化采用 TaskRepository 端口 + 整份快照读写,schemaVersion 迁移表
  显式登记升级规则(当前 v2,v1 -> v2 为归档功能补 archivedAt)
- 同列排序用相邻中点插入,间距不足时按列 normalize,避免浮点精度耗尽
- 拖拽只消费 Sortable 的事件,store 保持唯一数据源,不接管数据
- 数据损坏时先备份到 *.corrupt.<时间戳> 再报错,不覆盖用户数据
- 引入 vue-router 拆分看板与今日两个视图(见 docs/adr.md ADR-005)

验证
- 单元测试 437 个(33 个文件),行覆盖率 96.8%
- 端到端测试 23 个(Playwright),含真实拖拽与断网离线场景
- npm run build 通过,产物可静态托管

注:部署时需配置 SPA 回落,否则直接访问 /today 会 404(详见 README)。
2026-10-18 11:59:49 +08:00

403 lines
17 KiB
JSON
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.
{
"schemaVersion": 1,
"exportedAt": "2026-10-18T11:45:00+08:00",
"tasks": [
{
"id": "t-01",
"title": "T01 工程初始化与规范",
"description": "Vite + Vue3 + TS 初始化;接入 Tailwind v4(@tailwindcss/vite)、Pinia、Vitest;配置 ESLint + Prettier;统一目录结构。\n\n依赖:无\n验收:npm run dev / lint / format / test 均可用;Tailwind 原子类生效;目录为 src/{components,composables,stores,types,utils,views}。",
"status": "done",
"priority": 3,
"tags": [
"M0-基础"
],
"dueDate": null,
"sortOrder": 1024,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:20:00+08:00",
"completedAt": "2026-10-18T10:20:00+08:00"
},
{
"id": "t-02",
"title": "T02 技术决策记录(ADR)",
"description": "将四项决策写入 docs/adr.md:固定三列、TaskRepository + localStorage、dueDate 仅存日期、不引入 vue-router。\n\n依赖:无\n验收:覆盖 4 项决策,每项写明否决了什么及原因。",
"status": "done",
"priority": 3,
"tags": [
"M0-基础"
],
"dueDate": null,
"sortOrder": 2048,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:20:00+08:00",
"completedAt": "2026-10-18T10:20:00+08:00"
},
{
"id": "t-03",
"title": "T03 领域类型与常量",
"description": "定义 Task / ColumnId / Priority 类型与列定义常量。\n\n依赖:T02\n验收:status 为字面量联合类型而非 enum;dueDate 为 YYYY-MM-DD 字符串或 null;列常量为只读;无 any 且 vue-tsc 通过。",
"status": "done",
"priority": 3,
"tags": [
"M1-数据层"
],
"dueDate": null,
"sortOrder": 3072,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:20:00+08:00",
"completedAt": "2026-10-18T10:20:00+08:00"
},
{
"id": "t-04",
"title": "T04 TaskRepository 接口 + localStorage 实现",
"description": "定义 TaskRepository 接口并提供 localStorage 实现,含 schemaVersion 与迁移入口。\n\n实现修订:接口最终定为 load()/save(tasks) 的整份快照读写,而非细粒度 CRUD(数据量小,整份写入无不一致中间态)。\n\n依赖:T03\n验收:接口与实现分离;含 schemaVersion;过高版本明确报错;隐私模式不白屏;写入超限抛 StorageWriteError;数据损坏先备份到 *.corrupt.<时间戳> 再报错。",
"status": "done",
"priority": 3,
"tags": [
"M1-数据层"
],
"dueDate": null,
"sortOrder": 4096,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:15:00+08:00",
"completedAt": "2026-10-18T10:15:00+08:00"
},
{
"id": "t-05",
"title": "T05 排序算法 sortOrder + normalize",
"description": "相邻中点插入;间距小于 1e-6 时对该列 normalize 重排(间隔 1024)。\n\n依赖:T03\n验收:任意两卡之间插入顺序正确;连续 100 次中间插入不崩坏;normalize 后严格递增且间距均匀;纯函数可独立单测。",
"status": "done",
"priority": 3,
"tags": [
"M1-数据层"
],
"dueDate": null,
"sortOrder": 5120,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:15:00+08:00",
"completedAt": "2026-10-18T10:15:00+08:00"
},
{
"id": "t-06",
"title": "T06 Pinia store + 多标签页同步",
"description": "Store 作为内存态唯一来源,读写走 repository;BroadcastChannel 广播变更保持多标签页一致。\n\n依赖:T04、T05\n验收:标签页 A 修改后 B 在 1 秒内反映;跨列拖拽在一次事务内同时更新 status 与 sortOrder;无「已换列但顺序仍旧」中间态。",
"status": "done",
"priority": 3,
"tags": [
"M1-数据层"
],
"dueDate": null,
"sortOrder": 6144,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:15:00+08:00",
"completedAt": "2026-10-18T10:15:00+08:00"
},
{
"id": "t-07",
"title": "T07 看板三列骨架与空状态",
"description": "三列布局(待办/进行中/已完成),响应式,每列含标题、计数、空状态。\n\n依赖:T06\n验收:≥1024px 三列并排、<768px 纵向堆叠;列内容区独立滚动不抖动。",
"status": "done",
"priority": 3,
"tags": [
"M2-UI"
],
"dueDate": null,
"sortOrder": 7168,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:22:00+08:00",
"completedAt": "2026-10-18T10:22:00+08:00"
},
{
"id": "t-08",
"title": "T08 TaskCard 任务卡片组件",
"description": "展示标题、优先级、标签、截止日期;逾期与已完成有区分样式。\n\n依赖:T07\n验收:三种优先级可区分;逾期显示警示色;已完成置灰;标签超 3 个折叠为 +N;长标题截断。",
"status": "done",
"priority": 3,
"tags": [
"M2-UI"
],
"dueDate": null,
"sortOrder": 8192,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:22:00+08:00",
"completedAt": "2026-10-18T10:22:00+08:00"
},
{
"id": "t-09",
"title": "T09 UI 基础组件(Drawer/Toast/DatePicker/TagInput)",
"description": "自建或基于 headless 库实现。注意 Tailwind 只提供样式不提供行为,本任务是最易被低估的一项。\n\n依赖:T01\n验收:Drawer 支持 ESC、遮罩关闭、焦点陷阱;Toast 自动消失且可手关;DatePicker 输出 YYYY-MM-DD 且可清空;TagInput 支持回车添加/退格删除/去重;均有 aria 属性。",
"status": "done",
"priority": 3,
"tags": [
"M2-UI"
],
"dueDate": null,
"sortOrder": 9216,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:22:00+08:00",
"completedAt": "2026-10-18T10:22:00+08:00"
},
{
"id": "t-10",
"title": "T10 快速新增任务",
"description": "列顶部或全局输入框,回车即建卡,默认进入「待办」。\n\n依赖:T07\n验收:空标题不可提交;提交后清空且保持聚焦可连续录入;全流程 ≤3 秒。",
"status": "done",
"priority": 3,
"tags": [
"M3-业务"
],
"dueDate": null,
"sortOrder": 10240,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:22:00+08:00",
"completedAt": "2026-10-18T10:22:00+08:00"
},
{
"id": "t-11",
"title": "T11 任务详情表单(创建/编辑/校验)",
"description": "创建与编辑复用同一表单,置于 Drawer 内。\n\n依赖:T09、T10\n验收:可编辑标题/描述/优先级/截止日期/标签;标题必填且有内联错误;取消不产生脏数据;保存后看板同步;updatedAt 正确刷新。",
"status": "done",
"priority": 3,
"tags": [
"M3-业务"
],
"dueDate": null,
"sortOrder": 11264,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:28:00+08:00",
"completedAt": "2026-10-18T10:28:00+08:00"
},
{
"id": "t-12",
"title": "T12 删除与撤销",
"description": "删除任务后提供 Toast 撤销入口,保留最近一次删除内容。\n\n依赖:T11\n验收:删除后卡片立即消失;撤销后连同原顺序恢复;撤销窗口结束后真正移除。",
"status": "done",
"priority": 3,
"tags": [
"M3-业务"
],
"dueDate": null,
"sortOrder": 12288,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:28:00+08:00",
"completedAt": "2026-10-18T10:28:00+08:00"
},
{
"id": "t-13",
"title": "T13 拖拽改状态 + 列内排序",
"description": "基于 vue-draggable-plus 实现跨列拖动与列内排序。\n\n依赖:T08、T11\n验收:跨列与列内变更刷新后保持;筛选/搜索激活时禁用列内排序(仅允许跨列改状态);拖拽不触发点击;触摸设备可拖动。",
"status": "done",
"priority": 3,
"tags": [
"M3-业务"
],
"dueDate": null,
"sortOrder": 13312,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:28:00+08:00",
"completedAt": "2026-10-18T10:28:00+08:00"
},
{
"id": "t-14",
"title": "T14 导出/导入 JSON + 启动自动快照",
"description": "一键导出全部任务为 JSON;覆盖式导入;启动时自动快照到独立 key。local-first 下这是唯一的数据安全网,优先级为 P0 不得后置。\n\n依赖:T04\n验收:导出文件名含日期且内容含 schemaVersion;导入前校验结构,非法文件不破坏现有数据;覆盖式导入需二次确认;启动写入快照并保留最近 3 份;导出→清空→导入完全还原。",
"status": "done",
"priority": 3,
"tags": [
"M4-数据安全"
],
"dueDate": null,
"sortOrder": 14336,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:37:00+08:00",
"completedAt": "2026-10-18T10:37:00+08:00"
},
{
"id": "t-15",
"title": "T15 搜索与筛选",
"description": "关键词搜索(标题 + 描述)+ 按优先级/标签筛选。\n\n依赖:T07\n验收:中文搜索用 includes,不引入分词库;输入防抖 ≤300ms;多条件为「与」;列计数反映筛选结果;可一键清空条件。",
"status": "done",
"priority": 2,
"tags": [
"M5-效率"
],
"dueDate": null,
"sortOrder": 15360,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:37:00+08:00",
"completedAt": "2026-10-18T10:37:00+08:00"
},
{
"id": "t-16",
"title": "T16 逾期提示 + 键盘快捷键",
"description": "顶部统计条显示今日到期与逾期数量;提供常用快捷键。\n\n依赖:T10\n验收:顶部显示「今日到期 N / 逾期 N」且可点击筛选;N 聚焦新增、/ 聚焦搜索、Esc 关闭抽屉;输入框内不劫持快捷键;有快捷键帮助提示。",
"status": "done",
"priority": 2,
"tags": [
"M5-效率"
],
"dueDate": null,
"sortOrder": 16384,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:37:00+08:00",
"completedAt": "2026-10-18T10:37:00+08:00"
},
{
"id": "t-17",
"title": "T17 已完成归档 + 长列表性能",
"description": "已完成任务归档 + 长列表性能。\n\n实现修订:归档通过新增 Task.archivedAt 字段实现,因此 SCHEMA_VERSION 从 1 升到 2,并在 MIGRATIONS 表登记 1->2 的迁移(为旧数据补 archivedAt: null)。这是迁移通道第一次被真实使用。\n\n依赖:T13\n验收:可一键归档超过 N 天的已完成任务;归档默认不显示可切换;列内渲染子集时禁用列内排序;归档可撤销;单列 200 张卡片正确渲染。",
"status": "done",
"priority": 2,
"tags": [
"M5-效率"
],
"dueDate": null,
"sortOrder": 17408,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:50:00+08:00",
"completedAt": "2026-10-18T10:50:00+08:00"
},
{
"id": "t-18",
"title": "T18 核心逻辑单元测试",
"description": "核心逻辑单元测试。\n\n实际结果:381 个测试、28 个文件,行覆盖率 96.54%、语句 94.64%,远超原定 80% 目标。已接入 @vitest/coverage-v8(npm run test:coverage)。\n\n依赖:T05、T14",
"status": "done",
"priority": 3,
"tags": [
"M6-质量"
],
"dueDate": null,
"sortOrder": 18432,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T10:50:00+08:00",
"completedAt": "2026-10-18T10:50:00+08:00"
},
{
"id": "t-19",
"title": "T19 E2E 关键路径测试",
"description": "Playwright E2E 关键路径测试。\n\n实现修订:默认驱动系统已安装的 Chrome(channel: chrome),不下载自带 Chromium —— 后者约 197MB,本机对 Playwright CDN 的下载会超时(拉到 10% 断连)。可用 PLAYWRIGHT_CHANNEL 切换为 msedge / chromium。\n\n结果:11 个用例全部通过,覆盖真实拖拽、刷新持久化、导出导入还原、删除撤销、搜索筛选、键盘快捷键。\n\n依赖:T13、T14",
"status": "done",
"priority": 2,
"tags": [
"M6-质量"
],
"dueDate": null,
"sortOrder": 19456,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T11:20:00+08:00",
"completedAt": "2026-10-18T11:20:00+08:00"
},
{
"id": "t-20",
"title": "T20 生产构建与部署",
"description": "生产构建与部署。\n\n结果:npm run build 无错误(62 modules,CSS 19.87 kB / JS 162.09 kB gzip 后 4.78 / 60.11);preview 静态托管验证通过,含深链刷新返回 200;README 写明启动、构建、部署、备份与已知限制。\n\n依赖:T18",
"status": "done",
"priority": 3,
"tags": [
"M6-质量"
],
"dueDate": null,
"sortOrder": 20480,
"createdAt": "2026-10-18T10:08:00+08:00",
"updatedAt": "2026-10-18T11:20:00+08:00",
"completedAt": "2026-10-18T11:20:00+08:00"
},
{
"id": "t-21",
"title": "T21 PWA 图标生成",
"description": "用 Pillow 脚本生成 192/512/maskable/apple-touch 四套图标。\n\n关键点:Pillow 的 ImageDraw 不做 alpha 混合,半透明元素必须画在独立图层再 alpha_composite,否则会直接覆盖背景。\n\n验收:四套图标生成;maskable 内容位于中心 80% 安全区内。",
"status": "done",
"priority": 2,
"tags": [
"M7-增强"
],
"dueDate": null,
"sortOrder": 21504,
"createdAt": "2026-10-18T11:45:00+08:00",
"updatedAt": "2026-10-18T11:45:00+08:00",
"completedAt": "2026-10-18T11:45:00+08:00",
"archivedAt": null
},
{
"id": "t-22",
"title": "T22 接入 vite-plugin-pwa",
"description": "manifest(名称/图标/主题色/shortcuts)+ Workbox 预缓存 + navigateFallback。\n\n验收:构建产出 sw.js 与 manifest.webmanifest;Service Worker 激活并接管页面;断网后看板与深链 /today 均可打开。",
"status": "done",
"priority": 2,
"tags": [
"M7-增强"
],
"dueDate": null,
"sortOrder": 22528,
"createdAt": "2026-10-18T11:45:00+08:00",
"updatedAt": "2026-10-18T11:45:00+08:00",
"completedAt": "2026-10-18T11:45:00+08:00",
"archivedAt": null
},
{
"id": "t-23",
"title": "T23 PWA 更新提示",
"description": "registerType 为 prompt,新版本由用户确认后再生效,不打断正在进行的编辑。\n\n验收:有新版本时底部提示并可立即更新;已可离线使用时提示但不显示更新按钮;可关闭。",
"status": "done",
"priority": 2,
"tags": [
"M7-增强"
],
"dueDate": null,
"sortOrder": 23552,
"createdAt": "2026-10-18T11:45:00+08:00",
"updatedAt": "2026-10-18T11:45:00+08:00",
"completedAt": "2026-10-18T11:45:00+08:00",
"archivedAt": null
},
{
"id": "t-24",
"title": "T24 引入 vue-router 拆分视图",
"description": "按 ADR-005 引入路由,拆出 /(看板) 与 /today(今日),今日视图懒加载。\n\n验收:两条路由可切换并共享同一份数据;未知路径回落到看板;导航项带 aria-current。",
"status": "done",
"priority": 2,
"tags": [
"M7-增强"
],
"dueDate": null,
"sortOrder": 24576,
"createdAt": "2026-10-18T11:45:00+08:00",
"updatedAt": "2026-10-18T11:45:00+08:00",
"completedAt": "2026-10-18T11:45:00+08:00",
"archivedAt": null
},
{
"id": "t-25",
"title": "T25 今日视图",
"description": "按紧急度组织:逾期 > 今天到期 > 进行中,三组互斥,组内按优先级与截止日期排序。\n\n验收:分组互斥且已完成/已归档不出现;可直接勾选完成;空状态提示。",
"status": "done",
"priority": 2,
"tags": [
"M7-增强"
],
"dueDate": null,
"sortOrder": 25600,
"createdAt": "2026-10-18T11:45:00+08:00",
"updatedAt": "2026-10-18T11:45:00+08:00",
"completedAt": "2026-10-18T11:45:00+08:00",
"archivedAt": null
},
{
"id": "t-26",
"title": "T26 简单统计",
"description": "今日视图顶部统计卡片:今日待办 / 逾期 / 今天到期 / 本周完成(周一为一周之始)。\n\n验收:跨周边界正确;已完成任务不计入逾期与今天到期;归档任务仍计入完成数。",
"status": "done",
"priority": 2,
"tags": [
"M7-增强"
],
"dueDate": null,
"sortOrder": 26624,
"createdAt": "2026-10-18T11:45:00+08:00",
"updatedAt": "2026-10-18T11:45:00+08:00",
"completedAt": "2026-10-18T11:45:00+08:00",
"archivedAt": null
}
]
}