sheets-diff 2.5.0

Structured diff engine for Microsoft Excel .xlsx workbooks
Documentation
# RFC 004 — Reader-Based and Path-Safe Input Constructors

**Status.** Implemented (1.2.0)
**Tracks.** App-owned I/O and non-UTF-8 path safety.
**Touches.** `src/core/diff.rs`, `tests/constructors.rs`.

## Summary

Added `Diff::try_new(impl AsRef<Path>, ...)` for path-safe construction
without `to_str().unwrap()`, and `Diff::try_from_named_readers(name, R, name, R)`
for construction from already-opened `Read + Seek` streams.

## Delivered API

```rust
impl Diff {
    /// Path-safe fallible constructor. Accepts &str, String, PathBuf, etc.
    pub fn try_new(
        old_filepath: impl AsRef<std::path::Path>,
        new_filepath: impl AsRef<std::path::Path>,
    ) -> Result<Self, SheetsDiffError>;

    /// Reader-based fallible constructor.
    /// old_name / new_name populate Diff.old_filepath / Diff.new_filepath.
    pub fn try_from_named_readers<R1, R2>(
        old_name: impl Into<String>,
        old_reader: R1,
        new_name: impl Into<String>,
        new_reader: R2,
    ) -> Result<Self, SheetsDiffError>
    where
        R1: std::io::Read + std::io::Seek,
        R2: std::io::Read + std::io::Seek;
}
```

## Example usage

```rust
// GUI app with owned bytes
use std::io::Cursor;
let diff = Diff::try_from_named_readers(
    "old.xlsx", Cursor::new(old_bytes),
    "new.xlsx", Cursor::new(new_bytes),
)?;

// VCS checkout without lossy path conversion
let diff = Diff::try_new(old_path_buf, new_path_buf)?;
```

## Internal design

Both constructors share `Diff::try_from_workbooks`, the private engine
that receives already-opened `Xlsx<R>` values and their display labels.
No diff logic is duplicated.

## Open questions

None.