feat(tree): complete rust family runtime checklist
- add tree shell runtime artifact contracts and page/filetree/picker runtime reducers - sink tree.subtree.move write operation through Rust and formalize command event plans - harden file tree search projection contract and route thin-proxy boundaries - record completed harness tasks and move design docs into process/done
This commit is contained in:
@@ -6,6 +6,7 @@ import { nowIso } from "./_utils/time";
|
||||
import { buildParentById, collectSubtree, isAncestorOf } from "./_utils/documentTree";
|
||||
import {
|
||||
assertDocumentMoveOrderPlanMatches,
|
||||
assertDocumentMoveWriteOperationMatches,
|
||||
buildDocumentMoveOrderPlanFromDocuments,
|
||||
} from "./_utils/documentMoveOrder";
|
||||
import { enqueueIngestDocumentJob } from "./_utils/ingestJobs";
|
||||
@@ -1400,6 +1401,7 @@ export const move = mutation({
|
||||
parentId: v.union(v.string(), v.null()),
|
||||
sortOrder: v.number(),
|
||||
normalizedMove: v.optional(v.any()),
|
||||
treeWriteOperation: v.optional(v.any()),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
@@ -1436,23 +1438,52 @@ export const move = mutation({
|
||||
}
|
||||
}
|
||||
|
||||
if (args.normalizedMove != null) {
|
||||
assertDocumentMoveOrderPlanMatches(
|
||||
args.normalizedMove,
|
||||
buildDocumentMoveOrderPlanFromDocuments({
|
||||
documents: canonicalWorkspaceDocs,
|
||||
documentId: doc.id,
|
||||
parentId: toParentId,
|
||||
sortOrder: args.sortOrder,
|
||||
}),
|
||||
);
|
||||
}
|
||||
const currentMoveOrderPlan = buildDocumentMoveOrderPlanFromDocuments({
|
||||
documents: canonicalWorkspaceDocs,
|
||||
documentId: doc.id,
|
||||
parentId: toParentId,
|
||||
sortOrder: args.sortOrder,
|
||||
});
|
||||
const rustMoveOrderPlan =
|
||||
args.treeWriteOperation != null
|
||||
? assertDocumentMoveWriteOperationMatches(args.treeWriteOperation, currentMoveOrderPlan)
|
||||
: args.normalizedMove != null
|
||||
? assertDocumentMoveOrderPlanMatches(args.normalizedMove, currentMoveOrderPlan)
|
||||
: null;
|
||||
|
||||
// 说明:仅更新当前节点的 sort_order 会导致兄弟节点出现重复 sort_order,
|
||||
// 前端会按 sort_order + created_at 排序,结果常常表现为“拖拽无效/拖完又回弹”。
|
||||
// 这里统一对目标父节点(以及跨父移动时的原父节点)做重新编号,保证 sort_order 唯一且连续。
|
||||
const ts = nowIso();
|
||||
|
||||
if (rustMoveOrderPlan != null) {
|
||||
const documentsById = new Map(canonicalWorkspaceDocs.map((item) => [item.id, item]));
|
||||
for (const item of rustMoveOrderPlan.patches) {
|
||||
const target = documentsById.get(item.documentId);
|
||||
if (!target) {
|
||||
throw new Error("Rust move plan 与 Convex 当前排序状态不一致");
|
||||
}
|
||||
|
||||
const patch: Record<string, unknown> = {};
|
||||
if ((target.parent_id ?? null) !== item.parentId) patch.parent_id = item.parentId;
|
||||
if ((target.sort_order ?? null) !== item.sortOrder) patch.sort_order = item.sortOrder;
|
||||
// Rust plan 决定被移动节点;Convex 只负责把同一结果持久化。
|
||||
if (item.moved) patch.updated_at = ts;
|
||||
|
||||
if (Object.keys(patch).length > 0) {
|
||||
await ctx.db.patch(target._id, patch);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
parent_id: rustMoveOrderPlan.toParentId,
|
||||
sort_order: rustMoveOrderPlan.normalizedSortOrder,
|
||||
workspace_id: doc.workspace_id,
|
||||
updated_at: ts,
|
||||
};
|
||||
}
|
||||
|
||||
const compareDocOrder = (a: any, b: any) => {
|
||||
const orderA = typeof a.sort_order === "number" ? a.sort_order : Number.MAX_SAFE_INTEGER;
|
||||
const orderB = typeof b.sort_order === "number" ? b.sort_order : Number.MAX_SAFE_INTEGER;
|
||||
|
||||
Reference in New Issue
Block a user