- Tab navigates within the active section first; only switches to the
next section when the current element is the last in its section
- Arrow keys (Up/Down) no longer wrap at section boundaries
- moveFocusInSection gains a wrap: Bool parameter and returns Bool
- Add tests: tabNavigatesWithinSectionFirst, arrowKeysDoNotWrapAtBoundary
- Add StateRegistration.withHydration(context:_:) helper
- Replace duplicated save/set/restore pattern in renderToBuffer and measureChild
- Single source of truth for hydration context management
- Skip Renderable views in body-traversal path (they use renderToBuffer, not body)
- Set up full hydration context before evaluating composite view body
- Renderable views now fall through to render-to-measure fallback correctly
- Add regression test with @Observable model in measureChild
- Add Sources/TUIkit/VERSION as single source of truth for version string
- Read version at runtime via Bundle.module instead of hardcoded string
- Remove unused TUIkitVersion constant from Package.swift
- Fix version mismatch (was 0.1.0, actual is 0.4.0)
- 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 @Environment<Value> property wrapper for declarative environment access
- Add StateRegistration.activeEnvironment for ambient context during body evaluation
- Set/restore activeEnvironment in renderToBuffer() and RenderLoop.render()
- Refactor RenderLoop.render() into focused helpers to fix function_body_length warning
- Add 7 tests covering default values, active environment, dynamic reads, and propagation
- Add QuitShortcut type with presets: .q, .escape, .ctrlQ, .ctrlC
- Replace hardcoded "q" quit key in InputHandler with configurable shortcut
- Add quitShortcut property to StatusBarState (default: .q)
- Status bar display updates automatically based on configured shortcut
- Add 17 tests covering all presets, matching logic, and integration
- Add ContentUnavailableView with SwiftUI-matching API (no systemImage)
- Full @ViewBuilder init for label, description, and actions
- String convenience inits and static .search preset
- Private _ContentUnavailableViewCore with Renderable pattern
- Add 9 tests covering all init variants and rendering
- Full color theming based on Green SystemPalette HSL values
- Light and dark mode variants for all 130+ color variables
- Navigation, code syntax, asides, badges, buttons, forms styled
- Subtle intro gradient instead of garish neon green
- Nunito as primary font, SF Mono for code
- Enable onThisPageNavigator and quickNavigation features
- Ownership: LR layout with 5 logical groups (Core, UI State, Theming, TUIContext, Runtime)
- References: LR layout with RenderLoop/InputHandler on left, subsystems on right
- Both diagrams now readable at DocC content width (light + dark)
- Event loop diagram now reflects actual AppRunner.run() flow: init subsystems, setup, start timers/observers, initial render, then main loop
- Added input dispatch diagram showing 5-layer first-consumer-wins model with hasTextInputFocus conditional branching
- Updated Architecture.md text to match code: correct subsystem list, two render trigger paths, Layer 0/3 mutual exclusivity
- Light and dark variants for both diagrams
- Fix re-entrant NSLock deadlock in string(for:) -> translationValue -> translations(for:) by restructuring to lock-free internal helpers (_translations, _translationValue)
- Fix wrong Bundle.module subdirectory path ("Localization/translations" -> "translations") that prevented translations from ever loading
- Fix didSet side effects during init() by computing language once before single Phase-1 assignment
- Remove redundant setNeedsRender() call in AppState.setLanguage()
- Add config directory injection for test isolation (LocalizationService(configDirectoryPath:))
- Re-enable LocalizationServiceTests and LocalizationKeyConsistencyTests (42 tests)
- Persistence tests now use isolated temp directories instead of real config path
- Created new Localization.md article in DocC catalog
- Integrated user guide and developer guide content into single article
- Deleted separate Documentation/i18n-guide.md and i18n-developer.md
- Updated README.md to link to DocC documentation
- Updated GettingStarted.md to reference Localization guide
Documentation now follows project-wide DocC standards instead of separate Markdown files.
- Add LocalizationService singleton for managing language selection
- Implement XDG-compatible persistent language storage (macOS/Linux)
- Create translation JSON files for 5 languages (EN, DE, FR, IT, ES)
- Add LocalizationService to EnvironmentValues
- Support dot-notation keys for string resolution
- Implement fallback to English for missing keys
- Update Package.swift to include translation resources
- All 1069 tests pass
Replace RenderNotifier with clean AppState and RenderCache singletons:
- Add AppState.shared static singleton for global access
- Add RenderCache.shared static singleton for memoization
- Remove RenderNotifier enum completely
- Remove RenderNotifierKey from EnvironmentValues
- StateBox.didSet uses AppState.shared directly
- AppStorage.wrappedValue setter uses AppState.shared directly
- NotificationService.post() uses AppState.shared directly
- Spinner animation uses AppState.shared directly
- NotificationHostModifier animation uses AppState.shared directly
- TUIContext uses RenderCache.shared by default (injectable for tests)
- Update test infrastructure to isolate RenderCache per test
Architecture: Pure singleton pattern with no global state registry.
All 1069 tests pass, no new compiler warnings.
This completes the full elimination of RenderNotifier and provides a
clean, explicit architecture where AppState.shared is the single
source of truth for render triggers.
- Make RenderNotifier.current optional (nil by default) to support DI
- Add RenderNotifierKey and renderNotifier property to EnvironmentValues
- Initialize renderNotifier in RenderLoop.buildEnvironment()
- Migrate Spinner animation loop to read from environment
- Migrate NotificationHostModifier to read from environment via parameter
- Update StateBox.didSet to use optional chaining fallback
- Update AppStorage.wrappedValue setter to use optional chaining fallback
- Update NotificationService.post() to use optional chaining fallback
- Update test comments to reflect optional chaining pattern
- All 1069 tests pass, no new compiler warnings
Phase 3 implementation of P4.16 dependency injection refactor complete.
- Add comprehensive documentation to RenderNotifier static properties
explaining main-thread-only access guarantees and safety model
- Add inline safety comments to Terminal memory operations
explaining why unsafe memory rebinding is safe in context
Audit findings: All concurrency patterns are justified, documented,
and safe. No critical issues found. These are quality-of-life improvements
for code maintainability.
- Replace AnyHashable type erasure with generic SelectionValue parameter
- Remove configureSelectionBindings and assign typed bindings directly
- Change itemIDs from [AnyHashable] to [SelectionValue?] for nil-safe non-selectable rows
- Update _ListCore, _TableCore, and tests to use type-safe selection
- Add explicit .space case to TextFieldHandler for space character insertion
- Change ItemListHandler from .character(" ") to .space for selection toggle
- Change RadioButtonGroupHandler from .character(" ") to .space for selection
- Update tests to use KeyEvent(key: .space) matching actual key parsing
- Move KeyEvent.swift from Core/ to Focus/
- Move ListRowExtractor.swift, SelectableListRow.swift from Core/ to Views/
- Move 5 styling environment files from Styling/ to Environment/
- Move ViewConstants+EdgeInsets.swift from Styling/ to Extensions/
- Remove empty PrimitiveTypes+Extensions.swift stub
- Add ServiceEnvironment.swift with 9 EnvironmentKeys for runtime services
(stateStorage, lifecycle, keyEventDispatcher, renderCache, preferenceStorage,
pulsePhase, cursorTimer, focusIndicatorColor, activeFocusSectionID)
- Remove tuiContext, pulsePhase, cursorTimer, focusIndicatorColor, and
activeFocusSectionID as direct RenderContext properties
- Inject all services through EnvironmentValues in RenderLoop.buildEnvironment()
- Add convenience RenderContext init that accepts TUIContext and auto-injects
services into the environment
- Simplify isolatedForBackground() to only swap environment values
- Migrate ~49 access sites in ~25 source files from context.tuiContext.X and
context.pulsePhase/cursorTimer to context.environment.X
- Update 38 test files to use the new convenience init
- Extract 12 pure type definitions (Color, Palette, Appearance, BorderStyle, etc.) into TUIkitStyling module with no dependencies
- Extract 3 image processing files (RGBAImage, ImageLoader, ASCIIConverter) into TUIkitImage module (deps: CSTBImage, TUIkitStyling)
- Split 6 mixed files into type definitions (TUIkitStyling) and environment glue (TUIkit/Styling/)
- Decouple ThemeManager from AppState via renderTrigger closure
- Decouple ASCIIConverter from ANSIRenderer via local ANSIEscape constants
- Add @_exported imports in Exports.swift for backward compatibility
- All 1064 tests pass, no breaking API changes
- Add .imageMaxPixelCount(_:) modifier to reject oversized images
- Add .imageURLTimeout(_:) modifier with configurable timeout (default: 30s)
- Replace Data(contentsOf:) with URLSession.dataTask for proper timeout support
- Add ImageLoadError.imageTooLarge error case
- Wire environment values through _ImageCore to PlatformImageLoader
- Extract scattered opacity magic numbers into ViewConstants enum (15+ files)
- Extract "No items" string literals into ViewConstants.emptyListPlaceholder
- Add EdgeInsets.containerDefault and .dialogDefault named constants
- Standardize file headers to emoji format across 10 files
- Extract shared selection binding helper into ItemListHandler
- Rename unclear short variables (p, w, h, v, i) to descriptive names
- Split StatusBarItem.swift: extract Shortcut enum into Shortcut.swift
- Add sanitizedForTerminal property for ANSI escape sequence defense
- Replace Mirror-based button extraction in Alert with ButtonProvider protocol
- Add process name sanitization in file storage paths
- Add project analysis improvement plan with full checklist