feat: continue tree rust family cutover

- add rust renderer/state-family scaffolds and inline compat host thinning for page tree, file tree, and picker
- route tree/filetree preflight, file projection, resource artifact, and stream delta contracts through rust plans
- preserve canonical move-order validation, file-tree search projection, and related frontend/runtime regression coverage
This commit is contained in:
lix-2026
2026-04-26 19:35:52 +08:00
parent 338bb2e20f
commit e564dfde02
93 changed files with 17492 additions and 1856 deletions
@@ -6,6 +6,7 @@ const mockBuildDocumentBridgeContext = vi.fn();
const mockBuildDocumentCommandEnvelope = vi.fn();
const mockResolveRustBridgeCommandPlan = vi.fn();
const mockExecuteRustBridgeMutationTransport = vi.fn();
const mockRecordRustBridgeCommandArtifacts = vi.fn();
const mockRecordBridgeCommandArtifacts = vi.fn();
const mockRecordBridgeCommandFailureArtifacts = vi.fn();
const mockDocumentBridgeErrorResponse = vi.fn((error: unknown) =>
@@ -62,12 +63,112 @@ vi.mock("@/lib/documents/bridge", () => ({
},
buildDocumentBridgeContext: mockBuildDocumentBridgeContext,
buildDocumentCommandEnvelope: mockBuildDocumentCommandEnvelope,
documentBridgeErrorResponse: (...args: unknown[]) => mockDocumentBridgeErrorResponse(...args),
documentBridgeErrorResponse: (...args: Parameters<typeof mockDocumentBridgeErrorResponse>) =>
mockDocumentBridgeErrorResponse(...args),
}));
vi.mock("@/lib/documents/rust-runtime", () => ({
resolveRustBridgeCommandPlan: (...args: unknown[]) => mockResolveRustBridgeCommandPlan(...args),
executeRustBridgeMutationTransport: (...args: unknown[]) => mockExecuteRustBridgeMutationTransport(...args),
resolveRustBridgeCommandPlan: (...args: Parameters<typeof mockResolveRustBridgeCommandPlan>) =>
mockResolveRustBridgeCommandPlan(...args),
executeRustBridgeMutationTransport: (...args: Parameters<typeof mockExecuteRustBridgeMutationTransport>) =>
mockExecuteRustBridgeMutationTransport(...args),
recordRustBridgeCommandArtifacts: (...args: Parameters<typeof mockRecordRustBridgeCommandArtifacts>) =>
mockRecordRustBridgeCommandArtifacts(...args),
readRustTreeDomainEventType: (plan: { argsJson?: Record<string, unknown> }) => {
const eventPlan = plan.argsJson?.domainEventPlan as
| {
family?: string;
eventType?: string;
}
| undefined;
if (eventPlan?.family === "tree" && typeof eventPlan.eventType === "string") {
return eventPlan.eventType;
}
const hint = plan.argsJson?.domainEventHint as
| {
family?: string;
eventType?: string;
}
| undefined;
return hint?.family === "tree" && typeof hint.eventType === "string" ? hint.eventType : null;
},
materializeRustTreeDomainEventPlan: (input: {
plan: { argsJson?: Record<string, unknown> };
streamDelta?: Record<string, unknown> | null;
}) => {
const eventPlan = input.plan.argsJson?.domainEventPlan as
| {
family?: string;
schema?: string;
schemaVersion?: number;
eventType?: string;
}
| undefined;
if (
eventPlan?.family !== "tree" ||
eventPlan.schema !== "mnote.tree.domain_event" ||
eventPlan.schemaVersion !== 1 ||
typeof eventPlan.eventType !== "string"
) {
return null;
}
return {
family: "tree",
schema: "mnote.tree.domain_event",
schemaVersion: 1,
eventType: eventPlan.eventType,
...(input.streamDelta ? { streamDelta: input.streamDelta } : {}),
};
},
materializeRustTreeStreamDelta: (input: { plan: { argsJson?: Record<string, unknown> }; result: unknown }) => {
const hint = input.plan.argsJson?.streamDeltaHint as
| {
family?: string;
kind?: string;
args?: Record<string, unknown>;
}
| undefined;
if (hint?.family !== "tree" || !hint.kind) return null;
const args = hint.args ?? {};
const result = input.result as Record<string, unknown>;
if (hint.kind === "document_result") {
return result.document ? { op: "upsert_document", document: result.document } : null;
}
if (hint.kind === "upsert_document_patch") {
return {
op: "upsert_document",
document: {
id: args.documentId,
...(args.patch as Record<string, unknown>),
updated_at: result.updated_at ?? null,
},
};
}
if (hint.kind === "move_document") {
return {
op: "move_document",
documentId: args.documentId,
parentId: result.parent_id ?? args.parentId ?? null,
sortOrder: result.sort_order ?? args.sortOrder,
updatedAt: result.updated_at,
};
}
if (hint.kind === "remove_document") {
return { op: "remove_document", documentId: args.documentId };
}
if (hint.kind === "noop") {
return { op: "noop" };
}
if (hint.kind === "copy_result") {
return {
op: "upsert_documents",
upsertDocuments: Array.isArray(result.items)
? result.items.map((item) => item?.document).filter(Boolean)
: [],
};
}
return null;
},
}));
vi.mock("@/lib/documents/bridge-log", () => ({
@@ -92,6 +193,7 @@ describe("/api/tree/commands route", () => {
mockBuildDocumentCommandEnvelope.mockReset();
mockResolveRustBridgeCommandPlan.mockReset();
mockExecuteRustBridgeMutationTransport.mockReset();
mockRecordRustBridgeCommandArtifacts.mockReset().mockResolvedValue(null);
mockRecordBridgeCommandArtifacts.mockReset();
mockRecordBridgeCommandFailureArtifacts.mockReset();
mockEnsureDocumentScaffold.mockReset();
@@ -146,7 +248,17 @@ describe("/api/tree/commands route", () => {
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
argsJson: {},
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "document_result",
args: { documentField: "document" },
},
domainEventHint: {
family: "tree",
eventType: "tree.node.created",
},
},
});
mockExecuteRustBridgeMutationTransport.mockResolvedValue({
id: "doc_new",
@@ -158,6 +270,18 @@ describe("/api/tree/commands route", () => {
is_template: false,
created_at: "2026-04-23T00:00:00Z",
updated_at: "2026-04-23T00:00:00Z",
document: {
id: "doc_new",
workspace_id: "ws_root",
title: "无标题",
parent_id: null,
sort_order: 0,
is_starred: false,
access_scope: "private",
is_template: false,
created_at: "2026-04-23T00:00:00Z",
updated_at: "2026-04-23T00:00:00Z",
},
});
const { POST } = await import("./route");
@@ -196,25 +320,26 @@ describe("/api/tree/commands route", () => {
}),
);
expect(mockEnsureDocumentScaffold).toHaveBeenCalledWith("doc_new", "无标题");
expect(mockRecordBridgeCommandArtifacts).toHaveBeenCalledWith(
expect(mockRecordRustBridgeCommandArtifacts).toHaveBeenCalledWith(
expect.objectContaining({
context: expect.objectContaining({
requestId: "req_tree_1",
traceId: "trace_tree_1",
}),
envelope: expect.objectContaining({
name: "tree.node.create",
}),
commandPayload: expect.objectContaining({
streamDelta: {
op: "upsert_document",
document: expect.objectContaining({
id: "doc_new",
workspace_id: "ws_root",
title: "无标题",
parent_id: null,
sort_order: 0,
}),
},
plan: expect.objectContaining({
commandName: "tree.node.create",
functionName: "documents:createWithParentReference",
}),
result: expect.objectContaining({
id: "doc_new",
workspace_id: "ws_root",
}),
}),
);
expect(mockRecordBridgeCommandArtifacts).not.toHaveBeenCalled();
});
it("move action 走 tree.subtree.move,并把 position 归一化为 sortOrder", async () => {
@@ -283,7 +408,21 @@ describe("/api/tree/commands route", () => {
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
argsJson: {},
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "move_document",
args: {
documentId: "doc_1",
parentId: "parent_1",
sortOrder: 2,
},
},
domainEventHint: {
family: "tree",
eventType: "tree.subtree.moved",
},
},
});
mockExecuteRustBridgeMutationTransport.mockResolvedValue({
ok: true,
@@ -392,27 +531,18 @@ describe("/api/tree/commands route", () => {
}),
);
expect(mockEnsureDocumentScaffold).not.toHaveBeenCalled();
expect(mockRecordBridgeCommandArtifacts).toHaveBeenCalledWith(
expect(mockRecordRustBridgeCommandArtifacts).toHaveBeenCalledWith(
expect.objectContaining({
envelope: expect.objectContaining({
name: "tree.subtree.move",
}),
commandPayload: expect.objectContaining({
streamDelta: {
op: "replace_documents",
documents: [
{
id: "doc_1",
workspace_id: "ws_1",
parent_id: null,
},
{
id: "parent_1",
workspace_id: "ws_1",
parent_id: null,
},
],
},
plan: expect.objectContaining({
commandName: "tree.subtree.move",
functionName: "documents:move",
}),
result: expect.objectContaining({
parent_id: "parent_1",
sort_order: 1,
}),
}),
);
@@ -482,7 +612,21 @@ describe("/api/tree/commands route", () => {
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
argsJson: {},
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "move_document",
args: {
documentId: "doc_1",
parentId: "parent_1",
sortOrder: 2,
},
},
domainEventHint: {
family: "tree",
eventType: "tree.subtree.moved",
},
},
});
mockExecuteRustBridgeMutationTransport.mockResolvedValue({
ok: true,
@@ -902,7 +1046,20 @@ describe("/api/tree/commands route", () => {
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
argsJson: {},
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "upsert_document_patch",
args: {
documentId: "doc_1",
patch: { title: "新标题" },
},
},
domainEventHint: {
family: "tree",
eventType: "tree.node.renamed",
},
},
});
mockExecuteRustBridgeMutationTransport.mockResolvedValue({
ok: true,
@@ -949,19 +1106,16 @@ describe("/api/tree/commands route", () => {
}),
);
expect(mockEnsureDocumentScaffold).not.toHaveBeenCalled();
expect(mockRecordBridgeCommandArtifacts).toHaveBeenCalledWith(
expect(mockRecordRustBridgeCommandArtifacts).toHaveBeenCalledWith(
expect.objectContaining({
envelope: expect.objectContaining({
name: "tree.node.rename",
}),
commandPayload: expect.objectContaining({
streamDelta: {
op: "upsert_document",
document: expect.objectContaining({
id: "doc_1",
title: "新标题",
}),
},
plan: expect.objectContaining({
commandName: "tree.node.rename",
}),
result: expect.objectContaining({
updated_at: "2026-04-23T00:00:00Z",
}),
}),
);
@@ -1000,7 +1154,17 @@ describe("/api/tree/commands route", () => {
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
argsJson: {},
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "remove_document",
args: { documentId: "doc_1" },
},
domainEventHint: {
family: "tree",
eventType: "tree.node.archived",
},
},
});
mockExecuteRustBridgeMutationTransport.mockResolvedValue({
ok: true,
@@ -1042,17 +1206,15 @@ describe("/api/tree/commands route", () => {
},
}),
);
expect(mockRecordBridgeCommandArtifacts).toHaveBeenCalledWith(
expect(mockRecordRustBridgeCommandArtifacts).toHaveBeenCalledWith(
expect.objectContaining({
envelope: expect.objectContaining({
name: "tree.node.archive",
}),
commandPayload: expect.objectContaining({
streamDelta: {
op: "remove_document",
documentId: "doc_1",
},
plan: expect.objectContaining({
commandName: "tree.node.archive",
}),
result: expect.any(Object),
}),
);
});
@@ -1112,7 +1274,17 @@ describe("/api/tree/commands route", () => {
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
argsJson: {},
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "noop",
args: {},
},
domainEventHint: {
family: "tree",
eventType: "tree.node.embedded",
},
},
});
mockExecuteRustBridgeMutationTransport.mockResolvedValue({
revision: 8,
@@ -1170,16 +1342,15 @@ describe("/api/tree/commands route", () => {
}),
}),
);
expect(mockRecordBridgeCommandArtifacts).toHaveBeenCalledWith(
expect(mockRecordRustBridgeCommandArtifacts).toHaveBeenCalledWith(
expect.objectContaining({
envelope: expect.objectContaining({
name: "tree.node.embed",
}),
commandPayload: expect.objectContaining({
streamDelta: {
op: "noop",
},
plan: expect.objectContaining({
commandName: "tree.node.embed",
}),
result: expect.any(Object),
}),
);
});
@@ -1222,7 +1393,17 @@ describe("/api/tree/commands route", () => {
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
argsJson: {},
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "copy_result",
args: { itemsField: "items", documentField: "document" },
},
domainEventHint: {
family: "tree",
eventType: "tree.subtree.copied",
},
},
});
mockExecuteRustBridgeMutationTransport.mockResolvedValue({
items: [
@@ -1230,6 +1411,18 @@ describe("/api/tree/commands route", () => {
oldId: "doc_1",
newId: "doc_2",
title: "复制页面",
document: {
id: "doc_2",
workspace_id: "ws_1",
title: "复制页面",
parent_id: "parent_1",
sort_order: 0,
is_starred: false,
access_scope: "private",
is_template: false,
created_at: "2026-04-24T00:00:00Z",
updated_at: "2026-04-24T00:00:00Z",
},
},
],
});
@@ -1302,22 +1495,22 @@ describe("/api/tree/commands route", () => {
);
expect(mockEnsureDocumentScaffold).toHaveBeenCalledWith("doc_2", "复制页面");
expect(mockCopyMindmapFilesIfExists).toHaveBeenCalledWith("doc_1", "doc_2");
expect(mockRecordBridgeCommandArtifacts).toHaveBeenCalledWith(
expect(mockRecordRustBridgeCommandArtifacts).toHaveBeenCalledWith(
expect.objectContaining({
envelope: expect.objectContaining({
name: "tree.subtree.copy",
}),
commandPayload: expect.objectContaining({
streamDelta: {
op: "replace_documents",
documents: [],
},
plan: expect.objectContaining({
commandName: "tree.subtree.copy",
}),
result: expect.objectContaining({
items: [expect.objectContaining({ newId: "doc_2" })],
}),
}),
);
});
it("restore action 走 tree.node.restore,并附带 replace_documents delta", async () => {
it("restore action 走 tree.node.restore,并附带 upsert_document delta", async () => {
const client = {
mutation: vi.fn(),
query: vi.fn(async () => ({
@@ -1350,10 +1543,32 @@ describe("/api/tree/commands route", () => {
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
argsJson: {},
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "document_result",
args: { documentField: "document" },
},
domainEventHint: {
family: "tree",
eventType: "tree.node.restored",
},
},
});
mockExecuteRustBridgeMutationTransport.mockResolvedValue({
ok: true,
document: {
id: "doc_restore_1",
workspace_id: "ws_1",
title: "恢复页面",
parent_id: null,
sort_order: 0,
is_starred: false,
access_scope: "private",
is_template: false,
created_at: "2026-04-23T00:00:00Z",
updated_at: "2026-04-24T00:00:00Z",
},
updated_at: "2026-04-24T00:00:00Z",
});
mockLoadSidebarDataFromConvex.mockResolvedValue({
@@ -1412,16 +1627,16 @@ describe("/api/tree/commands route", () => {
},
}),
);
expect(mockRecordBridgeCommandArtifacts).toHaveBeenCalledWith(
expect(mockRecordRustBridgeCommandArtifacts).toHaveBeenCalledWith(
expect.objectContaining({
envelope: expect.objectContaining({
name: "tree.node.restore",
}),
commandPayload: expect.objectContaining({
streamDelta: {
op: "replace_documents",
documents: [],
},
plan: expect.objectContaining({
commandName: "tree.node.restore",
}),
result: expect.objectContaining({
document: expect.objectContaining({ id: "doc_restore_1" }),
}),
}),
);
@@ -1476,7 +1691,17 @@ describe("/api/tree/commands route", () => {
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
argsJson: {},
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "remove_document",
args: { documentId: "doc_1" },
},
domainEventHint: {
family: "tree",
eventType: "tree.node.archived",
},
},
});
mockExecuteRustBridgeMutationTransport.mockRejectedValue(new Error("archive failed"));