TUI-specific changes:
- Separator is now a space instead of vertical line to avoid double borders
- Sidebar (20 chars) and content (30 chars) have fixed widths
- Only the rightmost column adapts to terminal width changes
- Column content is padded to fill the full column width
- Fix escape sequence parsing for fast key repeat (read byte-by-byte)
- Ctrl+A: Select all text
- Ctrl+C: Copy selection to system clipboard (pbcopy/xclip)
- Ctrl+X: Cut selection to clipboard
- Ctrl+V: Paste from clipboard (pbpaste/xsel)
- Ctrl+Z: Undo last change (up to 50 states)
- 12 new tests (998 total)
- SecureField with masked display using ● (U+25CF) bullets
- Reuses TextFieldHandler for key input handling
- SwiftUI API parity: init(_:text:) and init(_:text:prompt:)
- Supports .onSubmit() and .disabled() modifiers
- Focus indicator with pulsing vertical bars
- 15 tests for masking behavior and rendering
- Example app page with password validation demo
- Slider: Interactive track control with keyboard navigation (arrow keys, +/-, Home/End)
- Stepper: Increment/decrement control with value or custom callbacks
- TrackStyle: Renamed from ProgressBarStyle, shared between ProgressView and Slider
- TrackRenderer: Extracted utility for track rendering
- 59 new tests (892 total), Example app demo pages for both components
- Remove textInputAutocapitalization (not relevant for TUI)
- TextField now has fixed width with horizontal scrolling
- New visual style: background color + focus indicator bars
- Increase frame rate to 30 FPS and process up to 5 events/frame
- Fix Space key handling in ActionHandler (.space instead of .character)
- VStack centers children relative to available width when spacers present
- CodePreview: Add Preview button that shows terminal screenshot
- Smooth fade-in animation with blur backdrop
- ESC or click outside to close
- Caption overlay on image
- Add hasExplicitWidth flag to RenderContext
- FlexibleFrameView sets flag when width constraints are specified
- ContainerView expands to fill available width when flag is set
- List calculates row width based on frame constraints
- InsetGroupedListStyle: disable alternating row colors by default
- InsetGroupedListStyle: move row padding into renderRow() so backgrounds extend to borders
Phase 4 & 6 of List SwiftUI API Parity:
- .selectionDisabled() prevents row selection in List
- .listRowSeparator() stub with warning (not supported in TUI)
- SelectionDisabledKey environment key
- Visibility and VerticalEdge.Set types for API compatibility
- 11 new tests (738 total)
- List now uses SelectableListRow<ID> instead of ListRow<ID> internally
- Section headers/footers are non-selectable (.header/.footer types)
- extractRows() detects SectionRowExtractor and flattens to typed rows
- selectableIndices populated for focus navigation skip logic
- renderRow() skips focus/selection background for headers/footers
- Alternating colors restart per section (sectionContentIndex)
- Added Sendable constraint to List<SelectionValue> for Swift 6
- Section conforms to ListRowExtractor (delegates to content ForEach)
- 10 new integration tests (732 total)
Foundation for integrating Section headers/footers with List row rendering:
- Create SelectableListRow.swift with ListRowType enum (header/content/footer)
and SelectableListRow struct for type-safe row classification
- Add FrameBuffer Sendable + Equatable conformance (required for SelectableListRow)
- Enhance ItemListHandler with selectableIndices property to track which rows
can be selected/focused (for skipping non-selectable headers/footers)
- Implement focus skip-logic in moveFocus() to jump over non-selectable rows
- Update Home/End keys to navigate to first/last selectable row
- Add comprehensive documentation to all new types
This foundation enables Phase 2c3 (List flattening and per-section styling).
All tests passing (722). Build + swiftlint clean.
- Implement SwiftUI-conformant Section with header/content/footer
- Add SectionRowExtractor for integration with List
- SectionTests with 14 test cases covering layout and behavior
- List SwiftUI API Parity plan: Section (phase 1), Badge/ListStyle (phase 2)
- Add ROADMAP documentation
- Add .vscode editor configuration
- Add ButtonRole (.cancel, .destructive) with SwiftUI-conformant API
- Alert renders buttons horizontally, sorted by role (cancel left)
- Alert max width capped at 60 characters for readability
- ESC key dismisses alerts via AlertPresentationModifier handler
- FocusManager: Left/Right arrows navigate within sections (like Up/Down)
- Move completed plans to done/ directory
- Update example pages with minor adjustments
List & Table:
- Replace focus indicator with full-width background bar (focusBackground)
- Change selection indicator from symbol to subtle background color
- Add Footer generic parameter and ViewBuilder initializers to List
- Environment values now propagate to child views
Keyboard:
- Add F1-F12 function key support (VT and SS3 escape sequences)
- Add Key.space case (previously parsed as .character(" "))
- Add Shortcut.f1 through Shortcut.f12
StatusBar:
- Change default style from .compact to .bordered
- Disable showAppearanceItem and showThemeItem by default
- Split StatusBarTests into 4 focused test files
Palette:
- Add focusBackground color for focus indication
Add measurement pass on first frame to determine actual header height
before outputting anything. This prevents visible content shift when
the estimated header height differs from the actual height.
- Add isFirstFrame flag to RenderLoop
- Perform silent measurement render on first frame
- Use actual header height for correct centering from the start
Phase 2 of View Architecture Refactor:
- ProgressView: Real body returning _ProgressViewCore
- Labels inherit environment (foregroundColor propagates)
- Spinner: Real body returning _SpinnerCore
- Respects environment.foregroundColor if no explicit color
- ButtonRow: Real body returning _ButtonRowCore
- Buttons inside inherit environment
All controls now follow the pattern:
- Public struct with body: some View
- Internal _Core struct with Renderable for rendering
656 tests pass.
Phase 1 of View Architecture Refactor:
- Add ForegroundColorKey environment key
- Add .foregroundColor() modifier on View (sets environment)
- Text reads environment.foregroundColor if no explicit color set
- Environment values propagate through body: some View
This enables:
VStack { Text("Hello") }.foregroundColor(.red)
// Text is now red!
7 new tests verify propagation through view hierarchy.
- List: Real View with body returning ContainerView
- Optional title displayed in container border
- Horizontal padding (1 char) for items
- Items rendered inside bordered container
- Table: Real View with body returning ContainerView
- Column headers inside container with separator
- Data rows with proper padding
- Scroll indicators inside container
- Updated example pages to use new title parameters
- Fixed tests for new container structure
This follows the SwiftUI pattern: public View with body,
internal _Core struct handles Renderable logic.
- ItemListHandler: Shared navigation/selection logic for List and Table
- Keyboard navigation (Up/Down/Home/End/PageUp/PageDown)
- Single and multi-selection modes via Binding
- Scroll offset management with auto-scroll
- Focus lifecycle hooks (onFocusLost/onFocusReceived)
- List: SwiftUI-compatible scrollable list component
- Single selection: List(selection: Binding<ID?>)
- Multi-selection: List(selection: Binding<Set<ID>>)
- ForEach content via ListRowExtractor protocol
- Multi-line row support
- Visual states (focused/selected with pulsing accent)
- Scroll indicators when content overflows
- Empty state placeholder
- .disabled() modifier support
- ListPage: Example with single and multi-selection demos
- 32 new tests (24 handler + 8 list rendering)
- Move InputHandler, RenderLoop, PulseTimer creation into run()
- These are now local variables instead of IUO stored properties
- Removes swiftlint:disable comments and potential crash risk
- 590 tests passing
- Add Important callout warning about runtime crash
- @available(*, unavailable) not possible on protocol requirements
- Existing fatalError solution is already the best approach