feat(kernel): finish phase3 bridge cutover and phase4 tree projection

- add generic mnote-web query transport and bridge routes for workspace/request/trace

- route sidebar compat traffic through mnote-web and tighten fixture fallback to dev/test

- unify sidebar/page tree/file tree/picker consumers on page_tree projection

- sync phase3/phase4 checklist, breakdown docs, and harness progress state
This commit is contained in:
lix-2026
2026-04-17 00:25:28 +08:00
parent b1d5d97142
commit ddf285b82d
27 changed files with 2650 additions and 340 deletions
@@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest";
import { buildPageTreeProjectionItems } from "@/lib/tree-projection";
import { buildVisibleRows } from "./rows";
import { parseFileTreeRowId } from "./types";
@@ -33,7 +34,7 @@ describe("buildVisibleRows", () => {
};
const rows = buildVisibleRows({
nodes: [a],
pageRows: buildPageTreeProjectionItems([a]),
expanded: new Set(["a"]),
assetsByDoc: {
a: [
@@ -108,7 +109,7 @@ describe("buildVisibleRows", () => {
};
const rows = buildVisibleRows({
nodes: [a, a],
pageRows: buildPageTreeProjectionItems([a, a]),
expanded: new Set(["a"]),
assetsByDoc: {},
});
+31 -38
View File
@@ -1,55 +1,52 @@
"use client";
import type { SidebarTreeNode } from "@/lib/kernel-sidebar";
import type { PageTreeProjectionItem } from "@/lib/tree-projection";
import type { MediaAsset } from "@/types/media";
import type { FileTreeRow } from "./types";
import { makeAssetFolderRowId, makeAssetRowId, makeDocRowId, makeIndexRowId } from "./types";
export function buildVisibleRows({
nodes,
pageRows,
expanded,
assetsByDoc,
assetChildrenByAssetId,
expandedAssetFolderIds,
}: {
nodes: SidebarTreeNode[];
// 只消费统一 page_tree projection;结构真相不再由文件树自行定义。
pageRows: PageTreeProjectionItem[];
expanded: Set<string>;
assetsByDoc: Record<string, MediaAsset[]>;
assetChildrenByAssetId?: Record<string, MediaAsset[]>;
expandedAssetFolderIds?: Set<string>;
}): FileTreeRow[] {
const rows: FileTreeRow[] = [];
const visitedDocIds = new Set<string>();
const walk = (node: SidebarTreeNode, depth: number) => {
// 防御性处理:上游数据异常时(例如同一 docId 在树中重复出现),避免生成重复 rowId 导致 React key 冲突。
// 同时也能避免潜在的“循环引用/重复引用”导致的递归问题。
if (visitedDocIds.has(node.id)) return;
visitedDocIds.add(node.id);
const assets = assetsByDoc[node.id] ?? [];
const hasChildren = node.children.length > 0 || assets.length > 0;
const isExpanded = expanded.has(node.id);
pageRows.forEach((item) => {
const assets = assetsByDoc[item.nodeId] ?? [];
const hasChildren = item.childCount > 0 || assets.length > 0;
const isExpanded = expanded.has(item.nodeId);
rows.push({
kind: "doc",
rowId: makeDocRowId(node.id),
depth,
docId: node.id,
parentDocId: node.parent_id,
node,
rowId: makeDocRowId(item.nodeId),
depth: item.depth,
docId: item.nodeId,
parentDocId: item.parentNodeId,
node: item.node,
hasChildren,
isExpanded,
});
if (!isExpanded) return;
if (!isExpanded) {
return;
}
rows.push({
kind: "index",
rowId: makeIndexRowId(node.id),
depth: depth + 1,
docId: node.id,
parentDocId: node.id,
node,
rowId: makeIndexRowId(item.nodeId),
depth: item.depth + 1,
docId: item.nodeId,
parentDocId: item.nodeId,
node: item.node,
});
assets.forEach((asset) => {
@@ -60,9 +57,9 @@ export function buildVisibleRows({
rows.push({
kind: "asset-folder",
rowId: makeAssetFolderRowId(asset.id),
depth: depth + 1,
docId: node.id,
parentDocId: node.id,
depth: item.depth + 1,
docId: item.nodeId,
parentDocId: item.nodeId,
asset,
hasChildren,
isExpanded,
@@ -72,9 +69,9 @@ export function buildVisibleRows({
rows.push({
kind: "asset",
rowId: makeAssetRowId(child.id),
depth: depth + 2,
docId: node.id,
parentDocId: node.id,
depth: item.depth + 2,
docId: item.nodeId,
parentDocId: item.nodeId,
asset: child,
});
});
@@ -85,16 +82,12 @@ export function buildVisibleRows({
rows.push({
kind: "asset",
rowId: makeAssetRowId(asset.id),
depth: depth + 1,
docId: node.id,
parentDocId: node.id,
depth: item.depth + 1,
docId: item.nodeId,
parentDocId: item.nodeId,
asset,
});
});
node.children.forEach((child) => walk(child, depth + 1));
};
nodes.forEach((node) => walk(node, 0));
});
return rows;
}