From c23e39cfccbefcf9f285ac9b1082007c1bed88d1 Mon Sep 17 00:00:00 2001 From: seltzdesign Date: Tue, 30 Jun 2026 00:34:19 +0200 Subject: [PATCH] perf(tree-view-table-layout): PATCH only the rows that actually changed on drag-save saveEdits re-PATCHed every edited row on every drag (600+ requests on a large tree), most no-ops, and a reparent could sit behind them so a fast reload raced the parent write. - diff each field against the current item; build a changed-only payload (skip no-op rows) - normalise m2o values before comparing (object {pk} / scalar / null) - send parent edits first so a reparent persists ahead of the minimal sort updates --- packages/tree-view-table-layout/src/index.ts | 41 ++++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/packages/tree-view-table-layout/src/index.ts b/packages/tree-view-table-layout/src/index.ts index ece6876f..e3e1c287 100644 --- a/packages/tree-view-table-layout/src/index.ts +++ b/packages/tree-view-table-layout/src/index.ts @@ -544,12 +544,45 @@ export default defineLayout({ return { saveEdits }; async function saveEdits(edits: Record) { + const pk = primaryKeyField.value?.field ?? 'id'; + const parentFieldName = parentField.value; + // Normalise an m2o value (object {pk}, scalar, or null) to its related key for comparison. + const relKey = (value: any) => + value == null ? null : (typeof value === 'object' ? value[pk] : value); + + // Only PATCH rows whose value actually changed. Upstream re-patches EVERY row's sort + // key on any drag (600+ requests), which is slow and lets a fast reload race a + // reparent queued behind those writes. We also send parent edits first so a reparent + // persists immediately, before the (now minimal) sort-position updates. + const entries = Object.entries(edits).sort(([, a], [, b]) => { + const aParent = parentFieldName && parentFieldName in (a as object) ? 0 : 1; + const bParent = parentFieldName && parentFieldName in (b as object) ? 0 : 1; + return aParent - bParent; + }); + try { - for (const [id, payload] of Object.entries(edits)) { - await api.patch( - `${getEndpoint(collection.value!)}/${id}`, - payload, + for (const [id, payload] of entries) { + const original = items.value.find( + (item) => String(item[pk]) === String(id), ); + + const changed: Item = {}; + + for (const [field, value] of Object.entries(payload)) { + const isParent = field === parentFieldName; + const before = isParent ? relKey(original?.[field]) : original?.[field]; + const after = isParent ? relKey(value) : value; + + if (original === undefined || before !== after) + changed[field] = value; + } + + if (Object.keys(changed).length > 0) { + await api.patch( + `${getEndpoint(collection.value!)}/${id}`, + changed, + ); + } } } catch (error: any) {