# 架构决策记录(ADR) 本文件记录个人看板工具 MVP 的关键技术决策。每条包含:背景、结论、被否决的方案及原因。 --- ## ADR-001 · 看板列固定为三列,不支持配置 **背景** 初版设计在设置页提供「列配置」,但数据模型中 `status` 为固定枚举 `todo | doing | done`,两者互相矛盾。 **结论** MVP 固定三列:`todo`(待办)/ `doing`(进行中)/ `done`(已完成)。`status` 使用 TypeScript 字面量联合类型,**不使用 `enum`**(同时满足 `erasableSyntaxOnly` 编译约束)。 **被否决的方案** - *引入 `columns` 表 + `task.columnId`*:需要额外的列 CRUD、列删除时的任务迁移、列顺序持久化。对单人任务管理而言,收益为零,成本约为原方案的两倍。 - *用 `enum` 定义列*:与 `erasableSyntaxOnly: true` 冲突,且 `enum` 在纯类型场景下没有优势。 **影响** 若未来确需自定义列,需一次性迁移 `status` → `columnId`,并在 `schemaVersion` 中登记。 --- ## ADR-002 · 存储采用 `TaskRepository` 接口 + localStorage 实现 **背景** 初版方案直接选用 Dexie(IndexedDB)。但个人看板在 200 个任务规模下,数据量约 60KB,远低于 localStorage 5MB 上限。 **结论** 定义 `TaskRepository` 接口(`list / get / create / update / remove / replaceAll`),MVP 只提供 localStorage 实现。UI 与 Store 仅依赖接口,不感知具体实现。 **被否决的方案** - *直接用 Dexie*:收益是索引查询与大容量,在该规模下用不上;代价是 Pinia 与 DB 双份状态、异步竞态、多标签页同步、测试需引入 `fake-indexeddb`,约多花 1.5 人天。 - *直接在组件里读写 localStorage*:数据访问散落各处,无法测试,也无法替换实现。 **影响** 数据量增长到万级或需要复杂查询时,新增一个 Dexie 实现并切换注入即可,接口不变。 --- ## ADR-003 · `dueDate` 只表示日期,不表示时刻 **背景** `dueDate` 若同时承载日期与时刻,需要处理时区转换(本机为 Asia/Hong_Kong),并会在「今天到期」的判定上产生歧义。 **结论** `dueDate` 类型为 `string | null`,格式固定 `YYYY-MM-DD`,不含时刻与时区信息。「是否逾期」以本地日历日比较得出。 **被否决的方案** - *存储 ISO 8601 完整时间戳*:需要引入时区处理,且个人看板不需要精确到分钟的截止时间。 - *存储 `Date` 对象*:JSON 序列化后仍会退化为字符串,且容易在反序列化时引入时区偏移。 **影响** 若未来需要提醒功能,应新增独立字段(如 `remindAt`),而不是改变 `dueDate` 的语义。 --- ## ADR-004 · 不引入 vue-router > **状态:已被 ADR-005 取代。** 本决策在 MVP 阶段成立;当「今日视图」真的落地后, > 它预判的触发条件("若未来增加今日视图、统计等真正的独立页面")已经满足。 **背景** 初版信息架构包含「主看板页」「任务详情」「设置页」三个页面。 **结论** MVP 为单视图应用:主看板常驻,任务详情使用 Drawer,导入导出与快捷键说明使用 Modal。不引入 `vue-router`。 **被否决的方案** - *引入 vue-router*:三个「页面」中有两个本质是覆盖层(overlay),用路由表达会让 URL 与状态同步复杂化,且多一个运行时依赖。 - *用 URL query 承载筛选状态*:MVP 不做分享与深链需求,暂不需要。 **影响** 若未来增加「今日视图」「统计」等真正的独立页面,再引入路由;届时筛选状态可一并迁移到 query。 --- ## ADR-005 · 引入 vue-router,拆分看板与今日两个视图 **背景** ADR-004 决定 MVP 不引入路由,并注明「若未来增加今日视图、统计等真正的独立页面,再引入路由」。 现在补做了「今日视图 + 简单统计」,触发条件成立。 「今日视图」与看板的关系值得说清楚:它不是看板的另一种筛选,而是**换了一个组织维度** —— 看板按「状态」组织(待办/进行中/已完成),今日视图按「紧急度」组织(逾期 → 今天到期 → 进行中)。 同一批数据,两种视角,用户需要在它们之间来回切换,而不是叠加在同一屏里。 **结论** 引入 `vue-router`,两条路由: - `/` — 看板 - `/today` — 今日视图(含统计卡片与按紧急度排序的待办列表) 使用 `createWebHistory`(而非 hash),并导出 `createAppRouter(history)` 工厂函数而非单例, 以便测试注入 memory history。 **被否决的方案** - *把今日视图做成一个 Drawer / Modal*:Drawer 适合「聚焦单条记录」(任务详情、备份面板), 不适合承载一个完整的列表视图;而且它无法被收藏或分享,PWA 的 manifest shortcuts 也无处可指。 - *在工具栏加一个「今日」筛选按钮*:那只是筛选,不是视图。用户仍然面对三列结构, 还得自己在心里把「逾期」和「进行中」拼起来。 - *用 hash 路由避免服务器配置*:URL 会变成 `/#/today`,不美观,且 PWA 的 `start_url` 与 shortcuts 指向带 hash 的地址会让某些平台的安装体验变差。代价是部署时必须配置 SPA 回落 —— 这一点已在 README 的部署章节明确写出。 **影响** - **部署要求变化**:服务器必须把未知路径回落到 `index.html`,否则直接访问 `/today` 会 404。 README 已给出 Nginx 与 Netlify 的配置。 - 今日视图按路由懒加载,不进入首屏包(独立 chunk 约 4KB)。 - Service Worker 的 `navigateFallback` 设为 `index.html`,离线时深链 `/today` 同样可用。 - 看板原有的筛选状态仍留在 Pinia 中,未迁移到 query —— 目前没有分享带筛选链接的需求, 迁移只会增加复杂度。