来源:
notes/documents/网页版笔记身份改用_Note_ID.md网页版笔记身份改用 Note ID
Status: needs-triage Type: spec
上游依据:本轮对话结论(2026-09-22);web/CONTEXT.md 的 Note Path 词条;
web/docs/adr/0005(v1 只读笔记)、0008(构建期产物)、0010(查询参数路由);
.scratch/notes-sqlite-feishu-rearchitecture/spec.md 的 user story 4(「路径不作为业务身份」)。
Problem Statement
工作区已经有一等身份,网页版还在按路径认人。
| 事实 | 数字(2026-09-22 实测,含本 spec 自身) |
|---|---|
notes/ 下的 Markdown |
161 篇 |
构建产物 web/generated/notes.json |
158 篇(上次构建的快照,缺今天新增的 3 篇) |
SQLite notes 表已注册 |
112 条,note_id 与 front matter 逐条一致 |
front matter 有 note_id 的文件 |
114 篇 |
有 note_id 但没进注册表 |
2 篇(06_dg板卡X5_BPU推理服务_开放词表检测YOLO-World接入.md、02_dg板卡X5_BPU推理服务_可视化HTML.md) |
完全没有 note_id |
47 篇:sources/ 25、books/ 11、topics/ 6、projects/ 4、knowledge_base.md 1 |
note_path_aliases(旧路径 → note_id) |
71 条 |
| 飞书日历的笔记线 | 事件描述里写的已经是「笔记ID」,即 note_id |
缺口是整齐的:只要有 front matter,就一定带 note_id(0 例外);那 47 篇是「整个文件没有 front
matter」,全部落在 sources/、books/、topics/、projects/ 这些迁移前历史目录与来源全文里,
其中 18 篇本身就是 README.md。 换句话说,缺口不是「注册漏了」,而是这些文件本来就没进过正式
笔记这条线——Q1 要决定的正是它们算不算「笔记」。
已注册的 112 条里有 5 个不是 note_<16hex> 形态(迁移期手写):
note_2026aug_review_7f3a91c2、note_dg_bpu_html_viz、note_monocloud_cli_20260920、
note_cc_connect_feishu_video_delivery_20260905、note_dg_smolvlm_vlm_20260919。
路径在这条链上被当身份用的地方: 产物里只有 path 字段(parseNoteDocument 已解析出 front
matter,finalizeNote 把它丢了);单篇接口 GET /api/notes/detail?path=;路由 ?n=<path>;
索引与搜索的列表 key、选中态比较;日程模块的笔记深链 notePath。
后果与工作区 spec 早就点到的一样:重命名/移动一篇笔记等于删一条再加一条,身份丢失;网页侧的
身份与 SQLite registry(revision、entity_projection_state)、飞书投影对不上号。
日程是唯一已经把身份准备好的一面:calendar_entries 视图里笔记行的 id 就是 note_id,
note_path 只是给深链用的。所以这次改造不是发明概念,是把网页版对齐到既有身份。
Solution
Note 的身份是 note_id,Note Path 降级为「兜底查找键 + 档号显示值」。
- 产物带上 id。
note-artifact.mjs从 front matter 取note_id,产物条目新增id;path保留。构建期不读 SQLite:ADR-0008 定的是产物只从notes/派生,所以 id 只能来自文件自己的 front matter。 - 未注册的笔记不伪造 id。 没有
note_id的(当前 47 篇),id取path:<path>。这样产物里id永远唯一且稳定,同时把「这不是一条注册笔记」这件事显式带出来。 - 接口只有一个身份入口。
GET /api/notes/detail?id=<note_id>,不再接受?path=。服务端只认 id,就不存在「两个参数谁优先」的规则。旧链接的兼容放在前端:GET /api/notes本来就一次性把全部 笔记元数据交给前端(158 条约 63 KB),路由解析时若?n=不是已知 id,就在这份索引里按 path 查 一次,命中后 canonical 成 id;查不到仍是「产物里没有这篇笔记」。兼容逻辑因此只有前端一处、 删起来是一个 commit,服务端身份保持单一。id 是单段字符串,ADR-0005 里「斜杠路径匹配不上」的约束 顺势消失,但仍走查询参数,避免动api/的路由约定。 - 路由切 id,显示不动。
?n=的语义改为 Note ID;页边 gutter 的档号继续显示path(note_ebcc838e0cde496a放进 72px 页边既读不出也放不下)。身份与标签分离。解析顺序:?n=已是产物里的 id 就直接用;否则在已加载的索引里按 path 解析;都不是才报「产物里没有这篇笔记」。 详情请求只在解析出 id 之后发出——冷启动走旧 path 链接会多等一次索引加载,这个代价只有旧链接付。 - 日程深链改吃 id。
Daily.tsx从event.notePath改成event.id;notePath字段保留做显示,calendar_entries视图不用改。
决定点(需要用户拍板)
- Q1 · 非正式笔记去哪:
sources/的来源全文、README.md、knowledge_base.md这 47 篇, 是继续留在笔记模块(id 用path:兜底),还是从笔记模块移出、只出现在「材料与来源」模块? (web-multimodule-shell的 spec 已经说过要分清「谁写的」,这次是顺势的机会。) - Q2 · 旧链接要不要保: 现有
?n=documents/xx.md书签、以及文件已改名的情况,只有拿到note_path_aliases才能恢复。别名在 SQLite 里,构建期读不到,要保就得给产物引入一份别名数据 (新 ADR,会碰 ADR-0008 的边界)。不保的话,重命名只保证新链接不断。 - Q3 · 档号: 维持「显示 path、身份用 id」(本 spec 的建议),还是干脆显示 id?
- Q4 · 补注册: 47 篇无 id + 2 篇有 id 未注册,是否这一轮补齐(
note_cli.py create --apply --force会沿用已有 id)?还是先只改网页、缺口记账。若补齐,顺带决定 5 个手写 id 是否规范成note_<16hex>(改 id 会让飞书投影按新身份重建,代价要单列)。
非目标
- 不改飞书 Docs / Base / Calendar 三层投影链路,不改
note_cli.py。 - 不让构建期依赖
data/daily_log.sqlite3。 - 不加笔记编辑能力;网页仍是只读视图。
- 不统一
note_dg_bpu_html_viz这种手写 id 的形态(除非 Q4 选「补齐」)。
验收口径
cd codex_personal_assistant/web
npm run build # 产物每条带 id,脚本断言 key 唯一
node --test tests/ # note-artifact / route 测试更新后全绿
npm run preview:local # 本地只读预览
- 产物里每条笔记都有
id;重复 id 让构建失败(而不是线上静默丢一条)。 ?n=note_xxx能打开正文,且/api/notes/detail只接受id=;旧形态?n=documents/xx.md也能 打开(前端在索引里解析 path → id),并把地址栏 canonical 成 id。- 刷新、收藏、浏览器回退仍停在同一篇;从日程模块的笔记线深链进笔记模块是同一篇。
web/CONTEXT.md的 Note Path 词条改写:path 是档号与兜底键,身份是 Note ID。
Tickets
| # | 标题 | 一句话 |
|---|---|---|
| 01 | 产物带 Note ID | note-artifact.mjs 保留 front matter 的 note_id,无 id 用 path: 兜底,唯一性断言 |
| 02 | 详情接口只认 ID | backend/src/lib/notes.ts 与 api/notes/detail.ts:唯一入口 ?id=,去掉 ?path= |
| 03 | 前端路由切 ID + 旧 path 兜底 | route.ts、App.tsx、Notes.tsx:选中态与详情请求按 id;?n= 是旧 path 时先用索引解析成 id;档号仍显示 path |
| 04 | 日程深链改用 Note ID | Daily.tsx 用 event.id,notePath 降为显示字段 |
| 05 | 术语与 ADR | web/CONTEXT.md 词条、web/docs/adr/0005/0008 的修订;是否新增「身份是 ID」的 ADR |
| 06 | 补齐注册与来源分家 | 按 Q1/Q4 的结论处理 47 篇无 id 与 2 篇未注册(可能拆成两票) |
Comments
- 2026-09-22:spec 新建,状态
needs-triage,等用户对 Q1–Q4 拍板后再开 ticket。 - 2026-09-22:详情接口不引入第二个参数。服务端只认
?id=,旧 path 的兼容由前端拿已加载的索引 解析(这一条不扩大 Q2 的别名问题:别名仍缺,重命名过的旧 path 依旧打不开)。