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") + } +}