Planet4.Me docs
Open the app

#Staples and the purchase stream

A check-off is two writes. The purchases/{id} document is the truth: it carries its own id, so two offline clients checking off the same staple cannot collide. Both the document and the cache carry sessionId, the trip the purchase happened on, which is how a purchase knows its shop: the session owns the shop, so re-attributing a whole trip stays one write and no purchase is ever rewritten. It is absent on entries written before the field existed and on purchases that honestly have no trip — a promotion seed, a receipt read days later, a past purchase typed in by hand — and absent means "no evidence about where this was bought", never "bought anywhere". rebuildPurchaseCache carries it back from the truth, or the repair would erase every shop the app had learned. staple.purchaseHistory[] is a denormalized read cache capped at 20 — rendering forty staples costs forty reads rather than three hundred, and the cadence engine never needs more than the last six intervals.

The cache is rebuildable. rebuildPurchaseCache re-reads the newest twenty purchase documents and rewrites the array, which is the repair for the one known failure mode: a capped array cannot use arrayUnion, so two concurrent appends can drop an entry. Losing one is tolerable precisely because it is not the truth. A staple seeded by promotion writes its seed to both, or a rebuild would erase history the cache claimed to have.

buildCheckOffWrites builds both payloads without performing them, so a test can assert directly that at is a plain device-clock number and never a serverTimestamp() sentinel.

Item-to-staple resolution at add time tries exact canonicalName matching first, then the trigram shortlist in src/lib/resolve/ with the model seam behind it; at check-off only the exact match is consulted. An unresolved item still checks off normally.