{"record":{"id":"eecfe0d617dbccec","repo":"toeverything/AFFiNE","slug":"databaseblockerror-eecfe0","errorCode":"DatabaseBlockError","errorMessage":"Unknown arrow keys, only support: up, down, left, and right keys.","messagePattern":"Unknown arrow keys, only support: up, down, left, and right keys\\.","errorType":"exception","errorClass":"BlockSuiteError","httpStatus":null,"severity":"error","filePath":"blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/selection.ts","lineNumber":381,"sourceCode":"\n    if (nextPosition === 'right') {\n      return getNextGroupFocusElement(\n        this.host,\n        groups,\n        selection,\n        groupIndex => (groupIndex === groups.length - 1 ? 0 : groupIndex + 1)\n      );\n    }\n\n    if (nextPosition === 'left') {\n      return getNextGroupFocusElement(\n        this.host,\n        groups,\n        selection,\n        groupIndex => (groupIndex === 0 ? groups.length - 1 : groupIndex - 1)\n      );\n    }\n    throw new BlockSuiteError(\n      ErrorCode.DatabaseBlockError,\n      'Unknown arrow keys, only support: up, down, left, and right keys.'\n    );\n  }\n\n  getNextFocusCell(\n    selection: KanbanCellSelection,\n    index: number,\n    nextPosition: 'up' | 'down' | 'left' | 'right'\n  ):\n    | {\n        cell: KanbanCell;\n        cardId?: string;\n        groupKey?: string;\n      }\n    | undefined {\n    const host = this.host;\n    if (!host) {","sourceCodeStart":363,"sourceCodeEnd":399,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/blocksuite/affine/data-view/src/view-presets/kanban/pc/controller/selection.ts#L363-L399","documentation":"KanbanSelectionController.getNextFocusCard (kanban/pc/controller/selection.ts:307-385) walks an if-chain over nextPosition ('up','down','left','right'); the trailing throw at :381 fires only when the caller passes a value outside that union. In well-typed TypeScript code it is unreachable; at runtime it appears when key names are forwarded un-narrowed (e.g. raw KeyboardEvent.key like 'ArrowUp') or via any-casts.","triggerScenarios":"A custom hotkey/keyboard handler mapping KeyboardEvent.key directly to getNextFocusCard without translating 'ArrowUp' to 'up'; JS callers or string-cast code bypassing the 'up'|'down'|'left'|'right' literal-union type.","commonSituations":"Custom kanban keyboard navigation extensions; tests calling the private controller with arbitrary strings; refactors that widen the direction parameter to string.","solutions":["Map KeyboardEvent.key to the union ('ArrowUp'->'up', 'ArrowDown'->'down', 'ArrowLeft'->'left', 'ArrowRight'->'right') before calling","Keep the parameter typed as the literal union and avoid any/string casts at event boundaries","Early-return for unrecognized keys instead of forwarding them"],"exampleFix":"// before\ncontroller.getNextFocusCard(selection, index, key as any); // key = 'ArrowUp' -> throws\n\n// after\nconst map: Record<string, 'up'|'down'|'left'|'right'> = {\n  ArrowUp: 'up', ArrowDown: 'down', ArrowLeft: 'left', ArrowRight: 'right',\n};\nconst dir = map[key];\nif (dir) controller.getNextFocusCard(selection, index, dir);","handlingStrategy":"type-guard","validationCode":"const KEY_TO_DIRECTION: Record<string, 'up' | 'down' | 'left' | 'right'> = {\n  ArrowUp: 'up',\n  ArrowDown: 'down',\n  ArrowLeft: 'left',\n  ArrowRight: 'right',\n};\n\nconst direction = KEY_TO_DIRECTION[event.key];\nif (direction) {\n  controller.getNextFocusCard(selection, index, direction);\n}","typeGuard":"type Direction = 'up' | 'down' | 'left' | 'right';\nconst isDirection = (d: string): d is Direction =>\n  d === 'up' || d === 'down' || d === 'left' || d === 'right';","tryCatchPattern":null,"preventionTips":["Translate KeyboardEvent.key to the canonical direction union at the event boundary","Keep getNextFocusCard's parameter typed as the literal union; never cast from string/any","Ignore unrecognized keys instead of forwarding them"],"tags":["database-block","kanban","keyboard-navigation","enum-validation"],"backgroundTag":"unhandled-enum-variant","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}