Prototyping with AI
Air + Airpine JS prototyping with an AI Agent
Using Alpine.js in Python can feel a bit clunky. If you’ve ever worked with a framework like air , dash, or fasthtml that generates HTML from Python objects, you’ve probably written code that looks like this:
# The “painful” way
Button(”Save”, **{’@click.prevent.once’: ‘save()’})
This works, but it’s a poor developer experience. That means it’s hard to maintain. You’re writing JavaScript inside a Python string, nested in a dictionary. There’s no tab completion, no syntax highlighting, and it’s painfully easy to make typos. For simple examples it’s bearable, but more complex cases get extremely messy. I knew there had to be a better way, so I started an experiment: could I use an AI assistant to rapidly explore options to end up with a prototype of a better solution?
This post is the story of that experiment. It took about 60 minutes to go from vague desire for something better to a functional prototype i’m calling Airpine. It’s a case study in using AI as a whiteboarding and prototyping partner.
Before it’s used in real commercial apps, Airpine will need a rewrite. But being able the play, and feel, and see lots of directions very quickly is invaluable. I got much very tangible feedback and better ideas for how to approach it. I wasn’t even sure it was worth doing when I started!
The Initial Pain Point
The core problem is visible right away. Directives like x-data, @click, and :class aren’t valid Python keyword arguments, forcing us to unpack dictionaries.
You could avoid unpacking dictionaries with built-in string substitution (many replace _ with - for that common case). Maybe you coul extend that to do __ be replaced with :? And then maybe _at_ to be replaced with @? I think you can see it can start getting quite messy just during pure substitution.
My initial prompt to the AI was simple:
Is there a better UX that could be created for using alpine js with air tags? The dict isn’t great. Come up with 4 unique options”
AI as a Brainstorming Partner
The first thing the AI did was research the air framework’s codebase to understand how it handles attributes.
From there, we started brainstorming. The AI proposed a few initial ideas:
A Static Method Builder: A simple class with methods like
Alpine.attrs()that would build the dictionary for you. This was super simple, and a bit better than what I was doing.Custom Tag Wrappers: Subclassing every
airtag (Div,Button, etc.) to add Alpine-specific parameters. This seemed like a good initial idea for a library, but after seeing it in a action I quickly abandoned it.A Context Manager: A
withblock to scope Alpine attributes. This was appealing at first because the scope would be very clear, but it felt a bit unnatural and awkard in pratice.
I had it make minimal app examples I could run of all of them. I settled on the static method builder as a good starting point and and starting thinking of more ideas. I decided to explore get true tab-completion with a syntax that felt natural to both Python and Alpine.js? I proposed an “ORM-like” API:
# The target syntax
Button(”Save”, **Alpine.at.click.prevent.once(”save()”))
Prototyping the “ORM for HTML”
I had already been making small prototypes with AI, but I felt like I needed to go a bit further with this to see where it breaks because this direction felt really promising. I described the desired API, and it generated a proof-of-concept. The core idea was to have a chain of small, immutable builder objects.
Seeing the AI generate this complex structure in seconds was fantastic. Manually coding this would have taken significant time and trial-and-error. I probably would have spend an hour or more on each direction, and yet in under an hour I was deep into evaluation option # 4. I had a working model in minutes.
There were many things I didn’t like. It created an AlpinePatterns class that stored common patterns. I felt this was too thin of a wrapper to be worth the redirection so I removed that. I flattened the structure a bit too much which started feeling like too much transformation between what I wrote and the final html, so I tweaked that. I kept having AI tweak small things while playing with the live app and coding with it.
From Prototype to Library: Scaffolding Airpine
The prototype worked so well that I decided it deserved to be its own standalone library. I told the AI to do it and it created all the boilerplate env, pakage structure, readme, etc.
Is the setup great? No. But I just wanted something that friends and I could play with to give more thoughts before real dev begins.
Putting it to the Test: The Demo App
With the library structure in place, I wanted to make a single demo with lot of examples. AI generated a full-fledged demo application using air to showcase various components, from simple counters to complex modals. This is the app I walk through in the video.
A simple counter shows the immediate benefit. Instead of a messy dictionary, the code is clean and explicit.
The code becomes:
# The Airpine way
def simple_counter():
return Div(
Button(”-”, **Alpine.at.click(”count--”)),
Span(**Alpine.x.text(”count”)),
Button(”+”, **Alpine.at.click(”count++”)),
**Alpine.x.data({”count”: 0})
)
The improvement is even more obvious with a tabs component, which involves coordinating state, click handlers, and dynamic classes.
The code for binding a class to the active tab is wonderfully readable:
Button(”Profile”, **(
Alpine.at.click(”tab = 0”) |
Alpine.x.bind.class_(”{ ‘active’: tab === 0 }”)
))
Collaborative Debugging with AI
It wasn’t all smooth sailing. The first run of the demo app was flooded with client-side JavaScript errors. The x-data attributes were generating invalid syntax. I’m aware of the high level details and many of the specific fixes. But I don’t understand all of the details.
But that’s ok, because it’s just a brainstorming session. If I draw an idea on a whiteboard before building it, I don’t understand every line of code I haven’t written that will be necessary. But it’s still useful as a thinking tool.
Conclusion: AI is amazing for prototyping
It took 90 minutes to go from initial vague idea of wanting something to feel better to a really specific vision that I actually experienced the feeling of in 15+ examples, and over 5 very different design philosophies. This speed would be unthinkable without AI.
AI acted as a whiteboarding tool. It allowed me to:
Explore multiple API designs almost instantly.
Generate proof-of-concept code to validate ideas in seconds.
Automate tedious boilerplate like project scaffolding.
Debug complex issues by rapidly iterating on solutions.
This entire process was a highly productive brainstorming and prototyping session. The AI amplified my ability to think, experiment, and build, turning a multi-day project into a 90-minute sprint. For developers looking to innovate quickly, that’s powerful.
Now the work of building a library I can depend on can begin in a direction i’m confident in.




