From 3cbbe3a6f77cb72bca4ae22891bfef01cf80f792 Mon Sep 17 00:00:00 2001 From: phranck Date: Sat, 7 Feb 2026 16:43:00 +0100 Subject: [PATCH] Chore: Add project memory and complete shared architecture analysis - .claude/memory.md: Complete project architecture snapshot (268 lines) - Enables instant context restoration via /remember - Move list-table-shared-architecture.md to done/ --- ...26-02-06-list-table-shared-architecture.md | 179 ++++++++++++++++++ ...26-02-06-list-table-shared-architecture.md | 131 ------------- 2 files changed, 179 insertions(+), 131 deletions(-) create mode 100644 plans/done/2026-02-06-list-table-shared-architecture.md delete mode 100644 plans/open/2026-02-06-list-table-shared-architecture.md diff --git a/plans/done/2026-02-06-list-table-shared-architecture.md b/plans/done/2026-02-06-list-table-shared-architecture.md new file mode 100644 index 0000000..e5c7447 --- /dev/null +++ b/plans/done/2026-02-06-list-table-shared-architecture.md @@ -0,0 +1,179 @@ +# List & Table: Shared Architecture Analysis + +## Completed + +2026-02-07 + +## Preface + +This analysis identifies the shared architecture between List and Table before implementation: both need focus management, keyboard navigation (Up/Down/Home/End), selection binding, scrolling, and item state rendering. Navigation logic and selection state are identical; rendering differs (List = vertical stack, Table = grid). Extract shared components (handlers, helpers, state managers) to eliminate duplication while letting each component specialize in layout. + +## Context / Problem + +Both List and Table need focus management, item navigation, selection binding, scrolling, and keyboard handlers. Without careful planning, the implementations will duplicate effort and diverge in behavior. + +## Specification / Goal + +Analyze shared patterns between List and Table to establish common architecture before implementing either component. + +## Existing Patterns Analysis + +### 1. Box.swift Pattern +- Composite View that delegates to `.border()` modifier +- Uses `body` property (not Renderable directly) +- Optional parameters with environment defaults +- Equatable conformance for caching + +### 2. Focus.swift Pattern +- `Focusable` protocol requires AnyObject (class-based handlers) +- Handler returns `true` from `handleKeyEvent()` to consume events +- FocusManager dispatches to focused element first, then handles Tab/arrows +- Section-based navigation with `context.activeFocusSectionID` + +### 3. RadioButtonGroupHandler Pattern (Primary Blueprint) +- Handler class with `focusedIndex` persisted via StateStorage +- Separate navigation index vs selection binding +- Wrap-around navigation (last to first) +- Consumes directional keys even when not acting +- `onFocusLost()` resets focusedIndex to selected item + +### 4. StateStorage Pattern +- `StateKey(identity, propertyIndex)` for unique identification +- `stateStorage.storage(for:default:)` returns StateBox +- `stateStorage.markActive(identity)` prevents garbage collection +- StateBox mutations trigger re-render automatically + +### 5. ContainerView Pattern +- Two-struct pattern: public View + private `_Core` Renderable +- Width calculation: render content first, then container +- Context width reduction: subtract 2 for borders +- Focus indicator consumption via `context.focusIndicatorColor` + +## Architecture Decision + +**Option A Selected**: List as View, Table as View, shared `ItemListHandler` class + +Rationale: +- Follows existing RadioButtonGroupHandler pattern +- Handler encapsulates all navigation logic +- List and Table differ only in rendering +- No need for complex protocol hierarchies + +## Shared Components Design + +### 1. ItemListHandler (Focusable class) + +```swift +final class ItemListHandler: Focusable { + let focusID: String + var focusedIndex: Int = 0 + var scrollOffset: Int = 0 + var itemCount: Int = 0 + var viewportHeight: Int = 10 + var canBeFocused: Bool = true + var onSelect: ((Int) -> Void)? + + func handleKeyEvent(_ event: KeyEvent) -> Bool + func onFocusLost() + func ensureFocusedInView() +} +``` + +Navigation keys: +| Key | Action | +|-----|--------| +| Up | focusedIndex -= 1 (wrap to end) | +| Down | focusedIndex += 1 (wrap to start) | +| Home | focusedIndex = 0 | +| End | focusedIndex = itemCount - 1 | +| PageUp | focusedIndex -= viewportHeight | +| PageDown | focusedIndex += viewportHeight | +| Enter/Space | onSelect?(focusedIndex) | + +### 2. Item Rendering States + +Four visual states based on focus and selection: + +| State | Style | +|-------|-------| +| Focused + Selected | Pulsing accent, bold | +| Focused only | Accent color (navigation cursor) | +| Selected only | Dimmed accent | +| Neither | Default foreground | + +Helper function in `ItemStateRenderer`: +```swift +func renderItem( + content: String, + isFocused: Bool, + isSelected: Bool, + pulsePhase: Double, + palette: Palette +) -> String +``` + +### 3. Scroll Management + +Auto-scroll when focus moves out of visible window: +```swift +func ensureFocusedInView() { + if focusedIndex < scrollOffset { + scrollOffset = focusedIndex + } else if focusedIndex >= scrollOffset + viewportHeight { + scrollOffset = focusedIndex - viewportHeight + 1 + } +} +``` + +Scroll indicators: Show up/down arrows when content extends beyond viewport. + +### 4. Container Reuse + +Both List and Table wrap content in ContainerView via `.border()` modifier: +- Optional title +- Border style from appearance +- Padding handled by modifier chain + +## File Structure + +``` +Sources/TUIkit/ +├── Focus/ +│ ├── ItemListHandler.swift # Shared navigation handler +│ └── ItemStateRenderer.swift # Shared item rendering +├── Views/ +│ ├── List.swift # List view (uses ItemListHandler) +│ └── Table.swift # Table view (uses ItemListHandler) +``` + +## Keyboard Navigation Model + +``` +┌─────────────────────────────────────────────────────────────┐ +│ FocusManager │ +│ dispatchKeyEvent() → focused.handleKeyEvent() │ +│ ↓ │ +│ ItemListHandler.handleKeyEvent() │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Up/Down/Home/End/PgUp/PgDown: Update focusedIndex │ │ +│ │ Enter/Space: Call onSelect │ │ +│ │ Return true (consume event) │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ Tab: FocusManager handles (next section/element) │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Implementation Order + +1. **ItemListHandler.swift**: Navigation logic, Focusable conformance +2. **ItemStateRenderer.swift**: Focus/selection visual states +3. **List.swift**: Simple vertical list using shared components +4. **Table.swift**: Grid layout with column alignment (future) + +## Checklist + +- [x] Architecture decisions documented +- [x] Shared patterns identified +- [x] Reference provided for List & Table implementation +- [x] No code changes required (analysis only) diff --git a/plans/open/2026-02-06-list-table-shared-architecture.md b/plans/open/2026-02-06-list-table-shared-architecture.md deleted file mode 100644 index 41099e0..0000000 --- a/plans/open/2026-02-06-list-table-shared-architecture.md +++ /dev/null @@ -1,131 +0,0 @@ -# List & Table: Shared Architecture Analysis - -## Preface - -This analysis identifies the shared architecture between List and Table before implementation: both need focus management, keyboard navigation (Up/Down/Home/End), selection binding, scrolling, and item state rendering. Navigation logic and selection state are identical; rendering differs (List = vertical stack, Table = grid). Extract shared components (handlers, helpers, state managers) to eliminate duplication while letting each component specialize in layout. - -## Similarities (Redundancy Risk) - -### Core Concepts -- **Focus Management**: Both need to track which item is focused (keyboard navigation) -- **Item Focus State**: Up/Down arrows, Home/End, Page Up/Down to navigate items -- **Selection**: Binding to track selected item(s) -- **Scrolling/Viewport**: Both have visible area vs total content -- **Keyboard Handlers**: Up/Down, Enter/Space, Page Up/Down, Home/End -- **Item Rendering with State**: Each item can be focused/selected (visual diff) - -### Visual Elements -- **Container**: Border, optional title, padding -- **Focus Indicator**: Visual marker on focused item (background color, bar, etc.) -- **Selection Indicator**: Visual marker on selected item(s) -- **Scroll Indicators**: Up/Down arrows when not at boundaries - -### Handler Logic -- Both need `Focusable` interface (for focus manager) -- Both need `StateStorage` for persistence across renders -- Navigation logic (focusUp, focusDown, etc.) is identical -- Scroll offset management is identical - -## Differences - -### List -- **Layout**: Vertical stack of items (simple) -- **Selection**: Usually single item -- **Item Content**: Arbitrary Views - -### Table -- **Layout**: Grid with columns and rows -- **Selection**: Row-based (but multiple columns in display) -- **Column Alignment**: ANSI-aware padding per column -- **Item Content**: Structured (column values, not arbitrary Views) - -## Shared Architecture Needed - -### 1. Base Focusable Item Handler -Extract common navigation logic into reusable class: -``` -FocusableItemListHandler: - - focusedIndex - - scrollOffset - - viewportHeight - - rowCount - - focusUp/Down/Home/End/Page Up/Down - - ensureFocusedInView() -``` - -### 2. Selection State -Shared pattern for tracking selected item: -``` -SelectionState: - - binding: Binding? - - select(index, value) - - isSelected(index, value) -> Bool -``` - -### 3. Container & Border Rendering -Both need border + optional title + padding. -Possibly extract into shared utility or reuse existing `ContainerView`. - -### 4. View Modifiers (Extensions) -Both should support: -- `.foregroundColor()` (via Environment) -- `.disabled()` -- `.focusable()` (custom?) -- `.padding()` (via modifiers) - -### 5. Focus Indicator & Item State Rendering -Common pattern: -``` -func renderItemWithState( - content: String, - isFocused: Bool, - isSelected: Bool, - palette: Palette -) -> String -``` - -## Implementation Strategy - -### Phase 1: Extract Common Handler -Create `FocusableItemListBase` or similar with shared navigation logic. -Both List and Table create instances and reuse. - -### Phase 2: Shared Selection State -Extract `SelectionStateManager` for consistent selection binding. - -### Phase 3: Architecture Decision -- **Option A**: List as View, Table as View, both use private _ListCore/_TableCore Views -- **Option B**: Single generic base (e.g., `_FocusableItemList`) that List and Table wrap -- **Option C**: Trait-based design (protocols for Focus, Selection, Rendering) - -## Context / Problem - -Both List and Table need focus management, item navigation, selection binding, scrolling, and keyboard handlers. Without careful planning, the implementations will duplicate effort and diverge in behavior. - -## Specification / Goal - -Analyze shared patterns between List and Table to establish common architecture before implementing either component. - -## Design - -### Shared Concepts Analysis (No Implementation) - -Just analyze for reference before implementing either List or Table. Areas to investigate: -1. How Box handles modifiers -2. How existing focus management works -3. How Focusable interface is used elsewhere -4. What StateStorage patterns already exist -5. Container rendering patterns - -Then design List & Table to maximize code reuse. - -## Implementation Plan - -This is an analysis document only. No implementation tasks. - -## Checklist - -- [ ] Architecture decisions documented -- [ ] Shared patterns identified -- [ ] Reference provided for List & Table implementation -- [ ] No code changes required