explainable 0.1.1

A zero-overhead educational layer for Rust libraries.
Documentation
# explainable

A zero-overhead educational layer for Rust libraries.

`explainable` lets a domain crate give its users a step-by-step, pedagogical view of an operation chain --- text explanations, before/after visuals, or both --- without touching the hot path, changing any existing call site, or adding any runtime cost unless the feature is explicitly invoked.

```bash
cargo add explainable
```

---

## Motivation

High-performance Rust libraries operate correctly and efficiently, but they offer no in-library mechanism for a user to understand what any given operation does, what its intermediate steps are, or what the data looks like before and after. This gap is the pedagogical problem `explainable` addresses.

The goal:

- Lives entirely within the Rust core --- no separate tutorial crate, no external documentation that can rot
- Adds a single entry point to any type --- `.explaining(ExplainMode)` --- and nothing more
- Leaves every existing call site valid and every existing operation completely untouched
- Imposes negligible overhead --- the hot path is unaffected; explanation machinery is never exercised unless explicitly invoked
- Scales to any crate that opts in with four lines of code

---

## Workspace layout

```
explainable/
├── Cargo.toml                  ← workspace root + the explainable library crate
├── src/
│   └── lib.rs                  ← public traits, types, and macro re-export
└── explainable-macros/
    ├── Cargo.toml              ← proc-macro = true
    └── src/
        └── lib.rs              ← #[explainable] attribute macro implementation
```

`explainable-macros` is an implementation detail. Depend on `explainable` and use
`explainable::explainable` --- do not depend on `explainable-macros` directly.

---

## User-facing API

The complete change to how a user interacts with a participating crate is one
additional call to open the chain. Everything else is identical.

**Normal use --- unchanged:**

```rust
audio.normalize();
audio.scale(0.5);
audio.trim(100, 200);
```

**Educational use:**

```rust
use audio_samples::AudioProcessingExt; // extension trait generated by the macro

let (result, _explanations) = audio
    .explaining(ExplainMode::Both)
    .normalize()
    .scale(0.5)
    .trim(100, 200)
    .explain();
```

`.explain()` at the end of the chain:
- Surfaces all accumulated explanations --- text to terminal, visuals via the domain crate's renderer
- Returns `(final_value, Vec<Explanation>)` --- the full explanation record for later inspection

**`ExplainMode` variants:**

```rust
audio.explaining(ExplainMode::Text)    // pedagogical text only
audio.explaining(ExplainMode::Visual)  // before/after plot only
audio.explaining(ExplainMode::Both)    // text and visual
```

---

## How a domain crate opts in

Four steps. The existing implementation is never touched.

### 1. Add the dependency

```toml
[dependencies]
explainable = { version = "0.1.0"}
```

### 2. Annotate your operation trait with `#[explainable]`

```rust
use explainable::explainable;

#[explainable]
pub trait AudioProcessing {
    fn normalize(&self) -> Result<AudioSamples, AudioError>;
    fn scale(&self, factor: f64) -> Result<AudioSamples, AudioError>;
    fn trim(&self, start: usize, end: usize) -> Result<AudioSamples, AudioError>;
}
```

The macro leaves the trait itself completely unchanged. It generates three
additional items (see [What the macro generates](#what-the-macro-generates)).

### 3. Implement the two `explainable` traits

```rust
use explainable::{ExplainDisplay, RenderVisual, Explainable};

// Rendering surface --- owns the plotting/display logic
struct AudioSamplesVisual { html: String }

impl ExplainDisplay for AudioSamplesVisual {
    fn display(&self) {
        open_in_browser(&self.html); // existing infrastructure
    }
}

// Produce a before/after visual for any operation
impl RenderVisual for AudioSamples {
    fn render_visual(before: &Self, after: &Self) -> Box<dyn ExplainDisplay> {
        Box::new(AudioSamplesVisual {
            html: plot_before_after(before, after),
        })
    }
}

// One line to opt the type into the system
impl Explainable for AudioSamples {}
```

### 4. Implement the companion text trait

The macro generates a `<TraitName>ExplainText` trait with one method per
operation. Each method receives the before and after state so that real runtime
values can be woven into the explanation:

```rust
impl AudioProcessingExplainText for AudioSamples {
    fn explain_text_normalize(before: &Self, after: &Self) -> String {
        format!(
            "Normalization scales every sample so the peak absolute value \
             becomes 1.0. Your signal had a peak of {:.4}, so every sample \
             was divided by that value, mapping your range to [-1.0, 1.0].",
            before.peak()
        )
    }

    fn explain_text_scale(before: &Self, after: &Self) -> String {
        format!(
            "Scaling multiplies every sample by a constant factor. \
             Your peak went from {:.4} to {:.4}.",
            before.peak(),
            after.peak()
        )
    }

    fn explain_text_trim(before: &Self, after: &Self) -> String {
        format!(
            "Trim discards samples outside the requested window. \
             Length went from {} to {} samples.",
            before.len(),
            after.len()
        )
    }
}
```

---

## What the macro generates

Given `#[explainable]` on a trait `Foo`, three items are emitted alongside the
**unmodified** original trait.

### `FooExplainText` --- companion text trait

```rust
pub trait FooExplainText: Explainable + Foo {
    fn explain_text_some_op(before: &Self, after: &Self) -> String;
    // one method per operation
}
```

Implemented by the domain crate author to supply pedagogical text. The design
constraint is that explanations use real runtime values from both `before` and
`after` --- neither pure abstraction ("normalization divides by the peak") nor
pure reflection ("peak was 0.87"), but both together.

### `FooExt` --- extension trait for `Explaining<T>`

```rust
pub trait FooExt {
    fn some_op(&mut self, /* original params */) -> &mut Self;
    // one method per operation
}
```

Bring this into scope to call operations on an explaining chain. The extension
trait pattern is required because `Explaining<T>` is defined in `explainable` ---
adding inherent methods to a foreign type from a downstream crate would violate
the orphan rule. A locally-defined extension trait is the canonical Rust
solution.

### Blanket `impl FooExt for Explaining<T>`

```rust
impl<T: Explainable + Foo + FooExplainText> FooExt for Explaining<T> {
    fn some_op(&mut self, /* params */) -> &mut Self {
        let before = self.inner.clone();
        self.inner = self.inner.some_op(/* params */); // calls the real operation
        // builds Explanation from FooExplainText + RenderVisual
        // pushes onto self.explanations
        self
    }
}
```

Each generated method:
1. Clones `inner` as `before`
2. Calls through to the real, unmodified operation
3. Builds an `Explanation` --- text from `FooExplainText`, visual from `RenderVisual` --- conditional on the active `ExplainMode`
4. Pushes the `Explanation` onto `self.explanations`
5. Returns `&mut Self` for method chaining

New operations added to the annotated trait automatically get explaining
variants --- zero maintenance.

### Return-type convention

For the generic `self.inner = self.inner.method(...)` assignment to compile,
trait methods should return either `Self` or a `Result<Self, E>` / type-alias
ending in `"Result"`. Result-returning methods are unwrapped automatically. Methods
that change the output type (e.g. FFT returning a `Spectrogram`) are not yet
handled and will produce a compiler error at the use site.

---

## Architecture

```
┌─────────────────────────────────────────────┐
│  explainable                                │
│                                             │
│  ExplainMode     (enum)                     │
│  ExplainDisplay  (trait --- opaque surface) │
│  RenderVisual    (trait --- domain renders) │
│  Explainable     (marker trait)             │
│  Explanation     (struct --- one per op)    │
│  Explaining<T>   (struct --- the chain)     │
│  #[explainable]  (proc-macro re-export)     │
└────────────────┬────────────────────────────┘
                 │  implements
┌─────────────────────────────────────────────┐
│  audio_samples (or any domain crate)        │
│                                             │
│  impl RenderVisual for AudioSamples { … }   │
│  impl Explainable  for AudioSamples {}      │
│                                             │
│  #[explainable]                             │
│  trait AudioProcessing { … }                │
│                                             │
│  impl AudioProcessingExplainText            │
│      for AudioSamples { … }                 │
└─────────────────────────────────────────────┘
```

`explainable` has no dependencies on any domain-specific
library.W It defines the interfaces. Domain crates own their rendering infrastructure entirely.

---

## Open problems

| # | Problem | Status |
|---|---|---|
| 8.1 | `ExplainMode` matching --- currently per-method; could be pushed into a construction helper for uniformity | Open |
| 8.2 | Macro handling of pedagogically significant parameters (`scale(factor)`, `trim(start, end)`) --- parameters are passed through correctly but not yet surfaced in generated explanation text | Open |
| 8.3 | Operations that change the output type (FFT → Spectrogram) --- wrapper type must transition; mechanism unresolved | Open |
| 8.4 | Feature flagging --- whether the system compiles away under `#[cfg(feature = "educational")]` needs working through with the macro | Open |

---

## License

[MIT](LICENSE)

## Contributing

TODO