348 Commits

Author SHA1 Message Date
phranck 0712d9e2d7 Docs: Update DocC fonts to Barlow 2026-03-10 19:31:50 +01:00
phranck 02719e6c98 Update status bar shortcut documentation 2026-03-10 16:34:23 +01:00
phranck 73a6a1e091 Chore: Bump version to 0.6.0 2026-03-04 02:15:17 +01:00
phranck a4c437f972 Fix: Tab-Navigation und Arrow-Key-Wrap in NavigationSplitView
- 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
2026-03-04 02:15:17 +01:00
phranck 4d3eb28e19 fix: hotfix palette surface backgrounds and macOS-only unfair lock 2026-02-27 08:08:13 +01:00
phranck f58ee24e94 Chore: Bump version to 0.5.1 2026-02-16 01:34:28 +01:00
phranck 0feb9e9ee9 Refactor: Extract shared hydration context setup into StateRegistration.withHydration
- Add StateRegistration.withHydration(context:_:) helper
- Replace duplicated save/set/restore pattern in renderToBuffer and measureChild
- Single source of truth for hydration context management
2026-02-16 01:32:10 +01:00
phranck f2b79d266d Fix: measureChild crashes on @Environment(Observable.self) lookups
- 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
2026-02-16 01:30:27 +01:00
phranck 340c65969f Feat: Add .onChange(of:) modifier
- Add onChange(of:initial:_:) with (V, V) -> Void and () -> Void variants
- Store previous values in StateStorage for cross-render-pass comparison
- Per-identity counter ensures chained .onChange modifiers get unique keys
- GC integration cleans up tracked values for removed views
- Add 7 tests covering change detection, initial parameter, and chaining
2026-02-16 00:55:29 +01:00
phranck b0b07ca09e Chore: Bump version to 0.5.0 2026-02-16 00:01:16 +01:00
phranck 1db0e36bea Chore: Read TUIkit version from bundled VERSION file
- 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)
2026-02-15 23:59:00 +01:00
phranck 5ad97132b8 Feat: Add @Observable support with Observation framework
- 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
2026-02-15 23:49:34 +01:00
phranck b964c642e7 Feat: Add @Environment property wrapper
- 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
2026-02-15 22:11:13 +01:00
phranck 561915b6d9 Feat: Add configurable quit shortcut
- 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
2026-02-15 21:15:01 +01:00
phranck 8cf6507016 Feat: Add ContentUnavailableView
- 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
2026-02-15 18:19:55 +01:00
phranck 790d46c059 Fix: Restore swiftlint:disable for false-positive empty_count
- count is an Int variable (pixel counter), not a collection .count
- CI treats empty_count as error level, causing build failure
2026-02-15 02:39:05 +01:00
phranck d9c8a934b5 Chore: Increase frame rate from ~35 FPS to ~42 FPS
- Reduce run loop sleep from 28ms to ~24ms
- Update notification and spinner timers to match
2026-02-15 02:35:26 +01:00
phranck db8ea40c0a Refactor: Fix SwiftLint warnings and refactor StatusBar to _StatusBarCore pattern
- Fix 80 SwiftLint warnings (159 -> 79): vertical_whitespace, prefer_self_in_static_references, modifier_order, trailing_newline, trailing_whitespace, prefer_for_where, unneeded_synthesized_initializer, redundant_type_annotation, implicit_optional_initialization, superfluous_disable_command, shorthand_optional_binding, syntactic_sugar, empty_string, vertical_whitespace_closing_braces, identifier_name in BadgeModifier
- Refactor StatusBar from direct Renderable to _StatusBarCore pattern (public View with real body wrapping private Renderable core)
2026-02-15 02:35:18 +01:00
phranck b935c1d05e Docs: Use colorful syntax highlighting in DocC theme
- Replace monochrome green syntax colors with Xcode-inspired palette
- Keywords pink, strings coral, numbers purple, types cyan
- Functions green, constants orange as brand accent
- Comments stay muted green for on-brand feel
2026-02-15 02:07:56 +01:00
phranck 3452f5bbd9 Docs: Add TUIkit green palette theme for DocC
- 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
2026-02-15 01:51:31 +01:00
phranck ea5706203c Docs: Rebuild all DocC diagrams with Typst
- Replace all Mermaid/D2 diagrams with Typst (fletcher) for better quality
- Transparent backgrounds for light and dark mode support
- Style D arrows: thin lines (0.7pt), small filled triangles, corner routing
- Subsystem init diagram: top-down vertical layout
- Runtime references diagram: annotation nodes instead of edge labels
- All 10 diagrams: pipeline, dispatch, event-loop, input-dispatch,
  keyboard-dispatch, subsystem-init, run-creates, main-loop,
  dep-ownership, dep-references
2026-02-15 01:18:54 +01:00
phranck 3aec350be0 Docs: Reduce render pipeline diagram size
- Re-rendered at scale 1 (was scale 2): 296x2270px instead of 592x4540px
2026-02-15 00:13:13 +01:00
phranck 7972f3a065 Docs: Redesign dependency graph diagrams for readability
- 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)
2026-02-14 23:18:32 +01:00
phranck d43e10fe19 Docs: Split subsystem init diagram into init + run
- Split single wide diagram into two: init() subsystems and run() components
- Redesigned init diagram: TD layout with horizontal subsystem row
- Added separate lifecycle-run-creates diagram (light + dark)
- Updated AppLifecycle.md with two @Image directives
2026-02-14 23:16:26 +01:00
phranck e3d739a1fb Docs: Change subsystem init diagram to left-to-right layout
- Recreated lifecycle-subsystem-init diagram with LR flow direction (light + dark)
2026-02-14 23:02:53 +01:00
phranck a140f4947a Docs: Audit and fix all DocC diagrams against actual code
- KeyboardShortcuts.md: 3 layers → 5 layers, fix FocusManager arrow key
  handling, recreate keyboard-event-dispatch diagram
- AppLifecycle.md: fix subsystem init (add AppState, AppHeaderState,
  separate init/run), fix terminal setup (add focus observer, timers),
  fix main loop (5 layers, re-render trigger table), rewrite key event
  dispatch (5 layers with Layer 0/3 mutual exclusivity), fix dependency
  graphs (complete subsystem list, correct layer references)
- RenderCycle.md: fix Step 1 (5 subsystems not 3), Step 2 (add
  RenderCache), Step 3 (complete environment code snippet), Step 9
  (add app header), Step 12 (add StateStorage GC, RenderCache cleanup),
  update trigger table (add timers, focus changes), recreate pipeline
  diagram with all 12 steps
- Recreated 7 diagrams as Mermaid renders (light + dark variants)
2026-02-14 22:42:58 +01:00
phranck 10cc6e8fce Docs: Replace event loop diagram with code-accurate Mermaid renders
- 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
2026-02-14 22:21:49 +01:00
phranck 301e9afcbd Docs: Replace ASCII diagram with rendered image in KeyboardShortcuts
- Add keyboard-event-dispatch.png (light + dark) rendered from Mermaid
- Replace ASCII code block with @Image directive matching existing DocC style
2026-02-14 22:08:44 +01:00
phranck 9979b67d93 Fix: Resolve NSLock deadlock and re-enable localization test suites
- 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
2026-02-14 21:31:14 +01:00
phranck d711979118 Refactor: Move i18n documentation from separate files to DocC
- 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.
2026-02-14 19:56:38 +01:00
phranck ff6c6c209c Feat: Complete i18n system with type-safe keys, tests, and documentation
Phase 3 Implementation - Complete internationalization framework with:

## New Features
- Type-safe LocalizationKey enum with 7 categories (Button, Label, Error, Placeholder, Menu, Dialog, Validation)
- All 80+ translation keys organized by category with IDE autocomplete support
- Convenient extensions on LocalizedString, Text, and LocalizationService

## Translations Expanded
- All 5 language JSON files (en, de, fr, it, es) expanded with new string categories:
  - Additional labels: page, item, items, total, from, to
  - Additional errors: timeout, file_not_found, permission_denied
  - Additional placeholders: choose_file
  - Additional menu items: new, open, save, exit
  - Additional dialogs: exit_confirmation, success, error
  - Validation category: email_invalid, password_too_short, username_taken, field_required

## Comprehensive Test Suite
- LocalizationServiceTests: Bundle loading, string resolution, fallback behavior, language switching, persistence, thread safety
- LocalizationKeyTests: Type-safe key resolution across all categories and languages
- LocalizationKeyConsistencyTests: Validates all enum keys exist in translations and vice versa

## Documentation
- i18n-guide.md: User-facing documentation for using localized strings
- i18n-developer.md: Developer guide for adding keys, languages, and understanding the architecture

## Technical Details
- All translation keys now compile-time verified via consistency tests
- Fallback chain: Current Language → English → Key itself
- XDG-compatible persistent storage (macOS: ~/Library/Application Support, Linux: ~/.config)
- Thread-safe operations with NSLock
2026-02-14 19:20:11 +01:00
phranck f54816d099 Feat: Phase 2 i18n Core API - LocalizedString View and Language Management
- Add LocalizedString View component for displaying localized strings
- Add Text(localized:) convenience initializer for Text views
- Add AppState.setLanguage(_:) method for runtime language switching
- Add AppState.currentLanguage computed property
- Integrate LocalizationService into RenderLoop environment
- Remove duplicate LocalizedString function (View takes precedence)
- All 1069 tests pass

Core i18n functionality complete. Ready for Phase 3 (String Replacement)
2026-02-14 18:28:54 +01:00
phranck 978c15993e Feat: Phase 1 i18n Infrastructure - LocalizationService and translation 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
2026-02-14 18:15:33 +01:00
phranck cab942c1cc Refactor: Complete elimination of RenderNotifier global singleton
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.
2026-02-14 18:03:38 +01:00
phranck 0148366bf6 Refactor: Replace RenderNotifier.current global with dependency injection
- 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.
2026-02-14 17:50:14 +01:00
phranck 91891a9ea7 Chore: P4.18 concurrency documentation improvements
- 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.
2026-02-14 17:38:28 +01:00
phranck cbf5b9d5b6 Refactor: Make ItemListHandler generic over SelectionValue
- 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
2026-02-14 17:22:08 +01:00
phranck b404a62731 Chore: README update, Foundation cleanup, file splitting
- Update README.md with multi-module structure, new components, macOS 14+
- Remove unnecessary import Foundation from 9 source files
- Split Focus.swift into Focus, Focusable, FocusState
- Split StatusBarItem.swift into StatusBarItem, SystemStatusBarItem, StatusBarItemBuilder
- Split ASCIIConverter.swift into ASCIIConverter, +Braille, +Dithering
- Update WHATS-NEXT.md with completed cleanup tasks
2026-02-14 16:57:16 +01:00
phranck ebd8237282 Fix: Handle .space key event consistently across all interactive views
- 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
2026-02-14 16:27:44 +01:00
phranck 04d71f42c2 Refactor: Selective RenderCache invalidation for performance
- Remove pulse-triggered cache clearing (was destroying entire cache
  ~7x/sec during focus animation, yielding 0% hit rate)
- Add RenderCache.clearAffected(by:) for identity-aware invalidation
- Wire StateBox to its ViewIdentity for targeted cache clearing on
  @State changes (sibling subtrees retain their cached buffers)
- Add 5 new tests for clearAffected covering ancestor, descendant,
  sibling preservation, exact match, and empty cache scenarios
- Update EquatableView documentation with pulse/focus guidance
2026-02-14 15:03:51 +01:00
phranck 3835ed87e3 Refactor: Dissolve Core/ and Styling/ directories in TUIkit module
- 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
2026-02-14 14:35:47 +01:00
phranck a945ae3b36 Fix: Add FoundationNetworking import for Linux compatibility
- Import FoundationNetworking conditionally on Linux where URLRequest,
  URLSession and URLSessionDataTask are not part of Foundation
2026-02-14 14:17:05 +01:00
phranck d0627bafdc Refactor: Extract TUIkitView module and organize sub-module directories
- Extract View system foundation into new TUIkitView module (Layer 1)
  - View, ViewBuilder, TupleViews, ViewModifier, PrimitiveViews, EquatableView
  - Renderable, RenderContext, RenderCache, ChildInfo, SpacerProtocol
  - State, StateStorage, StateRegistration, HydrationContext
  - EnvironmentValues, EnvironmentModifier, ViewServiceEnvironment
- Introduce SpacerProtocol to decouple ChildInfo from concrete Spacer type
- Split ServiceEnvironment.swift (StateStorageKey/RenderCacheKey to TUIkitView)
- Add @_exported import TUIkitView in Exports.swift for backward compatibility
- Make internal types public for cross-module visibility (Renderable, Layoutable,
  renderToBuffer, ChildView, ChildInfo, ModifiedView, StateStorage, RenderCache)
- Organize TUIkitCore into Rendering/, Environment/, Input/, Extensions/, Concurrency/
- Organize TUIkitStyling into Color/, Theme/, Styles/
2026-02-14 14:11:09 +01:00
phranck 3fb4944472 Refactor: Move runtime services from RenderContext to EnvironmentValues
- 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
2026-02-14 13:13:24 +01:00
phranck d39e02722b Refactor: Extract TUIkitCore micro-kernel module
- Move pure value types and protocols to new TUIkitCore target (zero deps)
- Move whole files: FrameBuffer, ViewIdentity, Lock, TerminalSymbols
- Split types from mixed files: LayoutTypes (from Renderable), KeyEvent
  (from KeyEventDispatcher), EnvironmentKey (from Environment),
  PreferenceKey (from Preferences), String+TerminalWidth (from String+ANSI)
- Inline ANSIRenderer.reset as private constant in FrameBuffer
- Promote internal types to public for cross-module visibility
- Add @_exported import TUIkitCore to Exports.swift
2026-02-14 04:23:50 +01:00
phranck ce850e1b29 Refactor: Extract TUIkitStyling and TUIkitImage modules
- 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
2026-02-14 03:14:14 +01:00
phranck be19689b84 Feat: Add image size limits and URL timeout configuration (P4.19)
- 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
2026-02-14 02:17:26 +01:00
phranck e214215610 Refactor: Replace MainActor.assumeIsolated with @preconcurrency Equatable
- Migrate 20 Equatable conformances across 17 files from
  nonisolated + MainActor.assumeIsolated to @preconcurrency Equatable (SE-0423)
- Remove unnecessary import Foundation from 29 source files
- Extract TextFieldHandler clipboard ops into TextFieldHandler+Clipboard.swift
- Extract RenderContext into RenderContext.swift (Renderable.swift 553 -> 279 lines)
- Extract ANSIColor enum into ANSIColor.swift (Color.swift 600 -> 533 lines)
- Add deprecation timeline note for progressBarStyle(_:)
- Migrate test usages from progressBarStyle to trackStyle
2026-02-14 02:10:26 +01:00
phranck 02d1921bf4 Refactor: Apply project analysis improvements (P1-P3)
- 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
2026-02-14 01:29:46 +01:00
phranck 7682efc516 Chore: Fix SwiftLint CI failures
- Add _ImageCore to type_name exclusion list in .swiftlint.yml
- Suppress false-positive empty_count on integer counter variable
2026-02-14 00:48:43 +01:00