zcatgui/docs/ADVANCED_TABLE_MERGE_PLAN.md
reugenio af1bb76aab feat(advanced_table): Multi-select, search, sorting, keyboard fixes
Bugs corregidos:
- Bug 1: Navegación teclado - cambio de keyPressed() a navKeyPressed()
- Bug 2: Sorting real - implementado sortRows() con bubble sort estable

Funcionalidades añadidas de Table:
- Multi-row selection (bit array 1024 rows, Ctrl+click, Shift+click, Ctrl+A)
- Incremental search (type-to-search con timeout 1000ms)
- Cell validation tracking (256 celdas con mensajes de error)

Nuevas funciones en AdvancedTableState:
- isRowSelected, addRowToSelection, removeRowFromSelection
- toggleRowSelection, clearRowSelection, selectAllRows
- selectRowRange, getSelectedRowCount, getSelectedRows, selectSingleRow
- addSearchChar, getSearchTerm, clearSearch, startsWithIgnoreCase
- hasCellError, addCellError, clearCellError, clearAllCellErrors
- hasAnyCellErrors, getLastValidationMessage

Cambios en types.zig:
- CellValue.compare() para ordenación
- allow_multi_select en TableConfig

Tests: 379 passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 18:34:34 +01:00

322 lines
10 KiB
Markdown

# AdvancedTable - Plan de Mejoras y Corrección de Bugs
> **Fecha:** 2025-12-17
> **Estado:** ✅ COMPLETADO
> **Consensuado:** Sí
---
## Contexto
Primera prueba real de AdvancedTable en zsimifactu (WHO panel) reveló 2 bugs críticos.
Además, el widget Table existente tiene funcionalidades que AdvancedTable debería incorporar.
**Decisión:** Incorporar funcionalidades de Table mientras corregimos los bugs.
---
## Bugs Detectados
| # | Bug | Severidad | Descripción |
|---|-----|-----------|-------------|
| 1 | **Navegación teclado** | ALTA | Flechas ↑↓←→ no mueven selección |
| 2 | **Sorting** | MEDIA | Click en header no reordena filas (solo cambia estado visual) |
---
## Funcionalidades a Incorporar de Table
### Fase A: Multi-Select + Search (~160 LOC)
**Origen:** `src/widgets/table/state.zig`, `src/widgets/table/keyboard.zig`
#### A1. Multi-Row Selection
```zig
// Añadir a AdvancedTableState
selected_rows: [128]u8 = [_]u8{0} ** 128, // Bit array (1024 rows)
selection_anchor: i32 = -1, // Para Shift+click
// Funciones a añadir
pub fn isRowSelected(row: usize) bool
pub fn addRowToSelection(row: usize) void
pub fn removeRowFromSelection(row: usize) void
pub fn toggleRowSelection(row: usize) void
pub fn clearRowSelection() void
pub fn selectAllRows() void
pub fn selectRowRange(from: usize, to: usize) void
pub fn getSelectedRowCount() usize
pub fn getSelectedRows(buffer: []usize) usize
```
**Keyboard:**
- Ctrl+click → toggle individual row
- Shift+click → select range from anchor
- Ctrl+A → select all (si config.allow_multi_select)
**LOC estimadas:** ~90
#### A2. Incremental Search (Type-to-Search)
```zig
// Añadir a AdvancedTableState
search_buffer: [64]u8 = [_]u8{0} ** 64,
search_len: usize = 0,
search_last_time: u64 = 0,
search_timeout_ms: u64 = 1000, // Reset después de 1s
// Funciones a añadir
pub fn addSearchChar(char: u8, current_time: u64) []const u8
pub fn getSearchTerm() []const u8
pub fn clearSearch() void
```
**Helper:**
```zig
// En advanced_table.zig o utils
pub fn startsWithIgnoreCase(haystack: []const u8, needle: []const u8) bool
```
**Comportamiento:**
- Usuario teclea sin Ctrl/Alt → acumula en search_buffer
- Salta a primera fila cuya columna 0 empieza con el término
- Timeout 1s → reset buffer
**LOC estimadas:** ~70
### Fase B: Cell Validation (~80 LOC)
**Origen:** `src/widgets/table/state.zig`
```zig
// Añadir a AdvancedTableState
validation_errors: [256]u32 = [_]u32{0xFFFFFFFF} ** 256, // cell IDs
validation_error_count: usize = 0,
last_validation_message: [128]u8 = [_]u8{0} ** 128,
last_validation_message_len: usize = 0,
// Funciones a añadir
pub fn hasCellError(row: usize, col: usize) bool
pub fn addCellError(row: usize, col: usize, message: []const u8) void
pub fn clearCellError(row: usize, col: usize) void
pub fn clearAllCellErrors() void
pub fn hasAnyCellErrors() bool
pub fn getLastValidationMessage() []const u8
```
**Visual:**
- Celdas con error: borde rojo + fondo rojizo
**LOC estimadas:** ~80
---
## Plan de Implementación
### Paso 1: Fase A1 - Multi-Row Selection
**Archivos a modificar:**
- `src/widgets/advanced_table/state.zig` - Añadir campos y funciones
- `src/widgets/advanced_table/advanced_table.zig` - Keyboard handling
- `src/widgets/advanced_table/types.zig` - Añadir config.allow_multi_select
**Tests a añadir:**
- `test "AdvancedTableState multi-row selection"`
### Paso 2: Fase A2 - Incremental Search
**Archivos a modificar:**
- `src/widgets/advanced_table/state.zig` - Añadir campos y funciones
- `src/widgets/advanced_table/advanced_table.zig` - handleKeyboard
**Tests a añadir:**
- `test "AdvancedTableState incremental search"`
- `test "startsWithIgnoreCase"`
### Paso 3: Bug 1 - Navegación Teclado
**Diagnóstico durante Fase A:**
Al incorporar el código de keyboard handling de Table, comparar línea por línea
con AdvancedTable para identificar por qué no funciona.
**Posibles causas:**
1. `has_focus` no es true cuando debería
2. `ctx.input.keyPressed()` no detecta las teclas
3. `config.keyboard_nav` es false
4. `handleKeyboard()` no se está llamando
**Verificación:**
```zig
// Debug temporal
if (has_focus) {
std.debug.print("AdvancedTable has_focus=true, keyboard_nav={}\n", .{config.keyboard_nav});
}
```
### Paso 4: Bug 2 - Sorting Real
**Problema:** `toggleSort()` solo cambia `sort_column` y `sort_direction` pero NO reordena `state.rows`.
**Solución:**
```zig
// En advanced_table.zig, después de toggleSort()
if (result.sort_changed) {
sortRows(table_state, table_schema, result.sort_column.?, result.sort_direction);
}
fn sortRows(
state: *AdvancedTableState,
schema: *const TableSchema,
col_idx: usize,
direction: SortDirection,
) void {
if (direction == .none) {
// Restaurar orden original si guardado
return;
}
const col_name = schema.columns[col_idx].name;
// Sort using std.mem.sort with comparator
std.mem.sort(Row, state.rows.items, .{}, struct {
fn lessThan(context: anytype, a: Row, b: Row) bool {
const val_a = a.get(context.col_name);
const val_b = b.get(context.col_name);
return val_a.compare(val_b) < 0;
}
}.lessThan);
// Si descending, invertir
if (direction == .descending) {
std.mem.reverse(Row, state.rows.items);
}
}
```
**Nota:** Hay que sincronizar los state maps (dirty_rows, new_rows, etc.) después del sort.
### Paso 5: Fase B - Cell Validation (Opcional)
Implementar si hay tiempo después de resolver bugs.
---
## Orden de Ejecución
```
┌─────────────────────────────────────────┐
│ 1. Fase A1: Multi-Row Selection │
│ - Copiar código de table/state.zig │
│ - Adaptar a AdvancedTableState │
│ - Añadir keyboard handling │
├─────────────────────────────────────────┤
│ 2. Fase A2: Incremental Search │
│ - Copiar código de table/state.zig │
│ - Añadir startsWithIgnoreCase │
│ - Integrar en handleKeyboard │
├─────────────────────────────────────────┤
│ 3. Bug 1: Diagnosticar Teclado │
│ - Comparar con Table funcional │
│ - Añadir debug prints │
│ - Identificar causa raíz │
├─────────────────────────────────────────┤
│ 4. Bug 2: Sorting Real │
│ - Implementar sortRows() │
│ - Sincronizar state maps │
│ - Guardar/restaurar orden original │
├─────────────────────────────────────────┤
│ 5. Tests y Verificación │
│ - Todos los tests pasan │
│ - Probar en zsimifactu │
└─────────────────────────────────────────┘
```
---
## Estimación
| Tarea | LOC | Tiempo estimado |
|-------|-----|-----------------|
| Fase A1 (multi-select) | ~90 | - |
| Fase A2 (search) | ~70 | - |
| Bug 1 (teclado) | ~20 | - |
| Bug 2 (sorting) | ~60 | - |
| Tests | ~40 | - |
| **Total** | **~280** | - |
---
## Criterios de Éxito
1. ✅ Flechas ↑↓←→ mueven selección en AdvancedTable
2. ✅ Click en header ordena las filas visualmente
3. ✅ Ctrl+click selecciona múltiples filas
4. ✅ Shift+click selecciona rango
5. ✅ Ctrl+A selecciona todas las filas
6. ✅ Teclear busca en primera columna
7. ✅ Todos los tests pasan
8. ✅ zsimifactu WHO panel funciona correctamente
---
## Resultados de Implementación
### Bug 1: Navegación teclado - ✅ CORREGIDO
**Causa raíz:** AdvancedTable usaba `ctx.input.keyPressed()` que solo detecta el primer press, no key repeats. Table usa `ctx.input.navKeyPressed()` que incluye key repeats.
**Solución:** Cambiar `handleKeyboard()` para usar `navKeyPressed()` con switch exhaustivo.
### Bug 2: Sorting real - ✅ CORREGIDO
**Causa raíz:** `toggleSort()` solo cambiaba el estado visual pero no reordenaba `state.rows`.
**Solución:** Implementar función `sortRows()` con bubble sort estable + sincronización de state maps.
### Fase A1: Multi-Row Selection - ✅ IMPLEMENTADO
**Añadido a AdvancedTableState:**
- `selected_rows: [128]u8` - bit array para 1024 filas
- `selection_anchor: i32` - para Shift+click
- Funciones: `isRowSelected`, `addRowToSelection`, `removeRowFromSelection`, `toggleRowSelection`, `clearRowSelection`, `selectAllRows`, `selectRowRange`, `getSelectedRowCount`, `getSelectedRows`, `selectSingleRow`
### Fase A2: Incremental Search - ✅ IMPLEMENTADO
**Añadido a AdvancedTableState:**
- `search_buffer: [64]u8` - buffer de búsqueda
- `search_len`, `search_last_time`, `search_timeout_ms` (1000ms)
- Funciones: `addSearchChar`, `getSearchTerm`, `clearSearch`
- Helper: `startsWithIgnoreCase()`
### Fase B: Cell Validation - ✅ IMPLEMENTADO
**Añadido a AdvancedTableState:**
- `cell_validation_errors: [256]u32` - cell IDs con error
- `cell_validation_error_count`, `last_validation_message`
- Funciones: `hasCellError`, `addCellError`, `clearCellError`, `clearAllCellErrors`, `hasAnyCellErrors`, `getLastValidationMessage`
### Tests añadidos
- `test "AdvancedTableState multi-row selection"`
- `test "AdvancedTableState select row range"`
- `test "AdvancedTableState incremental search"`
- `test "AdvancedTableState cell validation"`
- `test "CellValue compare"`
- `test "startsWithIgnoreCase"`
**Total tests:** 379 (todos pasan)
### LOC añadidas
| Archivo | LOC |
|---------|-----|
| `state.zig` | ~200 |
| `advanced_table.zig` | ~100 |
| `types.zig` | ~45 |
| **Total** | ~345 |
---
*Plan creado por: Claude Code (Opus 4.5)*
*Fecha: 2025-12-17*
*Estado: ✅ COMPLETADO*