# OpenSpec Change: enterprise-browser-react-kanban - **Change ID**: enterprise-browser-react-kanban - **Package**: `@suaveplan/react-kanban` - **Category**: browser - **Target path**: `browser/react/react-kanban` - **Status**: IN PROGRESS - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/react-kanban.md` --- ## Summary Deliver `@suaveplan/react-kanban` on the genesis branch at enterprise quality. The package provides a headless `useKanban` hook with pure helpers (`findCardColumn`, `isColumnAtWipLimit`, `moveCardInBoard`, `moveColumnInBoard`, `validateMove`), plus reference `Board`, `Column`, `Card` components with Storybook stories. Genesis fixes the broken export map (missing `./core/column/column` and `./core/card/card` source files), splits the over-cap `board.ts` (320 lines) and `board.test.ts` (541 lines), adds Zod schemas for board state, OTel metrics, keyboard-drag support, and axe-core clean stories. No `react-dom` in runtime deps. --- ## Current-state audit (genesis) Fresh build — does not exist on genesis yet. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | `WIP_LIMIT_EXCEEDED`, `INVALID_MOVE`, `CARD_NOT_FOUND` | | `@suaveplan/logger` (`workspace:^`) | Structured logging | | `@suaveplan/schema-registry` (`workspace:^`) | Board / card schema registration | | `zod` (`catalog:forms`) | Board, column, card Zod schemas | | `@opentelemetry/api` (`catalog:observability`) | Move / WIP-violation metrics | | `tslib` (`catalog:build`) | Runtime helpers | Dev: `vitest`, `@vitest/coverage-istanbul`, `happy-dom`, `@testing-library/react`, `@suaveplan/testing`, `@suaveplan/testing-dom`. No `react-dom` in runtime `dependencies` — peer only. --- ## Preserved public API surface ```ts // Hook function useKanban(config: KanbanConfig): [KanbanState, KanbanActions] // Pure helpers function findCardColumn(board: KanbanBoard, cardId: string): KanbanColumn | null function isColumnAtWipLimit(column: KanbanColumn): boolean function moveCardInBoard(board: KanbanBoard, source: DragSource, dest: DropTarget): KanbanBoard function moveColumnInBoard(board: KanbanBoard, fromIdx: number, toIdx: number): KanbanBoard function validateMove(board: KanbanBoard, source: DragSource, dest: DropTarget): MoveResult // Reference components (new) function Board(props: BoardProps): JSX.Element function Column(props: ColumnProps): JSX.Element function Card(props: CardProps): JSX.Element // Types interface KanbanBoard { columns: KanbanColumn[] } interface KanbanColumn { id: string; title: string; wipLimit?: number; cards: KanbanCard[] } interface KanbanCard { id: string; title: string; data?: T } interface KanbanConfig { board: KanbanBoard; onMove?(src: DragSource, dst: DropTarget): void; enforceWipLimits?: boolean } interface KanbanState { board: KanbanBoard; dragging: DragSource | null } interface KanbanActions { moveCard(src: DragSource, dst: DropTarget): MoveResult; moveColumn(from: number, to: number): void } ``` Subpath exports: `"."`, `"./core/board/board"`, `"./core/column/column"`, `"./core/card/card"`, `"./utils/drag-drop"`, `"./components/board"`. --- ## New additions for genesis ### 1. Implement the missing `Column` and `Card` modules Main-branch exported `./core/column/column` and `./core/card/card` from `package.json` but had no source files. Genesis ships `src/core/column/column.ts` and `src/core/card/card.ts` with typed interfaces, helpers, and tests. ### 2. Split `board.ts` and `board.test.ts` `board.ts` (320 lines) → `board.ts` + `moves.ts` + `wip.ts` + `swimlanes.ts`, each ≤ 300 lines. `board.test.ts` (541 lines) → split by responsibility: `board.test.ts`, `moves.test.ts`, `wip.test.ts`, each ≤ 500 lines. ### 3. Reference `Board`, `Column`, `Card` components + stories `src/components/` — reference visual components built on `useKanban`. Stories: default, playground, WIP-limit enforcement, swimlane grouping. ### 4. Keyboard-drag support Arrow-key move (card left/right/up/down) wired in `useKanban`; reference components respect it. Axe-core clean. ### 5. Zod schemas `KanbanBoardSchema`, `KanbanColumnSchema`, `KanbanCardSchema` registered in `@suaveplan/schema-registry`. ### 6. OTel metrics Counter `kanban.move_total{success}`, counter `kanban.wip_violation_total`, histogram `kanban.move_latency_ms`. ### 7. Error codes `WIP_LIMIT_EXCEEDED`, `INVALID_MOVE`, `CARD_NOT_FOUND`. --- ## File layout ```text browser/react/react-kanban/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts core/ board/ board.ts (useKanban hook, ≤300 lines) board.test.ts board.md moves.ts (move helpers) moves.test.ts wip.ts (WIP-limit enforcement) wip.test.ts swimlanes.ts (swimlane grouping) swimlanes.test.ts index.ts column/ column.ts column.test.ts column.md column.types.ts index.ts card/ card.ts card.test.ts card.md card.types.ts index.ts components/ board/ board.tsx board.stories.tsx board.test.tsx board.md index.ts column/ column.tsx column.stories.tsx column.test.tsx column.md index.ts card/ card.tsx card.stories.tsx card.test.tsx card.md index.ts utils/ drag-drop/ drag-drop.ts drag-drop.test.ts drag-drop.md index.ts schema/ board-schema/ board-schema.ts board-schema.md index.ts types/ common.ts common.md constants/ index.ts tests/ integration.test.ts ``` --- ## Impact **Downstream**: any Suaveplan browser app with a kanban board. **Breaking changes**: None — new components and modules are additive. `board.ts` split is internal. **Prerequisites**: `@suaveplan/error`, `@suaveplan/schema-registry` on Verdaccio.