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
+67 -110
View File
@@ -13,7 +13,6 @@ import {
documentBridgeErrorResponse,
} from "@/lib/documents/bridge";
import {
recordBridgeCommandArtifacts,
recordBridgeCommandFailureArtifacts,
} from "@/lib/documents/bridge-log";
import {
@@ -24,6 +23,7 @@ import { buildDocumentSavePayload } from "@/lib/documents/save-contract";
import { loadSidebarDataFromConvex } from "@/lib/server/sidebar-data";
import {
executeRustBridgeMutationTransport,
recordRustBridgeCommandArtifacts,
resolveRustBridgeCommandPlan,
} from "@/lib/documents/rust-runtime";
@@ -57,6 +57,26 @@ type TreeCommandPayload = {
items?: TreeCopyItem[] | null;
};
type TreeDeltaDocument = {
id: string;
workspace_id: string;
title: string | null;
parent_id: string | null;
sort_order: number | null;
is_starred: boolean | null;
access_scope: "private" | "shared" | "public";
is_template: boolean;
created_at: string;
updated_at: string | null;
};
type TreeMutationResult<TResult> = {
context: Awaited<ReturnType<typeof buildDocumentBridgeContext>>;
envelope: ReturnType<typeof buildDocumentCommandEnvelope>;
plan: Awaited<ReturnType<typeof resolveRustBridgeCommandPlan>>;
result: TResult;
};
function trimOrNull(value: unknown) {
if (typeof value !== "string") return null;
const trimmed = value.trim();
@@ -76,37 +96,23 @@ function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function attachStreamDelta(commandPayload: unknown, streamDelta?: Record<string, unknown> | null) {
if (!streamDelta) {
return commandPayload;
}
if (isRecord(commandPayload)) {
return {
...commandPayload,
streamDelta,
};
}
return {
payload: commandPayload,
streamDelta,
};
}
async function recordTreeCommandSuccess(args: {
context: Awaited<ReturnType<typeof buildDocumentBridgeContext>>;
envelope: ReturnType<typeof buildDocumentCommandEnvelope>;
client: Awaited<ReturnType<typeof getAuthedConvexClient>>["client"];
commandPayload?: unknown;
plan: Awaited<ReturnType<typeof resolveRustBridgeCommandPlan>>;
result: unknown;
}) {
try {
await recordBridgeCommandArtifacts({
await recordRustBridgeCommandArtifacts({
context: args.context,
envelope: args.envelope,
client: args.client,
commandPayload: args.commandPayload,
plan: args.plan,
result: args.result,
});
} catch (error) {
console.warn("[tree.commands] bridge success artifacts skipped:", error);
console.warn("[tree.commands] Rust bridge success artifacts skipped:", error);
}
}
@@ -136,24 +142,6 @@ async function loadTreeCommandSidebarSnapshot(args: {
}
}
function buildTreeCommandSnapshotDelta(
sidebarSnapshot: unknown,
): Record<string, unknown> | null {
if (!isRecord(sidebarSnapshot)) {
return null;
}
if (Array.isArray(sidebarSnapshot.documents)) {
return {
op: "replace_documents",
documents: sidebarSnapshot.documents,
};
}
return {
op: "replace_sidebar",
sidebar: sidebarSnapshot,
};
}
function buildTreeMovePreflightDataFromSidebarSnapshot(
sidebarSnapshot: unknown,
): Record<string, unknown> | null {
@@ -203,6 +191,7 @@ async function resolveTreeMutationResult<TResult>(args: {
return {
context,
envelope,
plan,
result,
};
} catch (error) {
@@ -275,8 +264,9 @@ async function handleCreate(request: Request, payload: TreeCommandPayload) {
const documentId = trimOrNull(payload.documentId) ?? randomUUID();
const title = normalizeTitle(payload.title);
const { context, envelope, result } = await resolveTreeMutationResult<{
const mutation = await resolveTreeMutationResult<{
id: string;
document?: TreeDeltaDocument | null;
title: string | null;
parent_id: string | null;
sort_order: number | null;
@@ -300,27 +290,15 @@ async function handleCreate(request: Request, payload: TreeCommandPayload) {
pageId: documentId,
client,
});
const { context, envelope, result } = mutation;
await ensureDocumentScaffold(result.id, result.title ?? title);
await recordTreeCommandSuccess({
context,
envelope,
client,
commandPayload: attachStreamDelta(envelope.payload, {
op: "upsert_document",
document: {
id: result.id,
workspace_id: result.workspace_id,
title: result.title ?? title,
parent_id: result.parent_id ?? parentId,
sort_order: result.sort_order ?? 0,
access_scope: result.access_scope,
is_starred: false,
is_template: result.is_template,
created_at: result.created_at,
updated_at: result.updated_at,
},
}),
plan: mutation.plan,
result,
});
return NextResponse.json({
@@ -356,7 +334,7 @@ async function handleMove(request: Request, payload: TreeCommandPayload) {
workspaceId,
});
const movePreflightData = buildTreeMovePreflightDataFromSidebarSnapshot(sidebarSnapshot);
const { context, envelope, result } = await resolveTreeMutationResult<{
const mutation = await resolveTreeMutationResult<{
ok?: boolean;
parent_id?: string | null;
sort_order?: number | null;
@@ -375,19 +353,13 @@ async function handleMove(request: Request, payload: TreeCommandPayload) {
pageId: documentId,
client,
});
const postMutationSidebarSnapshot = await loadTreeCommandSidebarSnapshot({
client,
auth,
workspaceId,
});
const { context, envelope, result } = mutation;
await recordTreeCommandSuccess({
context,
envelope,
client,
commandPayload: attachStreamDelta(
envelope.payload,
buildTreeCommandSnapshotDelta(postMutationSidebarSnapshot),
),
plan: mutation.plan,
result,
});
return NextResponse.json({
@@ -426,8 +398,9 @@ async function handleRename(request: Request, payload: TreeCommandPayload) {
const workspaceId = trimOrNull(payload.workspaceId) ?? trimOrNull(sourceDoc.workspace_id);
const title = assertTitle(payload.title ?? null);
const { context, envelope, result } = await resolveTreeMutationResult<{
const mutation = await resolveTreeMutationResult<{
ok?: boolean;
document?: TreeDeltaDocument | null;
updated_at?: string | null;
}>({
request,
@@ -441,18 +414,13 @@ async function handleRename(request: Request, payload: TreeCommandPayload) {
pageId: documentId,
client,
});
const { context, envelope, result } = mutation;
await recordTreeCommandSuccess({
context,
envelope,
client,
commandPayload: attachStreamDelta(envelope.payload, {
op: "upsert_document",
document: {
id: documentId,
title,
updated_at: result?.updated_at ?? null,
},
}),
plan: mutation.plan,
result,
});
return NextResponse.json({
@@ -478,8 +446,9 @@ async function handleArchive(request: Request, payload: TreeCommandPayload) {
}
const workspaceId = trimOrNull(payload.workspaceId) ?? trimOrNull(sourceDoc.workspace_id);
const { context, envelope, result } = await resolveTreeMutationResult<{
const mutation = await resolveTreeMutationResult<{
ok?: boolean;
document?: TreeDeltaDocument | null;
updated_at?: string | null;
}>({
request,
@@ -492,14 +461,13 @@ async function handleArchive(request: Request, payload: TreeCommandPayload) {
pageId: documentId,
client,
});
const { context, envelope, result } = mutation;
await recordTreeCommandSuccess({
context,
envelope,
client,
commandPayload: attachStreamDelta(envelope.payload, {
op: "remove_document",
documentId,
}),
plan: mutation.plan,
result,
});
return NextResponse.json({
@@ -516,7 +484,7 @@ async function handleArchive(request: Request, payload: TreeCommandPayload) {
}
async function handleRestore(request: Request, payload: TreeCommandPayload) {
const { auth, client } = await getAuthedConvexClient();
const { client } = await getAuthedConvexClient();
const documentId = assertDocumentId(payload.documentId ?? null);
const sourceDoc = await client.query(api.documents.getMeta, { id: documentId });
if (!sourceDoc) {
@@ -524,8 +492,9 @@ async function handleRestore(request: Request, payload: TreeCommandPayload) {
}
const workspaceId = trimOrNull(payload.workspaceId) ?? trimOrNull(sourceDoc.workspace_id);
const { context, envelope, result } = await resolveTreeMutationResult<{
const mutation = await resolveTreeMutationResult<{
ok?: boolean;
document?: TreeDeltaDocument | null;
updated_at?: string | null;
}>({
request,
@@ -538,19 +507,13 @@ async function handleRestore(request: Request, payload: TreeCommandPayload) {
pageId: documentId,
client,
});
const sidebarSnapshot = await loadTreeCommandSidebarSnapshot({
client,
auth,
workspaceId,
});
const { context, envelope, result } = mutation;
await recordTreeCommandSuccess({
context,
envelope,
client,
commandPayload: attachStreamDelta(
envelope.payload,
buildTreeCommandSnapshotDelta(sidebarSnapshot),
),
plan: mutation.plan,
result,
});
return NextResponse.json({
@@ -575,7 +538,7 @@ async function handlePurge(request: Request, payload: TreeCommandPayload) {
}
const workspaceId = trimOrNull(payload.workspaceId) ?? trimOrNull(sourceDoc.workspace_id);
const { context, envelope, result } = await resolveTreeMutationResult<{
const mutation = await resolveTreeMutationResult<{
ok?: boolean;
purged?: boolean;
purged_at?: string | null;
@@ -589,14 +552,13 @@ async function handlePurge(request: Request, payload: TreeCommandPayload) {
pageId: documentId,
client,
});
const { context, envelope, result } = mutation;
await recordTreeCommandSuccess({
context,
envelope,
client,
commandPayload: attachStreamDelta(envelope.payload, {
op: "remove_document",
documentId,
}),
plan: mutation.plan,
result,
});
return NextResponse.json({
@@ -659,7 +621,7 @@ async function handleEmbed(request: Request, payload: TreeCommandPayload) {
trimOrNull(sourceDoc.workspace_id) ??
trimOrNull((targetMeta as { workspace_id?: string | null } | null)?.workspace_id);
const { context, envelope, result } = await resolveTreeMutationResult<{
const mutation = await resolveTreeMutationResult<{
revision?: number | null;
conflict_detection_key?: string | null;
}>({
@@ -688,13 +650,13 @@ async function handleEmbed(request: Request, payload: TreeCommandPayload) {
pageId: targetId,
client,
});
const { context, envelope, result } = mutation;
await recordTreeCommandSuccess({
context,
envelope,
client,
commandPayload: attachStreamDelta(envelope.payload, {
op: "noop",
}),
plan: mutation.plan,
result,
});
return NextResponse.json({
@@ -712,7 +674,7 @@ async function handleEmbed(request: Request, payload: TreeCommandPayload) {
}
async function handleCopy(request: Request, payload: TreeCommandPayload) {
const { auth, client } = await getAuthedConvexClient();
const { client } = await getAuthedConvexClient();
const normalizedItems = (payload.items ?? [])
.filter((item) => item?.documentId)
.map((item) => ({
@@ -747,11 +709,12 @@ async function handleCopy(request: Request, payload: TreeCommandPayload) {
return NextResponse.json({ error: "缺少目标工作空间" }, { status: 400 });
}
const { context, envelope, result } = await resolveTreeMutationResult<{
const mutation = await resolveTreeMutationResult<{
items: Array<{
oldId: string;
newId: string;
title?: string | null;
document?: TreeDeltaDocument | null;
}>;
}>({
request,
@@ -765,6 +728,7 @@ async function handleCopy(request: Request, payload: TreeCommandPayload) {
pageId: targetParentId,
client,
});
const { context, envelope, result } = mutation;
await Promise.all(
(result.items ?? []).map(async (item) => {
@@ -772,19 +736,12 @@ async function handleCopy(request: Request, payload: TreeCommandPayload) {
await copyMindmapFilesIfExists(item.oldId, item.newId);
}),
);
const sidebarSnapshot = await loadTreeCommandSidebarSnapshot({
client,
auth,
workspaceId,
});
await recordTreeCommandSuccess({
context,
envelope,
client,
commandPayload: attachStreamDelta(
envelope.payload,
buildTreeCommandSnapshotDelta(sidebarSnapshot),
),
plan: mutation.plan,
result,
});
return NextResponse.json({