{"record":{"id":"a68e52dfb3a49719","repo":"siyuan-note/siyuan","slug":"s-w","errorCode":null,"errorMessage":"%s: %w","messagePattern":"%s: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/model/attribute_view_field_changes.go","lineNumber":217,"sourceCode":"\tif len(change.elements) == 0 {\n\t\treturn nil\n\t}\n\tchange.before, change.after = nil, nil\n\treturn change\n}\n\nfunc (change *attributeViewFieldChange) apply(current any, exists, undo bool) (any, bool, error) {\n\tconflict := fmt.Errorf(\"database field undo conflicts with a subsequent edit\")\n\tif change.members != nil {\n\t\tobject, ok := current.(map[string]any)\n\t\tif !exists || !ok {\n\t\t\treturn nil, false, conflict\n\t\t}\n\t\tfor _, key := range sortedAttributeViewFieldKeys(change.members) {\n\t\t\tvalue, present := object[key]\n\t\t\treplacement, keep, err := change.members[key].apply(value, present, undo)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, false, fmt.Errorf(\"%s: %w\", key, err)\n\t\t\t}\n\t\t\tif keep {\n\t\t\t\tobject[key] = replacement\n\t\t\t} else {\n\t\t\t\tdelete(object, key)\n\t\t\t}\n\t\t}\n\t\treturn object, true, nil\n\t}\n\tif change.elements != nil {\n\t\tvalues, ok := current.([]any)\n\t\tif !exists || !ok {\n\t\t\treturn nil, false, conflict\n\t\t}\n\t\telements, _, valid := attributeViewFieldElements(values, change.container)\n\t\tif !valid {\n\t\t\treturn nil, false, conflict\n\t\t}","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view_field_changes.go#L199-L235","documentation":"When a nested member change inside an attributeViewFieldChange fails to apply, apply wraps the child error with the member key name using fmt.Errorf(\"%s: %w\", key, err), producing messages like \"foo: database field undo conflicts with a subsequent edit\". The wrapping preserves the original error for errors.Is checks while pinpointing which field of the map failed. It is a path-annotating wrapper, not a new failure mode.","triggerScenarios":"Undoing a composite field change where a specific member (key) fails its own apply — e.g. that member was deleted or retyped by a later edit.","commonSituations":"Multi-field database updates undone after one field was separately removed; parsing which field caused a bulk undo failure; writing tests that assert on nested error text.","solutions":["Parse the key prefix before the first \": \" to locate the offending field","Use errors.Is(err, conflictErr) semantics — the wrapped cause remains a database field undo conflict","Re-sync or reload the attribute view so its structure matches before further undo attempts"],"exampleFix":"// before\nif err != nil { log.Println(err) }\n// after\nif err != nil {\n    if strings.Contains(err.Error(), \"conflicts with a subsequent edit\") { reloadAttributeView() }\n    log.Println(err)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isUndoConflict(err error) bool { return errors.Is(err, errDatabaseFieldUndoConflict) }","tryCatchPattern":"if err := undo(); err != nil {\n    if key, cause, ok := splitKeyedError(err); ok && isUndoConflict(cause) {\n        log.Printf(\"member %q cannot be undone: %v\", key, cause)\n    }\n}","preventionTips":["Unwrap with errors.Is/As to get the underlying conflict instead of string matching only","Keep member edits atomic so partial structure changes don't break undo","Test undo paths that span multiple member mutations"],"tags":["undo","error-wrapping","nested"],"backgroundTag":"invalid-state-transition","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}