{"record":{"id":"8b80dd7f1c095a23","repo":"siyuan-note/siyuan","slug":"attribute-view-order-changed-retry-the-drag","errorCode":null,"errorMessage":"attribute view order changed; retry the drag","messagePattern":"attribute view order changed; retry the drag","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/model/attribute_view_row_sort.go","lineNumber":232,"sourceCode":"\t\t\tret = append(ret, id)\n\t\t\tseen[id] = true\n\t\t}\n\t}\n\treturn ret\n}\n\nfunc applyAttributeViewRowOrder(attrView *av.AttributeView, view *av.View, data any) error {\n\tencoded, err := gulu.JSON.MarshalJSON(data)\n\tif nil != err {\n\t\treturn err\n\t}\n\tvar change attributeViewRowOrderChange\n\tif err = gulu.JSON.UnmarshalJSON(encoded, &change); nil != err {\n\t\treturn err\n\t}\n\tif nil == change.RowOrder || nil == change.Expected ||\n\t\t!reflect.DeepEqual(getAttributeViewRowOrder(view), change.Expected) {\n\t\treturn errors.New(\"attribute view order changed; retry the drag\")\n\t}\n\tif len(change.RowOrder.Groups) != len(view.Groups) {\n\t\treturn errors.New(\"attribute view groups changed; retry the drag\")\n\t}\n\tfor _, group := range view.Groups {\n\t\tif _, ok := change.RowOrder.Groups[group.ID]; !ok {\n\t\t\treturn errors.New(\"attribute view group not found\")\n\t\t}\n\t}\n\tif nil != change.ValidateGroup && len(change.RowOrder.Sorts) > 0 {\n\t\t// 预览之后字段值可能变化，提交时再次校验，不能悄悄接受冲突的落点。\n\t\tcopyView, copyErr := cloneAttributeViewRowSort(attrView)\n\t\tif nil != copyErr {\n\t\t\treturn copyErr\n\t\t}\n\t\tcandidate := copyView.GetView(view.ID)\n\t\tsetAttributeViewRowOrder(candidate, change.RowOrder)\n\t\tcollections, renderErr := renderAttributeViewRowSortCollections(copyView, candidate, false)","sourceCodeStart":214,"sourceCodeEnd":250,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/attribute_view_row_sort.go#L214-L250","documentation":"applyAttributeViewRowOrder is an optimistic-concurrency check: the client submits the row order it previewed plus the expected order it based the drag on. Before applying, the kernel re-serializes the current order (getAttributeViewRowOrder) and compares it with `change.Expected` using reflect.DeepEqual. Any difference — or a missing payload — rejects the drag so the client can retry against fresh data.","triggerScenarios":"Calling sortAttributeViewRow with an encoded order change whose `Expected` snapshot no longer matches the live view — rows added/removed/reordered by another client, a sync write, a sort recomputation, or the client sending a payload computed from an outdated view.","commonSituations":"Multi-window editing of the same database; background sync updating rows between drag preview and commit; filter/sort recalculations that change effective order; replayed stale drag requests.","solutions":["Re-open the attribute view, rebuild the drag payload from the current row order, and retry","Compare the submitted Expected order with the live order to identify what changed, and resolve the concurrent edit first","Avoid long delays between drag preview and commit; apply the order promptly after preview"],"exampleFix":"// before\nerr = applyAttributeViewRowOrder(tx, avID, viewID, staleEncodedChange)\n// after\nchange := buildRowOrderChange(view) // snapshot from the freshly loaded view\nerr = applyAttributeViewRowOrder(tx, avID, viewID, encode(change))","handlingStrategy":"retry","validationCode":"const expected = getCurrentRowOrder(avID, viewID);\nif (!deepEqual(expected, payload.Expected)) {\n  payload = rebuildRowOrderChange(avID, viewID); // refresh before commit\n}","typeGuard":null,"tryCatchPattern":"try {\n  await sortAttributeViewRow(avID, viewID, encodedChange);\n} catch (e) {\n  if (String(e.msg).includes(\"order changed\")) {\n    await reloadAttributeView(avID);\n    await retryDragCommit(); // rebuild payload from fresh state\n  }\n}","preventionTips":["Minimize the delay between drag preview and commit","Rebuild the Expected snapshot from the live view at commit time","Listen for data-change push events and cancel stale pending drags"],"tags":["attribute-view","row-sort","optimistic-concurrency","retry"],"backgroundTag":"invalid-state-transition","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}