Hiazee was supposed to be a graduation project. An AI-powered plant marketplace built for the Bangkit Academy capstone — scan a plant, identify the species, buy it from the nearest seller. The classroom version worked. It earned us a good grade. Then I tried to turn it into something real, and almost every assumption I'd made in school broke at once.
What "Working" Means in a Classroom vs. Production
In the capstone, "working" meant the demo ran without crashing. The ML model classified the three plants we'd shown it during training. The checkout flow accepted the one test card the lecturer used. The whole app was a beautiful Potemkin village — perfect from the angle we were grading it from, hollow everywhere else.
The production rewrite is where you learn that "working" actually means: handles a wrong image gracefully, fails gracefully on a slow connection, doesn't lose the cart when the user backgrounds the app, recovers when the payment provider returns a 502, behaves the same on three years of Android versions, and stays under 200KB of initial JS so it loads on a 3G connection in a kampung.
Capstone projects optimize for "demo demo demo." Real products optimize for everything that happens when nobody is watching the demo. The gap between those two is the gap between a portfolio piece and a product.
Recoil + React Query: A Pairing That Saved Me
The most concrete technical lesson from Hiazee was the split between server state and client state. In the capstone, everything lived in Redux. In production, that one global store turned into a swamp — stale server data, optimistic updates that drifted out of sync, components re-rendering for state they didn't read.
Splitting the layers cleanly fixed almost everything. React Query owns anything that comes from the API — products, plant identifications, order history, user profile. Recoil owns anything that's purely client-side — the cart selection, the camera preview state, the search filter chips. The boundary became obvious, and the bug class of "data is stale somewhere but I can't find where" disappeared entirely.
// One source of truth per concern — server vs. client
const cartItemsAtom = atom<CartItem[]>({
key: "cart/items",
default: [],
});
function useProduct(id: string) {
return useQuery({
queryKey: ["product", id],
queryFn: () => api.getProduct(id),
staleTime: 60_000,
});
}
// Composition stays clean — no global store muddling the two layers
function ProductDetail({ id }: { id: string }) {
const { data: product } = useProduct(id);
const [cart, setCart] = useRecoilState(cartItemsAtom);
// ...
}The ML Model Was Wrong More Than Right
Our plant classifier hit 94% accuracy on the test set. That number was a lie. In production, it was closer to 60%. The reason is mundane: the test set was photos in good light, of healthy plants, taken straight on. The real users submitted blurry photos in bedroom lighting of half-dead plants with one drooping leaf and a finger in frame.
"Your test set is a fantasy of your real users. Replace the fantasy with a steady stream of actual production data, or your model accuracy numbers are decorations."
The fix wasn't a better model — it was a better UX around the model's uncertainty. We added a confidence threshold and a "show me the top 3 candidates" flow when the model wasn't sure. The product got better and the user complaints dropped, even though the model itself never improved.
What I Unlearned
I unlearned "the architecture I picked in week one is the architecture I'll have in month six." It almost never is, and trying to lock it in early just makes the inevitable refactor more painful. I unlearned "if the demo works, the product works." The demo works because the demo is choreographed; the product has to work when nobody is choreographing it.
Treat your first version as a learning artifact, not a foundation. The second version is where you build to last — and you can only build to last once you've been wrong about enough things in the first.
Hiazee never reached its production launch in the form we originally imagined. The marketplace pivoted, the team scattered post-graduation, and the codebase eventually went cold. But the lessons stayed. Every project I've shipped since — Sygma Studio, Cakra, all of them — owes something to the version of me that learned, on Hiazee, that capstone-grade is not production-grade and never will be.




