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

10 KiB

AdvancedTable - Plan de Mejoras y Corrección de Bugs

Fecha: 2025-12-17 Estado: COMPLETADO Consensuado:


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

// 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

// 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:

// 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

// 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"

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:

// 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:

// 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