Files
DeepSeek-Reasonix/desktop/frontend-next/perf/crumb.mjs
T
SivanCola dbc39493d6 fix: harden grouped checkout ownership and sync studio
Problem: Grouped projects could open the source after an explicit worktree
selection, hide source-alias history, omit inferred launcher exclusions, or
remain unusable when their source folder disappeared. Copied Git metadata
could also claim another checkout's project identity.

Root cause: Display identity was reused as execution and catalog authority.
Project inference duplicated an unbounded marker walk without validating
managed checkout containment or the reverse Git registration.

Fix: Preserve selected directories and all catalog members, choose a surviving
project directory, and use complete member roots for editor discovery and
launch. Reuse shared checkout identity and validate its managed boundary,
local prefix and registration backlink. Merge studio, move root resolution to
its project owner to preserve the file-size budget, and align the title guard
with the intended checkout label while retaining spacing and overflow limits.

Verification: Failing original-head HTTP/path regressions pass after the fix.
Frontend tests (629 files, 5,221 tests), production build, focused race tests,
go vet, make lint and make check pass. Added a 100-root tree benchmark and a
deterministic delayed-header test; repository suites and browser guards cover
adjacent behavior. Hosted checks must validate the replacement PR head.
2026-10-17 17:28:59 +08:00

48 lines
2.5 KiB
JavaScript

// The session title sits right beside the project and checkout in the title bar, at every width, and the
// project name still gives way (ellipsis) before the title is pushed off. Layout is a real-browser question.
import { chromium } from "playwright";
const BASE = process.env.PERF_URL ?? "http://localhost:4399/perf.html?pref=zh&ws=2&sess=6&turns=2";
const fails = [];
const check = (name, ok, detail = "") => {
console.log(`${ok ? " ok" : "FAIL"} ${name}${detail ? " — " + detail : ""}`);
if (!ok) fails.push(name);
};
const browser = await chromium.launch();
try {
for (const width of [1400, 1000, 700]) {
const page = await browser.newPage({ viewport: { width, height: 640 } });
await page.goto(BASE, { waitUntil: "networkidle" });
await page.waitForSelector(".chrome .crumb-proj");
const at = await page.evaluate(() => {
const box = (sel) => document.querySelector(sel).getBoundingClientRect();
const sep = box(".chrome .crumbsep"), title = box(".chrome .crumb > b");
const projEl = document.querySelector(".chrome .crumb-proj");
const range = document.createRange();
range.selectNodeContents(projEl);
const text = range.getBoundingClientRect();
const tags = [...document.querySelectorAll(".chrome .crumb .isolab")].filter((el) => !el.hidden);
const edge = tags.at(-1)?.getBoundingClientRect().right ?? text.right;
return { gap: Math.round(sep.left - edge), titleRight: Math.round(title.right) };
});
check(`${width}px: the title follows the project and checkout`, at.gap >= 0 && at.gap <= 12, `gap ${at.gap}px`);
check(`${width}px: the title stays on screen`, at.titleRight <= width, `right edge ${at.titleRight}px`);
const long = await page.evaluate(() => {
document.querySelector(".chrome .crumb-proj").textContent = "a-very-long-project-folder-name-".repeat(8);
document.querySelector(".chrome .crumb > b").textContent = "a very long session title ".repeat(8);
const crumb = document.querySelector(".chrome .crumb").getBoundingClientRect();
const title = document.querySelector(".chrome .crumb > b").getBoundingClientRect();
return { overflow: Math.round(title.right - crumb.right), titleWidth: Math.round(title.width) };
});
check(`${width}px: long names stay inside the title bar`, long.overflow <= 0 && long.titleWidth > 0, `title overflows by ${long.overflow}px`);
await page.close();
}
} finally {
await browser.close();
}
if (fails.length) {
console.error(`\n${fails.length} failed: ${fails.join("; ")}`);
process.exit(1);
}