Documentation source
Data Table
Generic, reusable data table component with inline editing, keyboard navigation, virtualization, and domain adapters
## Overview
The Data Table module (`features/data-table/`) provides a generic `DataTable<T>` component built on TanStack Table v8. It is fully decoupled from any domain — entity-specific, task-specific, or any other data shape is mapped through adapter components that translate domain schemas into the generic `ColumnSchema<T>` interface.
The old entity-coupled data table (~1,089 lines in a single file with 31 supporting files) was replaced by this modular architecture.
## Key Concepts
### ColumnSchema\<T\>
The core type that defines a column. Each column declares its `id`, `header`, `accessorFn`, semantic `type` (string, number, boolean, enum, date, rich-text, array, relation, currency, percentage, status, custom), and optional features like `editable`, `hidden`, `pinnable`, `renderCell`, and `renderEditor`.
### Cell Modes
Cells operate in two modes via the InteractionMask pattern:
- **SELECT** — navigate with arrow keys, Enter/F2 to edit, Delete to clear
- **EDIT** — inline editor receives focus, Escape cancels, Enter/Tab commits
### Domain Adapters
Adapters bridge domain data to the generic table:
- `EntityDataTable` — maps `EntityTypeRecord.json_schema` → `ColumnSchema<EntityRecord>[]`, wires inline edits to `PATCH /api/entities/[id]`
- `TaskDataTable` — maps task fields → `ColumnSchema<TaskRecord>[]`
- `DataTableBlock` — renders `EntityDataTable` inside a view block with config overrides
## Architecture
```
features/data-table/
├── core/ # Types, constants, context, build-column-defs, components
│ ├── types.ts # ColumnSchema<T>, DataTableProps<T>, FilterState, etc.
│ ├── constants.ts # Density classes, page sizes, thresholds
│ ├── context.tsx # DataTableProvider — shared state for sub-components
│ ├── build-column-defs.tsx # ColumnSchema[] → TanStack ColumnDef[]
│ ├── data-table.tsx # Main orchestrator component
│ ├── data-table-cell.tsx # Cell renderer (display + edit mode)
│ ├── data-table-head.tsx # Header row with sort indicators
│ ├── data-table-body.tsx # Body with standard + virtualized rendering
│ ├── data-table-row.tsx # Single row component
│ └── data-table-footer.tsx # Summary footer row
├── editors/ # Inline editor registry + 6 editor types
│ ├── editor-registry.ts # Type → editor component lookup
│ ├── text-editor.tsx
│ ├── number-editor.tsx
│ ├── boolean-editor.tsx
│ ├── enum-editor.tsx
│ ├── date-editor.tsx
│ └── rich-text-editor.tsx # Tiptap via lazy next/dynamic
├── features/ # Toolbar features
│ ├── toolbar.tsx # Search, density, column visibility
│ ├── pagination.tsx # Page navigation + size selector
│ ├── bulk-actions.tsx # Selection-based actions bar
│ ├── column-pinning.tsx # Pin/unpin column toggles
│ ├── column-visibility.tsx # Show/hide column checkboxes
│ └── density-toggle.tsx # Compact/default/spacious
├── interaction/ # Hooks for cell-level interaction
│ ├── use-cell-navigation.ts # Arrow keys, Tab, Enter/Escape
│ ├── use-cell-editing.ts # Edit state machine + type coercion
│ ├── use-cell-clipboard.ts # Ctrl+C/V copy-paste
│ ├── use-column-resize.ts # CSS-variable resize + persistence
│ └── use-row-selection.ts # Checkbox + shift-click selection
└── virtualization/ # Large dataset support
├── use-virtual-rows.ts # @tanstack/react-virtual integration
└── focus-sink.tsx # Hidden input for keyboard capture
```
## How It Works
### Data Flow
1. **Adapter** builds `ColumnSchema<T>[]` from domain schema
2. **`buildColumnDefs`** converts to TanStack `ColumnDef[]`, adding select column
3. **`useReactTable`** manages sort, filter, pagination, visibility, pinning state
4. **Context** distributes table instance + interaction state to sub-components
5. **Cell renderer** looks up `ColumnSchema` by column ID, renders display or editor
6. **On commit** — `useCellEditing.commitEdit` coerces value → calls `onCellEdit` → adapter persists
### Pagination
Two modes:
- **Client-side** (default) — `getPaginationRowModel()` handles paging within loaded data
- **Server-side** — activated when `onPageChange` + `total` are provided; uses `manualPagination: true` and delegates page fetching to the consumer
### Hidden Columns
Columns with `hidden: true` in their schema are included in the TanStack model but start with `visibility: false`. Users can re-enable them via the column visibility toggle in the toolbar.
## API Reference
### DataTable\<TData\>
Main component. Key props:
| Prop | Type | Description |
|------|------|-------------|
| `data` | `TData[]` | Rows to render |
| `columns` | `ColumnSchema<TData>[]` | Column definitions |
| `getRowId` | `(row: TData) => string` | Stable row ID extractor |
| `total` | `number` | Total rows across all pages |
| `onCellEdit` | `(rowId, colId, value) => Promise<void>` | Inline edit persistence |
| `onPageChange` | `(page: number) => void` | Server-side page navigation |
| `config` | `DataTableConfig` | Feature toggles and display options |
| `bulkActions` | `BulkAction<TData>[]` | Actions for selected rows |
### ColumnSchema\<TData\>
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `id` | `string` | required | Unique column identifier |
| `header` | `string` | required | Display label |
| `accessorFn` | `(row: TData) => unknown` | required | Value extractor |
| `type` | `ColumnType` | required | Semantic type for rendering/editing |
| `editable` | `boolean` | `false` | Allow inline editing |
| `hidden` | `boolean` | `false` | Start hidden, user can reveal |
| `renderCell` | `(value, row) => ReactNode` | — | Custom display renderer |
| `renderEditor` | `(props) => ReactNode` | — | Custom editor component |
## For Agents
Agents interact with data tables indirectly through entity/task CRUD tools. The data table is a presentation component — agents create/update entities via `createEntity`, `updateEntity`, and the table reflects those changes on refresh.
## Design Decisions
- **Generic over TData** — React context can't be generic, so the context uses `any` for the table instance and column schemas. Type safety is enforced at the adapter boundary.
- **InteractionMask pattern** — a single hidden `<FocusSink>` input captures all keyboard events, avoiding per-cell focus management complexity.
- **CSS-variable column resize** — widths are computed once per resize event and consumed via `calc(var(--col-width))`, avoiding per-cell style recalculation.
- **Editor registry** — editors are looked up by `ColumnType` at render time via `getEditorForType()`, making it trivial to add new editor types.
- **Domain adapters over configuration** — instead of a mega-config object, each domain creates a thin adapter component that maps its schema to `ColumnSchema[]` and wires domain-specific callbacks.
## Related Modules
- [Entity System](/docs/features/entity-system) — entity adapter source
- [Block System](/docs/features/block-system) — data table block rendering
- [View System](/docs/features/view-system) — view configs that include data table blocks
- [Tasks](/docs/features/tasks) — task adapter source