mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
fix(studio): each pane's transcript has an id of its own
id="flowScroll" was written into Transcript, which renders once per pane, so two open panes answered to one id and the rail's aria-controls pointed at whichever came first — not necessarily the one it scrolls. The sweep only ever reported it with two panes open, which is a state its 23-step walk does not reach; it turned up on a session opened by hand. useId per pane. The stylesheet and the perf scripts move to [data-pane="flow"], an attribute the element already carried, so nothing depended on the id being the same string twice. Specificity drops from (1,0,0) to (0,1,0) and the four rules still land: .scroll is written earlier in the file, and the [hidden] rule gains a second attribute. #trajScroll in that same [hidden] rule has no element anywhere and is left alone.
This commit is contained in:
1 parent
e7bb095e67
commit
60b0288566
9 files changed
+23
-21
No files matched your search
@@ -82,7 +82,7 @@ async function stream(page, k) {
|
||||
long,
|
||||
longMs,
|
||||
nodes: document.querySelectorAll("*").length,
|
||||
items: document.querySelectorAll("#flowScroll .call, #flowScroll .find").length,
|
||||
items: document.querySelectorAll("[data-pane="flow"] .call, [data-pane="flow"] .find").length,
|
||||
trajRows: document.querySelectorAll("table.traj tbody tr").length,
|
||||
};
|
||||
}, k);
|
||||
|
||||
@@ -42,7 +42,7 @@ for (const { width, height } of [{ width: 1440, height: 900 }, { width: 640, hei
|
||||
const layout = await page.evaluate(() => {
|
||||
const box = (selector) => document.querySelector(selector)?.getBoundingClientRect();
|
||||
const prose = document.querySelector('.call[data-k="say"] .out .txt');
|
||||
const flow = document.querySelector("#flowScroll");
|
||||
const flow = document.querySelector("[data-pane="flow"]");
|
||||
const ask = document.querySelector(".ask");
|
||||
const tabs = document.querySelector(".ask-tabs");
|
||||
const foot = document.querySelector(".ask-foot");
|
||||
|
||||
@@ -51,7 +51,7 @@ const clickIn = (group, label) =>
|
||||
|
||||
const readPx = () =>
|
||||
page.evaluate(() => {
|
||||
const el = document.querySelector("#flowScroll .out .txt");
|
||||
const el = document.querySelector("[data-pane="flow"] .out .txt");
|
||||
return el ? parseFloat(getComputedStyle(el).fontSize) : 0;
|
||||
});
|
||||
const rootVar = (name) => page.evaluate((n) => getComputedStyle(document.documentElement).getPropertyValue(n).trim(), name);
|
||||
|
||||
@@ -107,7 +107,7 @@ const thumb = page.locator(".srail-view");
|
||||
await thumb.focus();
|
||||
await page.keyboard.press("End");
|
||||
await page.waitForTimeout(80);
|
||||
check("消息定位轨可用键盘滚动", await page.evaluate(() => document.querySelector("#flowScroll").scrollTop > 0));
|
||||
check("消息定位轨可用键盘滚动", await page.evaluate(() => document.querySelector("[data-pane="flow"]").scrollTop > 0));
|
||||
|
||||
const reduced = await browser.newPage({
|
||||
viewport: { width: 1440, height: 900 },
|
||||
|
||||
@@ -60,7 +60,7 @@ for (const n of SCALES) {
|
||||
const mem = await cdp.send("Runtime.evaluate", { expression: "performance.memory ? performance.memory.usedJSHeapSize : 0", returnByValue: true });
|
||||
rows.push({
|
||||
轮次: n,
|
||||
卡片: await page.locator("#flowScroll .call").count(),
|
||||
卡片: await page.locator("[data-pane="flow"] .call").count(),
|
||||
DOM节点: Math.round(after.Nodes ?? 0),
|
||||
"JS堆/MB": (mem.result.value / 1048576).toFixed(0),
|
||||
"开到这么长/s": (grow / 1000).toFixed(1),
|
||||
|
||||
@@ -43,12 +43,12 @@ await page.evaluate(async (n) => {
|
||||
}, 60);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const flow = page.locator("#flowScroll").first();
|
||||
const flow = page.locator("[data-pane="flow"]").first();
|
||||
const geom = () => flow.evaluate((el) => ({ top: el.scrollTop, h: el.scrollHeight, c: el.clientHeight }));
|
||||
|
||||
// 1) 挂载的块真的画出来了,而不是留下一片占位空白。
|
||||
const cards = await page.locator("#flowScroll .call").count();
|
||||
const blocks = await page.locator("#flowScroll .chunk").count();
|
||||
const cards = await page.locator("[data-pane="flow"] .call").count();
|
||||
const blocks = await page.locator("[data-pane="flow"] .chunk").count();
|
||||
check("底部卡片已挂载", cards > 0, `${cards} 张 / ${blocks} 块`);
|
||||
check("远处的块已卸载", cards < blocks * 48, `挂载 ${cards},全挂会是 ${blocks * 48}`);
|
||||
const visibleText = await flow.evaluate((el) => (el.innerText || "").trim().length);
|
||||
@@ -94,7 +94,7 @@ const tallBefore = (await geom()).h;
|
||||
await flow.evaluate((el) => el.scrollTo({ top: el.scrollHeight * 0.08 }));
|
||||
await page.waitForTimeout(700);
|
||||
const upTop = await page.evaluate(() => {
|
||||
const el = document.querySelector("#flowScroll");
|
||||
const el = document.querySelector("[data-pane="flow"]");
|
||||
const cards = [...el.querySelectorAll(".call")];
|
||||
const r = el.getBoundingClientRect();
|
||||
return cards.filter((c) => { const b = c.getBoundingClientRect(); return b.bottom > r.top && b.top < r.bottom; }).length;
|
||||
@@ -123,7 +123,7 @@ check("轨迹页有内容", trajRows > 50, `${trajRows} 行`);
|
||||
await page.screenshot({ path: `${SHOTS}/3-轨迹页.png` });
|
||||
await page.locator('[role="tab"]').nth(0).click();
|
||||
await page.waitForTimeout(400);
|
||||
const after = await page.locator("#flowScroll .call").count();
|
||||
const after = await page.locator("[data-pane="flow"] .call").count();
|
||||
check("切回后卡片仍在", after > 0, `${after} 张`);
|
||||
await page.screenshot({ path: `${SHOTS}/4-切回活动页.png` });
|
||||
|
||||
|
||||
@@ -1067,10 +1067,10 @@
|
||||
|
||||
/* 空流时压到底部。用 margin-top:auto 而不是 justify-content:flex-end ——
|
||||
后者在滚动容器上会把溢出的内容锁在视口外,滚不到。 */
|
||||
#flowScroll { display: flex; flex-direction: column }
|
||||
[data-pane="flow"] { display: flex; flex-direction: column }
|
||||
/* 空态是邀请,不是一片留白 */
|
||||
#flowScroll:has(.hero) { justify-content: center }
|
||||
#flowScroll:has(.hero) .flow { margin-top: 0 }
|
||||
[data-pane="flow"]:has(.hero) { justify-content: center }
|
||||
[data-pane="flow"]:has(.hero) .flow { margin-top: 0 }
|
||||
.hero { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 20px 24px 34px }
|
||||
.hero .rmark { width: 78px; height: 78px; color: var(--faint); opacity: .92 }
|
||||
.hero .rmark path { stroke-width: .42; animation: drawin 1.4s var(--ease-out) both }
|
||||
@@ -1097,7 +1097,7 @@
|
||||
}
|
||||
.stay:hover { color: var(--muted) }
|
||||
.pick:focus-visible, .stay:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px }
|
||||
#flowScroll[hidden], #trajScroll[hidden] { display: none }
|
||||
[data-pane="flow"][hidden], #trajScroll[hidden] { display: none }
|
||||
|
||||
/* Content is cut off under the tab strip with nothing saying "more above". The
|
||||
fade rides a scroll timeline, not a scroll listener: it stays off the main
|
||||
@@ -1133,8 +1133,8 @@
|
||||
.railhost { position: sticky; top: 0; height: 0; z-index: 4; pointer-events: none }
|
||||
/* 转录不画原生滚动条:视窗框和滚动条的滑块说的是同一件事(你在哪),
|
||||
并排出现就是两条一模一样的东西。留下能被标记的那一条。 */
|
||||
#flowScroll { scrollbar-width: none }
|
||||
#flowScroll::-webkit-scrollbar { display: none }
|
||||
[data-pane="flow"] { scrollbar-width: none }
|
||||
[data-pane="flow"]::-webkit-scrollbar { display: none }
|
||||
/* 宿主高度为零(它不能占布局),所以百分比高度在这里等于零 —— 轨道的高度
|
||||
由测量时写进来,它本来就在读滚动容器的几何。 */
|
||||
.srail {
|
||||
|
||||
@@ -32,6 +32,7 @@ interface Props {
|
||||
// Only the transcript on screen answers the keys; a pane on another tab keeps
|
||||
// its rail but must not fight for them.
|
||||
bound: boolean;
|
||||
controls: string;
|
||||
}
|
||||
|
||||
// The rail is a fisheye, not a map. Marks sit together in the middle at a fixed
|
||||
@@ -65,7 +66,7 @@ function pull(i: number, focus: number): number {
|
||||
return d > REACH ? 0 : (Math.cos((Math.PI * d) / REACH) + 1) / 2;
|
||||
}
|
||||
|
||||
export function Rail({ marks, total, scroll, flow, onJump, onGrab, bound }: Props) {
|
||||
export function Rail({ marks, total, scroll, flow, onJump, onGrab, bound, controls }: Props) {
|
||||
const host = useRef<HTMLDivElement>(null);
|
||||
const [tops, setTops] = useState<number[]>([]);
|
||||
const [view, setView] = useState({ top: 0, height: 0 });
|
||||
@@ -254,7 +255,7 @@ export function Rail({ marks, total, scroll, flow, onJump, onGrab, bound }: Prop
|
||||
style={{ top: view.top, height: view.height }}
|
||||
role="scrollbar"
|
||||
aria-label={t("你说过的话")}
|
||||
aria-controls="flowScroll"
|
||||
aria-controls={controls}
|
||||
aria-orientation="vertical"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type RefObject } from "react";
|
||||
import { memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type RefObject } from "react";
|
||||
import { decimals } from "../i18n/format";
|
||||
import { t } from "../i18n";
|
||||
import type { Item, Waiting } from "../state/session";
|
||||
@@ -120,6 +120,7 @@ export function Transcript({ items, entering, onEntered, revision, waiting, scro
|
||||
const [pinned, setPinned] = useState(true);
|
||||
const end = useRef<HTMLDivElement>(null);
|
||||
const flow = useRef<HTMLDivElement>(null);
|
||||
const scrollId = useId();
|
||||
const sizing = useRef({ px: SEED_PER_ENTRY, n: 0 });
|
||||
// Read from observer callbacks that must not be torn down and rebuilt every
|
||||
// time the reader crosses the bottom.
|
||||
@@ -446,12 +447,12 @@ export function Transcript({ items, entering, onEntered, revision, waiting, scro
|
||||
return (
|
||||
<div
|
||||
className="scroll"
|
||||
id="flowScroll"
|
||||
id={scrollId}
|
||||
data-pane="flow"
|
||||
ref={scroll}
|
||||
hidden={hidden}
|
||||
>
|
||||
<Rail marks={marks} total={items.length} scroll={scroll} flow={flow} onJump={jumpTo} onGrab={release} bound={!hidden} />
|
||||
<Rail marks={marks} total={items.length} scroll={scroll} flow={flow} onJump={jumpTo} onGrab={release} bound={!hidden} controls={scrollId} />
|
||||
<div className="flow-edge" aria-hidden="true" />
|
||||
{/* 空转录是「窗口的空白」,壁纸该在那儿;一有内容它就是内容了。 */}
|
||||
<div className="flow" ref={flow} data-empty={items.length === 0 ? "" : undefined}>
|
||||
|
||||
Reference in new issue
Block a user