From 0bf0653d00f668661997e8b40ce1dfe55f02c709 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 4 Oct 2026 07:05:11 +0800 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=E4=BC=98=E5=85=88=E7=BA=A7=E9=A1=B5?= =?UTF-8?q?=E7=9A=84=E4=BF=9D=E5=AD=98/=E9=87=8D=E7=BD=AE/=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=8C=89=E9=92=AE=E6=AF=8F=E6=AC=A1=E6=89=93=E5=BC=80?= =?UTF-8?q?=E9=83=BD=E8=A2=AB=E7=94=BB=E5=B8=83=E6=B8=B2=E6=9F=93=E5=86=B2?= =?UTF-8?q?=E6=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit renderSortEditor 会整体替换 mount 的 innerHTML。per-key AUTO 弹窗的 mount 只有画布,覆写无害;但全局优先级页把 .card(连同里面装着工具栏的 h2)当 mount 传了进去,于是每次打开页面,画布一渲染就把工具栏删了。 症状很隐蔽:泳道和积木照常渲染、拖拽也正常,只是「保存排序」「重置」 「+ 添加档位」和聊天/生图切换全部消失 —— 优先级变得无从调整,也无从保存。 DOM 里数画布元素一切正常,只有量按钮的几何尺寸才看得见它没了。 修法:工具栏留在 .card 里,另给一个 #sort-mount 专供画布。per-key 弹窗 本来就是这个结构,无需改动。 判据 1 条,断言工具栏必须落在 #sort-mount 之外,且 renderSortEditor 必须 接收 #sort-mount 而不是 .card。变异(mount 改回 .card)判红。 生产 CDP 实测(真实点击,非仅 DOM 断言): 5 个按钮全部可见,坐标尺寸正常(保存排序 72x29) 画布 4 泳道 / 10 块 点击「保存排序」→ toast「排序已保存并热重载 · 链上冷却已复位」 切走再回来,按钮仍在位 Co-Authored-By: ModelRouter --- internal/gateway/ui/index.html | 13 +++++++-- internal/gateway/ui_key_auto_test.go | 43 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/internal/gateway/ui/index.html b/internal/gateway/ui/index.html index f35a998..e780f5b 100644 --- a/internal/gateway/ui/index.html +++ b/internal/gateway/ui/index.html @@ -3648,13 +3648,20 @@ recsState.keyNames = keyNames || {}; sortState.origin = JSON.stringify(sortState.lanes); const kindToggle = `
`; + // renderSortEditor replaces its mount wholesale, so the mount must be + // the canvas slot ALONE. Passing the .card (which holds the h2 with the + // save/reset/add buttons) used to wipe the toolbar on every open — the + // lane canvas survived, the buttons did not. The per-key modal never hit + // this because its mount only ever contained the canvas. $("#tab-sort").innerHTML = ` -

${t("sortTitle")}${kindToggle} +
+

${t("sortTitle")}${kindToggle} -

+

+
`; - renderSortEditor($("#tab-sort").querySelector(".card"), { + renderSortEditor($("#sort-mount"), { canvasId: "scr-canvas", }); } diff --git a/internal/gateway/ui_key_auto_test.go b/internal/gateway/ui_key_auto_test.go index 851a6b1..c49fea9 100644 --- a/internal/gateway/ui_key_auto_test.go +++ b/internal/gateway/ui_key_auto_test.go @@ -393,3 +393,46 @@ func TestAutoScopeConflictMessageComesFromCheckQuota(t *testing.T) { t.Fatal("checkQuota's AUTO branch must name the scope as the cause") } } + +// renderSortEditor replaces its mount's innerHTML wholesale. The global +// priority page used to hand it the .card that also holds the h2 toolbar, so +// every open deleted the save/reset/add buttons: the lanes still rendered and +// the drag worked, but there was no way to persist a change. The per-key modal +// never showed this because its mount held only the canvas. +// +// The regression is invisible in the DOM when you only count lane elements, so +// this asserts on the structure: the toolbar lives in a sibling of the mount, +// not inside it. +func TestGlobalSortToolbarSurvivesCanvasRender(t *testing.T) { + src := uiSource(t) + + // The global editor must mount into a dedicated host, not the .card. + i := strings.Index(src, `$("#tab-sort").innerHTML = `) + if i < 0 { + t.Fatal("global sort markup not found") + } + chunk := src[i:min(len(src), i+1400)] + if !strings.Contains(chunk, `id="sort-mount"`) { + t.Fatal("global sort page must provide a dedicated #sort-mount for the canvas") + } + // The mount handed to renderSortEditor must be that host. + if !strings.Contains(src, `renderSortEditor($("#sort-mount")`) { + t.Fatal("renderSortEditor must be given #sort-mount; passing the .card " + + "wipes the h2 toolbar that holds the save button") + } + // The toolbar (save in particular) must be OUTSIDE #sort-mount. + close := strings.Index(chunk, "`;") + if close < 0 { + t.Fatal("malformed #tab-sort template") + } + head := chunk[:close] + mountAt := strings.Index(head, `id="sort-mount"`) + saveAt := strings.Index(head, `onclick="saveSort()"`) + if saveAt < 0 { + t.Fatal("the global sort page has no save button at all") + } + if mountAt >= 0 && saveAt > mountAt { + t.Fatal("the save button sits inside the canvas mount and will be " + + "erased the moment the canvas renders") + } +}