Meet Shoppy! It's a helper app for my recently revived shopping list, with which I'm hoping to grow the dataset for categories prediction. In fact, even early beta tests have made Shoppy significantly more savvy about alcoholic drinks (the initial data comes from my own shopping, and my entire family happens to be non-drinkers). See if you can confuse it about something it doesn't know!
But besides that, there's a few deeper philosophical and technical notes I wanted to share.
The app
It's a very, very simple Django app. When I first had the idea to build it I entertained some thoughts about trying some front-end based technology, because, you know, it's an "app"… But then after actually thinking about what it's going to be — a handful of static screens and a couple of forms — I decided to go the familiar way.
Now I have a small, view-source'able HTML app which I'm proud to offer as an example of how you can build something interactive without the layers of modern front-end technology.
If you're new here, simplicity is kind of my thing in software engineering. Although it's really hard to convince people to do simple.
CSS
Trying modern CSS after a long break felt really exciting! Nested blocks, variables, complete control over the box model, new useful units (like vw), and niceties like width: fit-content — all of these made my life much simpler.
I was especially impressed with border-image which allowed me to make speech and form bubbles flexible. Without it, trying to make text of variable length look nice in a fixed-size bubble caused me a lot of frustration.
For layout, I tried flexbox and grid, but they didn't really work for me. It's my own fault, really. You see, ever since I bought into the idea of separating the roles of markup and style, I dislike adding extra structure to markup purely for styling convenience. Markup needs to mean something!
And the one thing that grids and flexboxes really like is having straightforward container <div>s with stuff inside of them. But what I have is a <body> which consists of naked <img>, <p>, <form> and <footer>, in this order — and that's just not enough structure to say "this goes here, and that goes there".
So I ended up with good old absolute positioning and some paddings around Shoppy's avatar. CSS variables really do shine for things like this.
And! It was my first time making a responsive layout that looks nice both on mobile and desktop! Tell me if something is broken on your particular setup.
Model
The model is a mapping from "terms" to categories. I learned to build such things while working on the Search team at Shutterstock, and their simplicity still amazes me!
Here's how it works:
-
You get a search query, like "Honeycrisp apples".
-
You split it into words, stem them and sort them, which gives you
["appl", "honeycrisp"]— a predictable set of keys independent of morphology and the input order (they're called unigrams). -
Then you generate all two-word combinations (called bigrams) from this set, which in this case gives you just
["appl honeycrisp"], and add them to unigrams. -
And then you look up each of the search terms in the dataset and pick the entry that comes the earliest. In this case, there's only one:
appl,produce.
That's it!
But there's a few non-obvious tricks it lets you do:
-
You don't need to list all the apple varieties, unknown words are simply ignored, and you just recognize any apple as produce.
-
But what of "apple juice"? For that it has an entry
juic,drink, which is deliberately placed before the apples, so it gets picked up instead. In fact, what it means is that "any kind of juice is a drink, regardless of what it's made of". Same goes for "oat milk" (drink), "diced tomatoes" (canned products), etc. -
Now think of "apple sauce". "Apple" is produce, "sauce" is (usually) a condiment. But "apple sauce" is a snack! This is where bigrams come into play: the bigram entry
appl sauc,snackcomes before bothsauceandappl, which resolves the conundrum. (In fact, all of the bigrams must come before all the unigrams, because they're always more specific.)
There's some more to it all, and there are downsides, but I won't go any deeper right now.
LLMs

It's 2026, so I can't not talk about it, can I?
Generative AI happened to the world right in between of me first coming up with the idea of category prediction and having a chance to actually implement it. And I admit of having thoughts that may be there's no point in building your own model for such a thing now. After all, just ask any LLM "which grocery category is dill weed" and it will tell you… a lot of text with several variants, which you can't really use in a precise manner :-)
So of course I went back to my own idea, because it's much, much simpler. And local. And free. And ethical.
Luckily, the simpler solution doesn't really lose on feeling magical and intelligent. I've seen people play with the app and really engage with it, and be impressed! One of the testers, when trying to come up with a random grocery item for the first time, said, "There's probably a million of them!" It doesn't matter that my entire model is just around 500 entries, it still feels like it knows much more simply because people overestimate the size of the problem :-)
Graphics
You see, I can process photos, I can do business graphics, and I'm known to have put together a few toolbar icons in my time… but for the life of me I can't draw! And even if I could, I'm particularly hopeless at coming up with what to draw.
So I commissioned the graphics from an artist, who also introduced me to the concept of "object shows" and the whole OSC fandom. Not sure I'm joining as a fan yet, but I'm definitely very happy with the original character of Shoppy! Oh, and the background.
And none of it is AI!