work/sugarplum
live · 2026
sugarplum
paste a link. it tracks the price. no spreadsheet required.
a small self-hosted wishlist tracker. paste a product link, the server fetches title, price and image, and the list is shared between accounts. a bun-native pwa that runs as one process.
the story
the product brief was two sentences: we keep losing track of things we want to buy, and spreadsheets are hostile. sugarplum is the whole answer — paste a link, it fetches the product, prices get tracked over time, the list is shareable.
the build is deliberately small. one bun process serves the react spa and the api. bun:sqlite in wal mode for storage. sessions are httOnly cookies backed by a server-side table. no build pipeline, no container, no devops ceremony — deploy is a script that pulls main and restarts a systemd unit.
the interesting part is what the tracker refuses to do. prices are stored in integer cents and every trend label is computed by a pure function over a capped series — there is no "buy now" verdict anywhere, because a wishlist app that starts yelling at you has decided its own engagement metrics matter more than your money.
what it does
- paste-a-link is the primary flow; manual entry is the fallback, not the default.
- scrapers degrade gracefully — a fetch that can’t get price or image still creates the item, marked incomplete, never a dead end.
- trend labels are informational, never urgent: below-30d-avg, near-30d-low, trending-down — no "buy now", ever.
- installable pwa: offline app shell, web share target on android, icons and manifest kept honest.
- auth is scrypt-hashed passwords with server-side sessions; admin surface is deliberately boring — create, list, reset, deactivate.
decisions i’m proud of
▸ money math is integer cents
floating point is fine until the day it isn’t. every price in sugarplum is an integer number of cents from storage to display, and the trend module documents that constraint in its first comment. the bug class this deletes is worth the mild inconvenience of *100 at the edges.
▸ the trend function is pure — no db, no fetch, no io
price advice is exactly the kind of feature that accretes side effects: read the db, fetch current price, maybe send a notification. instead the trend is a pure function over a capped series, unit-tested at the boundary. if the advice is wrong, there is exactly one file to blame.
▸ no buy button, no urgency
engagement-brain says "notify when the price drops!". the user’s actual question is "is this a good price relative to the last month?" — a question you can answer with a calm label and a delta. an app you trust with your money never behaves like a clearance sale.