Aug. 13, 2024

Claude AI has a mode where it can generate something called "artifacts". One of the things you can do with this is generate simple single page web applications. It generates the web app and then mounts it in an iframe so you can quickly test it and give feedback. This gives you a fast iterative process using the AI to refine the web app incrementally.

This is pretty cool but I would much prefer a web app written in ClojureScript with Reagent forms instead of JavaScript or React. ClojureScript is more concise and I find it leads to less bugs and is faster to work with.

Note: this post is available as a YouTube video.

Claude AI app artifact generation

There is a version of ClojureScript that runs entirely in the browser called Scittle, created by Michiel Borkent who also created the babashka suite of Clojure utilities. Unfortunately Claude can only use libraries that are available on cdnjs when generating web apps, and Scittle was not available. So I raised a PR with cdnjs and it's now available to use in Claude generated artifacts.

What all this means is you can now prompt Claude to generate small ClojureScript apps and it will generate clean ClojureScript code. I've set up a basic repository with a ClojureScript + Reagent prompt and example HTML file you can give to Claude to get it started.

The best way to use this repository is to create a new project in Claude, and copy the prompt and the example in as the default project prompt. A project is Claude's way of letting you use the same prompt multiple times.

As a simple example, you can generate a basic compound interest calculator using the prompt from the repo and adding the following text to the end:

Please generate a simple compound interest calculator.

You can test the resulting app and see the code here. The code it produces is a single page HTML app with inline ClojureScript and I've shared the cljs part here:

(require
  '[reagent.core :as r]
  '[reagent.dom :as rdom])

(def state (r/atom {:principal 1000
                    :rate 5
                    :years 10}))

(defn calculate-compound-interest [principal rate years]
  (for [year (range 1 (inc years))]
    {:year year
     :balance (* principal (Math/pow (+ 1 (/ rate 100)) year))}))

(defn input-field [label key type]
  [:div
   [:label label]
   [:input {:type type
        :value (get @state key)
        :on-change #(swap! state assoc key (js/parseFloat (.. % -target -value)))}]])

(defn result-table []
  (let [{:keys [principal rate years]} @state
    results (calculate-compound-interest principal rate years)]
    [:table
     [:thead
      [:tr
       [:th "Year"]
       [:th "Balance"]]]
     [:tbody
      (for [{:keys [year balance]} results]
    ^{:key year}
    [:tr
     [:td year]
     [:td (str "$" (.toFixed balance 2))]])]]))

(defn compound-interest-calculator []
  [:div
   [:h1 "Compound Interest Calculator"]
   [input-field "Initial Principal ($): " :principal "number"]
   [input-field "Annual Interest Rate (%): " :rate "number"]
   [input-field "Investment Duration (years): " :years "number"]
   [result-table]])

(rdom/render [compound-interest-calculator] (.getElementById js/document "app"))

I hope this will be useful to people who want to build with LLMs and ClojureScript. Enjoy!

Need software development advice? Book a call with me.