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.





Brilliant. This experiment really highlights AI's power to accelerate the *discovery* phase, not just execution. Makes you reflect on what 'developer experience' truly means when AI is so integreated.
The ORM-like API for Alpine.js is brilliant. Using AI to rapidly prototype multiple design philosophies in 90 minutes shows just how powerfull AI can be as a whiteboarding tool. The jump from messy dictionaries to Alpine.at.click.prevent.once feels so much more natural, and getting that iterativ feedback loop with working prototypes is exactly how AI shines in development workflows.