From b95dab91d89cc81a2ae79f2f6d182efcf70fd731 Mon Sep 17 00:00:00 2001 From: yetone Date: Tue, 29 Sep 2026 16:29:33 +0800 Subject: [PATCH] =?UTF-8?q?agents:=20"Show=207=20more"=20unrolls=20the=20r?= =?UTF-8?q?ows=20downwards=20(Image=20#22:=20=E7=82=B9=E5=87=BB=E8=BF=99?= =?UTF-8?q?=E9=87=8C=E4=B8=BA=E4=BB=80=E4=B9=88=E6=98=AF=E5=90=91=E4=B8=8A?= =?UTF-8?q?=E5=B1=95=E5=BC=80)=20=E2=80=94=20a=20click=20holds=20what=20wa?= =?UTF-8?q?s=20clicked=20where=20it=20is=20on=20the=20screen=20(v0.1.377),?= =?UTF-8?q?=20and=20the=20button=20sits=20under=20the=20rows=20it=20opens,?= =?UTF-8?q?=20so=20as=20they=20grew=20above=20it=20the=20view=20scrolled?= =?UTF-8?q?=20to=20keep=20it=20put=20and=20the=20page=20rode=20up=20past?= =?UTF-8?q?=20them.=20A=20control=20marked=20data-unrolls=20is=20held=20by?= =?UTF-8?q?=20the=20part=20it=20is=20in=20instead:=20the=20rows=20open=20d?= =?UTF-8?q?own=20and=20the=20button=20goes=20down=20with=20them.=20click-s?= =?UTF-8?q?croll.test.cjs=20checks=20it=20in=20Chromium=20and=20WebKit=20(?= =?UTF-8?q?it=20fails=20without=20the=20change)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/gui/assets/app.js | 8 +++++++- internal/gui/tests/README.md | 3 ++- internal/gui/tests/click-scroll.test.cjs | 24 ++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/internal/gui/assets/app.js b/internal/gui/assets/app.js index 5d1777e8..c761bad9 100644 --- a/internal/gui/assets/app.js +++ b/internal/gui/assets/app.js @@ -373,6 +373,7 @@ function renderAgents() { } fold.append(inner); const more = el("button", "agent-more"); + more.dataset.unrolls = ""; // it goes down with the rows it opens const label = el("span", "", ""); const chev = el("span", "chev"); chev.append(svg(CHEV, 10, 1.8)); @@ -6310,6 +6311,10 @@ function savePrefs(body) { // filter, a day, a toggle leaves the page where it was. When what it does // leaves the page shorter under it (a list emptied), the view keeps room // at its foot for it to stay, room that goes as the reader scrolls back. +// A control under what it unrolls (data-unrolls: "Show 7 more") is the +// exception: it goes down with what it opens, and what's held is the +// part it is in, so the rows open downwards rather than the page riding +// up past them. // // Code moves a view only in answer to a click that asks to go somewhere, and // shows that with the reader's event: scrollOnPurpose(e). Called without one @@ -6402,7 +6407,8 @@ addEventListener("click", (e) => { const v = e.target.closest?.(".view"); if (!v || v.hidden) { held = null; return; } const chain = []; - for (let n = e.target; n && n !== v; n = n.parentElement) { + const from = e.target.closest?.("[data-unrolls]")?.parentElement || e.target; + for (let n = from; n && n !== v; n = n.parentElement) { for (const m of [n, n.previousElementSibling, n.nextElementSibling]) if (m instanceof HTMLElement && m.offsetParent) chain.push([m, onScreen(m, v)]); } held = chain.length ? { v, chain, until: performance.now() + 4000 } : null; diff --git a/internal/gui/tests/README.md b/internal/gui/tests/README.md index d7f6ebf3..a3ed5b90 100644 --- a/internal/gui/tests/README.md +++ b/internal/gui/tests/README.md @@ -9,7 +9,8 @@ and dismissal. It does not start the backend or read local user configuration. (see "where the reader is" in `app.js`): on the Routing page, Live and a day picked in turn with the list scrolled to its end; a click whose handler sets scrollTop, grows the page above, redraws itself, or scrolls from a timer or a -shared helper; a click that asks to go somewhere with `scrollOnPurpose(e)`; +shared helper; a control under what it unrolls (`data-unrolls`) going down +with it; a click that asks to go somewhere with `scrollOnPurpose(e)`; the room kept at the foot going as the reader scrolls back; and the wheel. With Node.js and Playwright available: diff --git a/internal/gui/tests/click-scroll.test.cjs b/internal/gui/tests/click-scroll.test.cjs index 0bee4b72..3fe196a6 100644 --- a/internal/gui/tests/click-scroll.test.cjs +++ b/internal/gui/tests/click-scroll.test.cjs @@ -133,6 +133,30 @@ for (const engine of (process.env.BROWSER ? [process.env.BROWSER] : ["chromium", } }); + await t.test("a control under what it unrolls goes down with it", async () => { + await reset(); + await page.locator(view).evaluate((v) => { + const s = document.createElement("div"); + s.style.cssText = "flex:none;height:1600px"; + const part = document.createElement("div"); + part.id = "part"; part.style.flex = "none"; + const head = document.createElement("div"); + head.id = "head"; head.textContent = "head"; + const fold = document.createElement("div"); + const b = document.createElement("button"); + b.id = "more"; b.textContent = "more"; b.dataset.unrolls = ""; + b.onclick = () => { fold.style.height = "300px"; }; + part.append(head, fold, b); + v.append(s, part, Object.assign(document.createElement("div"), { style: "flex:none;height:600px" })); + }); + await scrollTo("#more"); + const head = await top(page, "#head"), more = await top(page, "#more"); + await page.locator("#more").click(); + await settle(page); + assert(Math.abs((await top(page, "#head")) - head) <= 1, "what's above the fold must stay put"); + assert(Math.abs((await top(page, "#more")) - more - 300) <= 1, "the control must go down with the rows"); + }); + await t.test("a click that asks to go somewhere does", async () => { await reset(); await page.locator(view).evaluate((v) => {