Commit Graph
49 Commits
Author SHA1 Message Date
phranck 0dedddca59 Refactor: Remove maxVisibleRows from List and Table (not in SwiftUI API)
List and Table now always fill available height like SwiftUI.
The TUI-specific maxVisibleRows parameter was removed for API parity.
2026-02-10 12:46:09 +01:00
phranck f193fd45c8 Fix: Remove maxVisibleRows from SplitView demo Lists for full height 2026-02-10 12:39:44 +01:00
phranck c7ef9ac004 Fix: NavigationSplitView focus indicator and improve demo with List components 2026-02-10 12:05:49 +01:00
phranck b3a8cb7e5a Feat: Add NavigationSplitView demo to example app 2026-02-10 12:00:29 +01:00
phranck 91aadd40a1 Feat: Add SecureField component for password input
- 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
2026-02-09 22:20:43 +01:00
phranck bc9841cdc9 Feat: Add Slider and Stepper components with TrackStyle refactor
- 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
2026-02-09 21:52:37 +01:00
phranck b877cb58ae Feat: Add TextField demo page to Example app 2026-02-09 19:05:30 +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 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 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 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 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 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
phranck 924492f46b Feat: Integrate List component into example app menu
- Add .list case to DemoPage enum
- Add ListPage to menu with shortcut 9
- Shift Spinners to shortcut 0
- Update status bar menu range to include all shortcuts
- 617 tests passing
2026-02-06 21:02:14 +01:00
phranck 7ea5e51478 Feat: Add scrollable List component with keyboard navigation
- List<SelectionValue, Content> generic component with optional selection binding
- ListHandler (Focusable) manages focus, scroll offset, and keyboard navigation
- Keyboard: arrow keys, Page Up/Down, Home/End, Enter/Space for selection
- Auto-scroll: focused row always visible in viewport
- .tag() modifier for selection value association
- 30+ tests covering navigation, selection, scrolling, rendering
- ListPage example with static and dynamic content
- 617 total tests passing
2026-02-06 20:59:40 +01:00
phranck ce772ea6f3 Refactor: Checkbox style uses dot (●) instead of square
- Changed indicator from ◼ (U+25FC) to ● (large dot)
- More consistent with toggle indicator style
- Checkbox now shows [●] when on, [ ] when off
- Updated example page state summary
- All 591 tests passing
2026-02-06 18:03:01 +01:00
phranck b7de843009 Feat: Add RadioButtonPage to example app with menu integration
- RadioButtonPage shows vertical radio groups, horizontal radio groups, and disabled states
- Live state display for color, size, and layout choices
- Added to main menu as option 8
- Shortcut key '8' for quick navigation
- All 591 tests passing
2026-02-06 17:57:32 +01:00
phranck 941d8b704d Refactor: Checkbox uses U+25FC (black small square)
- Changed indicator from  to ◼ (U+25FC)
- Smaller, more compact checkbox appearance
- Updated example page state summary
- All 571 tests passing
2026-02-06 17:45:45 +01:00
phranck 6d1a4e876e Refactor: Checkbox uses U+25FE (black medium square) instead of U+25A3
- Changed indicator from ▣ to  (U+25FE)
- Better visual weight and consistency
- Updated example page state summary
- All 571 tests passing
2026-02-06 17:44:19 +01:00
phranck 0713f3ead7 Refactor: Checkbox uses U+25A3 (square with horizontal fill) instead of dot
- Changed indicator from ● to ▣ (U+25A3)
- More visually distinctive for checkboxes
- Updated example page state summary
- All 571 tests passing
2026-02-06 17:43:21 +01:00
phranck 989861738c Refactor: Checkbox style uses dot (●) instead of x
- Checkbox toggle now shows [●] when on, [ ] when off
- More consistent with dot-based toggle style [●○]
- Updated example page to show new checkbox indicator
- All 571 tests passing
2026-02-06 17:41:17 +01:00
phranck a0af77af15 Feat: Toggle component with toggle and checkbox styles
- Toggle struct with string initializer for SwiftUI API parity
- ToggleStyle enum: .toggle (slider ●○) and .checkbox ([x])
- ToggleHandler for Space/Enter keyboard events
- Focus indicator with pulsing accent dot (inherited from Button pattern)
- Disabled state with tertiary color
- .disabled() modifier for control
- Comprehensive tests (17 tests, 571 total passing)
- TogglePage example with both styles, disabled states, and live state demo
- Added to main menu with shortcut key 7
2026-02-06 17:34:00 +01:00
phranck d3f8ff60f3 Chore: Change license to MIT
- Add LICENSE file (MIT)
- Update 141 Swift file headers: CC BY-NC-SA 4.0 → License: MIT
2026-02-06 00:21:51 +01:00
phranck 45d08c447d Feat: Add ProgressView with 5 bar styles and SwiftUI-matching API
Determinate progress bar with block, blockFine, shade, bar, and dot
styles. 4 initializers matching SwiftUI signatures (value/total, label,
currentValueLabel, string title). Style selection via .progressBarStyle()
modifier. Colors: foregroundSecondary (filled), foregroundTertiary (empty),
accent (dot head). 26 tests in 3 suites.
2026-02-05 23:18:38 +01:00
phranck 0bde1d9f4d Refactor: remove block/flat appearances, ascii border style, and update documentation
Remove the block and flat appearance systems entirely, keeping only the
four standard border-based appearances (line, rounded, doubleLine, heavy).
Remove BorderStyle.ascii preset. Clean up all stale references in doc
comments, DocC articles, and README.

- Remove Appearance.flat/.block and BlockPalette protocol
- Remove surface color tokens (surfaceBackground, surfaceHeaderBackground, elevatedBackground)
- Remove BorderStyle.block, .ascii, and related statics
- Remove flat/block rendering paths from all views
- Simplify BorderRenderer, BorderModifier, ContainerView, Menu, Button, StatusBar, AppHeader
- Fix BorderStyle doc examples (add missing right padding)
- Update DocC: PaletteReference, ThemingGuide, AppearanceAndColors, RenderCycle, TUIkit.md
- Update README palette references to SystemPalette
- Delete FlatThemePage from example app
- Remove related tests (526 tests / 84 suites passing)
2026-02-05 22:08:53 +01:00
phranck 88d3165c4b Feat: add fire-and-forget notification system with NotificationService
Centralized NotificationService with .notificationHost() modifier for
rendering stacked, auto-dismissing notifications in the top-right corner.
Notifications use a single style with theme border colors — severity
differentiation belongs to Alerts, not notifications.

- NotificationService with static accessor and environment key
- NotificationHostModifier with fade-in/out animation and vertical stacking
- NotificationTiming for opacity interpolation and word-wrap
- Box(lines:) convenience init with BufferView for pre-styled content
- LifecycleManager.resetAppearance(token:) for re-triggering animations
- 17 tests covering service, timing, word-wrap, and rendering
2026-02-05 18:05:29 +01:00
phranck 20547de056 Feat: apply .equatable() to static example app subtrees
Add Equatable conformance and .equatable() wrapper to extracted views:
- FeatureBox (MainMenuPage): title/subtitle comparison, 3 instances
- ContainerTypesRow (ContainersPage): property-free, always cache-hits
- SettingsAndAlignmentRow (ContainersPage): property-free, always cache-hits

These subtrees are purely palette-driven with no @State dependencies.
During Spinner/Pulse animation frames (25 FPS), the render cache skips
their entire subtree rendering — only the animated views re-render.
2026-02-05 14:47:31 +01:00
phranck 095c67e021 Refactor: decompose ContainersPage and MainMenuPage into smaller view structs
Extract static, state-free subtrees into standalone View structs to
prepare for .equatable() memoization in Phase 5:

- FeatureBox: extracted from MainMenuPage's private featureBox() method
- ContainerTypesRow: Card/Box/Panel examples from ContainersPage
- SettingsAndAlignmentRow: settings panel + alignment demos

ButtonsPage left as-is — nearly all sections depend on @State clickCount
through Button actions, making decomposition ineffective for memoization.
2026-02-05 14:46:12 +01:00
phranck df4ee3253b Feat: AppHeader — framework-managed header bar rendered outside the view tree
AppHeader is rendered at the top of the terminal by RenderLoop, similar to
StatusBar at the bottom. Views declare header content via .appHeader { }
ViewBuilder modifier. Supports standard (thin divider) and block (half-block
with appHeaderBackground) appearance. Hidden when no content is set.
Diff cache invalidates on header height changes to prevent ghosting.
2026-02-03 21:21:41 +01:00
phranck f77cd8bdec Chore: Unified file headers across all Swift files
Update IDETemplateMacros.plist and replace headers in 136 Swift files with
new format: 🖥️ TUIKit — Terminal UI Kit for Swift. Remove sdsd.swift template draft.
2026-02-03 20:48:29 +01:00
phranck 55fbfb0f4f Refactor: Dimmed overlay background with palette colors, ANSI-aware compositing, centered overlays
DimmedModifier now strips all ANSI codes and ornament characters, re-renders
with uniform palette.foregroundTertiary on palette.overlayBackground.
FrameBuffer.insertOverlay preserves base ANSI styling via ansiAwarePrefix/Suffix
and leadingANSISequences restoration. Overlays center relative to terminal size
with -2 vertical offset. Alert presets no longer color borders — only titles.
2026-02-03 20:48:14 +01:00
phranck db03e4e41c Refactor: HSL-based color system for all palettes — lighter/darker preserve hue, unified baseHue architecture 2026-02-03 18:11:39 +01:00
phranck 186e444fcc Fix: ESC closes modal via StatusBar context switching
OverlaysPage now manages its own StatusBar items based on modal state:
- Modal open: ESC → close modal, Enter → dismiss
- Modal closed: ESC → back to menu, arrows → nav, Enter → show

Removes redundant ESC handlers from Modal/AlertPresentationModifier.
The correct approach is context-aware StatusBar items, not multiple
competing ESC handlers.
2026-02-03 12:35:07 +01:00
phranck ca9a0cf49c Fix: Modal focus isolation, ESC dismiss, footer layout, palette colors
- Wire FocusManager.dispatchKeyEvent() into InputHandler as Layer 3
  (Tab/Shift+Tab navigation, Enter/Space button activation)
- Modal/Alert presenters isolate base content from focus/key systems
  using RenderContext.isolatedForBackground() so only modal buttons
  receive focus and key events
- ESC automatically dismisses any modal or alert (framework-level)
- Fix ContainerView footer layout: Spacer() now fills correctly by
  constraining footer context to actual inner width
- Remove body background color from standard style containers
  (only block style uses distinct section backgrounds)
- Replace all hardcoded ANSI colors with palette semantic colors
  (.palette.warning/error/info/success) in Alert presets and example app
2026-02-03 12:28:39 +01:00
phranck 7e69b9e924 Fix: Right-align dismiss buttons in all overlay demo variants
Changed dismissButton to use HStack { Spacer(); Button } pattern for
consistent right-alignment across all alerts, dialogs, and modals.

Also simplified Alert preset usage — replaced explicit Alert<Button>.warning()
with standard Alert() initializer to avoid generic type inference issues.
2026-02-03 02:22:46 +01:00
phranck 44895b59cc Feat: Redesign OverlaysPage with interactive demo menu
Replace the static single-alert demo with an interactive page featuring:
- Menu with 8 overlay variants (5 Alert presets, 2 Dialog styles, 1 custom modal)
- Description panel showing details and API usage for the selected variant
- Enter key triggers the selected overlay with dimmed background
- Dismiss button closes any overlay

Variants demonstrated:
1. Alert (Standard) — default theme colors
2. Alert (Warning) — yellow preset
3. Alert (Error) — red preset
4. Alert (Info) — cyan preset
5. Alert (Success) — green preset
6. Dialog — content-only, no footer separator
7. Dialog with Footer — footer section with separator line
8. Modal (Custom) — arbitrary view content with .modal(isPresented:)
2026-02-03 02:19:12 +01:00
phranck 82c8eaf4b2 Fix: Enforce absolute SwiftUI API parity for .alert() modifier
Changed .alert() signature to match SwiftUI exactly:
- message parameter is now @ViewBuilder () -> Message (was String)
- Parameter order matches SwiftUI: title, isPresented, actions, message
- Message content is rendered to string internally for Alert view

Updated:
- AlertPresentationModifier generic signature (added Message type parameter)
- View+Presentation.swift API signatures
- OverlaysPage demo to use ViewBuilder message
- All AlertPresentationModifierTests to use ViewBuilder message

Added permanent rule to .claude/CLAUDE.md:
- ABSOLUTE SwiftUI API Parity is non-negotiable
- Must research exact SwiftUI signatures before implementing
- Only deviate when terminal constraints require it

This ensures TUIKit provides a familiar API for SwiftUI developers.
2026-02-03 02:03:39 +01:00
phranck 45503c0d80 Feat: Add SwiftUI-style presentation API with .alert() and .modal(isPresented:)
- Add AlertPresentationModifier with Binding<Bool> support
- Add ModalPresentationModifier for custom modal content
- Add View+Presentation.swift with .alert() and .modal(isPresented:) extensions
- Update OverlaysPage to demonstrate new declarative presentation API
- Add comprehensive tests for both presentation modifiers (13 tests)
- Follows SwiftUI API parity rule: same naming, parameter order, and behavior

The new API eliminates manual if/else branching for modals:

Before:
  if showModal {
    content.dimmed().overlay { AlertView() }
  } else {
    content
  }

After:
  content.alert("Title", isPresented: $showModal) {
    Button("OK") { showModal = false }
  }

This mirrors SwiftUI's .alert(isPresented:) and .sheet(isPresented:) patterns.
2026-02-03 01:34:30 +01:00
phranck 2cdb0dd3db Refactor: Simplify Spinner API — remove speed, track width, and trail length enums
Radically simplify the Spinner public API from 6 parameters to 3.
Remove SpinnerSpeed, BouncingTrackWidth, and BouncingTrailLength enums.
Hardcode calibrated intervals (dots 110ms, line 140ms, bouncing 100ms),
fixed track width (9), and trail opacities with edge overshoot for
smooth fade-in/fade-out at track edges.
2026-02-02 20:25:38 +01:00
phranck dec8c7c29f Feat: Add Spinner view with dots, line, and bouncing styles
Auto-animating loading indicator with three visual styles:
- Dots (braille rotation), Line (ASCII rotation), Bouncing (Larson scanner
  with fade trail). Time-based frame calculation ensures each spinner runs
  at its own speed independent of render triggers.

Configurable: SpinnerSpeed (.slow/.regular/.fast), BouncingTrackWidth
(.minimum/.default/.maximum/.fixed(Int)), BouncingTrailLength
(.short/.regular/.long), custom color.

Run loop upgraded from 100ms to 40ms polling (~25 FPS) to support
smooth animations.
2026-02-02 19:57:58 +01:00
phranck 50825d61da Feat: Structural identity for @State — persistent state across view reconstruction
Implement SwiftUI-style structural identity so @State values survive
full view-tree reconstruction on every render pass. State is keyed by
ViewIdentity (path-based) and property index in a central StateStorage.

- Add ViewIdentity (path-based structural identity for views)
- Add StateStorage with GC and branch invalidation
- Refactor @State to self-hydrate from StateStorage during init
- Set root hydration context in RenderLoop before app.body evaluation
- Extend RenderContext with identity propagation helpers
- Add ConditionalView branch invalidation on switch
- Remove scene cache from RenderLoop (app.body evaluated fresh per frame)
- Enhance Example App pages with @State (ButtonsPage, OverlaysPage, ContainersPage)
- Update DocC articles (StateManagement, RenderCycle)
- Add 12 tests for state storage identity
2026-02-02 18:31:53 +01:00
phranck 1877ae65e6 Refactor: Eliminate AppState singleton via RenderNotifier and constructor injection 2026-02-02 17:31:52 +01:00
phranck 68fcf47637 Refactor: Restrict access levels to minimize public API surface
Systematically audit and restrict public declarations across the entire
framework. Internal plumbing (rendering, registries, framework services)
is now internal/private while the user-facing API remains public.

Categories changed:
- Rendering infrastructure (Renderable, ANSIRenderer, BorderRenderer,
  Terminal, ViewRenderer) → internal
- Framework services (PreferenceStorage, LifecycleManager, TUIContext,
  KeyEventDispatcher) → internal
- Implementation modifier types (OnAppearModifier, OnDisappearModifier,
  TaskModifier, StatusBarItemsModifier, etc.) → internal
- View properties (Text.content, Button.label, Menu.items, etc.) → internal
- Registries (PaletteRegistry, AppearanceRegistry) → internal
- Styling internals (ANSIColor, SemanticColor, GeneratedPalette.Hue,
  BorderStyle block-styles) → internal
- ContainerView/ContainerConfig/ContainerStyle → fully internal
- ThemeManager.items/init → internal
- ViewBuilder glue types properties/inits → internal

Also includes:
- Doc warnings (/// - Important:) on types public only for technical reasons
- DocC landing page and CustomViews article updated for internal types
- All DocC symbol links to internal types converted to code text (0 warnings)
- README test badge updated to 498
- Example app ContainerView usage replaced with Panel

498 tests passing, 0 DocC warnings.
2026-01-31 14:29:39 +01:00
phranck 9f7092d4c8 Refactor: Replace EnvironmentStorage singleton with semantic colors and deprecate @Environment (Phase 6) 2026-01-31 01:13:31 +01:00
phranck 282d1662c1 Refactor: Eliminate AppState singleton
- Replace AppState.shared with AppState.active (settable static property)
- Make AppState.init() public instead of private
- Update all 15 source references across State.swift, App.swift,
  StatusBarState.swift, ThemeManager.swift, AppStorage.swift
- Update all 28 test references in AppStateTests and StatePropertyTests
- Serialize Environment Property Wrapper tests (shared-state race fix)
2026-01-30 23:08:22 +01:00
phranck 95029184e7 Chore: Rename TUIKit to TUIkit
- Rename module, targets and products in Package.swift
- Rename directories: Sources/TUIkit, TUIkitExample, Tests/TUIkitTests
- Rename files: TUIkit.swift, TUIkit.docc/TUIkit.md
- Update all import statements, module-qualified calls, file headers
- Update doc comments, string literals and DocC articles
- Update CI workflow, README, .swiftlint.yml and markdown docs
- Merge platform badges into single combined badge in README
- Add "Example App auslagern" task to to-dos.md
2026-01-30 20:29:29 +01:00