12 Commits

Author SHA1 Message Date
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 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 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 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 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 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 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