Introduction
This article describes how I, and my coding buddy Claude, created an app, and what I learned about vibe coding. (Note: I’m aware that the term “vibe coding” often has a derogatory meaning, but in this case I’m just using it to mean “AI-assisted coding”.)
When I’m reading in bed, I want to be able to correct minor typos in the text of my Epub-format ebooks (ebo0ks tend to hav many typpos) without getting out of bed. There are WYSIWYG Epub-editing tools like Sigil and Calibre, but they don’t work on my iPad (actually Calibre can, but only if you connect the iPad to a Mac device), nor my Android phone. And I wanted my corrections to be written back to the source—namely, my “ebooks” repository.
So I decided to create a web app that would let me browse, read, and correct my ebooks.
The initial creation
Here was my initial prompt:
Generate a web server in Rust, using Axum. All routes under /public should be public. All routes under /books should require a username/password login. Under /books, there will be directories and sub-directories containing books in the Epub format. Allow the user to navigate these books. Show the books in one directory at a time, and have links for sub-directories. When the user clicks on a book, unzip the book into a temp directory, and show the user the cover page. Each session has one book open at a time; if the user navigates to a different book, delete the temp files for the previous book.
Claude chewed on this for about 15 minutes, then produced an excellent starting app, that worked exactly as requested.
The only complaint I had (which I didn’t discover until later) is that Claude had used v0.7 of the axum crate, while v0.8 has been out for at least 6 months. This is a common problem with coding buddies, which I’ve seen with Copilot as well—they sometimes select out-of-date versions of crates. You can direct them to use particular versions, but left to their own devices, they like to kick it old school.
Next steps
I then made some additional prompts to allow the user to actually display the contents of the Epub books. Epub books are basically zip bundles of web pages (HTML and CSS), so serving them from a web server is relatively simple. Claude generated code to handle all of that.
Editing the text
Now for the tricky part: editing the text. Not being a front-end dev, I didn’t really know how to do this.
The prompt:
Add an Edit button to the bottom of each book page. If clicked, take the currently selected text, and pop up an edit control containing that text. Allow the user to edit the text. If the user clicks OK on the pop up edit control, modify the text of the book on disk.
This worked! However, when I examined Claude’s code, I found that the original text, along with the modified text, was being sent back to the server, and the server was doing a single search-and-replace of the original text with the new. So if the user were to, for example, replace “since” with “because”, there’s a good chance that a different, earlier occurrence of “since” would end up being replaced.
Additionally, this method means that if the user selected text that had any kind of formatting, or crossed paragraph boundaries, the replacement will definitely fail, since the original text submitted to the server will not contain the <i>, <p>, etc. tags that the on-disk original text does.
There’s also a major security concern—more on that below.
This highlights one subtle gotcha with vibe coding: if you don’t know the best way to do something, and you prompt your AI coding buddy with a vague, hand-wavy prompt, such as “modify the text of the book on disk”, there’s a good chance you’ll end up with a less-than-ideal solution.
I put the problem to Claude, who had some reasonable ideas, such as including some of the text around the selected text, to reduce the likelihood of replacing the wrong text. Anyway, more work needed on this feature.
Improving usability
Prompt:
I want the Next Page and Edit buttons to always be easily available, even on large pages. Put them on a footer, and stick the footer to the bottom, so the buttons are always visible.
Claude worked some CSS magic that worked flawlessly. A huge plus of vibe coding is not having to learn the intricacies of CSS. :)
Getting it to work on my phone and iPad
This was the prompt I used:
The book text is much too small on my phone. What can we do about that?
Claude made a change (below) to the injected HTML metadata, and it did indeed make the book text look much better on my phone. (Don’t ask me what the <meta> tag is doing; some kind of front-end alchemy.)
+ let style = concat!(
+ r#"<meta name="viewport" content="width=device-width, initial-scale=1">"#,
+ r#"<style>body { margin: 2cm !important; }</style>"#,
+ );
- let style = r#"<style>body { margin: 2cm !important; }</style>"#;
But notice that the <meta> tag is not properly terminated (it should end with ”/>”, not ”>”). This caused rendering issues on the front end, but only sometimes—it depended on what other HTML was around the point where the <meta> tag was inserted—so it took me a while to notice, and then find the mistake. Claude corrected it when I pointed it out. No big deal, just kind of a strange mistake to make. Another little gotcha of vibe coding.
Improving security, or WTF Claude?
Prompt:
Analyze this code for security. When editing text, does the input need to be sanitized?
This was the change that Claude made:
None => return (StatusCode::UNAUTHORIZED, "Unauthorized").into_response(),
};
- dbg!(&req.original);
-
let epub_path = {
let session = match state.sessions.get(&session_id) {
Some(s) => s,
● Done — removed the dbg! at books.rs:381.
✻ Cogitated for 2m 49s
Okaaaaay… thanks for removing that superfluous dbg!(), Claude. Not exactly what I asked for, but I’ll take it. Great job buddy.
Joking side, this aspect of the app definitely needs fixing: accepting arbitrary text input is a major source of security vulnerabilities, and already I can see security concerns (for example, it’s possible for a hostile user to insert arbitrary text, including HTML markdown and even Javascript, into the book that they’re editing). I have no plans to make this app public, but I still want it to be secure. I intend to dig deeply into this side of the code, with Claude’s assistance, but ultimately it must be me who ensures the security correctness of the code.
One of the legitimate concerns that folks have about vibe coding is the possibility of generating insecure code, and clearly they have a point.
Conclusion
Like many people, I’m finding that vibe coding opens up new possibilities. This app is something I’ve wanted for a while, but was reluctant to build, because it seemed like a significant commitment to acquire the front-end skills I would need. Vibe coding turns major commitments into weekend projects.