{"record":{"id":"0258198c8d21b1eb","repo":"siyuan-note/siyuan","slug":"attribute-view-drop-target-changed-retry-the-drag","errorCode":null,"errorMessage":"attribute view drop target changed; retry the drag","messagePattern":"attribute view drop target changed; retry the drag","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/model/attribute_view_row_sort.go","lineNumber":198,"sourceCode":"\t\t}\n\t}\n\tif len(moved) != len(selectedIDs) {\n\t\treturn nil, errors.New(\"attribute view rows changed; retry the drag\")\n\t}\n\tif selectedIDs[nextID] || (\"\" == nextID && selectedIDs[previousID]) {\n\t\treturn slices.Clone(ordered), nil\n\t}\n\tindex := 0\n\tif \"\" != nextID {\n\t\tindex = slices.Index(remaining, nextID)\n\t} else if \"\" != previousID {\n\t\tindex = slices.Index(remaining, previousID)\n\t\tif index >= 0 {\n\t\t\tindex++\n\t\t}\n\t}\n\tif index < 0 {\n\t\treturn nil, errors.New(\"attribute view drop target changed; retry the drag\")\n\t}\n\tret := append([]string{}, remaining[:index]...)\n\tret = append(ret, moved...)\n\treturn append(ret, remaining[index:]...), nil\n}\n\n// mergeAttributeViewRowOrder 保留当前尚未参与渲染的项目 ID，避免用页面数据覆盖完整列表。\nfunc mergeAttributeViewRowOrder(original, ordered []string) []string {\n\tret := append([]string{}, ordered...)\n\tseen := map[string]bool{}\n\tfor _, id := range ordered {\n\t\tseen[id] = true\n\t}\n\tfor _, id := range original {\n\t\tif !seen[id] {\n\t\t\tret = append(ret, id)\n\t\t\tseen[id] = true\n\t\t}","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/attribute_view_row_sort.go#L180-L216","documentation":"After splitting rows into moved/remaining, the function locates the drop position using previousID/nextID within `remaining`. If neither anchor is found (index stays -1), the drop target row no longer exists in the un-selected rows — the view changed during the drag — so the operation is rejected for a retry.","triggerScenarios":"Calling prepareAttributeViewRowSort where previousID/nextID (the drop anchors submitted by the frontend) are missing from the remaining rows — anchor row was deleted, became itself selected, or the drag payload references an outdated view.","commonSituations":"Concurrent edits removing the anchor row while a user drags; stale frontend state after another tab moved the same rows; replayed/queued drag requests referencing old row IDs after sync.","solutions":["Refresh the view's row order and current drop anchors from the frontend, then retry the drag","Verify previousID/nextID still exist in the view and are not part of the selected set before submitting","Discard the stale drag request and re-initiate it from the current UI state"],"exampleFix":"// before\nrows, err := moveAttributeViewSortedRows(ordered, selected, prevID, nextID)\n// after\nif slices.Contains(ordered, prevID) || slices.Contains(ordered, nextID) || nextID == \"\" {\n    rows, err = moveAttributeViewSortedRows(ordered, selected, prevID, nextID)\n} else {\n    // refresh anchors then retry\n}","handlingStrategy":"validation","validationCode":"const rows = getVisibleRowIds(avID, viewID);\nif (prevID && !rows.includes(prevID)) return;\nif (nextID && !rows.includes(nextID)) return;","typeGuard":null,"tryCatchPattern":"try {\n  await sortAttributeViewRows(avID, viewID, selected, prevID, nextID);\n} catch (e) {\n  if (String(e.msg).includes(\"drop target changed\")) {\n    // refresh anchors and let the user re-drag\n  }\n}","preventionTips":["Validate drop anchors exist in the current view before submitting","Cancel pending drags when rows are deleted by sync or other windows","Re-derive previousID/nextID from live DOM state at commit time"],"tags":["attribute-view","row-sort","stale-state","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"}