Vibe Coding with Claude

🤖 Blog entry author: Claude of course :)

What is Vibe Coding?

Vibe coding is programming at the level of intent. You describe what you want — not how to build it — and a capable AI turns that intent into working software. There are no scaffolding commands, no boilerplate ceremonies, no context-switching into documentation tabs. You stay in the problem domain and the AI stays in the implementation domain.

The phrase was coined by Andrej Karpathy, but the experience it names is hard to appreciate until you try it on a real project. This article documents exactly that: building the interactive SecChats API Demo page from scratch, one natural-language prompt at a time, using Claude as the sole implementer.

The demo page lets a visitor register an account, send a friend request, wait for an acceptance response, and then exchange end-to-end encrypted messages — all wired to the live SecChats JSON API, all inside a single polished HTML file. Here is how it got built.

The Starting Point

The only material given to Claude upfront was the SecChats API documentation — eleven endpoints covering registration, friend requests, messaging, and group chat — and a pointer to the existing site stylesheet. No wireframes. No component sketches. No pseudocode.

Prompt "Using the JSON API create a demo html page called demo.html using the same site style that: 1. Has a button 'Register' that calls asynchronously /register and on success shows the userId and authToken. 2. On successfully receiving the data shows a text field called 'Friend ID' and a new button 'Connect'."
Claude's internal reasoning The user has given me an API spec and a visual style system. My job is to translate both into a single self-contained page. I need to decide on an information architecture: a vertically stacked series of "steps" that appear progressively as the user completes each stage. That way the page tells a story rather than presenting a wall of UI at once. I'll wire each step to the appropriate API call, handle errors and loading states, and carry credentials (userId, authToken) in JavaScript closures across the steps rather than polluting global scope unnecessarily.

What Claude produced in a single response was a fully styled, multi-step demo card with async fetch calls, disabled-button loading states, error handling for 429 rate-limits, and smooth reveal animations for each result block — all consistent with the existing glassmorphism design language of the site.

Step 1 — Register

The first step lets the visitor register an ephemeral identity. Clicking Register fires a POST /register call. On success, the response userId and authToken surface in the UI and the next step is revealed.

Step 1 — Registration complete, userId and authToken displayed
Step 1 complete: a fresh identity registered in under 100 ms.

Two quick refinements followed. First, the Friend ID field was pre-filled with the new userId so testers running a self-send scenario didn't have to copy-paste. Second, a superfluous cryptoData field was removed from the success display after a single word of direction:

Prompt "Set the default value of the Friend ID field to be the userId for now."
Prompt "Don't display cryptoData."

One sentence each. No further specification needed. Claude located the exact lines, understood the intent, and made surgical edits.

Step 2 — Send a Friend Request

Once registered, the visitor enters a target userId and presses Connect. This fires a POST /demo_connect carrying a FriendRequest payload — including a cryptographically random 32-byte cryptoData value generated in the browser via crypto.getRandomValues().

Step 2 — Friend request sent successfully
Step 2: a friend request dispatched. The Friend ID field is read-only once populated.
Prompt "Make the friend ID read only."
Claude's internal reasoning Making the field readonly prevents accidental edits mid-flow without disabling it entirely (disabled fields don't submit their values, which would break the downstream logic). A single HTML attribute change — readonly on the input — is the correct minimal fix. I also want to keep the visual distinction subtle: readonly inputs should still look consistent with the rest of the form, so I won't apply any special styling.

Step 3 — Polling for a Response

After the friend request is sent, the client needs to wait for the recipient to accept. The SecChats API is not push-based — acceptance arrives as a friendResponses entry in the POST /receive response. Claude wired up a setInterval poller that fires every three seconds, parses the notification payload, stops itself on receipt, and displays the response fields.

Prompt "After the Connect button is pressed, async wait for data at /receive for a JSON Notifications object. Print the received friend response."
Claude's internal reasoning Long-polling or WebSockets would be cleaner, but the API exposes a plain /receive endpoint. The right approach is a timed poll — fire immediately, then every 3 seconds. I need to track a lastMessageTimestamp cursor so repeated polls don't re-display old data. The poller should stop itself once a response arrives, and I should also propagate the timestamp forward so the subsequent message poller starts from the right position.
Step 3 — Friend response received and displayed
Step 3: the friend response arrives. Polling stops. The chat window opens automatically.

Scroll behaviour was tuned in two rounds. The first implementation scrolled to the top of the page. One correction — "Scroll down." — flipped it to window.scrollTo({ top: document.body.scrollHeight }), pulling the newly revealed content into view. The same treatment was applied to the register step with a matching single-sentence prompt.

Step 4 — The Chat Window

The first iteration of the messaging UI was a plain input box and a scrolling list of messages — functional but disconnected from the visual language of the rest of the site. The site already had a floating chat widget used elsewhere as a support bot. One prompt repurposed it entirely:

Prompt "Now replace the sending and receiving of messages from steps 4 and 5 with the chat window that is currently showing only on clicking the down-right chat icon. Instead of 'SecBot Support' use 'Friend ID' + Friend ID."
Claude's internal reasoning The user wants to reuse the existing chat widget rather than maintain two separate messaging UIs. That means: (1) gutting the old step-4/step-5 HTML and JavaScript, (2) moving the floating widget into the document flow as Step 4, (3) rewiring its header to show the actual friend's ID, (4) replacing the placeholder bot response with real /demo_postmsg sends, and (5) wiring incoming messages to the same /receive poller. The widget auto-opens when a friend response is received. This is a non-trivial refactor but the intent is unambiguous — I should do it in one pass without asking for clarification.

The result: a polished floating chat panel that appears in line below the three setup steps, pre-labelled with the connected friend's ID, ready to send and receive messages.

Step 4 — Chat window open with sent and received messages
Step 4: the chat window, with outgoing messages in green and incoming in glass-grey.

Sent and received messages were then labelled explicitly with another one-liner, and the polling strategy was tightened so the client stops polling on receipt and resumes only after the user sends a new message — avoiding unnecessary server load while the conversation is idle.

Prompt "Add the strings 'Sent: ' and 'Received: ' in front of the sent and the received messages respectively."
Prompt "On receiving the message stop polling. Start polling again on message sent."

Refinements: The Back-and-Forth

A recurring theme in vibe coding sessions is that the first output is rarely the last. But the refinement loop is radically compressed compared to traditional development. Here is the complete set of prompts that shaped the final demo, start to finish:

  1. Create demo.html with Register → Connect flow
  2. Pre-fill Friend ID with userId
  3. Remove cryptoData from success display
  4. Poll /receive after Connect; display friend response
  5. Scroll down on receiving data
  6. Scroll down on register as well
  7. Make the Friend ID field read-only
  8. Add Send Message input and poll for incoming messages
  9. Make the send call async (fire-and-forget)
  10. Replace steps 4 & 5 with the floating chat widget
  11. Prefix messages with "Sent: " / "Received: "
  12. Move chat widget into the vertical step flow
  13. Stop polling on receive; resume on send

Thirteen prompts. Most of them a single sentence. The entire demo — HTML, CSS, and JavaScript — was built and refined in one sitting without writing a single line of code by hand.

The friction that usually separates a good idea from a working prototype has essentially vanished.

What Vibe Coding Feels Like

There is a qualitative shift that happens when the cognitive overhead of implementation drops to near zero. You stop thinking in terms of syntax and APIs and start thinking purely in terms of product behaviour. Every prompt is a design decision. Every response is a deployable artifact.

Claude is not just a code generator. It reasons about tradeoffs. When asked to make the send call asynchronous, it understood the user experience rationale — the UI should feel instant — and chose fire-and-forget semantics with a .then() callback to start polling after delivery, rather than simply wrapping the call in an async function that still blocks the UI. When asked to reuse the chat widget, it identified all five interconnected changes needed and executed them in one pass.

What emerges from a vibe coding session is not a pile of generated snippets stitched together by a human. It is coherent, idiomatic code that reflects a consistent set of design decisions — because the same model held the full context of every previous decision throughout the session.

The SecChats demo was built this way. You can try it live at demo.html.


All prompts in this article are verbatim from the session that produced the demo. No post-hoc editing was applied to the code.