plans: Add three implementation plans for View Architecture refactoring

Phase 1: ContainerView Refactoring (Low-risk, ~2-3h)
- Extract _ContainerViewCore with Renderable
- Make ContainerView a simple View with body
- Enable modifier support

Phase 2: Shared Handlers & Helpers (Moderate, ~4-5h)
- FocusableItemListHandler (navigation logic)
- SelectionStateManager (selection tracking)
- ItemStateRenderer (styling utilities)
- renderFocusableContainer() (helper function)

Phase 3: List & Table Implementation (Moderate-High, ~14-18h)
- Refactor List to use new architecture
- Implement Table with same foundation
- Both follow Box.swift pattern
- Maximum code reuse

Each phase gets its own branch and PR.
This commit is contained in:
phranck
2026-02-06 22:01:47 +01:00
parent 465c850227
commit 005ff6f85f
3 changed files with 251 additions and 0 deletions
@@ -0,0 +1,59 @@
# Implementation Plan: ContainerView Refactoring
## Objective
Refactor `ContainerView` from `body: Never` + `Renderable` pattern to correct `View` pattern with internal `_ContainerViewCore`.
## Current State
- `ContainerView<Content, Footer>: View` with `body: Never`
- 400+ lines of rendering logic in `renderToBuffer()`
- Modifiers don't work on ContainerView
## Target State
- `ContainerView<Content, Footer>: View` with `body: some View`
- `_ContainerViewCore<Content, Footer>: View, Renderable` contains all logic
- Modifiers work naturally (`.foregroundColor()`, etc.)
- Environment values propagate to content
## Implementation Steps
### Phase 1: Extract _ContainerViewCore
1. Create new private struct `_ContainerViewCore<Content: View, Footer: View>`
2. Copy all properties from ContainerView to _ContainerViewCore
3. Move `renderToBuffer()` logic to _ContainerViewCore
4. Conform _ContainerViewCore to `Renderable`
### Phase 2: Simplify ContainerView
1. Keep all public initializers on ContainerView
2. Add `body: some View` that creates and returns `_ContainerViewCore`
3. Remove `renderToBuffer()` from ContainerView
4. Verify `ContainerConfig` and `ContainerStyle` helpers still work
### Phase 3: Testing & Verification
1. Run all tests — verify no breakage
2. Check users: Card, Panel, Alert, Dialog still render correctly
3. Verify modifiers work: test `.foregroundColor()` on ContainerView
4. Test environment propagation to nested content
### Phase 4: Documentation
1. Update any comments/docs referencing the old pattern
2. Add note about `_ContainerViewCore` being internal implementation detail
## Success Criteria
- ✅ All 618 tests pass
- ✅ No breaking changes to public API
- ✅ Modifiers work on ContainerView
- ✅ Environment values propagate
- ✅ Code follows Box.swift pattern
- ✅ swiftlint clean
## Estimated Effort
**Low-risk**, mechanical refactoring. ~2-3 hours.
## Files Changed
- `Sources/TUIkit/Views/ContainerView.swift` (main)
- Possibly minimal changes to test files if needed
## Related Plans
- Follows from architectural review (CLAUDE.md updates)
- Foundation for List & Table refactoring
- Prerequisite for proper View modifier support
+95
View File
@@ -0,0 +1,95 @@
# Implementation Plan: List & Table (Refactored)
## Objective
Implement List and Table using shared handlers/helpers from Phase 2, with proper View architecture.
## Current State
- List exists but uses wrong pattern (body: Never + Renderable)
- Table doesn't exist yet
- No shared foundation
## Target State
- List: Public View with body, private _ListCore with Renderable
- Table: Public View with body, private _TableCore with Renderable
- Both use: FocusableItemListHandler, SelectionStateManager, renderFocusableContainer()
- Both follow Box.swift pattern
## Implementation Steps
### Phase 1: Refactor List (Using Shared Components)
1. Create `_ListCore` private struct
2. Replace direct `ListHandler` with `FocusableItemListHandler`
3. Replace ad-hoc selection logic with `SelectionStateManager`
4. Use `renderFocusableContainer()` helper
5. Update public List to have `body: some View` that creates _ListCore
6. Remove `renderToBuffer()` from public List
7. Verify all List tests still pass
### Phase 2: Implement Table (Using Shared Components)
1. Create `Table<SelectionValue, Content>: View`
2. Create `_TableCore` private struct with Renderable
3. Use `FocusableItemListHandler` (same navigation as List)
4. Use `SelectionStateManager` (row-based selection)
5. Implement table-specific logic:
- Column parsing and alignment
- ANSI-aware column padding
- Grid rendering (not vertical stack)
6. Create helper: `renderTableGrid()` or use `renderFocusableContainer()`
7. Tests: navigation, selection, column alignment
### Phase 3: Verify Architecture Consistency
1. Both List and Table follow same pattern:
- Public View with `body: some View`
- Private Core with Renderable
- Use shared handlers/helpers
2. Both support modifiers naturally
3. Environment values propagate correctly
4. Keyboard navigation consistent
5. Selection handling consistent
### Phase 4: Integration Testing
1. Test List with all modifiers: `.foregroundColor()`, `.padding()`, `.disabled()`
2. Test Table with all modifiers
3. Test environment propagation through ListItem content
4. Test selection state transitions
5. Test keyboard navigation in both
6. Test focus indicator rendering
### Phase 5: Example Apps & Docs
1. Update ListPage to use new List
2. Create TablePage example
3. Document keyboard navigation
4. Document selection patterns
5. Add code examples to DocC
## Success Criteria
- ✅ All 618 existing tests pass
- ✅ List tests pass (updated for new pattern)
- ✅ Table tests created and pass (>50 tests)
- ✅ Modifiers work on both List and Table
- ✅ Environment values propagate
- ✅ Zero code duplication between List and Table
- ✅ Both follow Box.swift pattern
- ✅ swiftlint clean
- ✅ No breaking changes to public API
## Estimated Effort
**Moderate to High**, requires careful implementation. ~6-8 hours for List, ~8-10 hours for Table.
## Files Changed/Created
- `Sources/TUIkit/Views/List.swift` (refactored)
- `Sources/TUIkit/Views/Table.swift` (new)
- `Tests/TUIkitTests/ListTests.swift` (updated)
- `Tests/TUIkitTests/TableTests.swift` (new)
- `Sources/TUIkitExample/Pages/ListPage.swift` (updated)
- `Sources/TUIkitExample/Pages/TablePage.swift` (new)
## Dependencies
- Phase 1: ContainerView refactoring ✓
- Phase 2: Shared handlers/helpers ✓
- These must be completed first
## Related Plans
- Uses FocusableItemListHandler, SelectionStateManager, renderFocusableContainer()
- Follows Box.swift pattern
- Final implementation of View Architecture guidelines
@@ -0,0 +1,97 @@
# Implementation Plan: Shared Focus/Selection Handlers & Helpers
## Objective
Extract and formalize reusable components needed by both List and Table:
- Focus navigation handler
- Selection state manager
- Item rendering utilities
- Container rendering helper
## Current State
- List has `ListHandler: Focusable` (focus logic only)
- No shared selection state manager
- No standardized container helper for focusable items
- Rendering patterns ad-hoc per control
## Target State
- `FocusableItemListHandler` — shared focus/navigation logic
- `SelectionStateManager<T>` — shared selection tracking
- `renderFocusableContainer()` — helper similar to `renderContainer()`
- `ItemStateRenderer` — utilities for styling items based on focus/selection
## Implementation Steps
### Phase 1: Create FocusableItemListHandler
1. Create new file: `Sources/TUIkit/Internal/FocusableItemListHandler.swift`
2. Extract from List's `ListHandler`:
- `focusedIndex`, `scrollOffset`, `viewportHeight`, `rowCount`
- `focusUp()`, `focusDown()`, `focusHome()`, `focusEnd()`, `focusPageUp()`, `focusPageDown()`
- `ensureFocusedInView()`
3. Conform to `Focusable` protocol
4. Add proper documentation
### Phase 2: Create SelectionStateManager
1. Create new file: `Sources/TUIkit/Internal/SelectionStateManager.swift`
2. Generic class: `SelectionStateManager<SelectionValue: Hashable>`
3. Properties:
- `binding: Binding<AnyHashable>?`
- `selectedValue: AnyHashable?`
4. Methods:
- `select(index: Int)` — select by index
- `select(value: SelectionValue)` — select by value
- `isSelected(index: Int) -> Bool`
- `isSelected(value: SelectionValue) -> Bool`
5. Type-erasure utilities for `Binding<SelectionValue>` → `Binding<AnyHashable>`
### Phase 3: Create ItemStateRenderer Utilities
1. Create new file: `Sources/TUIkit/Internal/ItemStateRenderer.swift`
2. Function: `renderItemWithState()`
- Parameters: `content: String`, `isFocused: Bool`, `isSelected: Bool`, `palette: Palette`
- Returns: styled string with background color, etc.
3. Optionally: helper for scroll indicators
### Phase 4: Create renderFocusableContainer Helper
1. Create new file: `Sources/TUIkit/Internal/FocusableContainerRenderer.swift`
2. Function: `renderFocusableContainer()`
- Similar signature to `renderContainer()`
- Plus: focus handler, selection manager, item renderer
- Returns: `FrameBuffer`
3. Handles:
- Extract rows from content
- Apply focus/selection styling
- Manage scrolling
- Wrap in container with title, border, padding
### Phase 5: Testing
1. Create unit tests for each handler/manager
2. Test navigation logic (focusUp, focusDown, wrapping, etc.)
3. Test selection state transitions
4. Test item rendering with various focus/selection states
### Phase 6: Documentation
1. Add doc comments to all new types
2. Document the shared architecture in guides
## Success Criteria
- ✅ All new components have comprehensive tests
- ✅ No duplication of logic between existing and new code
- ✅ Clean, minimal API surface
- ✅ Ready for List and Table to use
- ✅ swiftlint clean
- ✅ All existing tests still pass (618 tests)
## Estimated Effort
**Moderate**, requires careful design. ~4-5 hours.
## Files Created
- `Sources/TUIkit/Internal/FocusableItemListHandler.swift`
- `Sources/TUIkit/Internal/SelectionStateManager.swift`
- `Sources/TUIkit/Internal/ItemStateRenderer.swift`
- `Sources/TUIkit/Internal/FocusableContainerRenderer.swift`
- `Tests/TUIkitTests/FocusableItemListHandlerTests.swift`
- `Tests/TUIkitTests/SelectionStateManagerTests.swift`
## Related Plans
- Foundation for List & Table implementation
- Uses patterns from ContainerView refactoring
- Enables maximum code reuse