I recently grabbed myself a Claude Max subscription in hopes to enhance my workflow after spending a year using OpenAI tokens here and there to get assistance with specific problems. Having a new tool at my disposal, I wanted to try to make a start to finish project with it to put it through its paces. I decided I'd have it help me write a piano web app. Below are the exact prompts that I used to have the piano made. I did omit a few where I changed the database engine because Postgres was giving me a hard time on my VPS, but that is another story.
To start, I created a new Rails application by running rails new PianoWebApp. I popped open VS Code and opened the newly formed directory then got started with Claude. I told it that it was in an empty Rails application, then explained what I wanted and gave it some basic scaffolding for database structure and let it get to work.
This is an empty rails app, I want to make an piano webapp in it.
Add the ability for users to record their songs. To do this create a database table called “recordings”, and another table called “notes”. The recordings table will have an “id” column. The notes table will have an “id”, “recording_id”, “note”, and “ms” columns. The note column will be which note was played, and the ms column will be how long in milliseconds after the recording started the user played the note. Allow users to visit their past recordings by navigating to /play/:id. This end point will have play/pause/stop controls to play back the recording based on which note was played and when.
This alone gave me 90% of the finished product. It generated a simple UI that looked like a keyboard, it generated the JavaScript code to capture clicks and keypresses to play the appropriate note. It created the appropriate database migrations, routes, controllers, and models for saving recordings and playing them back. I was gobsmacked that a couple of sentences could result in almost exactly what I was looking for. There were a couple of changes and enhancements that I wanted to add though.
The originally generated code displayed the recording ID as the main header on the playback page. This felt half-baked so I asked it to give the user the ability to name their recording. I didn't bother telling it where to store the name, or what to call the column. It generated another migration to save the name, added the ability to give recordings a name, and displayed the name of the recording on the playback page.
Add the ability for users to name their recording, if they to not give their recording a name just name it the id of the recording.
It did however show the option to name the recording before a recording was created though, which felt like bad UX. As a user why would I want to name a recording that doesn't exist? So I asked Claude to make the text field to name the recording hidden until recording was completed.
Don’t show the recording name option until after the user finished recording.
It did, perfectly. I then figured no reason to keep the code hidden, so I asked it to put a link to the GitHub repository on the page in the footer, and asked for it to include the Octocat logo. I wasn't sure how it would go about adding the logo, but figured it'd be an interesting test at the least.
Add a link to the github repository (https://github.com/Jeremy1026/web-piano) in the footer of the site. Include the github logo and have the link text say “View on Github”
It added the link, and generated a SVG file for the Octocat logo. One thing that it did do poorly here was it didn't create a footer layout to include, it is repeating the code on each view. This isn't good practice, but I left it because that's what it came up with.
Next I didn't want people to be able to iterate through recording IDs, so I asked it make the recordings only available via a hash of the ID. It didn't do it exactly how I would have done it, but it did successfully make the recordings non-iteratable.
Make the recordings accessible only by a hash of the id. That way people can’t just increment the ID to see other peoples recordings.
I probably would have added an environment variable for a salt, add it to the id, then run it through md5 or similar. Claude added a new database column to the recordings table and changed the lookup code in the controller to find the correct recording based on the newly added access_token. I don't like the use of the name "access_token" either, but again I left it. After adding the access token, trying to save a recording started to fail.
When trying to save a recording it fails, the UI says Error saving recording. Please try again. after saving the recording, add a button to copy a link to the playback page, also show the url for the playback page
I told Claude it was failing, provided the error I was seeing, and asked for a way for users to easily grab the newly created recording URL for sharing. In hindsight, I probably should have made these two requests separately, but Claude had no problem generating a fix and adding the new functionality.
Finally I noticed that when playing back recordings, every note was played for the same duration (200ms) regardless of how long it was held in the original playing. I again asked for an enhancement.
Also record the length a key is pressed to ensure that playback is faithful to the original.
Claude created another database migration to add the duration a note was held, updated the playback and recording controllers to account for the duration in saving and reading, and updated the JavaScript to keep track of how long notes were held for. With that, I was happy enough with the prototype. There is an issue with mobile, sounds aren't generated, likely due to some browser protections to prevent autoplaying audio. I did ask Claude to try to fix that, it made some changes to the JavaScript, but they didn't work. Maybe I'll dig into that a bit more next time, but for now I'm pretty happy and impressed.
I wasn't sure what to expect going into this experiment. I've read all about "vibe coding", how it was game changing, how it would never replace humans, how the code it generated was awful and needed a ton of clean up, how it cut hours out of development time. After using generative AI to create this web app, I'm a believer. I'm not quite to the point where I'm ready to prophesize for AI, but I'm a lot more confident incorporating it into my day-to-day coding. Of course, if you'd like to play a little tune, check out the piano demo at webpiano.jcurcioconsulting.com.
Comments (2)
To be fair, what you let the AI do here, 70% of it has been done by rails generators since the beginning of rails in 2004:
```
rails generate model Recording
rails generate model Note recording:references note:string ms:integer dur:integer
```
The other 30% are the JavaScript for the piano front-end.
Claude was actually running a lot of `rails generate` commands as it built things out initially, which makes perfect sense. That is the easiest way to accomplish building out the MVC and database. Building the UI and the functionality in JavaScript it where it really shined in this test.
Leave a Comment