#!/usr/bin/env node
"use strict";

const assert = require("node:assert/strict");
const fs = require("node:fs");
const http = require("node:http");
const net = require("node:net");
const os = require("node:os");
const path = require("node:path");
const { spawn } = require("node:child_process");
const { chromium } = require("playwright");

const TASK = "task494-filetree-lazy-loading-dedup-smoke";
const UI_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_UI_TIMEOUT_MS || 30_000);
const TEST_PASSWORD = process.env.MNOTE_E2E_PASSWORD || ["Mnote", "E2E", "123!"].join("");

function resolveChromiumExecutablePath() {
  const explicit = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || "";
  if (explicit && fs.existsSync(explicit)) return explicit;
  return [
    "/usr/bin/google-chrome-stable",
    "/usr/bin/google-chrome",
    "/snap/bin/chromium",
    "/usr/bin/chromium",
    "/usr/bin/chromium-browser",
  ].find((candidate) => fs.existsSync(candidate)) || "";
}

function pickPort() {
  return new Promise((resolve, reject) => {
    const server = net.createServer();
    server.listen(0, "127.0.0.1", () => {
      const address = server.address();
      const port = address && typeof address === "object" ? address.port : 0;
      server.close(() => resolve(port));
    });
    server.on("error", reject);
  });
}

function waitForHttpOk(url, timeoutMs) {
  const deadline = Date.now() + timeoutMs;
  return new Promise((resolve, reject) => {
    const tick = () => {
      const request = http.get(url, (response) => {
        response.resume();
        if (response.statusCode >= 200 && response.statusCode < 500) {
          resolve();
          return;
        }
        retry();
      });
      request.on("error", retry);
      request.setTimeout(1000, () => {
        request.destroy();
        retry();
      });
    };
    const retry = () => {
      if (Date.now() > deadline) {
        reject(new Error(`server_not_ready: ${url}`));
        return;
      }
      setTimeout(tick, 250);
    };
    tick();
  });
}

function fileUrlToPath(value) {
  const url = new URL(value);
  return decodeURIComponent(url.pathname);
}

function localMarkdownDocumentPath(documentId) {
  const encoded = String(documentId || "").replace(/^local-md:/, "");
  return decodeURIComponent(encoded.replace(/~/g, "%"));
}

async function main() {
  const port = await pickPort();
  const baseUrl = `http://127.0.0.1:${port}`;
  const dataRoot = fs.mkdtempSync(path.join(os.tmpdir(), `${TASK}-`));
  const dbPath = path.join(dataRoot, "control-plane.sqlite3");
  const actorId = `${TASK}-${process.pid}-${Date.now()}`;
  const server = spawn("cargo", ["run", "-p", "mnote-web", "--bin", "mnote-web"], {
    cwd: path.join(__dirname, "..", "rust"),
    env: {
      ...process.env,
      MNOTE_WEB_BIND: `127.0.0.1:${port}`,
      MNOTE_WEB_PUBLIC_BIND: `127.0.0.1:${port}`,
      MNOTE_WEB_ENABLE_LEGACY_NEXT_COMPAT: "0",
      MNOTE_CONTROL_PLANE_DB_PATH: dbPath,
      MNOTE_LOCAL_WORKSPACE_BASE_DIR: dataRoot,
    },
    stdio: ["ignore", "pipe", "pipe"],
  });
  let stderr = "";
  server.stderr.on("data", (chunk) => {
    stderr += chunk.toString();
  });

  const executablePath = resolveChromiumExecutablePath();
  const browser = await chromium.launch({
    headless: true,
    ...(executablePath ? { executablePath } : {}),
  });
  const context = await browser.newContext();
  const page = await context.newPage();
  const browserDiagnostics = [];
  let targetChildrenRequests = 0;
  let staleScopeChildrenRequests = 0;
  let scopedRootProjectionRequests = 0;
  let workspaceRootProjectionRequests = 0;
  let localWatchRevision = 0;

  page.on("console", (message) => {
    browserDiagnostics.push(`console:${message.type()}:${message.text()}`);
  });
  page.on("pageerror", (error) => {
    browserDiagnostics.push(`pageerror:${error.message}`);
  });
  await page.route("**/api/tree/projections/file**", async (route) => {
    const url = new URL(route.request().url());
    const parentRelativePath = url.searchParams.get("parentRelativePath") || "";
    if (url.pathname.endsWith("/api/tree/projections/file") && parentRelativePath === "design") {
      scopedRootProjectionRequests += 1;
    }
    if (url.pathname.endsWith("/api/tree/projections/file") && !parentRelativePath) {
      workspaceRootProjectionRequests += 1;
    }
    if (url.pathname.endsWith("/api/tree/projections/file/children") && parentRelativePath === "design/03-rust-web") {
      targetChildrenRequests += 1;
      await new Promise((resolve) => setTimeout(resolve, 250));
    }
    if (url.pathname.endsWith("/api/tree/projections/file/children") && parentRelativePath === "design/slow-scope") {
      staleScopeChildrenRequests += 1;
      await new Promise((resolve) => setTimeout(resolve, 600));
    }
    await route.continue();
  });
  try {
    await waitForHttpOk(`${baseUrl}/health`, 60_000);
    const authResponse = await context.request.fetch(`${baseUrl}/api/auth`, {
      method: "POST",
      data: {
        action: "auth:signIn",
        args: {
          provider: "password",
          params: {
            email: `${actorId}@example.com`,
            username: actorId,
            name: actorId,
            password: TEST_PASSWORD,
            flow: "signUp",
          },
        },
      },
    });
    assert(authResponse.ok(), `测试账号注册失败: ${authResponse.status()} ${await authResponse.text()}`);

    await page.goto(baseUrl, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
    const createButton = page.locator('[data-testid="mnote-create-default-local-workspace"]').first();
    if (await createButton.isVisible({ timeout: 3000 }).catch(() => false)) {
      await createButton.click({ timeout: UI_TIMEOUT_MS });
      await page.waitForURL((url) => url.searchParams.get("sourceKind") === "local_folder", {
        timeout: UI_TIMEOUT_MS,
      });
    }

    const initialUrl = new URL(page.url());
    const workspaceId = initialUrl.searchParams.get("workspaceId") || "";
    const rootUri = initialUrl.searchParams.get("rootUri")
      || await page.evaluate(() => document.body.getAttribute("data-mnote-root-uri") || "");
    assert(rootUri, "应进入 local_folder workspace");
    const rootPath = fileUrlToPath(rootUri);
    fs.mkdirSync(path.join(rootPath, "design", "03-rust-web", "done"), { recursive: true });
    fs.mkdirSync(path.join(rootPath, "design", "03-rust-web", "process"), { recursive: true });
    fs.mkdirSync(path.join(rootPath, "design", "03-rust-web", "reference"), { recursive: true });
    fs.mkdirSync(path.join(rootPath, "design", "reveal-parent", "child"), { recursive: true });
    fs.mkdirSync(path.join(rootPath, "design", "slow-scope", "child"), { recursive: true });
    fs.mkdirSync(path.join(rootPath, "docs", "target"), { recursive: true });
    fs.writeFileSync(path.join(rootPath, "Home.md"), "# Home\n", "utf8");
    fs.writeFileSync(path.join(rootPath, "design", "Overview.md"), "Plain paragraph without office attachment.\n", "utf8");
    fs.writeFileSync(path.join(rootPath, "design", "03-rust-web", "plan.md"), "# Plan\n", "utf8");
    fs.writeFileSync(path.join(rootPath, "design", "reveal-parent", "child", "note.md"), "# Reveal\n", "utf8");
    fs.writeFileSync(path.join(rootPath, "design", "slow-scope", "child", "note.md"), "# Slow child\n", "utf8");
    fs.writeFileSync(path.join(rootPath, "docs", "target", "note.md"), "# Docs child\n", "utf8");
    fs.writeFileSync(path.join(rootPath, "docs", "opened-rename.md"), "# Opened rename\n", "utf8");

    const fileTreeUrl = new URL(baseUrl);
    if (workspaceId) fileTreeUrl.searchParams.set("workspaceId", workspaceId);
    fileTreeUrl.searchParams.set("sourceKind", "local_folder");
    fileTreeUrl.searchParams.set("rootUri", rootUri);
    fileTreeUrl.searchParams.set("treeView", "filetree");
    await page.goto(fileTreeUrl.toString(), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
    await page.locator('[data-mnote-sidebar-tree-tab="filetree"]').click({ timeout: UI_TIMEOUT_MS });

    await page.getByRole("button", { name: "新建页面" }).first().click({ timeout: UI_TIMEOUT_MS });
    await page.waitForURL((url) => url.pathname.startsWith("/documents/"), { timeout: UI_TIMEOUT_MS });
    await page.waitForTimeout(300);
    const firstCreatedDocumentId = decodeURIComponent(new URL(page.url()).pathname.split("/").filter(Boolean).pop() || "");
    const firstCreatedRelativePath = localMarkdownDocumentPath(firstCreatedDocumentId);
    const firstCreatedParentPath = firstCreatedRelativePath.split("/").slice(0, -1).join("/");

    await page.getByRole("button", { name: "新建页面" }).first().click({ timeout: UI_TIMEOUT_MS });
    await page.waitForURL((url) => {
      const documentId = decodeURIComponent(url.pathname.split("/").filter(Boolean).pop() || "");
      return url.pathname.startsWith("/documents/") && documentId !== firstCreatedDocumentId;
    }, { timeout: UI_TIMEOUT_MS });
    await page.waitForTimeout(500);
    const secondCreatedDocumentId = decodeURIComponent(new URL(page.url()).pathname.split("/").filter(Boolean).pop() || "");
    const secondCreatedRelativePath = localMarkdownDocumentPath(secondCreatedDocumentId);
    const secondCreatedParentPath = secondCreatedRelativePath.split("/").slice(0, -1).join("/");
    const createExpansionState = await page.evaluate(({ firstParent, secondParent }) => {
      const readRow = (relativePath) => {
        const row = document.querySelector(`#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path="${CSS.escape(relativePath)}"]`);
        return row
          ? {
              relativePath,
              expanded: row.getAttribute("aria-expanded"),
              selected: row.getAttribute("data-selected"),
              focused: row.getAttribute("data-focused"),
              active: row.getAttribute("data-active"),
            }
          : null;
      };
      const firstMarkdownPath = firstParent + "/" + firstParent.split("/").pop() + ".md";
      const secondMarkdownPath = secondParent + "/" + secondParent.split("/").pop() + ".md";
      return {
        first: readRow(firstParent),
        second: readRow(secondParent),
        firstMarkdown: readRow(firstMarkdownPath),
        secondMarkdown: readRow(secondMarkdownPath),
      };
    }, { firstParent: firstCreatedParentPath, secondParent: secondCreatedParentPath });
    assert.equal(
      createExpansionState.second?.expanded,
      "true",
      `新建页面后当前页面包目录应展开，避免焦点落到父文件夹: ${JSON.stringify(createExpansionState)}`,
    );
    assert.equal(
      createExpansionState.secondMarkdown?.selected,
      "true",
      `新建页面后应选中内部 Markdown 行: ${JSON.stringify(createExpansionState)}`,
    );
    assert.equal(
      createExpansionState.secondMarkdown?.focused,
      "true",
      `新建页面后文件树焦点应落在内部 Markdown 行: ${JSON.stringify(createExpansionState)}`,
    );
    assert.equal(
      createExpansionState.secondMarkdown?.active,
      "true",
      `新建页面后 active 应落在内部 Markdown 行: ${JSON.stringify(createExpansionState)}`,
    );

    const scopedUrl = new URL(baseUrl);
    if (workspaceId) scopedUrl.searchParams.set("workspaceId", workspaceId);
    scopedUrl.searchParams.set("sourceKind", "local_folder");
    scopedUrl.searchParams.set("rootUri", rootUri);
    scopedUrl.searchParams.set("treeView", "filetree");
    scopedUrl.searchParams.set("fileTreeScope", "design");
    await page.goto(scopedUrl.toString(), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
    await page.locator('[data-mnote-sidebar-tree-tab="filetree"]').click({ timeout: UI_TIMEOUT_MS });
    await page.locator('#sidebar-file-tree-root[data-mnote-filetree-scope="design"]').waitFor({
      state: "visible",
      timeout: UI_TIMEOUT_MS,
    });

    const scopedRootRequestsAfterInitialLoad = scopedRootProjectionRequests;
    const workspaceRootRequestsAfterInitialLoad = workspaceRootProjectionRequests;
    await page.evaluate(() => {
      window.__mnoteScopedFileOpenNoReloadMarker = "kept";
      document.documentElement.setAttribute("data-mnote-scoped-file-open-no-reload-marker", "kept");
    });
    await page.locator('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path="design/Overview.md"] .tree-link').first().click({
      timeout: UI_TIMEOUT_MS,
    });
    await page.waitForURL((nextUrl) => nextUrl.pathname.startsWith("/documents/"), { timeout: UI_TIMEOUT_MS });
    await page.locator('.document-pane[data-pane-role="primary"] .editor-surface').first().waitFor({
      state: "visible",
      timeout: UI_TIMEOUT_MS,
    });
    await page.evaluate(() => {
      if (typeof window.__mnoteEnhanceEditorAttachmentLinks === "function") {
        window.__mnoteEnhanceEditorAttachmentLinks();
      }
    });
    await page.waitForTimeout(800);
    const scopedOpenMarker = await page.evaluate(() => window.__mnoteScopedFileOpenNoReloadMarker || "");
    assert.equal(scopedOpenMarker, "kept", "scoped 文件树打开 md 应走 pane 内导航，不应整页 reload 重建侧栏");
    assert.equal(
      scopedRootProjectionRequests,
      scopedRootRequestsAfterInitialLoad,
      "scoped 文件树打开已可见 md 不应重新请求 scope 根 projection",
    );
    assert.equal(
      workspaceRootProjectionRequests,
      workspaceRootRequestsAfterInitialLoad,
      "普通 Markdown 打开不应为了 legacy office 附件兼容重拉 workspace root projection",
    );
    const scopedRootRequestsBeforeRootSnapshot = scopedRootProjectionRequests;
    await page.evaluate(() => {
      window.dispatchEvent(new CustomEvent("tree:snapshot", {
        detail: {
          payload: {
            dataset: {
              kernel_file_tree_projection: {
                parentRelativePath: "",
                items: [
                  {
                    rowId: "local:folder:root-probe",
                    nodeId: "local:folder:root-probe",
                    title: "root-probe",
                    rowKind: "folder",
                    resourceMeta: {
                      workspacePath: { relativePath: "root-probe" },
                    },
                  },
                ],
              },
            },
          },
        },
      }));
    });
    await page.waitForTimeout(350);
    assert.equal(
      scopedRootProjectionRequests,
      scopedRootRequestsBeforeRootSnapshot,
      "scoped 文件树收到非 scope root snapshot 不应兜底重拉 scope 根 projection",
    );
    const scopedRootRequestsBeforeCoarseWatch = scopedRootProjectionRequests;
    localWatchRevision += 1;
    await page.evaluate((revision) => {
      window.dispatchEvent(new CustomEvent("tree:local-folder-watch-batch", {
        detail: {
          payload: {
            schema: "mnote.local_folder_watch_batch.v1",
            sourceKind: "local_folder",
            revision: `forced-watch-${revision}`,
            affectedParents: [{ relativePath: "", reason: "coarse-watch" }],
            changedPaths: [{ relativePath: "design", kind: "Modify(Name(Both))" }],
            eventKinds: ["Modify(Name(Both))"],
            fallbackResync: false,
          },
        },
      }));
    }, localWatchRevision);
    await page.waitForTimeout(300);
    assert.equal(
      scopedRootProjectionRequests,
      scopedRootRequestsBeforeCoarseWatch,
      "scoped 文件树收到 coarse local-folder 事件不应重拉 scope 根 projection",
    );

    const rowSelector = '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path="design/03-rust-web"]';
    const childSelector = '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path^="design/03-rust-web/"]';
    await page.locator(rowSelector).waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
    assert.equal(targetChildrenRequests, 0, "scoped 首屏不应预加载 design/03-rust-web children");

    await page.locator(rowSelector).evaluate((row) => {
      const toggle = row.querySelector('[data-rust-action="toggle"]');
      for (let index = 0; index < 5; index += 1) toggle.click();
    });
    await page.waitForFunction((selector) => {
      const row = document.querySelector(selector);
      return row && row.getAttribute("aria-expanded") === "true" && row.getAttribute("data-filetree-children-loaded") === "true";
    }, rowSelector, { timeout: UI_TIMEOUT_MS });
    await page.locator(`${childSelector}[data-local-relative-path="design/03-rust-web/done"]`).waitFor({
      state: "visible",
      timeout: UI_TIMEOUT_MS,
    });
    assert.equal(targetChildrenRequests, 1, "同一路径快速重复展开最多只能产生一个 children 请求");

    const firstExpandRequests = targetChildrenRequests;
    await page.locator(`${rowSelector} [data-rust-action="toggle"]`).click({ timeout: UI_TIMEOUT_MS });
    await page.waitForFunction((selector) => {
      const row = document.querySelector(selector);
      return row && row.getAttribute("aria-expanded") === "false";
    }, rowSelector, { timeout: UI_TIMEOUT_MS });
    const expandStartedAt = Date.now();
    await page.locator(`${rowSelector} [data-rust-action="toggle"]`).click({ timeout: UI_TIMEOUT_MS });
    await page.waitForFunction((selector) => {
      const row = document.querySelector(selector);
      const child = document.querySelector('[data-local-relative-path="design/03-rust-web/process"]');
      return row && child && row.getAttribute("aria-expanded") === "true";
    }, rowSelector, { timeout: UI_TIMEOUT_MS });
    const cachedExpandMs = Date.now() - expandStartedAt;
    assert.equal(targetChildrenRequests, firstExpandRequests, "收起后再次展开应命中 cache，不应再次请求 children");

    await page.locator(`${childSelector}[data-local-relative-path="design/03-rust-web/done"]`).evaluate((row) => {
      row.setAttribute("data-selected", "true");
      row.setAttribute("data-focused", "true");
      row.setAttribute("data-active", "true");
    });
    await page.evaluate(() => {
      window.dispatchEvent(new CustomEvent("tree:local-command", {
        detail: {
          body: {
            action: "rename",
            documentId: "local-md:design~2F03-rust-web~2Fplan.md",
            title: "plan",
            parentRelativePath: "design/03-rust-web",
          },
          result: {
            parentRelativePath: "design/03-rust-web",
          },
        },
      }));
    });
    await page.waitForFunction((selector) => {
      const row = document.querySelector(selector);
      const done = document.querySelector('[data-local-relative-path="design/03-rust-web/done"]');
      const process = document.querySelector('[data-local-relative-path="design/03-rust-web/process"]');
      return row && done && process && row.getAttribute("aria-expanded") === "true";
    }, rowSelector, { timeout: UI_TIMEOUT_MS });
    assert.equal(targetChildrenRequests, firstExpandRequests, "局部 parent refresh 不应绕过 cache 触发 children 重复请求");
    const selectedAfterRefresh = await page.locator(`${childSelector}[data-local-relative-path="design/03-rust-web/done"]`).evaluate((row) => ({
      selected: row.getAttribute("data-selected"),
      focused: row.getAttribute("data-focused"),
      active: row.getAttribute("data-active"),
    }));
    assert.deepEqual(selectedAfterRefresh, {
      selected: "true",
      focused: "true",
      active: "true",
    }, "局部 parent refresh 后应按 logical state 复投影 selection/focus/active");

    fs.writeFileSync(path.join(rootPath, "design", "03-rust-web", "watch-created.md"), "# Watch\n", "utf8");
    await page.evaluate(() => {
      window.dispatchEvent(new CustomEvent("tree:local-folder-watch-batch", {
        detail: {
          payload: {
            schema: "mnote.local_folder_watch_batch.v1",
            revision: "smoke-watch-1",
            affectedParents: [{ relativePath: "design/03-rust-web", reason: "child-watch" }],
            changedPaths: [{ relativePath: "design/03-rust-web/watch-created.md", kind: "Create(File)" }],
            eventKinds: ["Create(File)"],
            fallbackResync: false,
          },
        },
      }));
    });
    await page.waitForFunction((selector) => {
      const row = document.querySelector(selector);
      const created = document.querySelector('[data-local-relative-path="design/03-rust-web/watch-created.md"]');
      return row && created && row.getAttribute("aria-expanded") === "true";
    }, rowSelector, { timeout: UI_TIMEOUT_MS });
    assert.equal(
      await page.locator(rowSelector).getAttribute("aria-expanded"),
      "true",
      "watch batch 局部刷新后已展开 parent 不应折叠",
    );

    await page.evaluate(() => {
      window.dispatchEvent(new CustomEvent("mnote:primary-document-activated", {
        detail: { documentId: "local-md:design~2Freveal-parent~2Fchild~2Fnote.md" },
      }));
    });
    const revealTargetSelector = '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path="design/reveal-parent/child/note.md"]';
    await page.waitForFunction((selector) => {
      const row = document.querySelector(selector);
      return row
        && row.getAttribute("data-selected") === "true"
        && row.getAttribute("data-focused") === "true"
        && row.getAttribute("data-active") === "true";
    }, revealTargetSelector, { timeout: UI_TIMEOUT_MS });

    const slowScopeSelector = '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path="design/slow-scope"]';
    await page.locator(slowScopeSelector).waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
    await page.locator(`${slowScopeSelector} [data-rust-action="toggle"]`).click({ timeout: UI_TIMEOUT_MS });
    const docsUrl = new URL(scopedUrl.toString());
    docsUrl.searchParams.set("fileTreeScope", "docs");
    await page.goto(docsUrl.toString(), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
    await page.locator('#sidebar-file-tree-root[data-mnote-filetree-scope="docs"]').waitFor({
      state: "visible",
      timeout: UI_TIMEOUT_MS,
    });
    await page.waitForTimeout(800);
    assert.equal(staleScopeChildrenRequests, 1, "慢目录切 scope 前应触发一次 children 请求");
    const staleRowsInDocsScope = await page.locator('#sidebar-file-tree-root .tree-row[data-local-relative-path^="design/slow-scope/"]').count();
    assert.equal(staleRowsInDocsScope, 0, "慢请求返回后不得把旧 design scope children patch 到 docs scope");

    const rootText = await page.locator("#sidebar-file-tree-root").innerText({ timeout: UI_TIMEOUT_MS });
    assert.match(rootText, /target/);

    const openedDocumentId = "local-md:docs~2Fopened-rename.md";
    const renamedDocumentId = "local-md:docs~2Fopened-renamed.md";
    const documentUrl = new URL(`${baseUrl}/documents/${encodeURIComponent(openedDocumentId)}`);
    if (workspaceId) documentUrl.searchParams.set("workspaceId", workspaceId);
    documentUrl.searchParams.set("sourceKind", "local_folder");
    documentUrl.searchParams.set("rootUri", rootUri);
    await page.goto(documentUrl.toString(), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
    await page.locator('.document-pane[data-pane-role="primary"] .editor-surface').first().waitFor({
      state: "visible",
      timeout: UI_TIMEOUT_MS,
    });
    const renameResponse = await context.request.fetch(`${baseUrl}/api/tree/commands`, {
      method: "POST",
      data: {
        action: "rename",
        workspaceId,
        sourceKind: "local_folder",
        rootUri,
        documentId: openedDocumentId,
        title: "opened-renamed",
      },
    });
    assert(renameResponse.ok(), `opened markdown rename command failed: ${renameResponse.status()} ${await renameResponse.text()}`);
    const bufferStateUrl = new URL(`${baseUrl}/api/documents/buffer-state`);
    bufferStateUrl.searchParams.set("documentId", renamedDocumentId);
    if (workspaceId) bufferStateUrl.searchParams.set("workspaceId", workspaceId);
    bufferStateUrl.searchParams.set("sourceKind", "local_folder");
    bufferStateUrl.searchParams.set("rootUri", rootUri);
    bufferStateUrl.searchParams.set("relativePath", "docs/opened-renamed.md");
    const bufferStateResponse = await context.request.fetch(bufferStateUrl.toString());
    assert(bufferStateResponse.ok(), `opened markdown rename should rekey buffer: ${bufferStateResponse.status()} ${await bufferStateResponse.text()}`);
    const bufferStatePayload = await bufferStateResponse.json();
    assert.equal(bufferStatePayload.result.documentId, renamedDocumentId, "opened markdown rename 后 buffer key 应更新到新 documentId");

    console.log(JSON.stringify({
      ok: true,
      task: TASK,
      baseUrl,
      targetChildrenRequests,
      staleScopeChildrenRequests,
      cachedExpandMs,
    }, null, 2));
  } finally {
    await browser.close();
    server.kill("SIGINT");
    fs.rmSync(dataRoot, { recursive: true, force: true });
    if (server.exitCode == null) {
      await new Promise((resolve) => server.once("exit", resolve));
    }
    if (server.exitCode && server.exitCode !== 130 && server.exitCode !== null) {
      process.stderr.write(stderr);
    }
  }
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});
