{"record":{"id":"dd34bac9a59850d4","repo":"siyuan-note/siyuan","slug":"at-least-one-visible-view-is-required","errorCode":null,"errorMessage":"at least one visible view is required","messagePattern":"at least one visible view is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/model/attribute_view.go","lineNumber":1240,"sourceCode":"func (tx *Transaction) doSetAttrViewBlockView(operation *Operation) (ret *TxErr) {\n\terr := SetDatabaseBlockView(operation.BlockID, operation.AvID, operation.ID)\n\tif err != nil {\n\t\treturn &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}\n\t}\n\treturn\n}\n\nfunc (tx *Transaction) doSetAttrViewBlockVisibleViews(operation *Operation) (ret *TxErr) {\n\terr := SetDatabaseBlockVisibleViews(operation.BlockID, operation.AvID, operation.ViewIDs)\n\tif err != nil {\n\t\treturn &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}\n\t}\n\treturn\n}\n\nfunc SetDatabaseBlockVisibleViews(blockID, avID string, viewIDs []string) (err error) {\n\tif 1 > len(viewIDs) {\n\t\treturn errors.New(\"at least one visible view is required\")\n\t}\n\n\tattrView, err := av.ParseAttributeView(avID)\n\tif nil != err {\n\t\tlogging.LogErrorf(\"parse attribute view [%s] failed: %s\", avID, err)\n\t\treturn\n\t}\n\n\tvisible := map[string]bool{}\n\tfor _, viewID := range viewIDs {\n\t\tif nil == attrView.GetView(viewID) {\n\t\t\treturn fmt.Errorf(\"view [%s] not found in attribute view [%s]\", viewID, avID)\n\t\t}\n\t\tvisible[viewID] = true\n\t}\n\n\tvar normalized []string\n\tfor _, view := range attrView.Views {","sourceCodeStart":1222,"sourceCodeEnd":1258,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/attribute_view.go#L1222-L1258","documentation":"Returned by SetDatabaseBlockVisibleViews (attribute_view.go:1240), dispatched through the transaction action \"setAttrViewBlockVisibleViews\". It is the first guard in the function: if len(operation.ViewIDs) < 1 the call is rejected because at least one view must remain visible. The same literal is reused at line 1264 (error 430) for a later, defensive re-check.","triggerScenarios":"Frontend sends setAttrViewBlockVisibleViews with an empty viewIDs array (or null/undefined decoded to an empty slice), attempting to hide every view of a database block.","commonSituations":"UI bug unchecking the last visible view; plugin clears the list then re-adds in a second operation but sends the clear first; desync where the checkbox list is emptied before the new selection arrives.","solutions":["Ensure at least one view ID remains in the viewIDs payload before sending the transaction.","On the UI side, prevent the user from deselecting the last visible view (disable the last checkbox while checked).","If you intend to change selection, send the full new non-empty list atomically rather than clearing then setting."],"exampleFix":"// before: can send an empty selection\ntransaction({ op: \"setAttrViewBlockVisibleViews\", viewIDs: selected })\n\n// after: guard against empty\nif (selected.length === 0) {\n    showMessage(\"At least one visible view is required\")\n    return\n}\ntransaction({ op: \"setAttrViewBlockVisibleViews\", viewIDs: selected })","handlingStrategy":"validation","validationCode":"// Never submit an empty viewIDs list.\nif (!viewIDs || viewIDs.length === 0) {\n  showMessage('At least one visible view is required')\n  return\n}","typeGuard":"function hasVisibleViewSelection(v: unknown): v is string[] {\n  return Array.isArray(v) && v.every(x => typeof x === 'string') && v.length >= 1\n}","tryCatchPattern":null,"preventionTips":["Disable the control that deselects the last visible view.","Send the full new selection atomically rather than clear-then-set.","Treat an empty selection as a no-op, not a submission."],"tags":["attribute-view","av","database","view","validation","transaction"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}