248 Commits
Author SHA1 Message Date
phranck c4ca87f70e Feat: Add TextField component with text input, cursor navigation, and onSubmit 2026-02-09 19:01:28 +01:00
phranck f288eb71a4 Chore: Remove unused ansiRegex (dead code after manual parsing optimization) 2026-02-09 18:33:30 +01:00
phranck 2f8b351bfb Perf: Optimize ANSI string operations
Replace regex-based strippedLength and stripped with manual parsing.

Results:
- strippedLength: 2-5x faster
- stripped: 5-12x faster
- padToVisibleWidth: 1.8-2.2x faster
- Menu render: 34% faster
- RadioButtonGroup render: 22% faster
2026-02-09 18:33:26 +01:00
phranck bfda38ff0d Perf: Optimize FrameBuffer and Stack rendering
- Add FrameBuffer init with pre-computed width
- Optimize appendVertically/appendHorizontally to avoid redundant width computation
- Single-pass computation in VStack/HStack (was 4+ array traversals)
- Add performance benchmark tests (21 tests)

Results:
- VStack (10 children): 3x faster
- Stack nesting depth 10: 2.3x faster
- Nesting overhead: ~0.002ms per level
2026-02-09 18:19:13 +01:00
phranck 26e667f2dc Feat: Add LazyVStack and LazyHStack with lazy rendering 2026-02-09 17:57:27 +01:00
phranck 7e7ad673ca Refactor: Convert VStack, HStack, ZStack to body: some View pattern 2026-02-09 17:55:22 +01:00
phranck 65418a4d5e Feat: Complete View Architecture Refactor - Phase 6 verification
- Add 13 modifier propagation tests
- All 757 tests pass
- SwiftLint clean (no serious violations)
- Move plan to done/
2026-02-09 17:49:24 +01:00
phranck 4e59a36ba4 Refactor: Convert RadioButtonGroup to body: some View pattern 2026-02-09 12:32:16 +01:00
phranck d9010bab39 Refactor: Convert Button, Toggle, Menu to body: some View pattern 2026-02-09 12:32:16 +01:00
phranck 6a4f6880bc Feat: Add preview button to CodePreview with terminal screenshot overlay
- 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
2026-02-09 02:57:48 +01:00
phranck c0c42e8349 Fix: frame(width:) now properly constrains container views like List
- 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
2026-02-09 02:26:45 +01:00
phranck 2a67398589 Fix: ContainerView respects frame width constraints 2026-02-09 01:28:28 +01:00
phranck cef2483dbf Feat: Add statusBarSystemItems modifier for theme/appearance toggles 2026-02-09 01:12:06 +01:00
phranck 90017af892 Fix: Remove stderr usage for Linux concurrency safety
The fputs(stderr) call was not concurrency-safe on Linux.
Removed the warning entirely since stub modifiers should be silent.
2026-02-08 23:49:45 +01:00
phranck 12e2645969 Feat: Add selectionDisabled and listRowSeparator modifiers
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)
2026-02-08 23:43:59 +01:00
phranck 4937de4748 Fix: Rename count to intValue to avoid SwiftLint false positive
SwiftLint interpreted `count == 0` as empty_count violation,
but count was an Int variable, not a collection property.
2026-02-08 23:04:32 +01:00
phranck 506c7621e9 Feat: Integrate Section into List rendering (Phase 2c3)
- 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)
2026-02-08 23:00:59 +01:00
phranck 91ca964d93 Feat: Add Section Integration Foundation (Phase 2c1-2c2)
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.
2026-02-08 23:00:59 +01:00
phranck f3b1b52254 Feat: Implement ListStyle system (List SwiftUI API Parity Phase 2b)
- Add ListStyle protocol with showsBorder, rowPadding, groupingStyle, alternatingRowColors
- Implement PlainListStyle (no borders, minimal appearance)
- Implement InsetGroupedListStyle (bordered, inset padding, alternating rows)
- Add ListStyleKey environment key with InsetGroupedListStyle default
- Create .listStyle() modifier extension for semantic API
- Integrate into List._ListCore.renderToBuffer() and renderRow()
- Apply alternating row colors (even/odd rows with accent opacity)
- Respect style.showsBorder in ContainerConfig for border rendering
- Add 20 comprehensive ListStyle tests (protocol, styles, environment, instantiation)

Total tests: 722 (20 new ListStyle tests all passing)
Previous: 702 (Badge 2a tests)

Behavior matches SwiftUI:
- PlainListStyle: minimal, no visual containers
- InsetGroupedListStyle: bordered, padded, alternating row backgrounds
- Even rows: accent.opacity(0.15), Odd rows: no background (unless focused/selected)
- Focus/Selection state overrides alternating colors (priority: focus+selected > focused > selected > alternating)
2026-02-08 23:00:59 +01:00
phranck e118871503 Feat: Implement Badge modifier (List SwiftUI API Parity Phase 2a)
- Add BadgeValue enum (Int, String variants) with displayText and isHidden logic
- Implement BadgeModifier view wrapper with environment-based propagation
- Add BadgeKey to Environment.swift for badge value storage
- Create three .badge() overload extensions: badge(Int), badge(String), badge<S>(S?)
- Integrate badge rendering in List.renderRow(): right-aligned, dimmed foreground
- Add 20 comprehensive test cases for BadgeValue, environment, and edge cases
- Create implementation plan for Phase 2 (Badge + ListStyle + alternating rows)

Total tests: 702 (20 new Badge tests all passing)
2026-02-08 23:00:59 +01:00
phranck f4d70d15cc Feat: Add Section view and List SwiftUI API Parity plan
- 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
2026-02-08 23:00:59 +01:00
phranck ac5e63eb84 Feat: Add ButtonRole, horizontal Alert buttons, ESC dismiss, arrow key navigation
- 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
2026-02-08 23:00:59 +01:00
phranck 989b90effa Refactor: Rename .foregroundColor() to .foregroundStyle() for SwiftUI API parity 2026-02-08 16:36:53 +01:00
phranck f2df17de30 Lots of changes 2026-02-08 14:34:42 +01:00
phranck 7b62fba8ac Docs: Add List and Table documentation
- Create ListAndTable.md DocC article with usage examples
- Document keyboard navigation, selection modes, visual states
- Add padding modifier documentation (1 unit = 1 line/1 char)
- Update plan checklist to reflect implementation status
- Add List, Table, TableColumn to TUIkit.md topics
2026-02-08 00:42:26 +01:00
phranck 80553547c1 Feat: List/Table focus bar, F-keys support, StatusBar defaults
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
2026-02-08 00:35:12 +01:00
phranck e3438f2c68 Fix: Prevent content jumping on first frame with app header
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
2026-02-08 00:26:07 +01:00
phranck 44fa7b3c74 Refactor: Convert simple controls to body: some View
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.
2026-02-07 19:58:21 +01:00
phranck 032e0bcfa9 Feat: Add foregroundColor environment propagation
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.
2026-02-07 19:44:47 +01:00
phranck 816f1958e1 Refactor: List and Table use ContainerView with proper body
- 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.
2026-02-07 19:29:57 +01:00
phranck 84fdb7ace2 Feat: Add Table component with column support
- TableColumn: Column definition with title, alignment, width modes
  - Key path and closure-based value extraction
  - Width modes: .fixed(Int), .flexible, .ratio(Double)
  - Alignment: .leading, .center, .trailing
  - Chainable modifiers: .alignment(), .width()

- Table: SwiftUI-compatible table with column headers
  - Single selection: Table(data, selection: Binding<ID?>)
  - Multi-selection: Table(data, selection: Binding<Set<ID>>)
  - Reuses ItemListHandler for navigation/selection
  - ANSI-aware column alignment
  - Header row with column titles
  - Space-only separators (no vertical lines)
  - Scroll indicators when content overflows
  - Empty state placeholder
  - .disabled() modifier support

- TableColumnBuilder: Result builder for column DSL
- TablePage: Example with file browser style demo
- 21 new tests for Table and TableColumn

Completes Phase 2 of List & Table plan.
2026-02-07 19:08:19 +01:00
phranck 9b8548bdbc Feat: Add List component with ItemListHandler
- 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)
2026-02-07 18:57:51 +01:00
phranck b7781d58ae Refactor: Remove implicitly unwrapped optionals from AppRunner (Phase 6)
- 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
2026-02-07 16:28:20 +01:00
phranck d0a66c28b3 Refactor: Consolidate ButtonHandler and ToggleHandler into ActionHandler (Phase 5)
- Create reusable ActionHandler for simple action-triggering views
- Button and Toggle now use ActionHandler instead of dedicated handlers
- RadioButtonGroupHandler unchanged (complex navigation logic)
- Update tests: ActionHandlerTests with custom trigger key test
- 590 tests passing
2026-02-07 16:28:20 +01:00
phranck 21b3ebc2b8 Docs: Improve ForEach.body documentation (Phase 4)
- Add Important callout warning about runtime crash
- @available(*, unavailable) not possible on protocol requirements
- Existing fatalError solution is already the best approach
2026-02-07 16:28:20 +01:00
phranck 4b5638da38 Feat: Add TerminalProtocol for testability (Phase 3)
- Create TerminalProtocol.swift with @MainActor isolated protocol
- Terminal now conforms to TerminalProtocol
- Add MockTerminal test double for capturing output and simulating input
- Update plan checklist
2026-02-07 16:28:20 +01:00
phranck f51a343adf Fix: Resolve Swift 6 Equatable conformance errors for View types
Add explicit nonisolated == implementations using MainActor.assumeIsolated
for all View types with @preconcurrency Equatable conformances:

- Box
- Card
- ContainerView
- _ContainerViewCore
- Dialog
- Panel
- ProgressView
- VStack, HStack, ZStack

This fixes CI failures on macOS where the derived Equatable conformance
cannot access @MainActor-isolated properties from nonisolated context.
2026-02-07 13:47:35 +01:00
phranck ff54e516cd Docs: Add comprehensive documentation for result builders
- Expand Scene protocol docs with overview, conformance guide, topics
- Add SceneBuilder docs with usage example and explanation
- Add ButtonRowBuilder docs with control flow support details
- Add RadioButtonGroupBuilder docs with usage example
- Add StatusBarItemBuilder docs with See Also references

All result builders now have consistent, DocC-compatible documentation
explaining their purpose, usage patterns, and supported control flow.
2026-02-07 13:37:18 +01:00
phranck b8634165fe Chore: Fix SwiftLint warnings and add code review agent
- Fix modifier order (nonisolated before static/public)
- Fix switch case alignment in ContentView.swift
- Fix empty string comparisons (use .isEmpty)
- Fix identical operands in KeyEventTests
- Remove vertical whitespace before closing braces
- Fix trailing whitespace and newlines
- Add swiftlint:disable comments for valid IUO patterns
- Add .claude/commands/review.md code review agent
2026-02-07 13:19:15 +01:00
phranck fd1dd5f858 Fix: Resolve Swift 6 Equatable conformance errors for MainActor-isolated Views
- Add explicit nonisolated == implementations using MainActor.assumeIsolated
- Fix DimmedModifier, FlexibleFrameView, OverlayModifier
- Remove unnecessary @preconcurrency attributes
2026-02-07 13:07:23 +01:00
phranck 366b74e7fc Refactor: Add cross-platform Lock wrapper for optimal performance
Create Lock<State> that uses OSAllocatedUnfairLock on Apple platforms (faster unfair lock) and NSLock on Linux. Also fix TupleView Equatable conformance for Swift 6 concurrency.
2026-02-07 03:01:37 +01:00
phranck 31c1c91fe0 Fix: Replace OSAllocatedUnfairLock with NSLock for Linux compatibility
OSAllocatedUnfairLock from the 'os' module is not available on Linux. Replaced with NSLock and @unchecked Sendable for cross-platform thread safety.
2026-02-07 02:57:38 +01:00
phranck 82544e0e02 Refactor: Add Swift 6 @MainActor to View hierarchy
Add @MainActor to View, ViewModifier, Renderable, App, Scene protocols and all builders (ViewBuilder, SceneBuilder, ChildInfoProvider). Update renderToBuffer(), makeChildInfo(), resolveChildInfos() for main-actor isolation. Mark generic Equatable conformances with @preconcurrency. Update all 45 test suites with @MainActor annotation.
2026-02-07 02:50:41 +01:00
phranck 4493126758 Refactor: Swift 6 concurrency improvements and ContainerView architecture
Phase 1: Thread-Safe Primitives
- AppState now uses OSAllocatedUnfairLock for thread-safe state management
- SignalManager flags remain nonisolated(unsafe) with documentation explaining
  why (signal handlers cannot use locks safely)

Phase 2: @MainActor Isolation (Core Types)
- Terminal, FrameDiffWriter, RenderLoop, AppRunner, ViewRenderer: @MainActor
- App.main() uses MainActor.assumeIsolated for synchronous entry point
- renderOnce() function: @MainActor
- Tests updated with @MainActor annotations

ContainerView Refactoring
- Changed from body: Never + Renderable to body: some View
- Extracted rendering logic to private _ContainerViewCore
- Enables modifiers to work correctly on ContainerView

All 591 tests pass, SwiftLint clean.
2026-02-07 02:50:41 +01:00
phranck 5210868685 Revert: Remove premature List implementation
The List was implemented without following the shared architecture plan.
Reverted to allow proper implementation after:
1. ContainerView refactoring
2. Shared handlers (FocusableItemListHandler, SelectionStateManager)
3. List & Table with common architecture

Files removed:
- Sources/TUIkit/Views/List.swift
- Sources/TUIkit/Modifiers/TagModifier.swift
- Tests/TUIkitTests/ListTests.swift
- Sources/TUIkitExample/Pages/ListPage.swift

Tests: 591 (was 618, -27 List tests)
2026-02-07 01:13:33 +01:00
phranck cfa8b31631 Refactor: Standardize UI card styling and remove em-dashes
- Unified card headers: gap-3, Icon size 20, text-accent color
- PlansCard: collapsible sections, animated expand/collapse
- update-plans-data.ts: export all plans (no limit)
- Replaced all em-dashes with colons or full sentences
- Content text standardized to text-lg across all cards
- FeatureCard, ArchHighlight descriptions adjusted
2026-02-07 00:24:19 +01:00
phranck bc925885d6 Refactor: List with title and body padding (simple approach)
- Add optional title parameter to List struct and init methods
- Title rendered above items with accent color
- Body items get horizontal padding (1 char left/right, 0 vertical)
- Focus indicators maintain full background width with padding
- Simplified ListPage: List is now self-contained with title parameter
- All 618 tests pass, 0 serious lint violations
- Maintains complete keyboard navigation and selection behavior
2026-02-06 21:34:39 +01:00
phranck 88bc3f1582 Fix: Remove dot from focused items, add dimmed background instead
- Focused items: dimmed background (foregroundSecondary, 15% opacity)
- Selected items: dimmed background (accent, 20% opacity) + accent text
- Unfocused items: plain text (no padding, no background)
- Dot indicator completely removed
- Selection and focus are visually distinct via background color
- 618 tests passing
2026-02-06 21:12:26 +01:00
phranck d2ce696d74 Fix: Correct List selection and focus rendering
- Selected items: dimmed background (20% opacity) + accent text (NO dot)
- Focused items: pulsing dot indicator (no background)
- Unfocused items: plain text with padding
- Selection and focus are now visually distinct
- 618 tests passing
2026-02-06 21:10:54 +01:00
phranck df5a6ea71c Feat: Implement List selection and improve rendering
- Selection now works: Enter/Space selects focused row (by index)
- Selected rows show with full-width background bar (accent color)
- Focused rows show with pulsing dot indicator
- Unfocused rows show with padding space for alignment
- ListPage content now uses theme foreground colors (not white)
- Selection binding updates when user presses Enter/Space
- 618 tests passing
2026-02-06 21:08:30 +01:00