siyuan-note/siyuan · warning

attribute view drop target changed; retry the drag

Error message

attribute view drop target changed; retry the drag

What it means

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.

Source

Thrown at kernel/model/attribute_view_row_sort.go:198

		}
	}
	if len(moved) != len(selectedIDs) {
		return nil, errors.New("attribute view rows changed; retry the drag")
	}
	if selectedIDs[nextID] || ("" == nextID && selectedIDs[previousID]) {
		return slices.Clone(ordered), nil
	}
	index := 0
	if "" != nextID {
		index = slices.Index(remaining, nextID)
	} else if "" != previousID {
		index = slices.Index(remaining, previousID)
		if index >= 0 {
			index++
		}
	}
	if index < 0 {
		return nil, errors.New("attribute view drop target changed; retry the drag")
	}
	ret := append([]string{}, remaining[:index]...)
	ret = append(ret, moved...)
	return append(ret, remaining[index:]...), nil
}

// mergeAttributeViewRowOrder 保留当前尚未参与渲染的项目 ID,避免用页面数据覆盖完整列表。
func mergeAttributeViewRowOrder(original, ordered []string) []string {
	ret := append([]string{}, ordered...)
	seen := map[string]bool{}
	for _, id := range ordered {
		seen[id] = true
	}
	for _, id := range original {
		if !seen[id] {
			ret = append(ret, id)
			seen[id] = true
		}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Refresh the view's row order and current drop anchors from the frontend, then retry the drag
  2. Verify previousID/nextID still exist in the view and are not part of the selected set before submitting
  3. Discard the stale drag request and re-initiate it from the current UI state

Example fix

// before
rows, err := moveAttributeViewSortedRows(ordered, selected, prevID, nextID)
// after
if slices.Contains(ordered, prevID) || slices.Contains(ordered, nextID) || nextID == "" {
    rows, err = moveAttributeViewSortedRows(ordered, selected, prevID, nextID)
} else {
    // refresh anchors then retry
}
Defensive patterns

Strategy: validation

Validate before calling

const rows = getVisibleRowIds(avID, viewID);
if (prevID && !rows.includes(prevID)) return;
if (nextID && !rows.includes(nextID)) return;

Try / catch

try {
  await sortAttributeViewRows(avID, viewID, selected, prevID, nextID);
} catch (e) {
  if (String(e.msg).includes("drop target changed")) {
    // refresh anchors and let the user re-drag
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/0258198c8d21b1eb. Report an issue: GitHub.