Initial Commit
This commit is contained in:
186
openspec/changes/enterprise-browser-react-kanban/proposal.md
Normal file
186
openspec/changes/enterprise-browser-react-kanban/proposal.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# 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<T = unknown> { 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.
|
||||
Reference in New Issue
Block a user