7c2fbe6c

Rebuild centjes-chart: pure report IR, diagrams backend, tests

## Summary

Restructures the `centjes-chart` package around a **backend-agnostic, pure report** so the data pipeline is total and testable and rendering is fully isolated. Net diff is the `centjes-chart` package plus its wiring (no `Balance.hs` change — the earlier WIP re-export hack is reverted by this work).

## Architecture

| Module | Role |
|---|---|
| `Centjes.Chart.Report` | Pure IR (`ChartReport`/`ChartSeries`), typed `ChartError` with a composing `ToReport`, total `produceChartReport`, `sampleStepFunction` |
| `Centjes.Chart.Render` | The *only* place precise money becomes `Double`; stacked-area SVG via `Chart` + **`Chart-diagrams`** |
| `Command/All` + `OptParse` | Wires Report→Render; currency / filter / output / title are settings |

## Highlights

- **No partial functions** in the pipeline — `fromJust`/`minimum`/`maximum` replaced by typed `ChartError`s rendered through the usual diagnostic machinery.
- **Exactness to the edge** — `really-safe-money` values flow into the IR; only the renderer converts to `Double`.
- **Dropped the cairo C dependency** by rendering through the pure-Haskell `Chart-diagrams` backend (confirmed in both the stack snapshot and nixpkgs).
- **No hardcoding** — currency, account filter (reusing `Centjes.Filter`), output path and title are CLI/config settings.
- **Daily revaluation** — held balances are re-valued at each day's exchange rate; the per-day price graph is shared across accounts.

## Tests (new `centjes-chart-test`)

- `sampleStepFunction` property tests (carry-forward invariants)
- A valid `ChartReport` golden
- Error goldens for every `ChartError` constructor: unknown-currency, no-data, missing-price

## Verification

- `stack test --pedantic` — 7/7
- pre-commit (ormolu, hlint, hpack, cabal2nix, deadnix, nixpkgs-fmt) — pass
- Nix `weeder-check` — compiles `-O2 -Werror`, no dead code
- End-to-end: `centjes-chart all` writes a valid 1920x1080 SVG (incl. into a nonexistent output dir via `ensureDir`)

## Notes / out of scope

- One chart kind today (assets stacked-area); the IR is built so additional kinds are mostly new `produce*Report` + subcommands.
- Stacked-area assumes non-negative series; negative balances invert a band (inherent charting limitation, pre-existing).

Suite timing

Time to Start Worker time Duration Time to finish Idle
Config 2s 2s 2s 4s 2s
Eval 4s 55s 55s 1m00s 0s
Build 1m00s 50s 41s 1m41s 0s
Suite 2s 1m48s 1m39s 1m41s 2s

Timeline

0s10s20s30s40s50s1m1m10s1m20s1m30s1m40s