agentleFS
jimku233jimku233/workflow-visualizer/SKILL.md

workflow-visualizer

Skill0 starsChanged 31 days agoMITView on GitHub

把业务流程渲染成 Mermaid 源码 + 自包含交互式 HTML 流程图(零依赖、纯离线,不请求任何 CDN)。用于把用户口述或文档里的流程、SOP、生产链路、复盘时间线画成能点的图:节点可点开看时间/负责人/说明,支持搜索筛选、明暗主题、导出 SVG/PNG,并同时产出可粘进飞书/语雀/GitHub 的 Mermaid 代码。当用户说"把流程画出来/画个流程图/可视化工作流/整理成流程"时使用。

3 other projects use a version of this.

Adopted by

Most recently taken up by

  • Other versions3 projects use a version of this.

    Word for word · 0

    None found.

    Edited · 0

    None found.

    Wrote their own version · 3

    1. Hassaan146/claude-skills★ 18same name
    2. Frictionless-Labs/claude-skills★ 1same name
    3. pb-jwhitlock/claude-skills★ 0same name

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repository2 other files it gives its agents.

The file

Files
SKILL.md

workflow-visualizer:业务流程 → 可交互流程图

渲染脚本:scripts/flow.mjs(相对本 skill 目录) 绝对路径:scripts\flow.mjs 完整字段与语法手册:references/spec.md(写规格前先读它)

一、核心约束(决定了为什么这么做)

  1. 零依赖:只用 node: 内置模块。不需要 npm install,不用装 mermaid。
  2. 纯离线:布局与渲染是本脚本自己实现的 SVG,不引用任何 CDN。断网、内网、无外网都能出图(本机 web_fetch/docs.deepseek.com 都不通,任何依赖 CDN 的方案都会失败)。
  3. 产物自包含:导出的 .svg 内含 <style>,单独发给别人也能正确显示;.html 单文件,双击即开。

二、三步用法

第 1 步:提取流程(这步不能省) 从用户的真实材料里提取,不要凭空编。来源优先级:

  • 工作区里的方案/复盘文档(*.md,通常已有"工作流/SOP/排期"章节)
  • 用户口述(这时要把不确定的地方标成 status: hold 并在 note 里写明待确认)
  • 会话历史或日志

提取时固定抓这五样:环节(谁做什么)、时间窗、先后依赖、判断分支、风险/需人工把关的点。

第 2 步:写规格

  • 图不复杂(纯线性 + 一两个分支)→ 直接写 Mermaid 存成 .mmd,最省事。
  • 要带时间/负责人/状态/说明/分组 → 写 JSON(信息密度高得多,面板才有内容可展示)。
  • 先跑 sample 拿到一份成品规格当模板最快:
node "scripts\flow.mjs" sample --out ".\out\我的流程.json"

第 3 步:校验 + 渲染

$sk = "scripts\flow.mjs"
node $sk check  ".\out\我的流程.json"     # 结构检查:孤立节点/不可达/无终点/环
node $sk render ".\out\我的流程.json" --all --out ".\out\我的流程.html"

产物默认落在输入文件同目录。

一次 render 出两个版式(同一个 HTML 里,按屏幕自动选,底部可手动切):

版式布局适配
竖版 portrait单列纵向(wrap: 0)手机:竖向滚动,宽度撑满
横版 landscape长流程自动分多栏(wrap: -1,目标宽高比 1.6)电脑:一屏放下

常用参数:--out <file.html>、--all(额外输出 <名>-竖版.svg、<名>-横版.svg、.mmd)、--wrap N(横版每栏固定 N 行)、--debug(打印两版的栏数/行数与各栏成员)。

验证两版(推荐):

node $sk preview ".\out\我的流程.html"   # 生成「【电脑用】对照预览.html」:手机/电脑两个 iframe 并排

在调试浏览器里打开它,一眼就能看出两版是否都对。要精确到真实设备尺寸,用 CDP 设备模拟:

# 手机 + 电脑两张实测图,并打印 canvas/aside/footer 的实际高度与自动选中的版式
node $sk\..\tools\devshot.mjs "http://127.0.0.1:8765/我的流程.html" 手机.png 电脑.png

# 想在页面里跑一段 JS 验证交互(例如点节点、输入搜索、开抽屉):
node $sk\..\tools\probe.mjs <url> 探针.js --w 390 --h 844 --mobile --touch

这两个工具都会新建自己的临时标签页,用完关掉,不会碰用户正在浏览的标签页(早期版本直接抓第一个 page target,把用户正在看的页面当成了被测页面,教训记在下面)。但要注意:新标签页会出现在用户的浏览器窗口里并抢占焦点——如果用户正在用那个调试浏览器,先问一句再用,否则会互相打断。

三、出图后必须自检(不要只看"命令成功了")

命令成功 ≠ 图能看。至少做一次视觉确认,用 browser-control:

# 1) 生成一个 ASCII 文件名的副本,避开 file:// 的中文编码坑
Copy-Item ".\out\我的流程.html" ".\out\preview.html" -Force
# 2) 用系统 URI 生成器拿到正确的 file:// 地址(手写百分号编码极易出错)
$u = [System.Uri]::new((Resolve-Path ".\out\preview.html").Path).AbsoluteUri
node browser-control\webctl.mjs shot $u "out\shot.png" 5000

然后看图确认:文字没被裁、分支没交叉成一团、分组带没盖住节点。 点击交互也可以脚本验证(webctl.mjs evalfile,注意必须是 (function(){...})() 表达式,裸 return 会报 Illegal return statement)。

四、给人看:电脑 / 手机两种打开方式

电脑:直接双击那个 .html(单文件、离线可用);要贴进文档就拖 .svg。

手机(HTML 在电脑上,手机浏览器没有"打开本地文件"这回事)——用局域网访问,不用传文件:

node "scripts\serve.mjs" ".\out" 8765

脚本会把可用的手机地址直接打出来(VPN/虚拟网卡/169.254 会自动标「忽略」),形如: 手机 → http://<你的局域网IP>:8765/ ✔ 用这个(WLAN) 手机连同一个 Wi-Fi,浏览器打开这个地址,点文件名即可。要点:

  • 只把产物目录暴露出去,别指向工作区根。
  • Windows 首次会弹防火墙询问,勾选「专用网络」允许入站;如果没弹又打不开,就是被防火墙拦了。
  • 目录页是文件列表,手机上可点进任意 .html / .svg / .png。
  • 看完可以让我停掉这个服务(它是后台任务),或者直接关掉那个终端窗口。

不在同一个网络 / 要发给别人:直接发那个 HTML 文件本身——它自包含、离线可开,微信/QQ 传文件都行;只想要图就点页面里的「导出 PNG」发图片,或在浏览器里 Ctrl+P 存 PDF。

手机上的操作:单指拖拽平移、双指捏合缩放、轻点节点看详情、底部「适配」回到整幅;竖版/横版按钮在页脚左侧(手机上只显示图标)。

区域能力
顶栏品牌点 + 标题、搜索框、「全部流程图」按钮(右上)、状态图例胶囊(点击筛选)
右栏未选中时是流程概览;搜索时先列命中清单(见下);选中节点后是详情卡 +「返回概览」
底栏竖版/横版切换、概览/详情(手机)、缩放、适配、导出 SVG/PNG、明暗主题、Mermaid 源码

搜索的三档命中(重要,别把它当 bug)

搜索同时查 标题 / id / 时间 / 负责人 / 说明 / 备注,但画布上只看得到标题,所以命中分两类:

  • 可见命中(标题/id/时间/负责人)→ 节点描蓝边,计数直接算一个
  • 只在说明/备注里的命中 → 节点描橙色虚线边,计数写成 2 个匹配(1 个只在说明/备注里),右侧同时列出命中清单(点一下定位过去)

命中色用专用变量 --hit(深色 #4a9dff、米色主题 #2b7fe0),不能借用 --accent —— 米色主题的强调色是赤陶色 #bf6b3a,借用它命中就变成"红边",跟"蓝边"的说明对不上(踩过)。

例:在示例流程里搜 杯 会命中 2 个 —— 开第二个出杯位(标题)和 午后补货(说明里的"杯具");搜 f 会命中 id 为 f 的节点,以及任何说明/备注里含字母 f 的节点。同一个节点只会计一次,这不是重复计数。

明暗主题:总设置 / 单张图 / 锁定(后改的优先)

三个键都存在浏览器的 localStorage(都存 JSON {mode, ts},靠时间戳比较,谁后改谁生效):

键谁写含义
flowviz-theme首页「显示设置」总设置:跟随系统 / 始终深色 / 始终浅色
flowviz-theme:<文件名>流程图页面的明暗键单张图的单独设置(取值 dark/light)
flowviz-lock:<文件名>流程图页面的「锁定颜色」锁定后冻结该图颜色({locked:true, mode})

生效规则(页面 <head> 的引导脚本与页面 JS 完全一致,避免闪色):

  1. 被锁定 → 用锁定那一刻冻结的颜色,总设置与单张设置都影响不到它
  2. 否则比较总设置与单张设置的时间戳,新的一方生效
  3. 都没有 → 跟随系统(prefers-color-scheme),系统切换时实时跟随

所以:先改单张、再动总设置 → 全部跟着总设置变;先动总设置、再改单张 → 只有那一张变。锁定后必须先解锁才能改颜色(锁定态点明暗键会被拦下并弹提示)。

实测矩阵(8 步全过、0 异常):清空 → 总=浅 → 图=浅;点单张明暗 → 图=深(写入单张键);再改总=浅 → 图=浅(总设置更新,覆盖单张);再点单张 → 只有这张变深;锁定 → 改总设置后仍保持被锁的深色;锁定态点明暗键 → 被拦下并提示"先解锁";解锁 → 回到总设置的浅色;首页卡片显示「🔒 已锁定 浅色」徽标。

其他要点:

  • 同一个键反复 setItem 是覆盖,不会越积越多(实测点 12 次明暗键后 Object.keys(localStorage) 仍只有 1 项、合计 18 字节),不需要"删掉上一次"
  • 偏好存在设备浏览器里,服务重启不受影响;换 IP/端口(换了 origin)、清浏览器数据、换浏览器才会重置
  • 浅色是米色千禧年风格,不是纯白:底 #f2ebdd、面板 #fdfaf3、正文 #33302a、强调色赤陶 #bf6b3a、图内底纹 #e6dcc8(深色主题保持原来那套)

顶部按钮

🏠 首页 回到文件列表页(href="./")|全部流程图 打开右侧抽屉|搜索框|状态图例|明/暗 切这张图|锁定颜色 冻结这张图的颜色。 抽屉里当前页渲染成非链接(点了不会有反应,避免"我以为坏了"),列表固定按文件名排序,所以 fetch 刷新清单时不会重排。

右上角按钮弹出右侧抽屉,列出同目录下由本 skill 生成的所有流程图(固定按文件名排序,当前页标「当前」且渲染成不可点的非链接),点一条即可切换。识别方式是页面里的 data-flow-viz="1" 标记,所以别的 HTML 不会被收进来。

  • 清单文件是目录里的 流程索引.json,每次 render 自动重建(也可 node flow.mjs index <目录> 单独重建)
  • 用 HTTP 打开时抽屉会实时 fetch 清单刷新;直接双击本地文件时用页面内嵌的快照兜底(每个页面都嵌了一份,所以本地也能切换)

搜索的交互("命中不明显"的三个补丁)

  1. 自动展开结果面板:搜出命中且当前没选中节点时就 setPanel(true) —— 手机端结果清单默认藏在收起的底部抽屉里,不展开等于没提示(这是"搜了等于没提示"的根因)
  2. 上一个 / 下一个:结果区标题栏有 ↑ / ↓ 和 当前/总数 计数;搜索框里 Enter = 下一个、Shift+Enter = 上一个;Esc 清空
  3. 自动聚焦(可关):结果区有「自动聚焦命中」开关。开启时——
    • 命中的包围盒能装下、且所需缩放 ≥ 当前缩放 × 0.6 → 框住全部命中(多个命中同屏可见)
    • 否则(命中离太远)→ 不缩小,保持当前缩放居中到"当前命中",靠 ↑/↓ 逐个跳 这样永远不会为了"把两个远距离命中都装下"而把文字缩没
  4. 搜索期间给 svg 加 .searching:非命中淡出到 14%(连线 10%、分组带 35%),命中描边加粗到 3px
  5. 程序化移动(聚焦/跳转)给 #vp 加 .vp-anim(0.28s 过渡),手动拖拽不受影响

重名流程图:先分清「内容相同」还是「只是同名」

同一标题下出现多个文件时(典型来源:删掉又恢复文件,系统生成了 xxx - 副本.html),首页顶部会弹出一个提示框,按内容分两种处理:

情况判据给的动作
内容相同(同一份流程的两次渲染)流程语义指纹相同「只保留最新的一份,其余移入回收文件夹」
内容不同(同名但是不同版本)指纹不同不删,只提供「改名…」(默认建议 原名(旧版 日期).html)
  • 指纹 = 流程语义,只取 标题/副标题/方向/分组/节点/边,忽略 selfFile、flows、slug 等页面级字段。所以"同一份流程在不同时间渲染出的两个文件"指纹相同,而"只改了页面元数据"不会被误判成不同内容。
  • 删除一律是"移入回收文件夹":同目录下建 .trash-YYYY-MM-DD/,把 HTML 连同它的 SVG / MMD(按归一化名字认亲)一起 rename 进去,可随时取回。
  • 处理动作走后端端点 POST /__flow/act({op:'trash', remove:[...]} / {op:'rename', file, to}):
    • 只允许 127.0.0.1 调用(手机、局域网一律 403)——否则等于把"删你文件"的能力挂到 Wi-Fi 上
    • 文件名过白名单:不含路径分隔符、长度 ≤200、必须以 .html 结尾;重名冲突返回 409
    • 手机 / 本地 file:// 模式下不显示按钮,只显示提示(那两种场景没有可写的后端)
  • 卡片上仍会保留文件名(标题重名时),方便人工辨认

两种打开方式(都要能进首页)

方式怎么进首页首页从哪来
本地双击(file://)流程图右上角 🏠 → ./index.htmlrender 写出的静态 index.html(浏览器自带的目录列表只有文件名,所以必须有这份)
局域网 / HTTP🏠 → ./serve.mjs 动态生成
  • render 每次都重建 index.html,它与 HTTP 首页共用同一段渲染代码(serve.mjs 导出的 buildHome();serve.mjs 只在直接运行时才起服务,被 import 时只提供函数)
  • 静态版与 HTTP 版的差别:面包屑显示 📁 目录名(本地文件夹)、徽标写「流程图页面」、页脚多一段"本地文件模式:手机要访问得先起服务"
  • 🏠 按钮是协议感知的:location.protocol==='file:' 时把 href 改成 ./index.html
  • index.html 自己不会出现在卡片列表里(早期版本会,第一张卡叫 "index");既不在清单里、又解析不出 flow-stats 的 HTML 会被归到「其它文件」

首页(电脑/手机同一页)

  • 面包屑:🏠 流程图 / 子目录(当前),每级可点
  • 磁盘路径块:直接显示 .\out,带「复制路径」按钮——要发原文件、找源文件时不用再猜
  • 检索 + 排序 + 汇总:按标题/文件名过滤;排序可切「最近打开优先 / 按名称 / 按修改时间」;汇总显示 3 张 · 共 36 环节 / 47 连接 · 需关注 4 处(2 张图)
  • 最近打开:点卡片时记进 flowviz-recent(最多 8 条),默认置顶并加「最近打开」徽标
  • 卡片还会显示「🔒 已锁定 X」「单独设为 X」徽标(读本机 localStorage)
  • 文案按设备分:同一份 HTML 里写两套提示,用 @media (hover:none),(pointer:coarse) 切换(比按宽度判断更准,横屏手机也对)——
    • 电脑(.only-pc):🖱️ 点卡片打开。图里可以滚轮缩放、按住拖拽平移;右上角「显示设置」切换深色/浅色
    • 手机(.only-touch):📱 点卡片打开:图上单指拖动、双指缩放、点环节看详情…
    • 卡片徽标「手机上点这个」加 .touchonly,电脑上不显示;对照页徽标改成中性的「并排对照页 · 给电脑看的」(两边都看得懂)
    • 流程图页面的页脚/侧栏提示同理:电脑看 滚轮缩放 · 拖拽平移 · Esc,手机看 单指拖动 · 双指缩放
  • 同名流程图(例如 xxx - 副本.html 和 xxx.html)光看标题分不清 → 这类卡片在标题下再显示一行文件名
  • 缩略图按归一化名字匹配:去掉空格、去掉 -横版/-竖版 再比,所以 示例流程 - 副本.html 能配上 示例流程-横版 - 副本.svg(后缀位置不同也能配上)
  • 目录页响应必须带 cache-control: no-store:它是动态生成的,浏览器一旦缓存就看不到新版(踩过一次:用户一直看到旧版纯列表,误以为"首页很简陋、没有缩略图")

手机上的操作

单指拖拽平移、双指捏合缩放、轻点节点看详情、底部「适配」回到整幅。 手机上概览/详情是覆盖式底部抽屉(position:fixed):点节点滑出、点空白收起、底栏「概览」按钮手动开。它是覆盖层而不是 flex 兄弟节点——所以开合面板时画布高度不变(实测 607→607),不会触发"画布变矮 → 重新适配 → 图被裁掉"的连锁反应。

两个和"缩放后看着不对"有关的实现细节:

  • 点阵底纹在 #vp 内部、范围是画布的 3 倍且以内容为中心(x=-W, y=-H, w=3W, h=3H)。这样底纹跟着缩放平移走,视野里不会露出底纹矩形的硬边(早期版本底纹只有画布那么大,放大到 150% 再平移就会看到一条边)。
  • HTML 版不画 SVG 标题块,只有导出的 .svg 才画(页面顶栏已经有标题了)。否则放大后标题会压住节点。
  • 窄屏上切横版一律整幅适配:判据是 画布宽/内容宽 > fit×1.8 且内容宽高比 ≤ 1.2 才用"宽度撑满"。横版是 1.58 的扁图,在 390px 屏上如果宽度撑满会放大到 150%(只看得见一角),现在稳定 100%。

五、画业务流程图的经验规则

  1. 节点粒度:一个节点 = 一个可交付的动作("写口播稿"是节点,"认真思考"不是)。单图 8–16 个节点最舒服,超过 20 个就拆成主图 + 子流程。
  2. 状态要用起来:done/doing/todo/risk/hold 不是装饰。真实流程里"等人确认/等素材"这类阻塞要标 hold,"必须人工把关"标 risk——这正是流程图比文档强的地方。
  3. 判断节点只放真分支:{...} 用于会走不同路径的决策。只是"检查一下然后继续"的,用 milestone。
  4. 时间窗尽量来自材料,不要自己编。材料里没有就留空,别猜。
  5. 版权/合规/红线的节点要显式画出来并写进 note,否则图看着很顺、实际会踩雷。
  6. 长链别用横向 LR:13 步的链做成 LR 是 1980×489 的 4:1 长条,在 1100px 宽的画布里 fit 出来只有 0.55 倍,字全糊;而分栏(横版)能把同一张图压到约 1.6:1,一屏放下还不缩字。经验:链式流程要横向 → 用分栏,不用 LR。
  7. 两版怎么选:手机看竖版(一列往下滚,符合竖屏直觉);电脑看横版(分栏,一屏全局)。autoVariant() 的阈值是 宽<760px 或 宽高比<1.08 → 竖版。
  8. 分栏的读向仍是向下:不做蛇形(上下交替),因为反向列会让箭头方向混乱。跨栏连接线走列间空隙(行内只有一个节点时)或底部走廊(行内多节点时),不会横穿节点。
  9. 分组跨栏时会自动切成多段并标「(续)」,避免一个矩形把不相干的节点圈进去。

六、已验证记录(2026-10-04)

  • 输入格式:JSON / Mermaid 子集 / .md 里的 ```mermaid 代码块,三条路径均实测通过。
  • Mermaid 往返:解析 → 重导出 .mmd,判断节点 {}、虚线 -.->、边标签 -->|有|、subgraph 分组均正确保留。
  • 两版布局(13 节点 / 13 连线 / 3 分组的示例流程):竖版 533×1936(单列 12 行);横版 1119×710(自动分 3 栏 × 4 行,宽高比 1.58,一屏放下)。7 节点的示例流程:竖版 533×890、横版 806×459(自动 2 栏)。
  • 自动选版(CDP 设备模拟实测):390×844 → show=portrait;1440×820 → show=landscape。
  • 手机版布局(390×844 实测高度):canvas 364 / 概览面板 253 / 页脚 81,概览内容完整渲染(panelLen 1533)。
  • 触屏手势(CDP 合成 TouchEvent 实测):单指拖拽平移 ✓、双指捏合缩放 ✓、轻点节点选中并弹出「← 返回概览 | s | T 日 12:00 开工 | 起点 | 已完成」✓。鼠标路径同样三项通过。
  • 交互:点击节点 → 详情卡 + 上下游高亮 + 其余变暗;图例筛选写入 data-hide-status;搜索命中计数;+/0 键缩放与适配回到 100%;明/暗主题切换;两版同时保持选中/搜索/筛选状态。
  • 加载期异常检查(Runtime.exceptionThrown):两个样例页面均 0 异常。
  • 美化版:卡片式节点(状态色竖条 + 柔和投影)、类型徽标、时间药丸、圆角正交旁路、胶囊边标签、分组带标签胶囊、右栏流程概览(计数 + 状态筛选 + 需关注列表)、点阵底纹、图标工具栏。

踩过的坑(都已在代码里修掉,记下来省下次的时间)

  1. Mermaid 判断节点不生效:touch() 写的是 shape 字段,normalize() 读的是 kind —— 字段名对不上,菱形全画成矩形。
  2. 标签引号被吃掉:标注"真题改编" 变成 标注"真题改编,因为无条件剥了首尾引号;改成只在首尾同引号时剥。
  3. 缩放百分比与实际不符:SVG viewBox 自己会做一次 meet 缩放,原来的 transform 是叠在它上面的,导致「100%」实际显示 26%。改成在元素像素空间统一算 view{K,TX,TY} 再反解。
  4. PowerShell Set-Content 加了 UTF-8 BOM,破坏 #!/usr/bin/env node 直接 SyntaxError。改这个脚本别用 shell 写文件。
  5. b.members 未定义:分组带对象漏传 members,带标签的图直接崩。
  6. fit 整幅让长流程图缩到 26%:改成"整幅与宽度撑满相差不到 1.8 倍就整幅,否则宽度撑满 + 顶部对齐"。
  7. 菱形压字:判断节点按内接矩形条件重算尺寸后,是语法还是语篇问题? 才能一行放下。
  8. 两版并存时 id 冲突:renderSvg 里的 <g id="vp"> 没加版本后缀,客户端 getElementById('vp-landscape') 拿到 null → paint() 抛异常,整个初始化中断,概览面板空白(现象是 aside 只剩 25px)。两版的所有 id(defs/filter/pattern/marker/vp)都必须带 -${vid}。
  9. wrap 参数语义写反:它表示"每栏行数",不换行应是 R = L 而不是 R = 1(R=1 会变成每层一栏的横向长条,竖版一度被排成 3476×101)。
  10. CDP 异常回放会骗人:Runtime.enable 后会把上一个文档的异常一起送来。判断"页面是否真的报错"前,先 Page.navigate 到 about:blank 再进目标页,否则会把旧页面的错算到新页面头上。
  11. 轻点选中的验证要先适配:手势操作后目标节点可能已被移出可视区,elementFromPoint 自然命中不到——这是测试脚本的问题,不是页面的问题。
  12. CDP 验证抢了用户的标签页:webctl shot / 直接抓第一个 page target,都会导航用户正在看的那个标签页;用户同时在用浏览器时,双方互相打断(实测:我导航过去、用户一搜就把它导航回来,location 每隔几十秒变成实习网站)。改成 tools/cdp.mjs 的 withOwnTab()——自己 Target.createTarget 建临时标签页、轮询等它出现在 /json/list、用完 Target.closeTarget 关掉。即便如此新标签仍会抢焦点,用户在用浏览器时要先问。
  13. 搜索"多出来"的匹配不是 bug:搜 口 命中 2 个,是因为搜索同时查说明/备注("口径校准")和 id(节点 id j)。修法不是改数字,而是把不可见的命中标出来(橙色虚线圈 + 分档计数 + 命中清单)。
  14. 后台标签页不能截图:Target.createTarget({background:true}) 不抢用户焦点,但后台标签页不产生帧,Page.captureScreenshot 会永远挂住(实测 120s 超时)。要截图就得用前台标签页(会抢焦点);后台标签页只适合纯几何测量(getBoundingClientRect / getComputedStyle 都正常)。另外后台标签页的 CSS transition 不推进——测几何前先注入 *{transition:none!important},否则会量到动画起点(我把抽屉的 rect.x=398 误判成"没打开",实际是节流)。
  15. 给"电脑用"的产物也会被挂上"手机上点这个":文件列表的徽标只看扩展名。现在 【/_ 开头的文件单独标「电脑用,手机上别点」并置灰——判断条件要跟着展示文案一起改。
  16. 验证产物不要放在被服务的目录里:shot-*.png、探针脚本会出现在手机的文件列表里。统一放进一个不被服务的目录(例如 out/_shots/),别放进被 serve.mjs 服务的产物目录。
  17. 抽屉列表在手指底下重排 = 点错条目:页面内嵌快照原来是"当前页排最前",而 fetch('流程索引.json') 回来是按 mtime 排序 —— 打开抽屉后约 100ms 列表会跳一次,用户点到的已经不是想点的那条,表现为"点了没反应 / 回不去"。修法两条:清单改成固定按文件名排序(快照与清单顺序永远一致),当前页渲染成非链接。教训:任何"打开后异步刷新"的列表都必须保证顺序稳定。
  18. 改了 serve.mjs 要重启服务:页面模板在进程内存里,不重启就一直是旧版(我为此白查了一轮"首页没有 h1")。
  19. 目录页漏了 cache-control 会让用户永远看到旧首页:用户两次说"电脑版首页是不是太简陋了/没有缩略图",实际是我早已改成卡片+缩略图,但浏览器一直用缓存的旧响应。动态生成的 HTML 必须 no-store。
  20. Start-Process 分离启动的服务活不过命令结束:沙箱在命令结束时连带清理进程树,服务会静默消失(表现为下一次请求"无法连接到远程服务器")。要用 harness 后台任务,或让用户自己在终端里跑。
  21. 别拿目录里同名/近名的 JSON 当输入:目录里有个 甲流程.json,名字像甲流程,内容其实是乙流程的规格;我照它 render,直接把已有的 甲流程.html 覆盖成了别的内容。渲染前先核对 JSON 里的 title/节点数,并且优先用与产物同名的输入。
  22. 中文注释里带直引号会截断双引号 JS 字符串(" // 说的是"纯文件名"列表" → 提前结束)。同一个坑踩了三次:中文注释里要么不用引号,要么用「」。
  23. file:// 下没有"目录首页":href="./" 在本地会落到浏览器的纯文件名列表,用户看到的就是"只有文件名"。动态生成的目录页必须另写一份静态 index.html,并让首页按钮按协议切换目标。
  24. 没有调试浏览器时的截图办法:沙箱里 Edge 起不来(mojo 命名管道被拒),但 msedge --headless=new --screenshot=... --window-size=W,H <url> 一次性渲染写文件是可行的(需一次不受限权限)。能拍 file:// 本地页、也能按尺寸模拟手机/电脑,是本轮定位"手机首屏看不到卡片""汇总算出 0"的关键。
  25. 模板字符串里写正则要双反斜杠:\d/\s 在模板字符串里会退化成 d/s,生成到页面上的正则就废了(汇总一度显示"共 0 环节")。写在页面脚本里的正则统一用 \\d、\\s。
  26. 文案不能跨设备硬写:电脑首页上写「手机上点这个」、手机流程图里教人「滚轮缩放/双击/Esc」,都是"设备错配"。做法是同一处写两套(.only-pc / .only-touch)+ @media (hover:none),(pointer:coarse) 切换 —— 用输入方式判断比用屏宽判断准(横屏手机、窄窗口电脑都不会错)。
  27. 同目录出现同名流程图时卡片会长得一模一样:xxx - 副本.html 与 xxx.html 标题相同,用户分不清。标题重名时补一行文件名;缩略图也要按"去掉空格与 -横版/-竖版"的归一化名字匹配。
  28. 模板字符串里的 \n 会变成真换行:写在 homeScript() 这类模板里的页面 JS,'...\n\n' + x 到了页面上就是字符串字面量里夹真换行 → 页面脚本语法错误(node --check 检查 serve.mjs 是查不出来的)。要用 \\n。验证办法:把 buildHome() 生成的两个 <script> 抽出来分别 node --check(我加了这个回归步骤)。
  29. 判断"内容是否相同"要按语义,不要按文件哈希:整页哈希必然不同(渲染时间、元数据都会变),会把同一份流程判成"内容不同"。只对流程本身(标题/分组/节点/边)取指纹。
  30. 危险端点必须限本机 + 用回收站代替删除:POST /__flow/act 直接 rm 就等于把删文件能力暴露到局域网。做法:req.socket.remoteAddress 必须是回环地址,删除动作一律 rename 进 .trash-日期/,文件名走白名单,并回 403/400/404/409 明确原因。
  31. 测试这种端点有两台坑:① 这台机器是 Windows PowerShell 5.1,Set-Content -Encoding utf8NoBOM 不存在(只有 UTF8,且会写 BOM——所以服务端要 body.replace(/^\uFEFF/,'') 容错);② Invoke-RestMethod -Body '<含中文的字符串>' 会按 Latin-1 发出,中文文件名到服务端就成乱码 → 用 curl.exe --data-binary @文件 并确保文件是 UTF-8。
  32. getBBox() 是节点自身局部坐标:我的节点是 <g transform="translate(x,y)">,getBBox() 返回的是 0,0~w,h。直接拿它当画布坐标算中心 → 聚焦/自动缩放会跳到画布左上角附近(用户看到的是"跳到别的环节去了、动画不对")。必须加上该 <g> 的 translate 偏移。
  33. 文件被移动/改名后要刷新派生产物:静态 index.html 与 流程索引.json 都是"生成时快照"。只移动文件不刷新,用户会看到"回收了首页还在显示"(尤其本地 file:// 打开的那份)。处理动作完成后要 refreshDerived():重建静态首页 + 从清单里剔除已不存在的条目。
  34. 指纹必须现算,不能指望清单:流程索引.json 里只有标题/计数,没有语义指纹。若"在清单里就不算",就会出现一份有指纹、一份没有 → 漏判重复。代价是每次列目录要把每个 HTML 读一遍(几十 KB,可接受)。
  35. 判断重复要按内容指纹,不能只按标题:从 Mermaid 生成的流程没有标题(flow-data.title 为空),标题会退回文件名 → xxx.html 与 xxx - 测试.html 分不到一组。正确顺序:先按语义指纹分组(同内容必重复),再按标题分组(同标题但内容不同 → 只提示改名)。
  36. 读 <head> 里的 meta 别只读前 4KB:页面 CSS 有好几 KB,flow-title/flow-stats 可能落在窗口之外,标题就变空了。读 128KB,或者干脆解析 flow-data(更可靠)。
  37. "浅色缩小后看不清"不要只加深底色:两个主题的「方块底 vs 背景」WCAG 比值几乎一样(浅 1.19 / 深 1.25),差别在相对亮度差(16% vs 65%)——接近白色时人眼分辨率低(韦伯定律)。而最先消失的是连线(浅 2.67:1 vs 深 6.85:1,差 2.6 倍)。所以正确顺序是:先加深描边与连线,再考虑加深画布,最后才是"缩小时自动切高对比"。只把底色改深、不管线条,用户依然会说"看不清"。
  38. 想让"祖先"跟随 <svg> 上的类,只能用 :has():缩小简化把 .zoomed-out 挂在 <svg> 上,画布 #canvas 是它的祖先,CSS 没有父选择器 → 写 html[data-theme='light'] body:has(svg.zoomed-out){--bg:...}(Chromium/Edge 105+)。改画布因子的同时给 #canvas 和 html,body 加 .22s 过渡,缩放跨阈值时才不会"啪"地闪一下。
  39. 别用 [int] 做"行/列"取整:PowerShell 的 [int] 是银行家舍入,[int](3/2)=2 而不是 1,拼图脚本第 4 张会被画到画布外面(我因此漏了一张对比图)。用 [math]::Floor($i/2)。
  40. 写到磁盘的 .ps1 里不要出现中文路径:Windows PowerShell 5.1 按 ANSI 读脚本文件(没有 BOM 时),中文路径直接变乱码 D:\<中文目录>。脚本里用 $PSScriptRoot 取自身目录,或把路径当参数传进去;.mjs 没有这个问题(但要写成 UTF-8,用 Set-Content -Encoding ASCII 写含中文的 .mjs 同样会变成 ?)。
  41. 分组框只会往右长 → 小节看着不居中:x1 = max(节点右边界+20, x0+标题所需宽度) 这种写法,在标题比节点列宽时只加右边,于是节点列贴左、右边留一大块空白("采集与下载"那组曾经是左 20 / 右 88),而且各组框中心线互相错开(243 / 209 / 209)。正确做法:extra = max(0, needW - (内容宽+40)),左边减 floor(extra/2)、右边加 ceil(extra/2),对称外扩;同时把画布原点算进去(ox = pad - min(0, 各组带最小 x)、W 取"节点∪分组带"的包围盒),否则带子会被裁掉。
  42. "药丸 + 计数"整组居中时,计数宽度要按真实字号算:计数文本是 11px,而 units(s)*12+14 是按 12px 估的,"3 步" 会多估约 19px → 整组偏左将近 10px(看着就是"没居中")。加一个 textW(s, size)(全角=size、半角≈0.56×size、空格≈0.3×size)来估,误差能压到 1px 内,量测偏差从 -9.1px 降到 -0.9px。
  43. 角标"刚好放得下"等于放不下:胶囊(起点/终点)的左上角是 h/2 圆弧,角标圆心在 (18.5, 20)、半径 9 时,到弧心 (h/2, h/2) 的距离 + 9 几乎正好等于 h/2 —— 量出来只剩 0.6~0.7px,而圆环自己描边 0.6、节点描边 0.75(缩小时 1.3)都在往外长,于是圆环就压在轮廓上、看着"出框"。修法不是给个 +1 的松弛量,而是按形状求安全位置:shapeClearance() 用正确的形状模型(圆角矩形 = 内缩矩形 ⊕ 半径 12 的圆;胶囊 = 线段 ⊕ 半径 h/2 的圆;菱形 = 四边距离),沿圆周采样取最小余量,不满足 4.5px 就把角标往右推(0.5px 步进),菱形不推(角标在正中本来就是最内的点)。修完胶囊 0.7→4.6px,圆角卡片本来就 9.5px 不动。
  44. 验证形状内包含关系时,先把形状模型写对:我第一版把"圆角矩形"建模成"对角线线段 ⊕ 圆",量出来一堆假的"出框 1.2~2.6px",白排查一轮。圆角矩形的正确模型是内缩矩形(不是内缩线段)⊕ 圆。
  45. 同一个坑的第二个受害者:右上角的时间药丸。修完角标别急着收工——tx = n.w - 12 - tw(右边距固定 12)对圆角卡片是对的(余量 8.5px),但胶囊节点的右上角是 h/2 的弧,药丸的右上角就顶出去了:某个终点胶囊(287×108)量出来 -3.6px(起点胶囊只剩 0.7px)。同一个 shapeClearance() 拿来给"高 21 的胶囊"采样(两端半圆 + 上下直边)就能用,fitPill() 往左推/往下挪找位移最小的解,修完 3.5px。规律:节点形状一旦不是圆角矩形,所有贴边的小配件(角标、药丸、边标签)都要重新验证。
  46. .ps1 里连注释都别写中文:不只是中文路径会乱码——注释里的中文按 GBK 解码后,某个字节可能正好落在 0x60(反引号)上,PowerShell 把它当续行符,下一行代码就被吃进注释里(我的一次拼图脚本因此 $cw 没赋值,输出了一张 16×72 的空图)。写 .ps1 只用 ASCII,中文放到 .mjs(UTF-8)或页面里。
  47. 静态首页是快照,删文件后必须重新生成:file:// 方式打开的 index.html 是渲染时顺手写的快照,而且会把目录里非流程图的 .html 当成 dev 卡片列出来(我的临时自测页 _selftest.html 就被列成了「无预览图」的卡片)。原来的 index 命令只重写 流程索引.json、不重写首页 → 临时页删掉后首页仍留幽灵卡片。已让 cmdIndex 也 await buildHome() 重写首页。排错时记住:HTTP(8765) 的首页是每次请求现算的、不会有幽灵卡片,只有 file:// 模式才会。
  48. 竖版默认"铺满宽度"在手机上是错的:长流程铺满宽度 = 手机一屏只能看到四五个环节(13 个环节的流程实测 294%、只见 4 个),用户的原话是"看到的部分太少了"。改成 K = min(铺满宽度, 整幅适配 × 1.45)(fitcap 可调)后一屏 9 个;同时 flowviz-zoom:<文件>:<版式> 记住用户手动缩放的档位(滚轮/捏合/± 与"适配"都写),下次打开就是他习惯的大小。教训:默认视野要按"最小可用信息量"定,而不是按"撑满屏幕"定。
  49. Windows 完整性标签(Integrity Label)丢了会让"覆盖旧文件"神秘失败:症状很怪——新建文件成功、覆盖已有文件 Access denied;原因是目录有 Low Mandatory Level 标签(新建的子文件会继承 → 能写),而更早创建的旧文件没有标签(默认 Medium)→ 沙箱的低完整性令牌对它是 "no-write-up" 被拒。查法:icacls <文件> 末尾有没有 Mandatory Label\Low Mandatory Level;修法(要提权/无沙箱执行):icacls <目录> /setintegritylevel "(OI)(CI)Low",旧文件单独补 icacls <目录>\* /setintegritylevel Low。背景:DSH 的工作区授权靠"当前用户 FullControl + Low 标签"两件事,dsh-sandbox-windows-acl 的"补权限"脚本只备份/恢复 DACL,如果它写 DACL 时丢了 SACL 里的标签,就会留下这个坑(我踩过:三个目录被补过权限后,只有新建文件能写)。
  50. 工作区里"删不掉文件"多半是设计如此,不是 bug:根目录上有 Everyone:(CI)(DENY)(DC)(DC = 删除子项),所以任何账号(含你自己)都删不掉工作区里已有的文件/子目录——DSH 用它保证 agent 不能删你的东西;serve.mjs 的"回收站"端点在受限沙箱里同样会失败(移文件也算删除)。要删文件只有三条路:资源管理器里手动删(同意 UAC/提权后仍受该 DENY 约束,实际上同样会失败)、把 DSH 会话切到 full access、或让 DSH 自己清理。别把它当成 ACL 坏了反复修。
  • 样例产物:.\out\(示例-饮品店日常.html、示例-报销审批.html、index.html、流程索引.json)。

七、2026-10 新增:版本号 / 批量刷新 / 解析契约 / 可访问性

  • 批量刷新:node scripts/flow.mjs refresh <目录> —— 扫目录里所有规格(*.json 含 nodes、*.mmd/*.md)重渲染,并重建「流程索引.json」与静态首页。产物多时用它。
  • 版本与「旧版」提示:产物写 <meta name="flow-version" content="x.y.z">(渲染器常量 FLOW_VERSION),清单记 generatorVersion 与每项 version;首页把版本不符的卡片标成「旧版渲染」并提示跑 refresh。改了渲染或前端行为就 +1。
  • 产物不会自动更新:自包含快照、离线无 CDN。要更新必须重渲染(或让 agent 干)。
  • 示例只在新手期出现:首页的空目录引导里有一个「先看个演示 →」,指向技能包自带的 docs/demo.html(服务模式 /__demo 只读路由,file:// 下用相对路径)。它只在该目录还没有流程图时渲染,有第一张就自动不再出现 —— 演示文件不进用户目录,所以没有"要记得删"的负担。给用户解释"演示图会不会自动消失"时按这个机制答。
  • 删除/改名后侧栏仍显示:file:// 下页面内嵌的是生成当时的清单快照 → 跑 flow.mjs index <目录>(或 refresh)重建;HTTP 模式(serve.mjs)会实时刷新并在删除/改名后自动重建(refreshDerived())。
  • "能不能跑"取决于有没有代码执行环境,而不是模型名字:ChatGPT / Codex / Claude Code / Cursor 终端 / 豆包等各家 agent 模式 / CI 这些带沙箱或终端的环境里可以直接跑(产物与本地逐字节相同,因为渲染是确定性的);**纯聊天(没有执行能力)**跑不了 Node:它们"生成"的 HTML 往往是拿 docs/demo.html 改数据或手写仿制品。DeepSeek 手机版(App)目前测试不可用:没有执行环境,给用户的建议是"电脑上渲染好再把 HTML 发到手机",或起 serve.mjs 让手机浏览器访问。验收三步:Ctrl+F 搜 flow-viz / flow-version / flowviz-zoom;搜是否残留示例的 饮品店;看画布文字与连线是否与规格一致。
  • 解析契约:退出码 0/1/2(成功 / check 有结构问题 / 参数或输入错误);错误格式 flow: 文件:行:列: 说明;未知参数直接报错,不静默。
  • 无害指令白名单:classDef / class / style / linkStyle / click / direction / accTitle / accDescr 忽略并提示;其余不支持语法继续报错。
  • 四道回归门(tools/tests/,零依赖):parse-errors.mjs(14 例解析契约)、mermaid-roundtrip.mjs(4 例往返)、contrast.mjs(主题对比度)、regress.mjs check demo(几何 + 逐字节产物回归)。

新增踩坑:

X-1. var 声明在"循环里"、却在"循环外"用 → 每次事件都抛 ReferenceError(最阴的一次):lastTouch 写在 VN.forEach(function(n){…}) 里,而 window 上的 mouseup 处理器在循环外引用它 ✗ → 每次松开鼠标都抛异常,于是"点节点没反应"(选中分支在异常之后)、"图黏在鼠标上"(drag=null 也在异常之后)两个症状同时出现,而 node --check 根本查不出来。教训:① 跨作用域的状态(drag / lastTouch)一律放最外层;② 浏览器行为探针必须监听 window.onerror 并把异常打进报告 —— 我在这条上绕了四轮,就是因为只看"现象"不看异常。 X0. 别拿"导出的 .mmd"当源重渲染:Mermaid 语法存不下 时间/负责人/说明/备注 四个字段,而导出件里恰好只有 id/label/形状。踩过一次(某张真实的业务流程图)。三道防线:① 导出件首行写 %% workflow-visualizer 导出件…,refresh 见到就跳过;② refresh 在做 .mmd 源之前会读一遍同名产物,只要里面有几个节点带时间/负责人/说明就跳过并提示;③ 实在要从页面反推源,用 flow-data 里的 JSON 重新生成规格(_verify/recover-spec-from-html.mjs 就是干这个的)。

  1. --hit 只定义在 <svg> 里 → 页面 CSS 拿不到:搜索结果清单在 <aside>(SVG 外),var(--hit) 解析失败会让「关键词底色 / 当前行描边 / 序号圆点」三条规则静默失效。自定义属性必须在使用处的作用域(或 :root)也有定义。
  2. visibility:hidden 的过渡延迟会让「打开即聚焦」失效:抽屉用 transition:visibility 0s .18s 收起,打开那一瞬 visibility 仍是 hidden(过渡取自变更前的状态),focus() 直接失败。要把关闭态移出 Tab 序,用 inert(立刻生效、不动动画)。
  3. color-mix 的比例是「加入的颜色占比」:color-mix(in srgb, 组色 53%, 墨色) 是 53% 组色——比例越大越浅。压深浅色主题时方向搞反过一次,被 contrast.mjs 当场抓住(最低只有 3.39:1)。配色改动一律先跑对比度门。
  4. 同一文件的多处替换不能「先全部算好再一起写」:每次写盘都基于原始内容,后一条会覆盖前一条。正确做法是逐条「读—改—写」。这个坑让我丢过一次改动(表现为「补丁报成功但功能没生效」)。
  5. PowerShell 的 [System.IO.File]::ReadAllText 用的是进程工作目录,不是 cd 之后的 PowerShell 位置;变量名大小写不敏感,$T(目录)被 $t(文件内容)覆盖过一次,于是把文件内容当路径用了。跨目录写文件一律用绝对路径。

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.