feat: 收口本地文件夹入口并推进 page aggregate rust-first

- 为 Rust Web 主入口补齐本地文件夹/云空间切换、最近目录与路径回填体验\n- 对齐 local markdown media 与 inline marks 的 Rust shell / TipTap converter 语义\n- 让 documents/page 优先消费 Rust page aggregate snapshot,并保留 TS fallback\n- 补强 tree live、local markdown 与主入口 smoke,并同步设计稿状态
This commit is contained in:
lix-2026
2026-05-09 19:05:06 +08:00
parent 71146de5f0
commit d148e1ccc2
20 changed files with 1635 additions and 374 deletions
@@ -5,7 +5,7 @@ const assert = require("node:assert");
const { chromium } = require("playwright");
const { fetchWithTimeout } = require("./task114-rust-web-gateway-entry-smoke.js");
const BASE_URL = (process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, "");
const BASE_URL = (process.env.MNOTE_WEB_SMOKE_BASE_URL || process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, "");
const UI_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_UI_TIMEOUT_MS || 30_000);
async function readJsonResponse(response, label) {
@@ -69,6 +69,65 @@ async function waitForSidebarRow(page, documentId) {
return row;
}
async function armTreeLiveProbe(page) {
await page.evaluate(() => {
const key = "__task120TreeLiveEvents";
window[key] = [];
if (window.__task120TreeLiveProbeArmed) return;
const push = (kind, detail) => {
const payload = detail && typeof detail === "object" && "payload" in detail ? detail.payload : detail;
window[key].push({
kind,
applied: document.documentElement.getAttribute("data-mnote-tree-live-applied") || "",
payload,
});
if (window[key].length > 20) window[key].shift();
};
window.addEventListener("tree:delta", (event) => push("delta", event.detail));
window.addEventListener("tree:resync", (event) => push("resync", event.detail));
window.__task120TreeLiveProbeArmed = true;
});
}
async function waitForTreeLiveApplied(page, label, expectedTitle) {
await page.waitForFunction(
({ title }) => {
const events = Array.isArray(window.__task120TreeLiveEvents) ? window.__task120TreeLiveEvents : [];
return events.some((event) => {
if (!event || (event.kind !== "delta" && event.kind !== "resync")) return false;
const applied = event.applied || document.documentElement.getAttribute("data-mnote-tree-live-applied") || "";
if (applied !== "delta" && applied !== "resync") return false;
try {
return JSON.stringify(event.payload || {}).includes(title);
} catch {
return false;
}
});
},
{ title: expectedTitle },
{ timeout: UI_TIMEOUT_MS },
);
const probe = await page.evaluate(({ title }) => {
const events = Array.isArray(window.__task120TreeLiveEvents) ? window.__task120TreeLiveEvents : [];
const match = events.find((event) => {
if (!event || (event.kind !== "delta" && event.kind !== "resync")) return false;
try {
return JSON.stringify(event.payload || {}).includes(title);
} catch {
return false;
}
});
return {
applied: document.documentElement.getAttribute("data-mnote-tree-live-applied") || "",
kind: match?.kind || "",
};
}, { title: expectedTitle });
assert(["delta", "resync"].includes(probe.applied || ""), `${label} 应应用 delta/resync,实际: ${probe.applied}`);
assert(["delta", "resync"].includes(probe.kind || ""), `${label} 应捕获匹配 rename 的 delta/resync 事件,实际: ${probe.kind}`);
return probe.kind || probe.applied;
}
async function readTreeSnapshot(workspaceId) {
const response = await fetchWithTimeout(
`${BASE_URL}/api/tree/events?workspaceId=${encodeURIComponent(workspaceId)}&maxPolls=0`,
@@ -120,6 +179,11 @@ async function main() {
assert.equal(await activeChildRow.getAttribute("data-active"), "true", "点击 child row 后 active 标记未切换");
const renamedTitle = `${childTitle}-renamed`;
await armTreeLiveProbe(page);
await page.evaluate(() => {
document.documentElement.removeAttribute("data-mnote-tree-live-applied");
window.__task120TreeLiveEvents = [];
});
await postTreeCommand(
{
action: "rename",
@@ -129,6 +193,9 @@ async function main() {
},
"重命名临时子页面",
);
const liveApplied = await waitForTreeLiveApplied(page, "重命名后 tree live", renamedTitle);
const renamedLiveRow = await waitForSidebarRow(page, childPage.documentId);
await expectRowTitle(renamedLiveRow, renamedTitle, `tree live ${liveApplied} 后 child row 标题`);
await page.reload({ waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const renamedRow = await waitForSidebarRow(page, childPage.documentId);
await expectRowTitle(renamedRow, renamedTitle, "重命名后 child row 标题");