Hot Reads Should Not Repair Cold State
Hot reads should not repair cold state
If sync promised to materialize data ahead of time, the read path should trust that promise. A hot read is not the place to quietly run a repair job.
Repair is real work: network calls, provider rate limits, database writes, locks, pool pressure, and weird latency. Folding that into "open message" turns a supposedly local action into a surprise distributed-system operation.
The rule
Keep the hot read cache-only. Put repair on an explicit or lower-priority path.
If the cache is missing something the product promised would be there, treat that as drift. Surface it, count it, repair it from a maintenance path, or offer an explicit command. Do not make every normal read pay the repair tax.
Mxr example
Mxr syncs envelopes and bodies eagerly. The body row should already be in SQLite by the time a user opens a message.
The TUI body preview path now reflects that:
crates/tui/src/runner.rsbatches body requests throughRequest::ListBodies.crates/daemon/src/handler/mailbox.rshandlesListBodiesviaload_cached_body_for_message.- That helper reads SQLite only, normalizes best-effort readable text, enriches calendar viewer fields, and returns a per-message failure if the row is missing.
- It does not call the provider and does not block the TUI queue doing body repair.
There is still an explicit repair escape hatch: GetBody can hydrate a missing or legacy body from the provider and persist it. That is fine because it is no longer the bulk preview path. The hot path stays local; the repair path stays exceptional.
General test
Ask this before putting repair in a read handler:
- Did an earlier phase promise this data would already exist?
- Is the user waiting on this exact read right now?
- Could repair require network, a writer connection, or a long lock?
- Would this make a missing-cache bug look like a loading state instead of drift?
If the answer is yes, split the paths. Hot read first. Repair elsewhere.