siyuan-note/siyuan · warning

at least one visible view is required

Error message

at least one visible view is required

What it means

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.

Source

Thrown at kernel/model/attribute_view.go:1240

func (tx *Transaction) doSetAttrViewBlockView(operation *Operation) (ret *TxErr) {
	err := SetDatabaseBlockView(operation.BlockID, operation.AvID, operation.ID)
	if err != nil {
		return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
	}
	return
}

func (tx *Transaction) doSetAttrViewBlockVisibleViews(operation *Operation) (ret *TxErr) {
	err := SetDatabaseBlockVisibleViews(operation.BlockID, operation.AvID, operation.ViewIDs)
	if err != nil {
		return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
	}
	return
}

func SetDatabaseBlockVisibleViews(blockID, avID string, viewIDs []string) (err error) {
	if 1 > len(viewIDs) {
		return errors.New("at least one visible view is required")
	}

	attrView, err := av.ParseAttributeView(avID)
	if nil != err {
		logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
		return
	}

	visible := map[string]bool{}
	for _, viewID := range viewIDs {
		if nil == attrView.GetView(viewID) {
			return fmt.Errorf("view [%s] not found in attribute view [%s]", viewID, avID)
		}
		visible[viewID] = true
	}

	var normalized []string
	for _, view := range attrView.Views {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Ensure at least one view ID remains in the viewIDs payload before sending the transaction.
  2. On the UI side, prevent the user from deselecting the last visible view (disable the last checkbox while checked).
  3. If you intend to change selection, send the full new non-empty list atomically rather than clearing then setting.

Example fix

// before: can send an empty selection
transaction({ op: "setAttrViewBlockVisibleViews", viewIDs: selected })

// after: guard against empty
if (selected.length === 0) {
    showMessage("At least one visible view is required")
    return
}
transaction({ op: "setAttrViewBlockVisibleViews", viewIDs: selected })
Defensive patterns

Strategy: validation

Validate before calling

// Never submit an empty viewIDs list.
if (!viewIDs || viewIDs.length === 0) {
  showMessage('At least one visible view is required')
  return
}

Type guard

function hasVisibleViewSelection(v: unknown): v is string[] {
  return Array.isArray(v) && v.every(x => typeof x === 'string') && v.length >= 1
}

Prevention

When it happens

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

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

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/dd34bac9a59850d4. Report an issue: GitHub.