After building a tiny Flutter offline timer "Kvart" (it got about 73 downloads so far!), I've set into building a fully functional fullstack web application in a few evenings next.
How and why I switched to building AI assisted free microapps is a whole another story that can be narrowed down to me underestimating the amount of effort my beloved Hounty takes and looking for ways to iterate and test ideas faster.
I'm pretty sure that services like Lovable or Cloudflare's own platform would allow me to vibe-code it much faster, but I'm not ready to let go of that amount of control just yet. Besides I just love choosing a tech stack for a new project, don't you?
You probably can already tell that this article is not going to be a smooth "how to build a perfect app with one prompt", but instead it tells a story about how I build this particular app, what worked and what didn't to hopefully make your journey smoother or just have a few laughs.
Idea
In a nutshell, CleanIt.Now allows you to take pictures of the mess and send it to the person who made the mess in the first place so that they'd clean it up and send you pictures as a proof back. And, as the name suggests, each task has an expiration date: 24 hours, to be precise.
The architectural approach I had in mind could be described as Cloudflare's R2 bucket with object livecycle policy and some-kind-of-signed URLs.
And when I was buying the domain name it was more like "Hehe, it would be cool to send cleanit.now/something to my daughter" rather than "I have a whole business plan figured out".
I still don't, but that's the beauty of it: I get to follow an idea and see it grow and turn into something. It's art, it's therapeutic.
Now, let's talk about tools!
Tech Stack
Maybe one day I will let LLM decide on the tech stack (when it would offer anything but Tailwind and React perhaps), but that day is yet to come.
At the moment my favourite fullstack framework is Hono and for this project I decided to give SolidJS a try for the frontend.
I quite liked it, though Claude & Copilot wrote most of SolidJS code, so I suppose "I liked reading SolidJS code" would be more precise.
Planning
I started with Claude Code in planning mode. We discussed a few approaches and settled into a plan that consisted of:
- We start with frontend single page application
- We use Cloudflare Workers for the backend
- It'll be a monorepo with two folders:
backendandfrontend - We will use custom JSON web tokens for signing urls
Excited, I asked Claude to document the implementation plan and it produced extremely detailed markdown document full of weird suggestions. I didn't save a copy of it, after a request to clean it up and some manual refinement I ended up with:
# CleanIt.Now - Implementation Plan
## Overview
A full-stack web application for creating and managing temporary cleaning task lists with anonymous access, real-time collaboration, and automatic 24-hour expiration.
---
## Phase 1: Frontend (Local Preview)
### Tech Stack
- **SolidJS** - Reactive UI framework
- **Vite** - Build tool & dev server
- **TypeScript** - Type safety
- **CSS Modules** - Scoped CSS styling
- **nanoid** - ID generation
- **npm workspaces** - Monorepo structure
### Type Definitions
typescript
TaskList {
id: string (nanoid)
title?: string
tasks: Task[]
created: string (ISO Date)
expiresAt: string (ISO Date)
}
Task {
id: string (nanoid)
images: { before: string, after?: string } (blob URLs, optional)
completed: boolean
created: string (ISO Date)
}
---
## Phase 2: Backend & Persistence
### Tech Stack
- **Hono** - Lightweight web framework
- **Cloudflare Workers** - Serverless compute
- **Cloudflare R2** - Object storage
- **JWT** - Authentication/authorization
- **Rate Limiting** - Abuse prevention
## Notes
- Event sourcing architecture ensures no data loss during sync
- All timestamps use ISO 8601 format
- JWT tokens expire with the list (24 hours)
- R2 storage is cost-effective for 24h retention
- Frontend can work offline, sync when online
- Consider adding optimistic UI updates for better UX
Suffices to say, that while the current version mostly follows the plan - the only useful part about it were types.
Frontend
I prefer to see how the app would look like before I start building the backend, so I usually start with the frontend, but it's viable either way.
So far LLMs very surprisingly good at frontend tasks or, perhaps, I was better at explaining what I needed.
I started with a request to scaffold a new SolidJS SPA with some lightweight CSS-in-JS. Claude offered me a few options, but they were either outdated or had weird syntaxis. So in the end Claude suggested to just go with CSS modules and I relented.
We started with "a modern app-like header" and step by step made our way to a god-awful multi-color monstrosity akin to my first projects in BootstrapCSS.
Luckily "make it look more like Instagram, use grayscale" seemed to have fixed it, though blue buttons kept popping up every now and then.
The app now consisted of an editable title (which proven far more challenging for LLM than I anticipated), two tabs: Edit and Preview and a sortable grid under the first tab.
Surprisingly, as Claude struggled with making "just a goddamned accessible input please", it had no issues with drag and drop and state management. My only guess is that there hasn't been that many articles on semantic html markup in comparison to drag and drop functionality and thus got lost in the sea of training data.
It took me just an evening to get to this point which was very reassuring! I even managed to squeeze in a landing page with text and all - Claude did exceptionally well there (or I didn't care enough to correct at this point - we'll never know).
Excited to build the backend endpoints, I went to bed.
Backend
I genuinely thought that this part would be a walk in the park: there wasn't any complicated logic and just a few endpoints. And so I got this brilliant idea of using TDD for it!
Now, I'm a big fan and supporter of test-driven development and when I do write tests they catch a great deal of errors, but I find the process of writing tests so excrusuatingly boring that in most cases I write only a few of them if any. Till stuff breaks that is.
But this time I could pull a whole bunch of them out of thin air!
And so I described the endpoints I wanted it to build and suggested Claude to use TDD for it.
Here is how my code structure looked like after that:

You could notice that there are far more tests than actual code and a top of that most of the logic is stuffed into an index.ts.
It took me a few hours to review what it generated, correct it, ask to refactor specific bits and in the end I ended up writing some of the endpoints myself with the help of code completion.
The tests aren't currently passing and I'm tempted to throw the whole folder away and rewrite them from scratch when something breaks. You know, old school.
But jokes aside, it's very easy to ask AI to generate something and it will, but just like it is with the humans - GenAI produces far better results when you ask them for a very specific thing. Next time I'll outline what I want to test and ask an LLM to fill in the gaps and see if it works better.
By the way, this is how the "Full flow" test starts:
it.skip('should complete full flow: create list -> upload images -> share', async () => {
// Skipping due to known vitest-pool-workers issue with FormData in long tests
// The individual handler tests cover this flow in isolation
// Step 1: Create a list
...
Yeah, no.
Testing in production
By the end of the second day, the backend implementation was ready, everything was working great locally and I deployed it all to Cloudflare.
Claude taught me a neat trick with Cloudflare assets binding which allowed me to do things like:
app.get('/:listId/:token', async (c) => {
// Check accept header and serve SPA frontend if HTML is requested
const acceptHeader = c.req.header('Accept') || '';
if (!acceptHeader.includes('application/json')) {
return c.env.ASSETS.fetch(new URL('/view.html', c.req.url).toString());
}
//...
});
At some point I should definitely look into how it all (landing page, SPA and worker code) is bundled together because I have a few issues with HMR (it doesn't work at all), but the app went live and static pages looked good.
And then I tried and failed to create a new list, then to upload a photo, then to upload a few photos and finally to complete the list.
After fighting the bugs for a few hours, releasing yet another and another version and seeing that while everything worked brilliantly on my laptop it failed on mobile at the last step again - I threw in a towel and went to sleep.
Luckily, sleeping on it helped and the morning of the third day (today) I was ready to show CleanIt.Now to the world!
What did I learn from this experience?
The biggest takeaway is that me and LLMs still have a long road ahead of us before we'd learn how to communicate properly with each other. And I am nowhere ready to offload all the coding to the agents, though they can have my frontend bits, if they promise to build accessible solutions (it's best to use an existing design system like Tailwind it appears, but it works more or less okay either way).
I will have to try and write the plan, types and tests cases myself next time. I was being lazy and let LLMs run wild and ended up with expectable outcome. Vision is on the creator, that's not something to delegate, as I've learned times and times again.
I'm curious, what do you think of CleanIt.Now? Did I miss something obvious? Anything I could do better?
Share your thoughts in the comments and thank you for sticking around!
Top comments (0)