gtin and credit card in the canvas
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -7959,9 +7959,11 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"arboard",
|
"arboard",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"card-validate",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"derivative",
|
"derivative",
|
||||||
"email_address",
|
"email_address",
|
||||||
|
"gtin-validate",
|
||||||
"iban_validate",
|
"iban_validate",
|
||||||
"jiff",
|
"jiff",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|||||||
2
server
2
server
Submodule server updated: 125410b9c6...324c20b39c
142
todo.md
142
todo.md
@@ -1,142 +0,0 @@
|
|||||||
# 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`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
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:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
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.
|
|
||||||
|
|
||||||
### Recommended design
|
|
||||||
|
|
||||||
Add a dedicated focus-target variant carrying the form-editor ID:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
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:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
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()`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
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`.
|
|
||||||
Submodule tui-canvas updated: 3f7df9f6fd...562769c3d2
Submodule tui-pages updated: f215b38346...981a77c863
@@ -1,33 +0,0 @@
|
|||||||
|
|
||||||
1. Action System
|
|
||||||
tui-pages intentionally does not provide ActionResolution like client/src/action_engine/action_decider/
|
|
||||||
handler.rs:23. Replace it in the client adapter/handler, not in tui-pages.
|
|
||||||
|
|
||||||
Best path: keep the routing logic as app code inside TuiActionHandler::handle_action, or keep a private helper
|
|
||||||
equivalent to ActionDecider::resolve. ctx.current_view and ctx.focus from tui-pages/src/runtime/mod.rs:181 give
|
|
||||||
you enough data to route page/canvas/global actions. This is a client philosophy change: routing becomes part of
|
|
||||||
the app handler, while tui-pages only provides focus/view/input context.
|
|
||||||
2. Overlay Types
|
|
||||||
No tui-pages change needed. Your old OverlayKind maps naturally to the generic O parameter.
|
|
||||||
|
|
||||||
Define something like client-side enum ClientOverlay { CommandBar, SearchPalette, FindFilePalette, Sidebar,
|
|
||||||
Picker }, then use FocusTarget::Overlay(ClientOverlay::CommandBar) etc. DialogButton(usize) should probably
|
|
||||||
become FocusTarget::ModalItem(usize) if you use the modal path, because tui-pages separates simple named overlays
|
|
||||||
from modal item focus in tui-pages/src/focus/target.rs:19.
|
|
||||||
3. String vs Type-Based Modes
|
|
||||||
This is not a real incompatibility. ModeId is just a typed wrapper over strings and implements AsRef<str>/
|
|
||||||
From<String> in tui-pages/src/runtime/mod.rs:13. Your current KeyMode::as_str() values already match the tui-
|
|
||||||
pages::modes constants.
|
|
||||||
|
|
||||||
The actual migration is moving mode calculation from FocusTarget::mode_hint() in client/src/focus_manager/
|
|
||||||
target.rs:50 into PageSpec::modes(...). Since page_spec(view, state, focus) receives focus, you can preserve the
|
|
||||||
same behavior there. Watch one behavior: the old client converts plain insert-mode chars into
|
|
||||||
CanvasAction::InsertChar in client/src/input_pipeline/pipeline.rs:90; tui-pages returns PipelineResponse::Type
|
|
||||||
instead. Preserve that in TuiActionHandler::handle_text.
|
|
||||||
4. Page Identification
|
|
||||||
PageIdentifier should likely disappear. It duplicates AppView. tui-pages already gives ctx.current_view, so
|
|
||||||
checks like PageIdentifier::Form(_) become matches!(ctx.current_view, AppView::Form(_)).
|
|
||||||
|
|
||||||
This is a natural cleanup, not a tui-pages gap. The only case to keep a separate identifier is if you
|
|
||||||
intentionally want to collapse several AppView variants into one routing bucket. Even then, make it a client-
|
|
||||||
local helper derived from AppView, not a runtime concept.
|
|
||||||
Reference in New Issue
Block a user