should-i-rent/CLAUDE.md
Marcel Enguehard e5400c8992 Set up cargo workspace backbone
Two-crate workspace: immo-core for pure financial logic (wasm-compatible,
no async/IO deps) and immo-web for HTTP and rendering. Both crates are
still cargo-new skeletons; no domain code yet.

Pins the toolchain to 1.97 with rustfmt, clippy and the
wasm32-unknown-unknown target so the core crate's wasm constraint is
checkable locally. Cargo.lock is committed since the workspace ships a
binary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 20:30:55 +02:00

4.4 KiB

CLAUDE.md

Project

Web app that answers one question with data: for a given property in France, is buying financially better than renting and investing the same cash? It outputs an amortisation schedule, total ownership costs, and breakeven curves under several market hypotheses.

This is a financial tool. A wrong number is worse than a slow response, an ugly page, or a missing feature. Correctness and traceable sources come first.

I am learning Rust through this project. See "Working with me" below.

Commands

cargo run -p immo-web                   # start the server on :3000
cargo watch -x 'run -p immo-web'        # live reload during development
cargo test --workspace                  # all tests
cargo test -p immo-core                 # domain tests only, fast
cargo fmt --all
cargo clippy --workspace --all-targets -- -D warnings

Run cargo clippy and cargo test -p immo-core before telling me a change is finished.

Architecture boundary

Two crates. The boundary is a rule, not a description.

crates/immo-core holds all financial logic as pure functions over plain structs.

  • No tokio, no axum, no reqwest, no filesystem, no network access.
  • Must stay compilable to wasm32-unknown-unknown. After adding any dependency, verify with cargo check -p immo-core --target wasm32-unknown-unknown.
  • Time is a parameter. Never call Utc::now() or read the system clock inside immo-core.

crates/immo-web holds HTTP, HTML rendering, and input parsing. It contains no financial arithmetic.

If a computation is tempting to write inline in a handler, it belongs in immo-core.

Stack, pinned

  • axum 0.8. Path parameters use {param}, not :param — that is 0.7 syntax and will not compile.
  • #[async_trait] is not needed on FromRequest / FromRequestParts in 0.8. Do not add it.
  • askama for templates, tower-http for static files and compression, serde, rust_decimal.
  • Server-rendered HTML. No npm, no bundler, no JS framework. Charts are a CDN <script> tag fed by a JSON endpoint.

Before writing axum code from memory, check the version in Cargo.toml and verify the API against docs.rs for that exact version.

Domain rules

  • Money is rust_decimal::Decimal or integer cents. Never f64. Never as casts between numeric types in financial code.
  • Rates are stored as annual nominal and converted explicitly. Name variables so the period is unambiguous (taeg_annual, monthly_rate), never a bare rate.
  • Keep French domain vocabulary in type and field names: FraisDeNotaire, TaxeFonciere, IndiceReferenceLoyers, LoyerDeReferenceMajore, ZoneTendue, Taeg. Do not translate them.
  • Every hardcoded assumption lives in crates/immo-core/src/assumptions.rs with a /// comment giving its source and the date it was checked. Nothing hardcoded anywhere else.
  • If you do not know a real figure — a barème, a tax rate, a fee schedule, an IRL value — say so and leave a clearly named placeholder. Do not invent plausible-looking French tax numbers.
  • TAEG and taux nominal are different things. Distinguish them explicitly wherever both appear.

Error handling and tests

  • thiserror for typed errors in immo-core; anyhow only at the immo-web boundary.
  • No unwrap(), expect(), or panic!() outside tests and main.
  • Every core function producing a number the user sees has a known-answer test, with the worked example stated in the test name or a comment above it.
  • Test the amortisation schedule against hand-checked values, never against the implementation's own output.

Working with me

I know traits, Result, and the borrow checker. I do not know the ecosystem well, and learning it is half the point of this project.

  • When you use a non-obvious idiom — extractors, Layer, lifetimes on a struct, impl Trait in return position — explain why in one or two sentences, in chat rather than as a code comment.
  • If my request implies an unidiomatic design, say so before implementing it.
  • Prefer the simple version over the clever one, and tell me when you deliberately chose the simple one.

Do not

  • Add a dependency without asking first, including small ones.
  • Introduce npm, a bundler, TypeScript, or a frontend framework.
  • Write financial logic in crates/immo-web.
  • Leave commented-out code, or comments that restate what the code does.
  • Reformat or restructure files you were not asked to touch.