- Replace custom Observable protocol and @Published with Apple's @Observable macro
- Add withObservationTracking in renderToBuffer for automatic per-property dependency tracking
- Add type-based @Environment(Type.self) and .environment(object) for observable objects
- Add ObjectEnvironmentModifier for injecting observable objects into the environment
- Add needsCacheClear flag to AppState for thread-safe cache invalidation
- Add cross-platform test script (scripts/test-linux.sh) for Docker-based Linux verification
- Add DemoAppHeader with system info display (OS, version, architecture)
- Consolidate Example App: extract ImageDemoHelpers, KeyboardHelpSection, ValueDisplayRow
- Add pre-push verification rule to CLAUDE.md
- Verified on both macOS and Linux (swift:6.0 container), 1155 tests passing
- Add Image view rendering local files and URLs as colored ASCII art
- Add CSTBImage C target wrapping stb_image for cross-platform image decoding
- Add ASCIIConverter with block, ASCII, and braille character sets
- Support trueColor, ANSI-256, grayscale, and mono color modes
- Add Floyd-Steinberg dithering for improved visual quality
- Add async image loading with URLImageCache for URL sources
- Add bracketed paste mode for bulk text insertion in text fields
- Add TextContentType modifier for input character filtering
- Add ContentMode enum and aspectRatio(_:contentMode:) View modifier
- Add text-input priority in key dispatch to prevent shortcut conflicts
- Add Image (File) and Image (URL) demo pages to example app
- Update DocC documentation with new symbols and layout table
- Redesign Button rendering with half-block caps (U+2590/U+258C) and accent-tinted background
- Apply matching cap style and background to TextField and SecureField
- Add foregroundQuaternary to Palette for subtle UI elements like spinner tracks
- Fix border saturation in all palette presets to preserve hue instead of going gray
- Rework Spinner bouncing trail to interpolate from highlight to quaternary via Color.lerp
- Fix Spinner label to use palette foreground color
- Fix FrameDiffWriter to use ESC[2K for reliable line clearing
- Add Layoutable conformance to NavigationSplitView for height-flexible layout
- Update tests for new button cap style
- Add regression test for two Lists in HStack sharing width correctly
- Test verifies both lists render, buffer fits available width, consistent line widths
- Add horizontal padding to TextFieldPage and SecureFieldPage
- TextField now expands to fill available width (SwiftUI behavior)
- Label parameter is for accessibility only, not rendered visually
- Use prompt: parameter for placeholder text
- Update example app to use prompt: correctly
- SecureField also expands to fill available width
AppearanceRegistry.all now starts with .rounded instead of .line.
Since ThemeManager starts with currentIndex=0, this ensures the
default appearance is rounded corners as intended.
- Make Box struct internal (use .border() modifier instead)
- Update example app to use .border() instead of Box
- Simplify resolveContainerWidth to just return content width
- Simplify resolveContainerHeight to just return content height
- Remove auto-expansion logic from containers
- Containers now size to fit their content, not fill available space
- Spacers in HStack still expand correctly to fill remaining space
- Update tests for renamed BorderViaContainerViewTests
- Add ToggleStyle protocol matching SwiftUI's pattern
- Add .automatic, .checkbox, .switch built-in styles
- Add .toggleStyle(_:) view modifier via Environment
- All styles render as [ ]/[x] in TUI (documented constraint)
- Remove old ToggleStyle enum (.toggle/.checkbox)
- Update example app and tests for new API
- Add cursorColor to Palette protocol with HSL-computed values for all SystemPalette presets
- Create TextCursorStyle with shapes (block, bar, underscore) and animations (none, blink, pulse)
- Add .textCursor(_:) View modifier propagating through environment
- Update TextField and SecureField to use cursorColor and respect cursor style
- Add 17 tests for TextCursorStyle
- 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
- 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: 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)
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)
- 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
- 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
- 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
- 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
- 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
- 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
- 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
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)
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
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.
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.
Update IDETemplateMacros.plist and replace headers in 136 Swift files with
new format: 🖥️ TUIKit — Terminal UI Kit for Swift. Remove sdsd.swift template draft.
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.
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.
- 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
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.