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>
322 lines
10 KiB
Markdown
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*
|