{"record":{"id":"b6b3d4bdeb9774ad","repo":"siyuan-note/siyuan","slug":"attribute-view-rows-changed-retry-the-drag","errorCode":null,"errorMessage":"attribute view rows changed; retry the drag","messagePattern":"attribute view rows changed; retry the drag","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/model/attribute_view_row_sort.go","lineNumber":183,"sourceCode":"\nfunc moveAttributeViewSortedRows(ordered, selected []string, previousID, nextID string) ([]string, error) {\n\tselectedIDs := map[string]bool{}\n\tfor _, id := range selected {\n\t\tselectedIDs[id] = true\n\t}\n\tif 0 == len(selectedIDs) {\n\t\treturn nil, errors.New(\"no attribute view rows selected\")\n\t}\n\tvar moved, remaining []string\n\tfor _, id := range ordered {\n\t\tif selectedIDs[id] {\n\t\t\tmoved = append(moved, id)\n\t\t} else {\n\t\t\tremaining = append(remaining, id)\n\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...)","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/attribute_view_row_sort.go#L165-L201","documentation":"moveAttributeViewSortedRows filters the current row order into `moved` (selected rows) and `remaining`. If the number of matched selected rows differs from the number of distinct selected IDs, some selected rows no longer exist in the ordered list — the attribute view changed under the drag. The operation is rejected so the client retries with fresh data.","triggerScenarios":"Calling prepareAttributeViewRowSort when one or more selected row IDs are absent from the view's current row order — rows deleted by another client/tab, block IDs changed after a transaction, or the drag started against a stale view snapshot.","commonSituations":"Two editors open on the same database, one deletes a row while the other drags; sync applied remote row deletions between drag start and commit; a plugin removed rows concurrently.","solutions":["Reload the attribute view row order and re-capture the selection, then retry the drag (the error message explicitly asks for a retry)","Reconcile the selection against current rows: drop selected IDs that no longer exist and re-submit","Check for concurrent editors/sync activity and re-attempt after the view stabilizes"],"exampleFix":"// before\nrows, err := moveAttributeViewSortedRows(ordered, selected, prevID, nextID)\nif err != nil { return err }\n// after\nrows, err := moveAttributeViewSortedRows(ordered, selected, prevID, nextID)\nif err != nil {\n    ordered, selected = refreshRowOrderAndSelection(avID) // re-read current state\n    rows, err = moveAttributeViewSortedRows(ordered, selected, prevID, nextID)\n}","handlingStrategy":"retry","validationCode":"const currentIds = getVisibleRowIds(avID, viewID);\nconst stillValid = selected.every(id => currentIds.includes(id));\nif (!stillValid) { refreshSelection(); }","typeGuard":null,"tryCatchPattern":"try {\n  await sortAttributeViewRows(avID, viewID, selected, prevID, nextID);\n} catch (e) {\n  if (String(e.msg).includes(\"attribute view rows changed\")) {\n    await reloadAttributeView(avID);\n    // retry once with fresh selection\n  }\n}","preventionTips":["Re-capture the selection right before committing the drag","Handle sync/deletion events by cancelling in-flight drags","Retry once after reloading the view, then surface the conflict to the user"],"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"}