mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 21:26:39 +00:00
* fix(frontend): make docs search work on desktop and mobile Docs search on deerflow.tech was broken twice over: no build step ever generated the Pagefind index that Nextra's <Search> loads, and the custom docs header that replaces Nextra's navbar never rendered a search box. - `pnpm build` now runs `search:index` (pagefind 1.5.2, pinned) to write public/_pagefind/ (gitignored). - The landing Header and its mobile sheet take an optional `search` slot; only the docs layout fills it, with localized labels and an aria-label. - Each docs page carries a Pagefind `lang` filter and queries are scoped to it, since the root <html lang> is fixed and would mix en/zh results. - globals.css restyles the results panel with app tokens because Nextra's <Head> variables are unset, lifts it above the mobile sheet, and keeps it tappable; the sheet no longer closes before a result navigates and closes after navigation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(frontend): add DESIGN.md describing the shipped design system Records the existing tokens, typography, components, docs (Nextra) overrides, motion, accessibility and copy rules, plus the landing-only decorative exceptions, so UI changes have something to be checked against. Linked from frontend/AGENTS.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(frontend): address Codex review of docs search - Chinese search: the root <html lang> is fixed, so Pagefind indexed zh pages with the English tokenizer and multi-character words (e.g. 偏好) never matched. scripts/build-search-index.mjs now sets each page's lang from its route before indexing, producing separate en-US and zh-CN indexes, and DocsHtmlLang syncs <html lang> at runtime (resetting Pagefind on a language switch) so queries hit the right index. - Mobile menu: reset the open state when the route changes instead of comparing against the saved path, so Back no longer reopens the sheet. - Shortcut: stop mounting a second <Search> in Nextra's unused mobile menu, which captured Cmd/Ctrl+K and focused an offscreen input at 640-767px. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(frontend): keep guidance within size budget * fix(frontend): address docs search review feedback Keep Nextra results within the mobile sheet scroll boundary and dismiss same-hash selections by pointer or keyboard. Rebuild Pagefind output without stale fragments and replace UI source-string checks with behavioral coverage. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
112 lines
3.9 KiB
TypeScript
112 lines
3.9 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
test.use({ viewport: { width: 390, height: 700 }, hasTouch: true });
|
|
|
|
for (const lang of ["en", "zh"]) {
|
|
test(`mobile ${lang} search results scroll and same-hash selection closes the menu`, async ({
|
|
page,
|
|
context,
|
|
}) => {
|
|
await page.goto(`/${lang}/docs#result-0`);
|
|
await expect(page.locator("html")).toHaveAttribute(
|
|
"lang",
|
|
lang === "zh" ? "zh-CN" : "en-US",
|
|
);
|
|
// Keep the real Nextra combobox, portal, Radix modal and browser scrolling.
|
|
// Only the search-engine response is deterministic; no generated index is needed.
|
|
await page.evaluate((language) => {
|
|
Object.defineProperty(window, "pagefind", {
|
|
configurable: true,
|
|
value: {
|
|
debouncedSearch: async (
|
|
_query: string,
|
|
options?: { filters?: { lang?: string } },
|
|
) => ({
|
|
results:
|
|
options?.filters?.lang === language
|
|
? [
|
|
{
|
|
data: async () => ({
|
|
url: window.location.pathname,
|
|
meta: { title: "Documentation" },
|
|
sub_results: Array.from({ length: 40 }, (_, index) => ({
|
|
url: `${window.location.pathname}#result-${index}`,
|
|
title: `Result ${index}`,
|
|
excerpt:
|
|
"A matching documentation section about sandboxes.",
|
|
})),
|
|
}),
|
|
},
|
|
]
|
|
: [],
|
|
}),
|
|
},
|
|
});
|
|
}, lang);
|
|
|
|
await page.getByRole("button", { name: "Open menu" }).tap();
|
|
const dialog = page.getByRole("dialog");
|
|
await dialog.getByRole("combobox").fill("sandbox");
|
|
const results = page.getByRole("listbox");
|
|
await expect(results.getByRole("option")).toHaveCount(40);
|
|
await expect(results).toBeVisible();
|
|
expect(
|
|
await results.evaluate((panel) =>
|
|
Boolean(panel.closest('[role="dialog"]')),
|
|
),
|
|
).toBe(true);
|
|
|
|
// Wait for the sheet/anchored panel's opening transition before gestures.
|
|
await results.hover({ position: { x: 20, y: 60 } });
|
|
const bounds = (await results.boundingBox())!;
|
|
await page.mouse.wheel(0, 300);
|
|
await expect
|
|
.poll(() => results.evaluate((panel) => panel.scrollTop))
|
|
.toBeGreaterThan(0);
|
|
|
|
const beforeTouch = await results.evaluate((panel) => panel.scrollTop);
|
|
const client = await context.newCDPSession(page);
|
|
const point = {
|
|
x: bounds.x + bounds.width / 2,
|
|
y: bounds.y + bounds.height - 60,
|
|
};
|
|
await client.send("Input.dispatchTouchEvent", {
|
|
type: "touchStart",
|
|
touchPoints: [point],
|
|
});
|
|
await client.send("Input.dispatchTouchEvent", {
|
|
type: "touchMove",
|
|
touchPoints: [{ ...point, y: point.y - 150 }],
|
|
});
|
|
await client.send("Input.dispatchTouchEvent", {
|
|
type: "touchEnd",
|
|
touchPoints: [],
|
|
});
|
|
await expect
|
|
.poll(() => results.evaluate((panel) => panel.scrollTop))
|
|
.toBeGreaterThan(beforeTouch);
|
|
await expect(dialog).toBeVisible();
|
|
|
|
await results.evaluate((panel) => {
|
|
panel.scrollTop = 0;
|
|
});
|
|
await results.getByRole("option", { name: /^Result 0\b/ }).tap();
|
|
await expect(dialog).toBeHidden();
|
|
await expect(page).toHaveURL(new RegExp(`/${lang}/docs#result-0$`));
|
|
|
|
await page.getByRole("button", { name: "Open menu" }).tap();
|
|
const input = dialog.getByRole("combobox");
|
|
await input.fill("sandbox");
|
|
await expect(results.getByRole("option")).toHaveCount(40);
|
|
await input.press("ArrowDown");
|
|
await input.press("Home");
|
|
await expect(input).toHaveAttribute(
|
|
"aria-activedescendant",
|
|
(await results.getByRole("option").first().getAttribute("id"))!,
|
|
);
|
|
await input.press("Enter");
|
|
await expect(dialog).toBeHidden();
|
|
await expect(page).toHaveURL(new RegExp(`/${lang}/docs#result-0$`));
|
|
});
|
|
}
|