[{"data":1,"prerenderedAt":1328},["ShallowReactive",2],{"\u002F2026-06-13-obsidian":3,"\u002F2026-06-13-obsidian-rel":744},{"id":4,"title":5,"body":6,"column":729,"date":730,"description":12,"extension":731,"hero_image":732,"meta":733,"navigation":269,"path":734,"seo":735,"series_id":732,"severity":732,"stem":736,"summary":737,"tags":738,"__hash__":743},"posts\u002F2026-06-13-微信与Obsidian的智能捕获管线.md","让模型决定分类，但不让它碰文件系统",{"type":7,"value":8,"toc":717},"minimark",[9,13,18,21,29,40,43,65,68,71,108,111,126,130,133,144,147,167,170,173,179,297,309,314,320,323,328,339,342,345,375,378,381,384,387,440,446,452,465,468,471,474,480,555,561,614,619,624,627,630,633,639,650,657,661,664,667,678,681,684,687,707,710,713],[10,11,12],"p",{},"在微信里记笔记，内容要进 Obsidian 知识库，手工转换既慢又容易出错。写一个 Obsidian 插件把格式规则固定化，也无法适应微信和公众号笔记五花八门的样式。我决定把 Claude 加进来：收消息→分类→起标题→格式化→落盘，再回执通知。",[14,15,17],"h2",{"id":16},"核心架构claude-决策node-写盘","核心架构：Claude 决策，Node 写盘",[10,19,20],{},"首先明确职责边界，这是避免混乱的关键。",[10,22,23,24,28],{},"Claude 的工作是",[25,26,27],"strong",{},"纯决策","：给定一条消息和分类表，输出结构化结果——归入哪个分类、建新文档还是追加到现有文档、标题是什么、标签列表、正文格式化后长什么样。结果都用 JSON 表示。",[10,30,31,32,35,36,39],{},"Node 的工作是",[25,33,34],{},"确定性执行","：接收 Claude 的决策结果后，负责所有文件读写——新建文档、追加到已有文档、处理图片、管理去重、并发序列化。",[25,37,38],{},"不让 Claude 直接操盘文件系统","。",[10,41,42],{},"这个分工的好处：",[44,45,46,53,59],"ol",{},[47,48,49,52],"li",{},[25,50,51],{},"风险隔离","：LLM 的输出有概率不确定（分类可能不准），但文件操作必须确定。如果 Claude 直接写盘，分类抖动可能污染已有文档、重复建文件、覆盖历史记录。现在坏决策最多只会建错新文件，不会改坏既有内容。",[47,54,55,58],{},[25,56,57],{},"幂等与并发","：并发来的消息按消息 ID 去重，串行通过写入队列写盘。没有 LLM 的非决定性卡在关键路径上。",[47,60,61,64],{},[25,62,63],{},"可测试性","：Claude 的逻辑用假实现隔离测试，vault-writer 的逻辑用临时目录单测。",[14,66,67],{"id":67},"数据流与核心组件",[10,69,70],{},"单条消息的完整生命周期：",[44,72,73,79,85,91,97],{},[47,74,75,78],{},[25,76,77],{},"listener","：WeChatFerry 监听主号私聊，提取消息ID、文本、图片，构造 Capture 对象。",[47,80,81,84],{},[25,82,83],{},"dedup","：msgId 按已处理集合去重（持久化到 processed.json，防 WCF 重发导致重复落盘）。",[47,86,87,90],{},[25,88,89],{},"classifier","：遍历 vault 列出已有文档按分类分组，把消息文本+分类表描述发给 Claude。Claude 一次性决定归类、action(new|append)、新标题、标签、正文。失败或超时降级为 00-Inbox 原样保存。",[47,92,93,96],{},[25,94,95],{},"vault-writer","：按决策新建或追加文件。图片从临时区移到 vault 附件目录。所有操作走串行队列，天然排斥并发冲突。",[47,98,99,102,103,107],{},[25,100,101],{},"receipt","：回执主号：\"✓ 新建 → ",[104,105,106],"span",{},"01-技术","，标题：Redis 锁续期\"。",[10,109,110],{},"失败处理分两层：",[112,113,114,120],"ul",{},[47,115,116,119],{},[25,117,118],{},"第一层","：classifier 异常→降级到 Inbox 分类，原消息文本作为 body。",[47,121,122,125],{},[25,123,124],{},"第二层","：vault-writer 也失败→回执错误信息，原消息不丢（至少进了 dedup）。",[14,127,129],{"id":128},"claude-分类的提示词设计","Claude 分类的提示词设计",[10,131,132],{},"Claude 的 prompt 包三部分信息：",[134,135,140],"pre",{"className":136,"code":138,"language":139},[137],"language-text","【可选分类】\n- 01-技术: 技术笔记、踩坑、方案、命令、代码片段\n- 02-项目: 具体项目的记录、进展、待办\n...\n- 00-Inbox: 兜底：拿不准归哪类的进这里\n\n【各分类下已有文档标题】\n- 01-技术: Redis 分布式锁 | OTA 增量包大小优化 | Python 装饰器\n- 02-项目: Q2 KPI 指标 | 客户需求评审\n...\n\n【规则】\n1. 只能选上面列出的分类 key\n2. 若与某个\"已有文档标题\"主题相似且确信，用 action=append，targetFile 填该标题；否则 action=new\n3. 拿不准就 00-Inbox + action=new\n4. 只输出一个 ```json 代码块，字段：category, action(new|append), targetFile, title, tags(数组), body\n","text",[141,142,138],"code",{"__ignoreMap":143},"",[10,145,146],{},"关键设计点：",[112,148,149,155,161],{},[47,150,151,154],{},[25,152,153],{},"传已有标题清单","而不是全部文档：vault 大了之后这会撑爆 token，后续优化改向量检索 Top-N 候选。v1 库空，列全量可接受。",[47,156,157,160],{},[25,158,159],{},"action=append 时 targetFile 必须来自该分类的已有标题清单","：确保 append 不会创建新文件。如果 Claude 编造了不存在的标题，validator 改回 action=new。",[47,162,163,166],{},[25,164,165],{},"拿不准就 new","：错建新文件\u003C错污染已有文档。",[14,168,169],{"id":169},"文件写入的不可变原则",[10,171,172],{},"vault-writer 的核心逻辑：",[10,174,175,178],{},[25,176,177],{},"新建","：",[134,180,184],{"className":181,"code":182,"language":183,"meta":143,"style":143},"language-javascript shiki shiki-themes github-light github-dark","路径: \u003Cvault>\u002F\u003Ccategory>\u002F\u003C安全标题>.md\n内容:\n---\ntitle: Redis 分布式锁续期方案\ncategory: 01-技术\ntags: [redis, 分布式锁, 并发]\ncreated: 2026-06-13 14:30\nsource: wechat\n---\n\n\u003CClaude 格式化后的正文>\n\n![[20260613-1430-image.png]]\n","javascript",[141,185,186,217,223,229,235,241,247,253,259,264,271,286,291],{"__ignoreMap":143},[104,187,190,194,198,202,205,208,210,214],{"class":188,"line":189},"line",1,[104,191,193],{"class":192},"sScJk","路径",[104,195,197],{"class":196},"sVt8B",": \u003C",[104,199,201],{"class":200},"s9eBZ","vault",[104,203,204],{"class":196},">\u002F\u003C",[104,206,207],{"class":200},"category",[104,209,204],{"class":196},[104,211,213],{"class":212},"sj4cs","安全标题",[104,215,216],{"class":196},">.md\n",[104,218,220],{"class":188,"line":219},2,[104,221,222],{"class":196},"内容:\n",[104,224,226],{"class":188,"line":225},3,[104,227,228],{"class":196},"---\n",[104,230,232],{"class":188,"line":231},4,[104,233,234],{"class":196},"title: Redis 分布式锁续期方案\n",[104,236,238],{"class":188,"line":237},5,[104,239,240],{"class":196},"category: 01-技术\n",[104,242,244],{"class":188,"line":243},6,[104,245,246],{"class":196},"tags: [redis, 分布式锁, 并发]\n",[104,248,250],{"class":188,"line":249},7,[104,251,252],{"class":196},"created: 2026-06-13 14:30\n",[104,254,256],{"class":188,"line":255},8,[104,257,258],{"class":196},"source: wechat\n",[104,260,262],{"class":188,"line":261},9,[104,263,228],{"class":196},[104,265,267],{"class":188,"line":266},10,[104,268,270],{"emptyLinePlaceholder":269},true,"\n",[104,272,274,277,280,283],{"class":188,"line":273},11,[104,275,276],{"class":196},"\u003C",[104,278,279],{"class":212},"Claude",[104,281,282],{"class":192}," 格式化后的正文",[104,284,285],{"class":196},">\n",[104,287,289],{"class":188,"line":288},12,[104,290,270],{"emptyLinePlaceholder":269},[104,292,294],{"class":188,"line":293},13,[104,295,296],{"class":196},"![[20260613-1430-image.png]]\n",[112,298,299,302],{},[47,300,301],{},"文件名去除 Windows 非法字符、限长 80 字。",[47,303,304,305,308],{},"重名加紧凑时间后缀（",[141,306,307],{},"Redis 锁-1430.md","），不覆盖。",[10,310,311,178],{},[25,312,313],{},"追加",[134,315,318],{"className":316,"code":317,"language":139},[137],"原内容 \u003C不改一字>\n\n## 14:30 追加\n\n\u003C新增内容>\n\n![[img2.png]]\n",[141,319,317],{"__ignoreMap":143},[10,321,322],{},"只在文末加小节，绝不改写旧内容。追加同样走串行队列，多个消息对同一文档的追加不会交错。",[10,324,325,178],{},[25,326,327],{},"图片处理",[10,329,330,331,334,335,338],{},"从临时区移到 ",[141,332,333],{},"\u003Cvault>\u002F99-附件\u002F\u003C紧凑时间戳>-\u003C原文件名>.png","，正文用 wikilink 引用 ",[141,336,337],{},"![[20260613-1430-image.png]]","。Obsidian 自动刷新并识别嵌入。",[14,340,341],{"id":341},"去重与幂等",[10,343,344],{},"WCF 有重发行为，msgId 相同的消息可能来两次。processed.json 记录已处理的 msgId：",[134,346,350],{"className":347,"code":348,"language":349,"meta":143,"style":143},"language-json shiki shiki-themes github-light github-dark","[\"msg_id_1\", \"msg_id_2\", \"msg_id_3\"]\n","json",[141,351,352],{"__ignoreMap":143},[104,353,354,357,361,364,367,369,372],{"class":188,"line":189},[104,355,356],{"class":196},"[",[104,358,360],{"class":359},"sZZnC","\"msg_id_1\"",[104,362,363],{"class":196},", ",[104,365,366],{"class":359},"\"msg_id_2\"",[104,368,363],{"class":196},[104,370,371],{"class":359},"\"msg_id_3\"",[104,373,374],{"class":196},"]\n",[10,376,377],{},"listener 每条消息来时检查，命中过则忽略。标记操作在最后，确保消息全部处理成功才记录。",[10,379,380],{},"这设计下，网络抖动导致的消息重发、甚至小号掉线重连后的历史消息再次推送，都不会重复落盘。",[14,382,383],{"id":383},"中转站密钥隔离",[10,385,386],{},"Claude Code CLI 的调用走 spawn 子进程，环境变量注入：",[134,388,390],{"className":181,"code":389,"language":183,"meta":143,"style":143},"const env = {\n  ANTHROPIC_BASE_URL: config.baseUrl,      \u002F\u002F https:\u002F\u002Fkey.agtk.cn\n  ANTHROPIC_AUTH_TOKEN: config.authToken,  \u002F\u002F 用户自注册的 key\n};\nawait runCommand(nodePath, args, { timeoutMs, env });\n",[141,391,392,407,416,424,429],{"__ignoreMap":143},[104,393,394,398,401,404],{"class":188,"line":189},[104,395,397],{"class":396},"szBVR","const",[104,399,400],{"class":212}," env",[104,402,403],{"class":396}," =",[104,405,406],{"class":196}," {\n",[104,408,409,412],{"class":188,"line":219},[104,410,411],{"class":196},"  ANTHROPIC_BASE_URL: config.baseUrl,      ",[104,413,415],{"class":414},"sJ8bj","\u002F\u002F https:\u002F\u002Fkey.agtk.cn\n",[104,417,418,421],{"class":188,"line":225},[104,419,420],{"class":196},"  ANTHROPIC_AUTH_TOKEN: config.authToken,  ",[104,422,423],{"class":414},"\u002F\u002F 用户自注册的 key\n",[104,425,426],{"class":188,"line":231},[104,427,428],{"class":196},"};\n",[104,430,431,434,437],{"class":188,"line":237},[104,432,433],{"class":396},"await",[104,435,436],{"class":192}," runCommand",[104,438,439],{"class":196},"(nodePath, args, { timeoutMs, env });\n",[10,441,442,445],{},[141,443,444],{},".env"," 文件里存关键数据：",[134,447,450],{"className":448,"code":449,"language":139},[137],"ANTHROPIC_BASE_URL=https:\u002F\u002Fkey.agtk.cn\nANTHROPIC_AUTH_TOKEN=sk_xxxx\n",[141,451,449],{"__ignoreMap":143},[10,453,454,457,458,460,461,464],{},[141,455,456],{},".gitignore"," 排除 ",[141,459,444],{},"，不进代码库。自包含发行包也不带任何密钥——用户在 setup 时从 ",[141,462,463],{},"key.agtk.cn"," 自己取 key 粘贴，由客户自付费用。",[10,466,467],{},"这样做的风险隐含：内容会经第三方中转站再到 Claude（而非直连 claude.ai）。setup 里明确风险告知和确认。",[14,469,470],{"id":470},"配置的参数化",[10,472,473],{},"三个配置文件：",[10,475,476,479],{},[25,477,478],{},"config.yaml","：业务配置",[134,481,485],{"className":482,"code":483,"language":484,"meta":143,"style":143},"language-yaml shiki shiki-themes github-light github-dark","vault_path: \"D:\\\\Obsidian\\\\MyVault\"\nattachments_dir: \"99-附件\"\nowner_wxid: \"wxid_main\"           # 首条私聊自动绑定\nmodel: \"claude-opus-4-8\"          # 用户选择\nrequest_timeout_ms: 30000\n","yaml",[141,486,487,509,519,532,545],{"__ignoreMap":143},[104,488,489,492,495,498,501,504,506],{"class":188,"line":189},[104,490,491],{"class":200},"vault_path",[104,493,494],{"class":196},": ",[104,496,497],{"class":359},"\"D:",[104,499,500],{"class":212},"\\\\",[104,502,503],{"class":359},"Obsidian",[104,505,500],{"class":212},[104,507,508],{"class":359},"MyVault\"\n",[104,510,511,514,516],{"class":188,"line":219},[104,512,513],{"class":200},"attachments_dir",[104,515,494],{"class":196},[104,517,518],{"class":359},"\"99-附件\"\n",[104,520,521,524,526,529],{"class":188,"line":225},[104,522,523],{"class":200},"owner_wxid",[104,525,494],{"class":196},[104,527,528],{"class":359},"\"wxid_main\"",[104,530,531],{"class":414},"           # 首条私聊自动绑定\n",[104,533,534,537,539,542],{"class":188,"line":231},[104,535,536],{"class":200},"model",[104,538,494],{"class":196},[104,540,541],{"class":359},"\"claude-opus-4-8\"",[104,543,544],{"class":414},"          # 用户选择\n",[104,546,547,550,552],{"class":188,"line":237},[104,548,549],{"class":200},"request_timeout_ms",[104,551,494],{"class":196},[104,553,554],{"class":212},"30000\n",[10,556,557,560],{},[25,558,559],{},"categories.yaml","：分类表，改它即改 Claude 的分类依据",[134,562,564],{"className":482,"code":563,"language":484,"meta":143,"style":143},"- key: \"01-技术\"\n  desc: \"技术笔记、踩坑、方案、命令、代码片段\"\n- key: \"02-项目\"\n  desc: \"具体项目的记录、进展、待办\"\n...\n",[141,565,566,579,589,600,609],{"__ignoreMap":143},[104,567,568,571,574,576],{"class":188,"line":189},[104,569,570],{"class":196},"- ",[104,572,573],{"class":200},"key",[104,575,494],{"class":196},[104,577,578],{"class":359},"\"01-技术\"\n",[104,580,581,584,586],{"class":188,"line":219},[104,582,583],{"class":200},"  desc",[104,585,494],{"class":196},[104,587,588],{"class":359},"\"技术笔记、踩坑、方案、命令、代码片段\"\n",[104,590,591,593,595,597],{"class":188,"line":225},[104,592,570],{"class":196},[104,594,573],{"class":200},[104,596,494],{"class":196},[104,598,599],{"class":359},"\"02-项目\"\n",[104,601,602,604,606],{"class":188,"line":231},[104,603,583],{"class":200},[104,605,494],{"class":196},[104,607,608],{"class":359},"\"具体项目的记录、进展、待办\"\n",[104,610,611],{"class":188,"line":237},[104,612,613],{"class":192},"...\n",[10,615,616,618],{},[25,617,444],{},"：敏感信息（密钥、中转站地址）",[134,620,622],{"className":621,"code":449,"language":139},[137],[141,623,449],{"__ignoreMap":143},[10,625,626],{},"三层分离使得：业务逻辑能复用，分类策略能快速迭代（改 YAML 即可），敏感信息不泄露。",[14,628,629],{"id":629},"自包含发行包",[10,631,632],{},"最终产物是一个 zip，无需用户装 Node、不连公共 npm 源：",[134,634,637],{"className":635,"code":636,"language":139},[137],"obsidian-weix-0.1.0.zip\n├─ node\\node.exe              （便携 Node）\n├─ app\\\n│  ├─ src\\                      （核心代码）\n│  ├─ node_modules\\            （预装依赖）\n│  └─ templates\\               （配置模板）\n├─ install.bat\n└─ start.bat\n",[141,638,636],{"__ignoreMap":143},[10,640,641,642,645,646,649],{},"用户下载、解压、双击 ",[141,643,644],{},"install.bat","→setup 向导→粘贴 key→输入 vault 路径→风险确认→生成配置。然后双击 ",[141,647,648],{},"start.bat"," 启动。",[10,651,652,653,656],{},"构建机在 Windows x64 编译，执行 ",[141,654,655],{},"npm install","（得到目标平台的 native 产物），打包后上传到项目专用 CDN 分发。",[14,658,660],{"id":659},"为什么不是-obsidian-插件","为什么不是 Obsidian 插件",[10,662,663],{},"Obsidian 插件跑在浏览器沙盒里，无法直接调 WCF、spawn CLI、管理进程生命周期。插件能做的是提供 UI 让用户手工输入内容，或者轮询本地文件监听外部写入。",[10,665,666],{},"相比之下，独立 Node 进程的好处：",[44,668,669,672,675],{},[47,670,671],{},"常驻内存，实时监听 WCF 消息事件。",[47,673,674],{},"能 spawn Claude Code CLI 并注入中转站密钥（需要环境变量隔离）。",[47,676,677],{},"确定性串行写盘，无沙盒限制。",[10,679,680],{},"代价是多了一个进程，但换来的是解耦和可靠性。",[14,682,683],{"id":683},"工程检查清单",[10,685,686],{},"这套管线从设计到代码，需要覆盖的点：",[112,688,689,692,695,698,701,704],{},[47,690,691],{},"单元测试：文件名净化、时间格式化、Markdown 渲染、去重、分类降级、append 格式。",[47,693,694],{},"集成测试：classifier 对中转站打桩；vault-writer 写临时目录校验产物；并发投递验证队列串行。",[47,696,697],{},"幂等性：msgId 去重持久化，重发消息不重建文件。",[47,699,700],{},"并发安全：写入队列保证串行，追加操作不交错。",[47,702,703],{},"失败降级：Claude 超时 30s 后重试 1 次，仍失败降级 Inbox；vault-writer 失败也降级。",[47,705,706],{},"脱敏：密钥进 .env 不进代码库，配置项参数化。",[10,708,709],{},"写盘前必须问清自己：如果这条消息落地失败了，会不会丢掉？不会——最坏情况进 00-Inbox 原文保存。如果 Claude 分类抖动，会改坏其他文档吗？不会——append 时校验 targetFile 存在性，不存在改成 new。",[10,711,712],{},"这些约束叠加起来，保证了管线的韧性。",[714,715,716],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":143,"searchDepth":219,"depth":219,"links":718},[719,720,721,722,723,724,725,726,727,728],{"id":16,"depth":219,"text":17},{"id":67,"depth":219,"text":67},{"id":128,"depth":219,"text":129},{"id":169,"depth":219,"text":169},{"id":341,"depth":219,"text":341},{"id":383,"depth":219,"text":383},{"id":470,"depth":219,"text":470},{"id":629,"depth":219,"text":629},{"id":659,"depth":219,"text":660},{"id":683,"depth":219,"text":683},"工程手记","2026-06-13","md",null,{},"\u002F2026-06-13-obsidian",{"title":5,"description":12},"2026-06-13-微信与Obsidian的智能捕获管线","设计一套通过微信私聊向 Claude 发送内容，自动分类、起标题、格式化后落入本机 Obsidian 的管线，核心是让 Claude 只做决策，Node 确定性写盘。",[503,279,739,740,741,742],"Node.js","微信","知识管理","流水线","etSXBPunJdcpyR1gjx9AvLgkzRosbGDmUbZj8-W7kkY",[745,1050,1194],{"id":746,"title":747,"body":748,"column":729,"date":1038,"description":752,"extension":731,"hero_image":732,"meta":1039,"navigation":269,"path":1040,"seo":1041,"series_id":732,"severity":732,"stem":1042,"summary":1043,"tags":1044,"__hash__":1049},"posts\u002F2026-07-29-一年八千次提交AI辅助开发工作流.md","一年八千次提交，我是怎么干的",{"type":7,"value":749,"toc":1021},[750,753,756,759,764,767,770,773,777,780,783,797,800,804,807,810,813,817,820,823,843,846,850,853,864,867,871,874,888,891,894,898,901,904,908,911,914,917,921,924,927,951,958,961,967,970,973,976,983,986,989,1015,1018],[10,751,752],{},"2026 上半年，主要项目累计提交 7787 次（其中 newCodex 因为是 fork 项目，1922 次提交含上游历史；纯新增代码的项目是 yun-claude、yun-claw、new-openclaw 等）。提交数看起来多，但每个提交背后的价值不在数量，而在流程的可重复性。这篇文章把工作流写清楚。",[14,754,755],{"id":755},"流程的六个环节",[10,757,758],{},"整个开发周期分六步：需求澄清 → 设计文档审阅 → 拆实施计划 → 分阶段实现 → 代码审查 → 故障归档。每一步都有明确的输入输出和验证方式。",[760,761,763],"h3",{"id":762},"_1-需求澄清","1. 需求澄清",[10,765,766],{},"开始前问清楚，不要猜。典型问题：这个功能要处理哪些用户场景？边界条件是什么？现有系统的哪部分会受影响？",[10,768,769],{},"这一步的产出是一份结构化的需求文档，包括功能范围、约束条件、风险假设。不是长篇幅的铺垫，而是一份清单式的澄清记录。",[10,771,772],{},"在 new-openclaw 项目里，这一步通常是用 Claude 的 superpowers:brainstorming skill 来展开。提问方式很重要：我会列出已知条件，然后问\"这个设计下会遇到什么问题？\"而不是\"你觉得应该怎么做？\"让 AI 在我的约束框架内工作。",[760,774,776],{"id":775},"_2-设计文档与人工审阅","2. 设计文档与人工审阅",[10,778,779],{},"这是整个流程里最关键的检查点。花一小时审一份 200 行的设计文档，比花五小时审一份 2000 行的代码便宜得多。而且回头修改设计的成本远低于修改实现。",[10,781,782],{},"new-openclaw 项目现有 30 份设计文档（specs 目录），每份文档都包括：",[112,784,785,788,791,794],{},[47,786,787],{},"范围：这个设计覆盖什么、不覆盖什么",[47,789,790],{},"决策与权衡：为什么选这个方案，放弃了什么",[47,792,793],{},"接口契约：如果涉及多个模块，清晰定义每个边界",[47,795,796],{},"风险清单：已知的坑和防护措施",[10,798,799],{},"写完设计文档以后，我会读一遍、问几个\"为什么\"，然后提出修改意见。这一步排除了 80% 的方向错误。常见的修改方向有：缩小范围（第一个版本不用处理那么多边界情况）、明确约束（系统资源、网络延迟、并发数的假设）、补充防护（熔断、限流、幂等）。",[760,801,803],{"id":802},"_3-实施计划与-dod-定义","3. 实施计划与 DoD 定义",[10,805,806],{},"设计文档定下来以后，拆成实施计划。计划的粒度是\"一个可验证的功能单元\"——通常是一个小时到半天的工作量，完成后能单独验证成功。",[10,808,809],{},"计划文档里必须写清完成定义（DoD，Definition of Done）。不是\"实现登录功能\"，而是\"写出能拒绝无效格式的登录端点、覆盖单点故障下的重试、有端到端的冒烟测试\"。",[10,811,812],{},"new-openclaw 项目现有 36 份实施计划（plans 目录），跨度从一周的 S0 阶段（骨架 + Mock 后台）到数周的 S1 阶段（真实业务后台）。每份计划都带着清晰的 checklist，这样我在执行时能随时问 Claude：\"下一步应该是什么？\"而不是脑子里模糊地记着进度。",[760,814,816],{"id":815},"_4-分阶段实现与逐步验证","4. 分阶段实现与逐步验证",[10,818,819],{},"有了计划以后，按顺序实现。关键是每一步都要有验证：单元测试、集成测试、或者一个小的 end-to-end 冒烟测试。验证不通过就停在这一步，不往下推。",[10,821,822],{},"这一步会用到三个代理：",[112,824,825,831,837],{},[47,826,827,830],{},[25,828,829],{},"superpowers:test-driven-development"," —— 先写测试，再写实现",[47,832,833,836],{},[25,834,835],{},"superpowers:subagent-driven-development"," —— 复杂任务拆成独立的子任务，并行推进",[47,838,839,842],{},[25,840,841],{},"superpowers:systematic-debugging"," —— 遇到测试失败，用这个代理追根溯源，不要盲目修改代码",[10,844,845],{},"实施过程中如果发现设计假设错了（比如某个接口响应时间远超预期，或者并发场景下出现竞态条件），就停下来回到第 2 步重新审视设计，而不是继续往下推。",[760,847,849],{"id":848},"_5-代码审查","5. 代码审查",[10,851,852],{},"实现完成以后，不是立即合并，而是过一遍 code-reviewer 代理。审查的重点不在代码风格（那个自动工具做），而在：",[112,854,855,858,861],{},[47,856,857],{},"这段代码实现的是设计文档里的哪一部分？偏离了吗？",[47,859,860],{},"错误处理有没有遗漏？边界情况有没有考虑？",[47,862,863],{},"有没有意外改动无关的代码？",[10,865,866],{},"审查通常能抓住两类问题。一类是逻辑问题：某个条件判断漏了一个分支，或者并发场景下两个操作的顺序反了。另一类是\"设计和实现对不上\"：实现了一个设计里没提到的特性，或者某个约束（比如\"这个值不能为空\"）没在代码里强制。",[760,868,870],{"id":869},"_6-故障归档","6. 故障归档",[10,872,873],{},"系统上线以后，bug 是难免的。重要的是怎么处理它。new-openclaw 项目有一套 bug 知识库规范（CLAUDE.md 里定义），每个 bug 修好以后都要新建一份独立文档，包括：",[112,875,876,879,882,885],{},[47,877,878],{},"现象和复现路径",[47,880,881],{},"根因分析：为什么会发生，触发链路是什么",[47,883,884],{},"修复方法：改了什么，为什么选这个方案",[47,886,887],{},"预防措施：代码改进、测试添加、还是构建期检查",[10,889,890],{},"80 份 bug 文档（截至 5 月中旬）不是问题的多，而是追根溯源的记录的多。下次遇到类似现象，能直接查库而不是重新排查。",[14,892,893],{"id":893},"三条铁律",[760,895,897],{"id":896},"_1-假设必须显式声明","1. 假设必须显式声明",[10,899,900],{},"不要猜。不清楚的地方就问，把问题写成澄清清单。\"这个 API 能处理多大的请求体？\"、\"离线场景下要缓存多长时间？\"、\"错误重试间隔是指数退避还是固定时间？\"。",[10,902,903],{},"这些问题看起来小，但决定了实现的复杂度和测试用例的多少。猜错了会导致前期设计精美，但方向错误，后面要推倒重来。",[760,905,907],{"id":906},"_2-修改必须精确","2. 修改必须精确",[10,909,910],{},"只改需要改的部分。这听起来像常识，但在实际工作中容易出现\"顺手改一下边上的代码\"的情况 —— 格式不规范了，变量命名不一致了，某个函数太长了，\"顺便\"重构一下。",[10,912,913],{},"结果是一个改动影响了五个文件，代码审查花了双倍时间，引入了新 bug 的风险。",[10,915,916],{},"规则是：改动必须对应需求的某一行。格式、风格、无关的重构，单独立项，不要混在功能改动里。",[760,918,920],{"id":919},"_3-成功标准必须可验证","3. 成功标准必须可验证",[10,922,923],{},"\"添加验证\"这个说法是模糊的。改写成：\"写出一个测试用例，输入非法邮箱格式，验证 API 返回 400；输入合法邮箱，验证返回 200 和预期数据结构\"。",[10,925,926],{},"每个 plan 文档里的 DoD 都是这样写的。拿一个阶段做例子：",[928,929,930,933],"blockquote",{},[10,931,932],{},"S0 阶段的 DoD：",[44,934,935,938,945,948],{},[47,936,937],{},"openapi\u002Fapi-v1.yaml 包含 spec 全部 11 个端点的字段级 schema，可被 swagger-ui 加载 ✓",[47,939,940,941,944],{},"backend\u002F Mock 服务 ",[141,942,943],{},"npm start"," 后能响应全部端点，返回符合契约的 mock 数据 ✓",[47,946,947],{},"launcher\u002F Rust 项目能编译为 launcher.exe，运行后完成所有阶段扫描并输出 diagnostics.json ✓",[47,949,950],{},"至少一个 end-to-end 冒烟测试通过（launcher 上报 → mock 后台收到 → 审计日志记录） ✓",[10,952,953,954,957],{},"每一条都能通过一个具体的命令来验证。\"能工作\"太模糊，\"运行 ",[141,955,956],{},"npm test"," 且所有测试通过\"才是可验证的。",[14,959,960],{"id":960},"流程失效的情况",[10,962,963,964,39],{},"这套流程在一个关键点会失效：",[25,965,966],{},"需求本身没想清楚时",[10,968,969],{},"我遇到过的例子是这样的。客户说\"要支持 USB 存储检测\"，这个需求很清楚，所以设计文档写了 20 多页，规划了三个阶段，列出了 30 多个 test case。然后两周后，客户补充说\"哦对了，还要处理网络驱动器\"。",[10,971,972],{},"这时前面的设计和计划都要回头改。检测逻辑复杂了，测试场景翻倍，阶段划分要调整。这不是流程的问题，这是需求的问题。流程本身反而帮助我及时暴露了这个风险 —— 如果没有设计文档，可能要到代码审查阶段，甚至系统上线以后才发现这个遗漏。",[10,974,975],{},"应对办法是在第 1 步（需求澄清）多花时间。列出你想到的所有场景，问\"还有其他我忽略的情况吗？\"。不是要求完全预测未来，而是把已知的不确定性显式写出来，而不是假设需求是固定的。",[10,977,978,979,982],{},"另一个失效的情况是",[25,980,981],{},"设计和实现的沟通不畅","。如果设计文档是给另一个人读的（或者给 AI 代理读的），但执行者没有理解透彻，实现出来会偏离设计。预防办法是在开始实施前，再过一遍设计文档，确认\"我清楚要做什么\"。",[14,984,985],{"id":985},"提交数字背后的故事",[10,987,988],{},"为什么能积累 7787 次提交？不是因为每次都在写新功能。真实的分布大概是：",[112,990,991,997,1003,1009],{},[47,992,993,996],{},[25,994,995],{},"功能实现","：40%",[47,998,999,1002],{},[25,1000,1001],{},"设计文档编写和迭代","：25%",[47,1004,1005,1008],{},[25,1006,1007],{},"测试编写","：20%",[47,1010,1011,1014],{},[25,1012,1013],{},"bug 修复与回归测试","：15%",[10,1016,1017],{},"关键是这些提交都有上下文。每个提交的 message 都指向一个设计文档或一个 plan 的某个环节，或者一个 bug 记录。下次有问题要追查根因时，能快速定位到那个提交，看当时的设计决策是什么。",[10,1019,1020],{},"另外，有 80 份 bug 记录和 66 份设计 + 计划文档（30 specs + 36 plans）这件事本身说明了一点：文档不是负担，文档是工作的实际产出。代码只是文档的一个落地形式。",{"title":143,"searchDepth":219,"depth":219,"links":1022},[1023,1031,1036,1037],{"id":755,"depth":219,"text":755,"children":1024},[1025,1026,1027,1028,1029,1030],{"id":762,"depth":225,"text":763},{"id":775,"depth":225,"text":776},{"id":802,"depth":225,"text":803},{"id":815,"depth":225,"text":816},{"id":848,"depth":225,"text":849},{"id":869,"depth":225,"text":870},{"id":893,"depth":219,"text":893,"children":1032},[1033,1034,1035],{"id":896,"depth":225,"text":897},{"id":906,"depth":225,"text":907},{"id":919,"depth":225,"text":920},{"id":960,"depth":219,"text":960},{"id":985,"depth":219,"text":985},"2026-07-29",{},"\u002F2026-07-29-ai",{"title":747,"description":752},"2026-07-29-一年八千次提交AI辅助开发工作流","从需求澄清到故障归档，一套系统化的 AI 辅助开发流程：先设计文档过审，再分阶段实施验证，最后代码审查与故障存档，三条铁律支撑整个流程。",[279,1045,1046,1047,1048],"工作流","AI 辅助开发","代码审查","文档驱动","DN7sqbGmg2UybSvRXTMLW-dj0UURXET3KsSNeW_1V9I",{"id":1051,"title":1052,"body":1053,"column":729,"date":1181,"description":1057,"extension":731,"hero_image":732,"meta":1182,"navigation":269,"path":1183,"seo":1184,"series_id":732,"severity":732,"stem":1185,"summary":1186,"tags":1187,"__hash__":1193},"posts\u002F2026-07-20-远程桌面管理器DPAPI与60万次迭代.md","密码，不该由我保管",{"type":7,"value":1054,"toc":1175},[1055,1058,1062,1065,1068,1071,1085,1088,1092,1095,1098,1101,1104,1107,1118,1121,1124,1127,1133,1139,1145,1155,1169,1172],[10,1056,1057],{},"远程服务器资料管理工具需要妥善保存账户凭据。这个工具采用两层加密设计：本机存储用 Windows DPAPI，备份使用基于密码的密钥派生。两层各有边界，理解这些边界对使用决策至关重要。",[14,1059,1061],{"id":1060},"第一层dpapi-的便利与代价","第一层：DPAPI 的便利与代价",[10,1063,1064],{},"本机存储的凭据使用 Windows DPAPI（Data Protection API）加密，由当前 Windows 用户身份保护。这是 Windows 内置的用户级加密机制，密钥由操作系统管理，与登录用户的 SID 和机器的本地安全数据库绑定。不需要用户记一个额外的主密码，启动应用即可直接使用保存的凭据。",[10,1066,1067],{},"DPAPI 的好处显而易见：启动应用即用，无需输入密码，用户体验最优。代价是它的加密密钥锁定在当前用户、当前机器。凭据无法直接迁移到另一台电脑或另一个 Windows 用户——这不是工具的限制，而是 DPAPI 的设计约束。Windows 操作系统就是这样设计的，其他工具也无法绕过。",[10,1069,1070],{},"实际操作中的含义很明确：",[112,1072,1073,1076,1079,1082],{},[47,1074,1075],{},"重装 Windows 前必须先导出备份。原有凭据会因为用户 SID 变化和机器密钥更新而无法解密，即便登录同一账户也不行。",[47,1077,1078],{},"更换电脑前需要先创建备份并妥善保存备份密码，目标电脑上导入时需要重新输入这个备份密码。",[47,1080,1081],{},"在同一电脑上切换 Windows 用户登录，旧用户的凭据对新用户完全不可见，因为加密密钥是用户级的。",[47,1083,1084],{},"多用户共享一台电脑的场景下，凭据不会跨用户暴露。",[10,1086,1087],{},"这些限制会在实际使用中暴露出来——比如忙于工作时重装系统忽略了导出，或者在公用工作电脑上多个人使用。正因为如此，工具强制要求提供备份机制。备份不是可选功能，而是必需的。",[14,1089,1091],{"id":1090},"第二层备份加密的固定迭代设计","第二层：备份加密的固定迭代设计",[10,1093,1094],{},"备份采用 PBKDF2-SHA256（基于密码的密钥派生函数 2，使用 SHA-256 哈希）加密，固定执行 600,000 次迭代。用户创建备份时设置一个密码，导入时输入这个密码。PBKDF2 通过重复应用哈希函数来增加破解难度，迭代次数越多，从密码派生密钥所需的计算时间越长，攻击者进行暴力破解也需要投入成倍的计算资源。",[10,1096,1097],{},"600,000 次迭代的来源是什么？这是一个有意识的设计决定，而不是随意选择。迭代次数越多，暴力破解的成本越高，但加密和解密的耗时也越长。设计者需要在两者之间找到平衡点：够强以抵御现代硬件的破解能力，又不能强到让普通用户的导入操作变得难以忍受。600,000 次这个数字反映的是这个平衡的结果。",[10,1099,1100],{},"为什么固定而非可配置？这是一个纪律问题。可配置听起来更灵活、更给用户掌控权，但在实践中会诱使用户为了更快的备份导入速度而降低迭代次数，从而削弱安全强度。安全不应该由便利让步。人们往往倾向于选择快速方案，尤其当他们没有安全专业知识时。固定的迭代次数消除了这种选择权，保证了所有备份都有相同的防护等级。工具的职责是做出最合理的决定，而不是把这个决定推给用户。",[14,1102,1103],{"id":1103},"性能实测与数据解读",[10,1105,1106],{},"在 Windows 11 构建机上，对 1 MiB 大小的备份数据进行加密与解密的完整往返，预热缓存后的连续五次耗时分别为：108.919 ms、104.079 ms、100.418 ms、94.933 ms、103.470 ms。中位数为 103.470 ms。这意味着从你按下\"导入备份\"到凭据被解密并加载到内存，大约需要 100 毫秒的等待时间。",[10,1108,1109,1110,1113,1114,1117],{},"这组数据收集的目的是",[25,1111,1112],{},"记录性能表现","。它提供了一个具体的参考：用户在 Windows 11 系统上可以预期导入备份的延迟大约是这个量级。这对评估工具的可用性很有用。但它明确",[25,1115,1116],{},"不","作为调整迭代次数的依据。这种表述听起来有些冗余，但它是设计纪律的一部分——必须写下来的目的是防止后续有人看到\"100 毫秒确实有点慢\"就建议降低迭代次数。防止的是这样的推理：因为性能数据显示延迟不够快，所以降低迭代次数。这个逻辑链条在安全工程中是禁止的。",[10,1119,1120],{},"相反，如果实践证明 100 毫秒对用户体验构成问题，正确的做法是要么接受这个成本作为安全性的代价，要么在未来硬件更新换代后自然加速。绝不是削弱密钥派生强度。性能和安全的权衡应该在上层的需求决策中做，而不是在密码学参数中做。",[14,1122,1123],{"id":1123},"工具的明确边界",[10,1125,1126],{},"这个工具的安全设计有明确的保护范围和限制：",[10,1128,1129,1132],{},[25,1130,1131],{},"DPAPI 层的限制","：本机凭据的安全性最终依赖于 Windows 用户密码。如果 Windows 账户被破解，攻击者用该账户登录电脑，DPAPI 解密会自动进行。如果用户以管理员身份运行工具（虽然不需要管理员权限），攻击者获得管理员权限后理论上也可能绕过某些保护。安全链的强度由最弱的一环决定——如果你的 Windows 用户密码很弱，或者电脑物理上被他人访问，DPAPI 的保护就名存实亡。",[10,1134,1135,1138],{},[25,1136,1137],{},"备份密码的强度","：导入备份时用户设置的密码决定了备份的抗暴力破解能力。PBKDF2 提供的防护再强，也无法弥补一个简单密码的缺陷。\"123456\"这样的备份密码，在 600,000 次迭代和现代 GPU 的破解能力面前，可能在几秒到几分钟内被破解。",[10,1140,1141,1144],{},[25,1142,1143],{},"系统策略的约束","：工具运行在 Windows 系统上，不会绕过任何系统级的安全机制。Windows SmartScreen 对未签名程序的警告、远程桌面连接的安全确认对话、Group Policy 的限制——这些工具都无法绕过。安装包为未签名的内部制品，Windows SmartScreen 会在首次运行时显示\"未知发布者\"警告。这不是工具的缺陷，而是系统安全策略的正常行为。",[10,1146,1147,1150,1151,1154],{},[25,1148,1149],{},"加密设计的范围","：这套两层加密设计防的是",[25,1152,1153],{},"离线攻击","——攻击者获得了备份文件或本机的加密数据，在没有用户交互的情况下尝试破解。它防不了的情况：",[112,1156,1157,1160,1163,1166],{},[47,1158,1159],{},"备份密码通过社工或偷看被直接获取",[47,1161,1162],{},"备份文件在网络传输过程中被中间人截获（如果使用不安全的传输方式）",[47,1164,1165],{},"凭据被恶意软件在内存中窃取（工具启动后、密码解密到内存这段时间内）",[47,1167,1168],{},"Windows 账户本身被已经登录电脑的恶意软件控制",[10,1170,1171],{},"对这些风险的防护需要用户在安全习惯和网络安全措施上自行补足——设置强密码、在信任的网络上操作、定期更新系统补丁、使用反恶意软件工具。",[10,1173,1174],{},"使用这套工具前要明确：本机凭据带来了便利，代价是将安全依赖在 Windows 用户身份上；备份凭据提供了迁移能力，代价是密码强度必须由用户自己把关。都不是\"一次设置永久安全\"的方案，都需要持续的安全意识和维护。",{"title":143,"searchDepth":219,"depth":219,"links":1176},[1177,1178,1179,1180],{"id":1060,"depth":219,"text":1061},{"id":1090,"depth":219,"text":1091},{"id":1103,"depth":219,"text":1103},{"id":1123,"depth":219,"text":1123},"2026-07-20",{},"\u002F2026-07-20-dpapi60",{"title":1052,"description":1057},"2026-07-20-远程桌面管理器DPAPI与60万次迭代","两层加密保护远程桌面凭据，本机用 DPAPI 便利性换易用性，备份用 PBKDF2 固定 60 万迭代；为什么迭代次数不可配置，性能数据如何解读。",[1188,1189,1190,1191,1192],"Windows","DPAPI","PBKDF2","凭据存储","加密设计","TzKUoZoFLtAsO_VcB1TOOdWOpwcoDj6AUwEdS6d7pv0",{"id":1195,"title":1196,"body":1197,"column":729,"date":1315,"description":1201,"extension":731,"hero_image":732,"meta":1316,"navigation":269,"path":1317,"seo":1318,"series_id":732,"severity":732,"stem":1319,"summary":1320,"tags":1321,"__hash__":1327},"posts\u002F2026-07-17-把设计文档变成可讲的演示.md","转不成演示的设计文档，本来就没讲清",{"type":7,"value":1198,"toc":1309},[1199,1202,1208,1211,1214,1217,1220,1238,1245,1249,1252,1258,1268,1276,1279,1282,1285,1288,1291,1294,1297,1300,1303,1306],[10,1200,1201],{},"一年积累了 200 多份设计文档，最初想法是直接拿这些文档去讲。结果发现这些东西不适合讲。设计文档是给人写的，演示文稿是给人听的，形式完全不同。",[10,1203,1204,1205,39],{},"解决这个问题的思路不是\"写个通用 PPT 编辑器\"，而是\"从结构化文档一键生成演示\"。本质差异在这里——编辑器要处理用户的任意编辑行为，演示工具只要转换",[25,1206,1207],{},"已有的结构",[14,1209,1210],{"id":1210},"为什么选单向转换而不是编辑器",[10,1212,1213],{},"设计文档有稳定的模板：背景、方案、架构、取舍、参考。这个顺序不是随意的，恰好就是讲一个设计时的叙述顺序。",[10,1215,1216],{},"用户不需要\"先生成再改\"，需要的是\"文档秒变幻灯片\"。一旦你改，就回到编辑器的坑里去了——要支持拖拽、删除、排版，工作量爆炸，而且多数用户不会调，生成好的东西就是定版。",[10,1218,1219],{},"这个判断来自实际数据。我的工具做两个决策：",[44,1221,1222,1232],{},[47,1223,1224,1227,1228,1231],{},[25,1225,1226],{},"产物是自包含 HTML","，不是 Office 文件（",[141,1229,1230],{},".pptx"," 需要可编辑格式，门槛高；HTML 在浏览器里就能放映，自包含意味着内联了所有 CSS、JS、图片）。",[47,1233,1234,1237],{},[25,1235,1236],{},"内容由 LLM 生成，不开放编辑面板","（用一个\"预览挑选\"的两阶段流程，让用户在 3 种风格的封面里选一个，然后生成整份）。",[10,1239,1240,1241,1244],{},"这两个约束听起来很严格，实际上契合了需求的本质：",[25,1242,1243],{},"文档的目的是记录决策，演示的目的是讲述决策","。不需要演示过程中再改决策，改了就回到文档去改。",[14,1246,1248],{"id":1247},"什么样的结构能转什么样的不能","什么样的结构能转、什么样的不能",[10,1250,1251],{},"设计文档要是能一键转成演示，必须满足几个条件。",[10,1253,1254,1257],{},[25,1255,1256],{},"架构图可以直接映射","。我的演示工具支持 AI 生图，也支持用户指定图片 URL。当文档里写了架构设计时，LLM 看到这个描述会生成对应的图，然后内联到演示文稿里。舞台是固定的 1920×1080，所有图片容器有最小尺寸约束（GSAP 时间轴动画要能在任意时间点求值，不能靠动态布局）。",[10,1259,1260,1263,1264,1267],{},[25,1261,1262],{},"关键数据用表格记录最不容易犯错","。表格这种结构容易转化——",[104,1265,1266],{},"待补：具体表格如何转成演示页的实现规则","。如果文档里的数据以段落文字形式写，转成演示时就卡住了，LLM 要从自然语言反推结构。",[10,1269,1270,178,1273],{},[25,1271,1272],{},"取舍（trade-off）部分",[104,1274,1275],{},"待补：设计文档中的取舍说明如何映射成演示内容的规则",[10,1277,1278],{},"一个更深的观察：文档转不出来好演示，通常不是演示工具的问题，是文档本身没讲清楚。",[10,1280,1281],{},"我见过一类项目文档，有 20 多份模板文件，但只要换个配色其他全一样。这说明什么？说明那些\"模板\"实际上没有结构差异，只有视觉差异。结构不清，自然转不出演示。或者反过来说，如果要证明自己的文档结构是清晰的，试试能不能一键转成演示——转不出来就是信号，说明需要先梳理文档本身。",[14,1283,1284],{"id":1284},"工具的硬约束",[10,1286,1287],{},"演示不同于其他产物，有独特的硬约束。我的工具选择了 1920×1080 的固定舞台，所有动画用 GSAP 时间轴。这些看起来像限制，实际上是为了保证可靠性。",[10,1289,1290],{},"固定舞台尺寸意味着设计者写风格预设时要算好留白和排版，不能寄希望于\"容器自适应就行了\"。GSAP 时间轴的特点是能在任意时间点求值（渲染管线会 seek 到任意帧截图），不能用 CSS animation 这种依赖真实时间流逝的东西。这限制了动效，但换来的是确定性——动效一定会在预期时间点发生，不会因为网络卡而错位。",[10,1292,1293],{},"生成流程分两个阶段，有个细节很实用。第一阶段只生成 3 个风格的封面单页，第二阶段才生成整份演示文稿。这个设计看似多一步，实际上优雅地解决了两个问题。一是给用户选择风格的机会（不是非此即彼的\"生成或不生成\"）；二是掩盖异步生图的等待时间（生图 1-2 分钟，正好被用户在选择封面时吸收了）。",[14,1295,1296],{"id":1296},"从文档能否转演示看结构清晰度",[10,1298,1299],{},"最后回到起点。为什么要做这个工具？",[10,1301,1302],{},"表面原因是 200 多份文档用演讲方式讲会更有力。深层原因是这个过程本身就是对文档质量的检验。",[10,1304,1305],{},"一份设计文档如果结构清晰——背景交代得清，方案对比得充分，架构图画得明确，取舍理由说得透彻——那转成演示就是平移内容，不费劲。转不出来、或者转出来很别扭，就是信号，说明某个环节讲得不够好。",[10,1307,1308],{},"这个反馈机制比任何 review 注释都直白。\"这个表述为什么转不成幻灯片？\"往往能逼出真实的问题——\"哦，因为我其实还没想清楚这个方案为什么比另一个好\"。",{"title":143,"searchDepth":219,"depth":219,"links":1310},[1311,1312,1313,1314],{"id":1210,"depth":219,"text":1210},{"id":1247,"depth":219,"text":1248},{"id":1284,"depth":219,"text":1284},{"id":1296,"depth":219,"text":1296},"2026-07-17",{},"\u002F2026-07-17",{"title":1196,"description":1201},"2026-07-17-把设计文档变成可讲的演示","不做通用PPT编辑器，只做文档→演示的单向转换；关键在识别文档的稳定结构。",[1322,1323,1324,1325,1326],"演示文稿","文档结构","设计工具","HTML","GSAP","Reh87_TKXZ8nl5BhDhHzVOONAzwRS-Im8LTYrIfeuNk",1785406912235]