{"record":{"id":"2fa3d174f9876488","repo":"siyuan-note/siyuan","slug":"invalid-card-cover-position-v-v","errorCode":null,"errorMessage":"invalid card cover position [%v, %v]","messagePattern":"invalid card cover position \\[(.+?), (.+?)\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view.go","lineNumber":1924,"sourceCode":"\tdataJSON, err := json.Marshal(operation.Data)\n\tif nil != err {\n\t\treturn\n\t}\n\tvar data setAttrViewCardCoverPositionData\n\tif err = json.Unmarshal(dataJSON, &data); nil != err {\n\t\treturn\n\t}\n\tif !av.IsValidCardCoverSource(data.Source) {\n\t\treturn fmt.Errorf(\"invalid card cover source [%s]\", data.Source)\n\t}\n\tif nil != data.Position {\n\t\tif \"\" == data.Position.Image || 32*1024 < len(data.Position.Image) {\n\t\t\treturn errors.New(\"invalid card cover image\")\n\t\t}\n\t\tif math.IsNaN(data.Position.X) || math.IsInf(data.Position.X, 0) ||\n\t\t\tmath.IsNaN(data.Position.Y) || math.IsInf(data.Position.Y, 0) ||\n\t\t\tdata.Position.X < 0 || 100 < data.Position.X || data.Position.Y < 0 || 100 < data.Position.Y {\n\t\t\treturn fmt.Errorf(\"invalid card cover position [%v, %v]\", data.Position.X, data.Position.Y)\n\t\t}\n\t}\n\n\tattrView, err := av.ParseAttributeView(operation.AvID)\n\tif nil != err {\n\t\treturn\n\t}\n\tif nil == attrView.GetBlockValue(operation.RowID) {\n\t\treturn fmt.Errorf(\"attribute view item [%s] not found\", operation.RowID)\n\t}\n\tview, err := getAttrViewViewByBlockID(attrView, operation.BlockID)\n\tif nil != err {\n\t\treturn\n\t}\n\tif av.LayoutTypeGallery != view.LayoutType && av.LayoutTypeKanban != view.LayoutType {\n\t\treturn av.ErrWrongLayoutType\n\t}\n\tvar source string","sourceCodeStart":1906,"sourceCodeEnd":1942,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/attribute_view.go#L1906-L1942","documentation":"Thrown by setAttrViewCardCoverPosition when a card cover position's X or Y coordinate is NaN, +/-Inf, or outside the allowed [0, 100] range. SiYuan stores cover-image focal position as percentages, so any value below 0 or above 100 (or non-finite) is rejected before the AttributeView is saved. The image itself is validated separately (non-empty and <= 32KB) just above this check.","triggerScenarios":"A transaction operation of action setAttrViewCardCoverPosition whose Data.Position is non-nil with X or Y outside 0..100, or a payload produced by a buggy/older frontend that sends pixel offsets instead of percentages. NaN/Inf reach the check when the JSON payload contains null cast to float64 or a corrupted numeric field.","commonSituations":"Frontend version skew where an older client sends raw pixel coordinates; a manually crafted API call; a rounding/clamping bug in the drag handler that occasionally yields -0.0001 or 100.0001.","solutions":["Clamp both X and Y to the inclusive range [0, 100] on the client before sending the operation, and ensure the values are finite numbers.","If calling the kernel API directly, validate with math.IsNaN/math.IsInf and bound-check before constructing the operation Data.","Update the frontend to a version matching the kernel so the cover-position contract (percentage 0-100) is honored."],"exampleFix":"// before\nop.Data = { source: \"content\", position: { image: b64, x: rawPxX, y: rawPxY } }\n// after\nconst clamp = v => Math.max(0, Math.min(100, (Number.isFinite(v) ? v : 50)))\nop.Data = { source: \"content\", position: { image: b64, x: clamp(xPct), y: clamp(yPct) } }","handlingStrategy":"validation","validationCode":"function isValidCoverPosition(p) {\n  if (p == null) return true // null position clears the entry\n  return Number.isFinite(p.x) && Number.isFinite(p.y) && p.x >= 0 && p.x <= 100 && p.y >= 0 && p.y <= 100 && typeof p.image === 'string' && p.image.length > 0 && p.image.length <= 32 * 1024\n}\nif (!isValidCoverPosition(op.data.position)) { /* do not send */ }","typeGuard":"type CardCoverPos = { image: string; x: number; y: number }\nfunction isCardCoverPos(v: unknown): v is CardCoverPos | null {\n  if (v == null) return true\n  if (typeof v !== 'object' || v === null) return false\n  const p = v as Record<string, unknown>\n  return typeof p.image === 'string' && typeof p.x === 'number' && typeof p.y === 'number' && Number.isFinite(p.x) && Number.isFinite(p.y)\n}","tryCatchPattern":null,"preventionTips":["Clamp position coordinates to [0,100] in the drag handler before they enter state.","Never store raw pixel offsets in the same field as percentage positions.","Add a client-side unit test that every emitted position is finite and bounded."],"tags":["attribute-view","validation","card-cover","transaction"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}