# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.8.2] - 2026-07-02

### Fixed

- Fixed new subscriptions being started in hash iteration order instead of the
  order returned by `Application::subscriptions`
  - `SubscriptionManager::update()` previously diffed subscription IDs with
    `HashSet::difference`, so stream creation could happen in a nondeterministic
    order for newly added subscriptions
  - New subscriptions now start in input order while existing subscriptions
    continue running unchanged

- Fixed WebSocket TCP connections not being released during runtime shutdown
  when `SubscriptionManager::shutdown()` aborts the subscription task via
  `handle.abort()` (requires `ws` feature)
  - Previously, `stream()` spawned a background `run_subscription_loop` task
    that held the WebSocket TCP handles; when the outer consumer task was
    aborted the background task needed to be scheduled to react, but during
    Tokio runtime teardown it might never get that chance and the TCP
    connection was left open
  - The WebSocket connection loop now runs entirely inside the `stream::unfold`
    future (no separate `tokio::spawn`); aborting the outer task drops the
    TCP handles synchronously, so the OS-level connection is released
    regardless of whether the runtime has already started tearing down
  - **Note**: a WebSocket-level `Message::Close` frame is still not sent on
    task abort — Rust has no async `Drop`.  For a clean WebSocket close,
    send `WebSocketCommand::Close` before the runtime shuts down.  A future
    improvement could add a cooperative shutdown path via a `CancellationToken`
    combined with a grace period in `SubscriptionManager::shutdown()`

- Fixed `CacheEntry::check_staleness` having a misleading `&mut self` signature
  (requires `http` feature)
  - The method set `self.is_stale = true` on the receiver, but `QueryClient::get_cache`
    returns a clone of the entry, so the mutation was silently discarded and the
    actual cached entry's `is_stale` field remained `false` forever
  - The method now takes `&self`; staleness is evaluated as
    `self.is_stale || stale_time_elapsed`, preserving the existing return-value
    semantics while making the immutability explicit

- Fixed `Query` subscriptions with the same string key but different value types
  overwriting each other's cache entries (requires `http` feature)
  - The cache was keyed by the plain string key, so `Query<i32>` and `Query<String>`
    using the same key would each overwrite the other's entry; every `downcast_ref`
    to the "wrong" type would fail and cause an unnecessary refetch
  - The cache is now keyed by `(TypeId, string key)` so each value type has its
    own independent slot regardless of what other types share the same string key

- Fixed subscriptions not being restarted after their task completes naturally
  - If a subscription's underlying task finished on its own (e.g., a WebSocket connection
    dropped, or a finite stream reached its end), `SubscriptionManager::update()` still
    found its ID in the running map and skipped restarting it — the subscription was
    silently dead until the app explicitly toggled it off and on again
  - Finished tasks are now detected on every `update()` call and removed from the map so
    that the subscription is restarted if still requested, or cleaned up if not

- Fixed `Query` subscriptions missing invalidations broadcast while a fetch is in-flight
  (requires `http` feature)
  - Previously, `perform_fetch` subscribed to the invalidation channel *after*
    `fetcher().await` returned, so any invalidation broadcast during the fetch was
    silently dropped; the subscription would serve stale data indefinitely until the
    next explicit invalidation
  - Also fixed a narrower window in `State::Initial` where an invalidation could arrive
    between the cache read and the subscription setup in the fresh-data path
  - A single `broadcast::Receiver` is now subscribed at the very start of `State::Initial`
    and threaded through the entire state machine so no invalidation is ever missed

## [0.8.1] - 2026-06-25

### Changed

- **Performance**: Subscriptions are no longer re-evaluated on every frame
  - `Runtime` previously called `Application::subscriptions()` on every frame tick (e.g. 60×/s),
    allocating a `Vec` and rebuilding every `Subscription` (boxing spawners, cloning `Arc`s) even
    while idle
  - Since `subscriptions()` is a pure function of the application state, it is now re-evaluated
    only after a message is processed, eliminating this per-frame work for idle applications
  - Fully backward compatible for applications whose `subscriptions()` depends only on their state

### Added

- `QueryClient::gc()` to manually garbage collect expired cache entries (requires `http` feature)
  - Removes cached entries older than the configured `cache_time`
  - Useful for reclaiming memory for keys that are no longer being fetched

### Fixed

- Fixed WebSocket connections leaking when a subscription is cancelled (requires `ws` feature)
  - The connection task could stay parked on `read.next()` and `cmd_rx.recv()` after the
    subscription's message stream was dropped, keeping the underlying connection open
  - The connection task now observes the dropped message receiver via `msg_tx.closed()` and
    shuts the connection down cleanly, during both connection setup and the main loop
- Fixed the query cache growing without bound (requires `http` feature)
  - Cached entries were never removed, so `cache_time` had no effect and memory grew as new
    keys were fetched over time
  - Entries older than `cache_time` are now garbage collected automatically on each cache
    insertion, and can also be reclaimed manually via `QueryClient::gc()`
- Fixed `Query` subscriptions permanently terminating when invalidation notifications lagged
  (requires `http` feature)
  - A burst of invalidations exceeding the broadcast channel capacity caused the watcher to
    receive a `Lagged` error, which was treated as a closed channel and silently ended the
    subscription, so it never refetched again
  - A lagged receiver now refetches (since a dropped notification may have been for its key)
    and only a genuinely closed channel ends the subscription
- Improved frame rate timing accuracy in `Runtime`
  - The frame interval was computed with integer millisecond division (`1000 / frame_rate`),
    truncating the period; for example 60 FPS ran at 16ms (~62.5 FPS) and 144 FPS at 6ms
  - The period is now derived from an exact `Duration` division, so the requested frame rate
    is honored precisely

## [0.8.0] - 2026-01-22

### Added

- Added `Command::is_none()` and `Command::is_some()` methods for checking command state
  - Useful for testing whether `update()` returns the expected command type
  - Provides better encapsulation than accessing internal `stream` field directly

### Changed

- Added `#[must_use]` attribute to `Command` type to prevent accidental ignoring of side effects
- **BREAKING**: Upgraded `thiserror` from 1.0 to 2.0 (used in `http` feature)
  - This is a breaking change for downstream crates that depend on `tears` with the `http` feature and also depend on `thiserror` 1.x
  - The `QueryError` type is part of the public API and derives from `thiserror::Error`
  - If you use `thiserror` 1.x in your project alongside `tears`, you may need to upgrade to `thiserror` 2.0 to avoid dependency conflicts

## [0.7.0] - 2026-01-07

### Changed

- **BREAKING**: Simplified Runtime API
  - Changed from `Runtime::new(flags).run(&mut terminal, frame_rate)` to `Runtime::new(flags, frame_rate).run(&mut terminal)`
  - Frame rate is now specified during Runtime creation instead of when calling `run()`
  - Example:
    ```rust
    // Before
    let runtime = Runtime::<MyApp>::new(());
    runtime.run(&mut terminal, 60).await?;

    // After
    let runtime = Runtime::<MyApp>::new((), 60);
    runtime.run(&mut terminal).await?;
    ```

- **Performance**: Event-driven message processing for improved input responsiveness
  - Runtime now processes messages immediately as they arrive via `tokio::select!`
  - Message processing is no longer bound to frame rate interval
  - User input (keyboard, mouse) is now handled with sub-millisecond latency
  - Frame rate interval is still used for periodic subscription updates and batch rendering
  - Significantly improves perceived responsiveness, especially at lower frame rates (e.g., 16 FPS)
  - Combines with `should_render()` check to avoid unnecessary renders while maintaining instant input feedback

## [0.6.1] - 2026-01-06

### Added

- **`http` feature** for HTTP subscription and mutation support
  - New optional feature that can be enabled with `features = ["http"]`
  - Adds `subscription::http` module with Query and Mutation types
- HTTP subscription support for data fetching and mutations (requires `http` feature)
  - `Query` subscription for automatic data fetching with caching
    - Subscription-based design: monitors cache state and automatically refetches when needed
    - Stale-while-revalidate pattern: shows cached data while refetching in background
    - Automatic cache management with configurable stale time and cache time
    - `QueryClient` for cache management and invalidation
    - `QueryState` enum for Loading/Success/Error states with stale flag
  - `Mutation` for HTTP data modifications (POST, PUT, PATCH, DELETE)
    - Command-based API: returns `Command<Result<T, QueryError>>`
    - Works seamlessly with `Command::map` for flexible result handling
  - `QueryClient::invalidate()` for cache invalidation (returns `Command`)
    - Automatically triggers refetch in active Query subscriptions
    - TEA-compliant: all side effects expressed as Commands
  - Design philosophy documentation explaining subscription-based vs transaction-based patterns
  - Example: `examples/http_todo.rs` demonstrating Query, Mutation, and cache invalidation
- `Command::map` for transforming command message types
  - Similar to iced's `Task::map` (v0.14.0)
  - Enables flexible message type conversion
  - Preserves `Action::Quit` correctly
  - Used with `Mutation` for result handling without `to_message` parameter
- Added `reqwest` 0.12 with `json` feature (dev dependency for examples)
- Added `serde` 1.0 with `derive` feature (dev dependency for examples)
- Added `serde_json` 1.0 (dev dependency for examples)

### Changed

- **Performance**: Implemented conditional rendering with dirty flag
  - Runtime now skips rendering when application state hasn't changed
  - Added `needs_redraw` flag to `Runtime` for tracking render necessity
  - `Runtime::process_messages()` now returns `bool` indicating if messages were processed
  - Rendering only occurs when messages are processed or on initial draw
  - Significantly reduces CPU usage and terminal I/O operations
  - Expected ~98% reduction in rendering calls for idle applications
  - Near-zero CPU usage when no events are occurring
  - Fully backward compatible - no changes required to existing applications
- **Performance**: Optimized subscription updates with hash-based caching
  - Runtime now caches subscription IDs hash to skip unnecessary updates
  - Added `subscription_ids_hash` field to `Runtime` for change detection
  - `SubscriptionManager::update()` is only called when subscriptions actually change
  - Provides 37% CPU reduction in subscription processing (measured with flamegraph)
  - 50.7% overall performance improvement for applications with static subscriptions
  - Particularly effective for applications with fixed or infrequently-changing subscriptions
  - Maintains full support for dynamic subscriptions without performance penalty
  - Fully backward compatible - no changes required to existing applications

## [0.6.0] - 2026-01-05

### Added

- `Command::message()` - Send a message to the application immediately
  - This is a tears-specific feature for immediate message dispatch
  - Replaces `Command::single()` with a clearer name that better represents the operation
  - More explicit than `single` and reserves `send`/`dispatch`/`emit` for future extensions

### Changed

- **BREAKING**: `Command::single()` has been removed
  - Use `Command::message()` instead for sending messages immediately
  - This change aligns with iced v0.14.0 design principles while maintaining tears' self-messaging feature
- Simplified `Runtime` internals by removing `Instance` wrapper
  - `Runtime` now directly holds the application instead of wrapping it in `Instance<App>`
  - Eliminates unnecessary indirection (`.inner`) throughout the codebase
  - Improves code readability with no functional changes
- Optimized `Runtime::process_messages()` for better performance
  - Now collects all pending messages and batches their commands together
  - Reduces tokio task spawning overhead when processing multiple messages
  - Improves performance when there are many pending messages in a single frame
- Improved error handling in `examples/counter.rs`
  - `Message::TerminalError` now holds `io::Error` instead of `String`
  - Preserves full error information instead of converting to string
  - Removed unnecessary `Clone` derives from `Message` and `Counter`
- Upgraded `tokio` from 1.48.0 to 1.49.0
- Upgraded `tokio-stream` from 0.1.17 to 0.1.18
- Upgraded `tokio-util` from 0.7.17 to 0.7.18

### Migration Guide (v0.5.0 → v0.6.0)

#### Command API Changes

Replace all uses of `Command::single()` with `Command::message()`:

```rust
// Before (v0.5.0)
Command::single(Message::Refresh)

// After (v0.6.0)
Command::message(Message::Refresh)
```

This is a mechanical replacement with identical functionality.
The new name better clarifies the intent and reserves more generic verbs (`send`, `dispatch`, `emit`) for potential future features.

## [0.5.0] - 2026-01-04

### Changed

- Upgraded `ratatui` from 0.29 to 0.30
- Upgraded `crossterm` from 0.28 to 0.29
- Updated MSRV (Minimum Supported Rust Version) from 1.85.0 to 1.86.0
- Updated `Runtime::render` and `Runtime::run` return types to use generic backend error types
- **BREAKING**: WebSocket subscription now supports bidirectional communication
  - Added `WebSocketCommand` enum for sending messages (`SendText`, `SendBinary`, `Close`)
  - Added `WebSocketMessage` enum for subscription output (`Connected`, `Disconnected`, `Received`, `Error`)
  - `WebSocket` subscription now emits `WebSocketMessage` instead of raw `Message`
  - `WebSocketMessage::Connected` provides command sender when successfully connected
  - `WebSocketMessage::Disconnected` is emitted on normal connection closure
  - `Message::Close` frames are handled internally and result in `Disconnected` event
  - Single WebSocket connection handles both receiving and sending
  - Updated `examples/websocket.rs` to demonstrate bidirectional communication

## [0.4.1] - 2026-01-04

### Added

- Mock subscription source for deterministic testing
  - New `subscription::mock::MockSource` for controllable event emission in tests
  - Enables testing without real I/O or time dependencies
  - Shared (cloneable) design allows use in both application code and test code
  - Based on `tokio::sync::broadcast` for efficient multi-receiver support
  - Comprehensive documentation with testing examples in README.md
  - Added `sync` feature to `tokio-stream` dependency for broadcast stream support
- WebSocket subscription support for real-time bi-directional communication
  - New `subscription::websocket::WebSocket` subscription source (requires `ws` feature)
  - Supports both secure (wss://) and insecure (ws://) connections
  - TLS backend options:
    - `native-tls` - Platform's native TLS implementation
    - `rustls` - Pure Rust TLS with ring crypto provider and native root certificates
    - `rustls-tls-webpki-roots` - Pure Rust TLS with ring crypto provider and webpki root certificates
  - Automatic connection management and reconnection handling
  - Streams all WebSocket message types (Text, Binary, Ping, Pong, Close)
  - Example: `examples/websocket.rs` demonstrating WebSocket echo chat
  - Comprehensive documentation with usage examples and TLS configuration guide

## [0.4.0] - 2026-01-03

### Changed

- **BREAKING**: Improved `Timer` subscription performance and accuracy
  - Migrated from `tokio::time::sleep` to `tokio::time::interval` for better timing accuracy
  - Uses `MissedTickBehavior::Skip` to maintain consistent tick rate (drops missed ticks instead of catching up)
  - Provides drift correction for high frame rates (60+ FPS)
  - Added `tokio-stream` dependency for interval stream support
- Improved `Runtime` frame timing accuracy
  - Migrated from `tokio::time::sleep` to `tokio::time::interval` for consistent frame rate
  - Uses `MissedTickBehavior::Skip` to skip missed frames when rendering takes longer than frame duration
  - Provides more accurate and stable FPS delivery

## [0.3.0] - 2026-01-02

### Fixed

- Fixed signal subscriptions emitting spurious events on initialization

### Removed

- **BREAKING**: Removed `ignore_initial()` method from signal subscriptions (`Signal`, `CtrlC`, `CtrlBreak`) as it is no longer needed after fixing the spurious event bug

## [0.2.0] - 2026-01-02

### Added

- Signal subscription helpers
  - `ignore_initial()` method to filter spurious signals during TUI initialization

### Changed

- **BREAKING**: Signal subscriptions refactored for simplicity
  - `Signal`, `CtrlC`, and `CtrlBreak` include grace period configuration
  - Removed `Copy` implementation from signal types (not needed in practice)
- Initialized `color_eyre` in example applications for improved error reporting and debugging experience

## [0.1.1] - 2026-01-02

### Added

- Signal subscription support for handling OS signals
  - Unix: `signal::Signal` for SIGINT, SIGTERM, SIGHUP, SIGQUIT, and 20+ other signals
  - Windows: `signal::CtrlC` and `signal::CtrlBreak` for Ctrl+C and Ctrl+Break events
  - Uses `tokio::signal` types directly (no unnecessary wrappers)
  - Returns `Result<(), io::Error>` for proper error handling
  - Example: `examples/signals.rs` demonstrating signal handling with graceful shutdown

### Fixed

- Downgraded `crossterm` from 0.29 to 0.28 to match `ratatui`'s dependency requirements

## [0.1.0] - 2025-12-30

### Added

- Initial release of tears framework
- Core Elm Architecture implementation with `Application` trait
- Asynchronous command system with `Command` type
  - `Command::none()` - No-op command (const fn)
  - `Command::single(msg)` - Send a single message immediately
  - `Command::perform()` - Execute async operation with result transformation
  - `Command::future()` - Execute async operation that produces a message
  - `Command::effect()` - Execute an action immediately
  - `Command::batch()` - Execute multiple commands concurrently
  - `Command::stream()` - Create command from message stream
  - `Command::run()` - Transform and consume a stream
- Subscription system for event sources
  - Dynamic subscriptions (can change based on application state)
  - Built-in `TerminalEvents` subscription for keyboard/mouse/resize events
  - Built-in `Timer` subscription for periodic ticks
  - Support for custom subscriptions via `SubscriptionSource` trait
- `Runtime` for managing application lifecycle
  - Event loop with configurable frame rate
  - Automatic subscription management
  - Command execution and message dispatching
- Error handling
  - Subscriptions return `Result<T, E>` allowing user-controlled error handling
  - Terminal event errors are propagated to application
- Full async/await support with tokio
- Comprehensive API documentation with examples
- Counter example demonstrating timer and keyboard input

[unreleased]: https://github.com/akiomik/tears/compare/v0.8.2...HEAD
[0.8.2]: https://github.com/akiomik/tears/releases/tag/v0.8.2
[0.8.1]: https://github.com/akiomik/tears/releases/tag/v0.8.1
[0.8.0]: https://github.com/akiomik/tears/releases/tag/v0.8.0
[0.7.0]: https://github.com/akiomik/tears/releases/tag/v0.7.0
[0.6.1]: https://github.com/akiomik/tears/releases/tag/v0.6.1
[0.6.0]: https://github.com/akiomik/tears/releases/tag/v0.6.0
[0.5.0]: https://github.com/akiomik/tears/releases/tag/v0.5.0
[0.4.1]: https://github.com/akiomik/tears/releases/tag/v0.4.1
[0.4.0]: https://github.com/akiomik/tears/releases/tag/v0.4.0
[0.3.0]: https://github.com/akiomik/tears/releases/tag/v0.3.0
[0.2.0]: https://github.com/akiomik/tears/releases/tag/v0.2.0
[0.1.1]: https://github.com/akiomik/tears/releases/tag/v0.1.1
[0.1.0]: https://github.com/akiomik/tears/releases/tag/v0.1.0
