A new picture puts itself first in its line, so a press on a picture ended on another one and was no click. While the main button is held on a button of the list, what comes waits until the press and its click are over. Tested with a real mouse on an isolated ComfyUI: a press held until a new picture came opened the pressed picture 4 times of 4 (0 before), and 16 of 16 long presses during runs answered (13 before). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
16 KiB
踩过的坑(Lessons)
反复出现的坑和严重故障。每条写清:现象、根因、怎么发现的、以后怎么做。
2026-09-26 · v1.57.2:一条 :has() 样式让拖动画布掉帧(严重)
现象:B 站用户反馈装了插件后拖动工作流 10% low 只有二十几帧;和 ComfyUI Prompt Assistant 一起装时,大工作流只剩个位数帧甚至卡死。插件界面关着也一样。
根因:web/styles/01-shell-tools.css(b6acd09,2026-09-19,进了 v1.57 / v1.57.1)
body:has(#anomalous-toolbox-modal[style*="display: flex"]) #anomalous-sidebar-tooltip-bubble, …
:has() 里放属性选择器(这里是 [style*=…]),浏览器在页面上任何元素的该属性变化时,都要重新判断 body:has(...)。ComfyUI 平移、缩放画布时每一帧都在改每个 DOM 控件(文本框)的 style,于是每帧都要付出和页面元素数量成正比的样式计算。Prompt Assistant 给每个文本框加一组按钮,页面元素多十几倍,开销跟着放大。两个插件本身没有冲突。
实测(真 ComfyUI 前端 1.53.6 + Chromium,300 节点工作流平移 120 帧,样式重算合计):
| 情况 | 修复前 | 修复后 |
|---|---|---|
| 不装插件 | 60 ms | — |
| 只装 AMB | 690 ms | 63 ms |
| AMB + Prompt Assistant | 2300 ms(约 19 ms/帧,超过 60 帧的一整帧) | 86 ms |
| 只装 Prompt Assistant | 80 ms | — |
逐条删规则二分:只删掉这 7 条 :has() 就回到基线,其中罪魁是带 [style*=…] 的那条;button:has(.x) 等其他 :has 测不出开销。
修复:删掉这条规则。它是多余的:工具箱和设置窗口打开前都会调用 hideTooltipImmediately(),showSharedTooltip 在窗口开着时也会拒绝显示。
以后怎么做
- 不要用
:has()或[style*="display: …"]去判断界面开关状态。要表示“开着”,由 JS 加减一个 class(作用在自己的容器上),或者直接在 JS 里处理。 - 全局生效的昂贵选择器(
body:has、html:has、*、未限定作用域的button、textarea)会作用在 ComfyUI 的每一帧上。新增前先问:它会不会在画布拖动时被反复判断? - 性能问题要实测,别只看代码猜:
- 装一个真的 ComfyUI(
--cpu即可),用 Playwright 在空白处拖动画布; - 用 CDP 的
Performance.getMetrics比较RecalcStyleDuration; - 再在页面里删掉可疑规则做二分(遍历
document.styleSheets,对插件自己的样式表deleteRule)。 - 没有显卡时总帧时间不准,但样式计算的倍数可信。
- 装一个真的 ComfyUI(
2026-09-26 · 修复只在本机 main,发布版没带上
现象:素材库“选中节点时卡片错位”(应用按钮重复、图标重复成 ⚡ ⚡、标签日期挤在一起)的修复 01fc925、9e761d0 早在 9 月 24 日就做了,但只进了 feature/audio-engines(通过“Merge main … into feature”)。GitHub 上的 main 并没有,发布版 v1.57.1 的用户一直带着这个问题。
根因:修复提交在本机 main 上,只把 main 合进了 feature,本机 main 从未推送。
以后怎么做
- 发布或回答“修了没有”之前,用
git branch -r --contains <提交>确认修复在远端的哪个分支,别只看本机。 - 本机 main 有未推送提交时,先推送或说明,再做合并。
- 这次已把两个提交 cherry-pick 进远端 main(e1ac84f、4e8603b),随 v1.57.2 发布。
2026-09-26 · 发布热修复的检查项
- 改了 CSS 或 JS,要同步改缓存参数:
web/styles.css里对应的@import …?v=、browser_entry.js的styles.css?v=、main.js引用browser_entry.js的?v=。不然用户刷新后浏览器可能还用旧文件。feature 分支已去掉部分?v=,合并时以各分支现状为准。 CHANGELOG.md是 CRLF、LF 混合换行。用脚本编辑时按原字节读写(newline=''),否则整份文件都会显示成改动。- 云端会话能推分支,推不了标签,GitHub 工具也不能创建 Release。发布要在网页上完成:Draft a new release → 填新标签 → Target 选
main并核对提交号 → 粘贴说明 → Publish。插件的版本面板只列正式 Release,用 ComfyUI Manager 更新的用户则直接拉 main 最新提交。
2026-09-27 · 拆文件漏了 import,保存工作流在发布版里整个坏掉
现象:v1.57 到 v1.57.2,工作流页“保存当前工作流”只要工作流里有模型加载器就提示“保存工作流配方失败”。B 站用户反馈“就没成功过”。素材库走的是另一条路,所以能存。
根因:refactor(recipes): separate schema images and storage(6ad975c)把函数搬进 recipe_images.py,但它调用的 _resolve_exact_model_reference 和 hashlib 留在了旧文件里。同一轮拆分还让 media_routes.py 少了 time、tempfile:卡片缩略图出错后被 except Exception 吞掉,静默退回原图。另外 model_metadata.py 少了 _resolve_paths_to_model_info_sync。Python 只在那一行真正执行时才报 NameError,导入和其他测试都通过。唯一的保存测试关掉了模型预览快照,正好绕开了出错的那一行。
以后怎么做
- 拆模块后跑
tests/test_backend_names.py:后端文件读到却从没绑定的名字,它都会列出来。 - 行为测试要走默认路径。
recipe_payload默认关着快照,新测试test_saving_with_preview_snapshots_captures_the_model_cover专门走默认的“开着快照”。 - 真实按钮至少点一次:在隔离的 ComfyUI 里点“保存当前工作流”,一眼就能看出问题。
2026-10-12 · 页面往下滚后,点“返回”点到了下面的模型(第二次)
现象:从画布节点存的搭配(节点搭配),页面往下滚一点后,点“‹ 全部搭配”打开的是被滚到标题下面的模型封面。普通搭配和搭配列表没事。
根因:和 2026-10-09 修过的是同一个 Chrome 行为:滚动框里有 overflow: hidden 的元素(这里是模型封面的圆角框)或 content-visibility: auto 时,滚出可见区域的部分在点击判断里仍算“在那里”,盖住了上面的标题按钮。上次只给当时有问题的两个滚动框加了 clip-path: inset(0),之后新做的节点搭配页面又放进了带圆角封面的行。
怎么发现的:在隔离的 ComfyUI 里把节点存成搭配,把编辑区分 12 步滚到底,每一步用 document.elementFromPoint 取标题里每个按钮中心点的元素,看是不是按钮自己;滚到 140px 起就变成了封面 IMG。
以后怎么做
- 标题、标签栏下面的滚动框一律加
clip-path: inset(0)(它对点击判断也生效)。overflow本身裁不住这种情况。 - 新页面或新内容放进滚动框后,用上面的办法扫一遍:每个滚动框分步滚到底,检查每个可见按钮都能拿到自己的点击。
- 2026-11-03 第三次:当前节点 →「查看全部」打开的历史生成图库,滚下去后点“关闭图库”点到了图片。它的样式写在代码里(
ui_gallery.js),搜 CSS 找不到,所以上次没扫到。现在它和模型选择器的卡片区都加了clip-path: inset(0);扫的时候连写在代码里的滚动框一起扫(在页面上找overflow-y是 auto 或 scroll 的元素,不只搜 CSS 文件)。
2026-10-08 · ComfyUI-Manager 审核 Anomalous_TTS:三类安全口子
现象:Anomalous_TTS 申请收录进 ComfyUI-Manager(Comfy-Org/ComfyUI-Manager#3342),维护者两轮审核退回,先后指出:
- 下载来的东西被当代码执行:中文多音字模型从个人 ModelScope 账号的
master下载,不核对指纹,解压后执行包里的config.py;英文词典读 pickle。而网页请求还能改“去哪个文件夹找这些文件”,等于网页能决定运行什么代码。 - 网页请求决定删改哪里:
POST /anomalous_tts/storage从请求里取新的存放位置,跨盘移动后对旧位置rmtree。只检查“是不是本机请求”不够:本机网页也不该决定在哪里建、删文件夹。 - 按请求里的路径读任意文件:导入预览、导入检查会返回请求点名的任何文件内容。
根因:这些接口最初是为了方便(在界面里选文件夹、试听本机音频、一键挪角色),没有把“网页请求是不可信的输入”当成前提;“只接受本机请求”被当成了足够的保护。
怎么改的(接口版本 13):配置文件当数据解析,词典从文本建,不读写 pickle,测试拦住 pickle.load / exec_module / eval(;下载锁定版本并先核对 SHA-256 再解压;存放位置、角色文件夹、底模来源只从用户手写的设置文件读;导入只收上传的文件,没有接口再接受本机路径。
以后怎么做
- 两个仓库的规范都写了安全底线(本仓库 AGENTS.md 第 5 节“安全底线”,Anomalous_TTS 的 ARCHITECTURE.md“不能破的规矩”)。新接口按它写,审查时逐条对照。
- 本仓库的现状(2026-10-09 统计):55 个会写文件的接口,只有 MCP、模型导入、整理三处检查了本机请求;删除模型、改模型信息、开始下载、导入备份等在
--listen时局域网里的其他设备也能调用。没有执行下载内容、pickle、eval 的代码。2026-10-12 的 v1.58.1 一次补齐:api/local_guard.py一个中间件把关/anomalous/下所有会改东西的请求,只接受本机(见 backend.md“Requests from other devices”)。
2026-10-13 · v1.58.0–v1.58.2:同一种 :has() 又掉帧(第二次)
现象:#22(Lindemellis):216 个节点的工作流,开着 Prompt Assistant 时一次画布重绘 465–770 ms,拖动只有 2–3 帧。只删掉一条规则就回到 53 ms。
根因:图标栏(46facb9,2026-09-29,随 v1.58.0 发布)在 web/styles/15-shell-rail.css 里又写了
#anomalous-container.anomalous-has-rail:has(> .anomalous-nb-modal[style*="display: flex"]) #anomalous-sidebar-wrapper
和上面 v1.57.2 那条是同一种写法::has() 里按 style 属性匹配。v1.57.2 修好三天后就写回来了,因为经验只写在这份文档里,没有东西拦着;当时的性能实测也只跑了 main 和音频分支,没跑图标栏所在的分支。
修复:工作区开没开由 JS 在容器上加减 anomalous-workspace-open,CSS 只看这个 class(#22 的修复)。
以后怎么做
node tools/check_structure.mjs现在会拒绝:has()里出现[style…],报出文件和行号。- 同一个坑第二次出现,就把它做成检查,不再只补一段经验。
2026-10-29 · 一张 2.7 亿像素的图让图库卡死
现象:AI 把 5120×3328 的图再放大 4 倍,存成 20480×13312、324 MB 的 PNG。之后 AMB 图库一翻到它就很卡,AI 工作台里也一样。
根因:Pillow 默认拒绝打开超过约 1.79 亿像素的图(防“解压炸弹”)。缩略图生成用 except Exception: return source_path 兜底,失败时把原图当缩略图发给浏览器;浏览器每次都要下载 324 MB、解开 2.7 亿像素。看大图、AI 时间线放大、图片详情读参数也都直接拿原图。
怎么发现的:用户说图库“真的卡”,按文件大小找到这张图,在 ComfyUI 的 Python 里打开它,报 DecompressionBombError。
以后怎么做
- 缩略图失败的兜底只能是“小文件原样”或占位图,永远不是大原图(
media_routes.ORIGINAL_CARD_BYTES)。 - 超过 Pillow 上限的图在子进程里缩(
huge_picture.py),内存用完就还,ComfyUI 进程不碰它。 - 要比缩略图大的地方用
/anomalous/output_preview,不用/view原图;只有用户点“打开原图”才给原图。
2026-10-29 · 为一个小函数引出循环导入,AMB 整个页面加载失败
现象:隔离的 ComfyUI 上 AMB 没有加载,window.anomalousBrowserInstance 不存在,AI 的每个画布操作都报“页面没有及时回应”。控制台:Cannot access 'marker' before initialization。
根因:看大图的模块 ui_gallery_viewer.js 为了 formatSize 去导入 ui_model_download.js,后者经设置页、browser.js、导航一路导回 ui_ai_workbench.js;而 ui_ai_images.js → ui_gallery_detail.js → ui_gallery_viewer.js 又在这条链的开头。于是 ui_ai_workbench.js 在模块顶层调用 setMarker 时,ui_ai_images.js 还没执行完。结构检查和测试都只看语法和单个模块,抓不到。8197 上看过的页面恰好没触发这个加载顺序。
以后怎么做
- 底层模块(看图、卡片、纯函数)只从同样底层的模块取工具函数,不从“页面”模块(下载、设置、浏览器主对象)取,哪怕只是一个小函数;需要就挪到没有导入的小文件里(
file_size.js)。 - 改了导入关系,在真的 ComfyUI 页面上确认
window.anomalousBrowserInstance存在、控制台没有before initialization,再提交。
2026-11-05 · 偶尔打开 ComfyUI,AMB 整个不见了
现象:没有悬浮按钮,没有窗口,控制台里是几十条 Cannot access 'shared' before initialization;刷新一下又好了。大约十几次加载碰到一次。
原因:ComfyUI 把 web/ 下每个 .js 都当成扩展,同时各自导入一遍,谁先跑完全看网络。AMB 的模块有一个很大的循环导入,ui_ai_workbench.js 加载时就要写 ui_ai_results.js 里的 const shared。从 main.js 进来顺序是对的;碰巧先从 ui_ai_results.js 或 ui_ai_step_body.js 进来,shared 还没初始化,这个模块就报错,而且错误会被缓存,整个 AMB 跟着起不来。
做法:shared 搬进不导入任何东西的 ui_ai_shared.js。tests/module_cycles.mjs 查“加载时就读同一个循环里的 const”,这种写法以后过不了测试。
以后:会在加载时用到的值,放在循环外面的模块里。别只从 main.js 想加载顺序。要逐个模块验证,在浏览器里给每个模块开一个 iframe 单独 import。
2026-11-05 · 存档条和存档页的按钮第一下没反应
现象:在提示词框打完字直接点条上的 +,什么都没存,再点一下才存上;先点一下画布空白处就没事。改完次数直接点运行也一样。运行途中,正在输入的次数会被清掉。
根因:draw 每次都把整行换成新元素,而按下和松开之间元素离开了页面,浏览器就不发 click。这段时间里能触发重画的有三样:ComfyUI 到 window 的 mouseup 才发现文本框的改动并发 graphChanged(不是失焦时的 change,打字时它也不看);保存次数的请求十几毫秒就回来;运行结束的 status。
做法:ui_dom.js 的 settle 在已有元素上更新,一格只留给同一个存档,正在输入的框不动。图片行自己填内容,图没变就原样留着。元素留着还不够:新图排第一,整行右移,松手时鼠标下已经是另一张图,也不会有 click。所以按在存档页的按钮上时列表不动,这期间来的内容等松手、click 发完再画(holdStill)。
以后怎么做
- 用户可能正按着或正输入的控件,不要在异步事件里整块重建。
- 处理函数搬到留下的元素上后,要用事件上的元素,不能用闭包里新建的那个;存着元素的字段(
part.line)也要重新取。第一版漏了这点,存下的次数全是 1,只查“点击到了”的测试还是全过:要查结果存对了。 - 这类问题要用真鼠标、按住约 90 毫秒来测,
element.click()测不出来。
2026-11-04 · 存档跑出来全是画布上的内容
现象:两个存档跑 3 + 1 次,只出了一轮图。四次提交一模一样,第二个存档的改动和新种子都没进去,后三次全走缓存;运行前的检查查的也是画布。
根因:ComfyUI-Manager 把 app.graphToPrompt 包了一层,调用原函数时不带参数,传进去的离屏图被丢掉,ComfyUI 就用默认的画布。测试环境没装 Manager,所以当时没测出来。
以后怎么做
- 往 ComfyUI 的公共方法传参数前,先想到别的插件可能包过它;能从结果核对的就核对(
snapshot_plan.compiled看返回的workflow.id)。 - 依赖
app、api上这类方法的功能,验证时要带上 Manager 这样的常见包装;测试环境没有,就在页面上照它的写法包一层。