Files
komp_ac/todo.md
2026-07-19 18:40:44 +02:00

5.5 KiB

TUI Pages TODO

Give form editors a dedicated page-level focus target

Current change

Dynamic form-field visibility belongs to tui-canvas. A FormEditor knows its stable logical field indexes and decides which fields are currently visible and navigable through DataProvider::field_visibility.

The phone-number example previously duplicated that state in tui-pages:

PageFocusBuilder::new()
    .canvas_field_indices(state.editor.navigable_fields())

This rebuilt a second per-field list every time the page specification was refreshed. When the phone extension appeared or disappeared, the page focus targets changed. TuiPages::sync_focus_to_spec consequently called FocusManager::register_page, which resets page-level focus state. Besides the repeated visibility scan and allocation, this reconciliation caused observable input lag and made focus behavior depend on two independently maintained lists.

The immediate fix replaces that dynamic list with one stable page-level target:

PageFocusBuilder::new().canvas_form_editor()

Internal field navigation and visibility are now handled only by tui-canvas. tui-pages only needs to know whether focus is inside the form or outside it.

Concern with the immediate API

canvas_form_editor() currently represents the complete form using FocusTarget::CanvasField(0). The zero is a marker, not the editor's current field. This is correct for the current example because it contains one form editor and the form-editor key hook only checks FocusTarget::is_canvas().

However, overloading CanvasField(0) is ambiguous and should not become the finished public design:

  • Two form editors on the same page cannot be identified independently.
  • A form editor and a standalone Canvas widget using field index 0 have the same page-level identity.
  • Every form-editor hook currently accepts any Canvas focus target, so multiple registered form-editor hooks could compete for the same key event.
  • Application code inspecting FocusTarget::CanvasField(index) may reasonably assume that index is a real field index. For a form editor it is now only a sentinel.
  • Directly setting page focus cannot express which form editor should receive focus.
  • Future focus restoration cannot reliably distinguish multiple editors.

Add a dedicated focus-target variant carrying the form-editor ID:

pub enum FocusTarget<O = ()> {
    CanvasFormEditor(usize),
    CanvasField(usize),
    InternalCanvasField(usize),
    // ...
}

Change the builder API to require the same ID used when registering the Canvas hook:

PageFocusBuilder::new().canvas_form_editor(0)

The responsibilities would then be explicit:

  • CanvasFormEditor(id) identifies one complete form at the page level.
  • CanvasField(index) remains for independently focusable Canvas widgets.
  • InternalCanvasField(index) retains its existing internal/non-top-level meaning where it is still needed.
  • FormEditor remains the sole owner of its current field, internal navigation, hidden fields, and computed/non-navigable fields.
  • tui-pages owns navigation between the form and other page components such as buttons, sections, and other form editors.

The form-editor key hook must match its registered editor ID instead of merely checking FocusTarget::is_canvas():

matches!(
    ctx.focus.as_ref(),
    Some(FocusTarget::CanvasFormEditor(focused_id)) if focused_id == id
)

Cursor behavior and input-layer selection should use the same ID-aware check. FocusTarget::is_canvas() should include the new variant so generic canvas boundary behavior continues to work.

Migration work

  1. Add FocusTarget::CanvasFormEditor(usize).
  2. Change PageFocusBuilder::canvas_form_editor() to canvas_form_editor(editor_id).
  3. Update form-editor key dispatch, paste dispatch, cursor behavior, and input context selection to require the matching editor ID.
  4. Keep standalone text inputs and text areas matched through their existing CanvasField(index) identities.
  5. Update examples to pass the same ID to the page focus builder and TuiPagesBuilder::canvas_form_editor(id).
  6. Search applications for per-field form focus lists and replace those lists with one CanvasFormEditor(id) target at the correct position among the other page targets.

Do not retain CanvasField(0) as a compatibility sentinel unless backwards compatibility is explicitly requested.

Required tests

  • One form editor keeps the same page focus target while internal field visibility changes.
  • Showing and hiding a dependent field does not call page focus registration or reset the current page-level target.
  • A page containing two form editors routes keys only to the focused editor ID.
  • A form editor and standalone Canvas text input can coexist without identity collisions.
  • Exiting the first or last navigable form field moves to the correct adjacent page component.
  • Returning focus to a form preserves or deliberately restores the editor's internal current field according to the chosen policy.
  • Paste and cursor-style routing use the same editor-ID matching as key input.

Performance acceptance criteria

  • Page refresh must not enumerate a form editor's visible or navigable fields.
  • Changing internal field visibility must not change the page-level focus target vector.
  • sync_focus_to_spec must not call register_page merely because a form row appeared or disappeared.
  • Typing into a visibility-controlling field must not allocate a second list of form-field focus targets in tui-pages.