Driving organic adoption: a personal stylist app my family actually uses
My mother-in-law, Abue, has great taste and a real eye for a deal, so I built her a tool to curate clothes for me and my wife in one place. The build was a weekend. The interesting part was everything after: getting a non-technical person to use it every day, and getting the rest of the family to adopt it without me pushing. That's a product problem, not an engineering one, and it's the part I'm proud of.
The hardest problem was friction, not features. Abue shops on her iPad. Every step between "I found something" and "it's in the closet" is a step where adoption dies. So her entire entry point is an iOS Shortcut that lives in the share sheet. She taps Share on any product page, picks who it's for, and that's it. No app to open, no login, no form to fill out. One Shortcut covers every case, including fanning a single piece out to my wife and her sister for a quick vote on something Abue is considering for herself. The lowest-friction path I could build was the one that already existed on her device.
The Shortcut is tiny. It picks up whatever page she shared, asks who it's for, and POSTs one small payload to the app:
POST /api/share
{
"url": "<the page she shared>",
"recipients": ["peter"], // one closet, or several for a group vote
"forBuyer": "abue" // only on the "get their take" branch
}
The scraper does the rest. When she shares a link, a scraper reads the page's structured data -- the JSON-LD that retailers embed for Google -- and pulls the real sale price, the actual savings, and a clean product image, without me parsing a different HTML layout for every store. She shares a URL; a finished card appears. Her effort is one tap.
// Retailers embed product data as JSON-LD for Google. Read that
// instead of scraping a different HTML layout for every store.
function findProduct(node) {
const t = node?.["@type"];
if (t === "Product" || t === "ProductGroup") return node;
// Recurse into arrays and into an @graph list of nodes.
const children = Array.isArray(node) ? node : node?.["@graph"];
for (const child of children ?? []) {
const hit = findProduct(child);
if (hit) return hit;
}
return null;
}
for (const tag of $('script[type="application/ld+json"]')) {
const product = findProduct(JSON.parse($(tag).text()));
if (product) return {
title: product.name,
brand: product.brand?.name ?? product.brand,
price: product.offers?.price,
image: [].concat(product.image)[0],
};
}
The feedback loop is what made it stick. The thing that turned a clever tool into a daily habit wasn't the app at all, it was a morning email. Every day Abue gets a digest: what we liked, what we passed on, and the part that actually mattered, which items she sent that we went on to buy. That closes the loop. She can see her taste landing, in dollars, which is the signal that what she's doing is useful. Adoption came from her seeing the value herself, not from me reminding her to keep going.
Then it spread on its own. Once Abue's picks were good, word got around the family. My sister-in-law wanted a closet. So did my father-in-law. My wife wanted her own login instead of a shared URL. The first version had no accounts at all -- nginx Basic Auth and two hardcoded closets -- which doesn't survive a family, so I added Clerk with an email allowlist and a small role model: an admin (me) who can see everything, a curator (Abue) who sends links and sees how her picks land, and recipients who only see their own closet. The app grew from a one-person tool into a small family network with a curator at the center, and the growth came from the product being good, not from me selling it.
It's an iPad-first web app shipped the dull reliable way: push to main, a GitHub Action SSHes the server, rebuilds, restarts. With Claude doing most of the engineering, the build was the easy part. The hard part was up the stack: removing every gram of friction from the input, and building a loop that showed Abue her work was valuable. That product work is what turned it into something my family actually uses.
← Writing