March 2025

Web development sucks

A while ago, my son, who was 10 at the time, started telling me about a “click test”, something gamers use to measure how fast one can click a mouse button. I had never heard of it before, but a quick web search shows that there are many “click test” websites that feature a big button surrounded by a bunch of ads. The goal is to click as fast as possible during, say, 5 seconds. Then it says something like “You got a CPS of 7.3!! You are a Cheetah!!”

I looked at this, and my programmer brain thought “That’s not hard! We could make that ourselves! It’s just a button that increments a counter, with a timer and a little math and some conditionals.” I figured it would be a good opportunity for my son and me to do a little programming together. He had used e.g. LightBot, LEGO Mindstorms (based on Scratch), and CodeCombat before, so he was familiar with some programming concepts, but he hadn’t made a webpage before. Long story short, we had to install VS Code (TextEdit wasn’t up to the task) and started doing a crash course in HTML, CSS, and JavaScript. I had to look up how to center a button in CSS myself.

Hours later, we got something working locally, and immediately he wanted to be able to share it with friends. Then we got bogged down with web hosting and domain names. He lost interest. (In hindsight, we should have started with e.g. JSFiddle to solve problems with text editing and web hosting, but that doesn’t solve the fundamental complexity of HTML + CSS + JavaScript.)

The whole experience reminded me why I actively avoided front-end web development for so long in my professional career. The web was never originally designed for software development. Today, just to do simple things, you need to learn 3 different technologies, with 3 different mental models. Even the syntaxes are different:

Oh, and one more:

The spirit of HyperCard

Fast forward a year. I participated in Antler, a VC-backed startup incubator program. met There I met Pontus, and we bonded over some common interests, including the design of recipes and a shared frustration over modern-day programming.

During our many discussions, my mind kept going back to HyperCard, Apple’s interactive media authoring software introduced in 1987. I was 10 at the time myself, and I learned to program in part by inspecting and tweaking other people’s HyperCard stacks — often simply to inject my name or add funny sounds.

The beauty of HyperCard is that it didn’t feel like a programming environment at all. It looked and felt like a drawing application, with familiar tools like the pencil, eraser, and lasso. But you could also add buttons and text fields, allowing you to add elements of interactivity and programmability. HyperCard’s creator, Bill Atkinson, described it as “a software erector set that lets non-programmers put together interactive information”.

But HyperCard was never designed for the Internet. It was built for a single user in front of a single Macintosh computer. I started trying to imagine what something “in the spirit of HyperCard” would look like in 2025.

Teaming up with Pontus

Together, Pontus and I got excited over a few shared beliefs and goals:

We did decide to focus on the niche variously known as “end-user programming”, “small computing”, “casual programming”, “home-cooked software”, “personal software”, and “software for one”. This is in contrast to typical software development frameworks and “no-code” development platforms which cater to professionals working on industrial-strength apps and websites. Another way of putting it is that we wanted to target that gap between going shopping for apps and having to hire someone to build bespoke solutions — or becoming a skilled programmer yourself.

Pontus came up with the project name “Scrappy” and the tagline of enabling people to “make small, scrappy, useful apps just for you and your friends”. Compared to “apps” which are produced somewhere else by someone else, often a for-profit corporation, “scrapps” are…

Concrete use cases

This time, rather than starting by identifying a business problem and then building a technical solution for it, like we did in Antler, we wanted to keep things open-ended. Computers are fundamentally general-purpose machines, after all. I felt like there are enough purpose-built apps and websites in the world already, and we didn’t get excited over building another SaaS.

Honestly, it wasn’t really clear to us where we were going with this project. We just knew that we were no longer being pushed to start a billion-dollar company. And that was kind of refreshing.

But even though we set out to build a Swiss Army knife product as opposed to a specialized tool, we still found it useful to think about concrete use cases. We compiled a list of a few dozen use cases and grouped them into a handful of clusters:

Cluster Example Use Cases Established Solutions
Action-Based Click Test, Car Counter, Attendee Counter, Meme Soundboard, Fart App, Word Game, Typing Tutor, Math Tutor custom apps
Custom Clock Pomodoro Timer, Meeting Cost Clock, Dementia Day Clock custom apps
Custom Calculator Pickle/Bread/Beer/Sushi Calculator, Branding Calculator, Kid Chores/Allowance Calculator Excel, Jotform
Multi-User Canvas Cabin Booking Schedule, Weekly Cleaning Schedule, Beer Planner, Party Seating Arrangement, Meeting Agenda, Group Travel Planner, Virtual Game Board, Family Bulletin Board Miro, Notion, Doodle
Database/Forms Grade Computer, Hourly Worker Clock In/Out, Consulting Time Tracker, Community Member Directory, Flashcards, Landing Page with Form Excel, Notion, Google Forms, Anki
Data Workflow Local Event Tracker Shortcuts, Zapier

Some use cases, like Kahoot-like multi-user quiz, spanned multiple of the above clusters.

Prototyping

We set off using the open-source tldraw SDK as a starting point, since it provided an infinite canvas editor with copy/paste, a persistent local data store with undo/redo, real-time multi-user sync, and data attachments, among many other features, all for free. It took some effort to get started, in part because neither of us were proficient in React nor TypeScript.

We took inspiration from HyperCard and spreadsheets, both of which are commonly held up as gold standards of end-user programming:

We got to the point where we could demonstrate a number of custom Things in our tldraw-based prototype. One could for example, make a Click Test in a matter of minutes, not hours or days! For example, you can drop a button and a number field onto the canvas and write a tiny bit of code like countField.value++, and you’ve already got a button that increments a number! Try that with Excel, Figma, Notion, or HTML/CSS/JavaScript!

However, the underlying implementation of the prototype felt contorted, because tldraw was designed for working with static shape data. More significantly, it became clear that customizing the user interface beyond a certain point would require fully re-writing major components.

Conferences

Around this time, Pontus and I both traveled to Berlin to attend the Causal Islands conference, a small, indie gathering of folks who share an interest in “challenging ideas about what software is, how it’s produced, and how it could be different”. The talks ranged from technical topics like email encryption to more sociopolitical and artistic topics like decentralized systems and computational poetry. I had followed a number of the presenters and attendees on Twitter Bluesky, and it was fun to meet them in person. The conference gave us the feeling that there are at least a few other crazies in the world who believe in a broader view of programming.

A few weeks later, ACM SIGPLAN (Special Interest Group on Programming Languages) held its SPLASH conference in California. We didn’t attend, but there was a very relevant keynote talk by Jonathan Edwards . He talked about “substrates”, namely Smalltalk, Lisp, and (yes!) HyperCard, in contrast to the tall “stack” of software layers used in typical modern-day software engineering projects. Surprisingly, he outlined some of the same exact design goals as our little project, the one that we started months earlier: keeping data persistent, being able to manipulate data and code graphically, unifying the concepts of “programming = using”. Needless to say, we started saying “substrate” a lot more.

Rewriting the prototype

Faced with the prospect of continuing with our tldraw-based prototype, I started getting the itch to start over from scratch. How hard could it be to write a infinite canvas editor completely from the ground up? Armed with subscription to Claude Pro, I gave it a shot. Starting from zero gave me an appreciation of how much functionality we take for granted from similar modern-day GUI applications. I added one dependency, Yjs, to get a persistent local data store, undo/redo, and multi-user sync.

A month or so and a few thousand lines of code later, I had a graphical infinite canvas environment with panning, zooming, selection, dragging, resizing, and copy/paste working—basically the basics of tldraw, not nearly as polished, and still without important pieces of functionality. But in exchange, we gained control of the codebase and full freedom in changing the user interface to suit our needs. We decided to forge ahead with this codebase.

AI AI AI

When we would describe the project to others, invariably people would start talking about AI. Won’t the problem of end-user programming go away with AI? people ask. No, we don’t believe so. The whole reason why programming languages exist is because they’re precise; natural language is not. With today’s LLMs, you end up with pages of AI-generated code, even if it’s hidden away under a shiny UI, and you’re left helpless when something doesn’t work the way you intended.

We believe that going from a prompt in English to pages of React code is way too big of a leap for non-programmers. What’s needed is a medium — a substrate, if you will — in which you configure and compose software building blocks, more like Legos. This would provide the missing middle ground between natural language and programming language, where humans and AI can collaborate on equal footing.

Well, it turns out that our system provides exactly this kind of substrate. All of our software building blocks, Things, can be represented as plain text. A simple text field containing the text “Ahoy, world!” looks like this:

{
    "name": "aplomb",
    "type": "thing:text",
    "x": 296,
    "y": 98,
    "width": 100,
    "height": 24,
    "visible": true,
    "borderColor": "#000000",
    "borderWidth": 2,
    "borderRadius": 0,
    "backgroundColor": "#ffffff",
    "opacity": 100,
    "fontFamily": "sans-serif",
    "fontSize": 16,
    "textColor": "#000000",
    "alignHorizontal": "left",
    "value": "Ahoy, world!",
    "id": "ahoy:e757e02f-9089-4e42-9570-ed5f30830739"
}

Even though it’s in a computer-readable format (JSON), this data is simple enough for a human to look at and understand. Our graphical editor is simply taking the same data and presenting it in a more visual format. When you copy a Thing to the clipboard, you’re copying this JSON representation.

One of the technical problems I had to solve was how to encode all the knowledge about a Thing, i.e. its metadata, in order to present it in the properties inspector and provide a nice user experience. For example, a Button has certain properties, like “height” and “background color” and “id”. Underneath the hood, those are simply stored as numbers and strings. But in the user interface, I wanted to do things like prohibit the user from entering a negative number for the height, summon a graphical picker for choosing the color, and prohibit the user from editing the id altogether. I had to encode this kind of knowledge somehow. It can be done with TypeScript, but it can’t be used at runtime. I also wanted to have human-readable descriptions for each of the properties and Things as a whole, which isn’t a job for TypeScript types.

I started putting all that stuff in code as a static data, but editing properties required touching multiple areas, and that became error prone. Eventually I settled on using JSDoc to store the metadata directly at the point of property definition, and writing a custom script to run at build-time to parse all the JSDoc tags and output a file containing all the metadata of all the Things. In other words, now I had all the knowledge about all the Things in one big JSON file.

As an experiment, I tried feeding that big JSON file to Claude AI, with some custom instructions so that it would respond with Things in JSON format, rather than React code. I tried asking it to produce a few examples, like a simple alarm clock and a calculator. It obediently generated a bunch of Things, which I could then copy and paste into our prototype. And, for the most part, they worked! Importantly, you could still continue to edit and build upon those Things.

This felt like a breakthrough moment. We proved the concept of having a middle-ground substrate where you configure and compose software building blocks, with or without the help of AI. Whatever you create remains understandable and fully customizable. You’re not dependent on interacting with a big black box with magic inside. Ultimately, we want our system to be maximally transparent, understandable, and reliable, which means not having AI at the core of the product.

Compared to HyperCard

There have been a number of successors to HyperCard, both commercial (HyperStudio, SuperCard, LiveCode) and non-commercial (Decker and WildCard, among a number of open-source remakes, most of which are abandonware). Most of these have been quite literal replicas of HyperCard, down to the black-and-white graphics.

We wanted to create something in the spirit of HyperCard, rather than recreate HyperCard. Our prototype system is different from HyperCard and its direct descendants in a few key ways:

The non-success of substrates

It is not an exaggeration to say that HyperCard single-handedly popularized the concept of hypermedia. Years later, it would be credited by the inventors of the World Wide Web, the wiki, and JavaScript. And yet, there hasn’t been anything like HyperCard since its time. Why not?

First, HyperCard died in part because even Apple didn’t know what to do with it. HyperCard was born inside Apple as a free product, then moved out of Apple (along with the rest of Apple’s application software) and turned into a commercial product, and then moved back into Apple (in the QuickTime team) as a free product again, where it languished. Against Apple’s “beleaguered” financial situation, strategic focus on sexy new hardware products and the Internet, and platform transition to Mac OS X, HyperCard succumbed.

In his “A Eulogy for HyperCard”, Tim Oren diagnosed the problem: “HyperCard always had a marketing problem of not being clearly about any one thing.” He explained that HyperCard enabled a “burst of creativity by users” but that “the proliferation of ideas created its own confusion. What was this thing? Programming and user interface design tool? Lightweight database and hypertext document management system? Multimedia authoring environment? Apple never answered that question.” From the very beginning, there was also a fear of “competing with our developers”, of weakening the third-party Macintosh software market that Apple needed to be strong, because ultimately Apple made money selling computers.

In terms of interactive media authoring, the biggest success after HyperCard was arguably Adobe Flash. Like HyperCard, Flash provided a visual environment that enabled non-programmers to create interactive experiences. It had a scripting language called ActionScript, which was influenced by HyperCard’s own scripting language. Flash came to power everything from simple animations to complex games on the web in the late 1990s and 2000s. It worked across browsers and platforms, which is something HyperCard never achieved beyond the Apple ecosystem. But Flash suffered a similar fate, as it was never designed for mobile devices and touchscreens, and web technologies steadily became more capable of delivering rich interactive media.

Since HyperCard and Flash, there have been a number of other projects with similar goals, including Etoys and Lively Kernel from the programming side, and Dynamicland , Tapestry, and Kosmik from the media side. Notably, after a demonstration by Lively Kernel creator Dan Ingalls, an audience member made the following comment (at 51:52):

“We’ve had Smalltalk, we’ve had Squeak for a really long time. These are frequently shown off as like, ‘Look, it’s a great prototype of’… what? What’s the business model or what’s the development model?

Like, you’ve got some buttons, you push them, they go beep. That’s great. But in a deployed application, I certainly don’t want people to be able to arbitrarily interact with or browse the code.

What is it you hope people build? … Saying ‘I hope people build’ is perfectly legitimate in educational environments, or in whiteboarding environments. Do you see this being useful beyond that?

In his answer, Ingalls alluded to the use case of “mashups” and responded to the commenter’s concerns about lack of mechanisms for producing stable software for real-world use, like versioning, by saying that the system was still a “proof of concept”. When asked “What’s it a proof of concept of? What’s the final model? What would that look like?”, he responded with a nebulous, meandering answer, finally stating that “it’s a prototyping environment” but “I’m not going to limit it.”

We can clearly see that it’s hard to market and sell something that fundamentally can be many things to many people. But Apple actually faced this problem before, when it needed to market personal computers in the early 1980s and again when it needed to market smartphones in 2008. With personal computers, it took some time but “killer application” use cases emerged, with gaming, word processing, and spreadsheets for the Apple II and later desktop publishing for the Macintosh. In contrast, with the iPhone, Steve Jobs identified three reasons for why you’d want it in your pocket (“a widescreen iPod with touch controls; a revolutionary mobile phone; and a breakthrough Internet communications device”) right from the beginning.

Meanwhile, with substrates, it’s been decades and no obvious killer apps have emerged. There are plenty of fun and interesting demos, but education, casual gaming, and scrapbooking seem to be the most compelling use cases so far. The one standout success story is Myst, the best-selling PC game prior to The Sims: Myst was actually a heavily pimped-up HyperCard stack. Perhaps, at least for marketing purposes, we need to focus on a few use cases, stealing a page from Steve Jobs’ playbook. That said, even though we’ve compiled our own catalog of use cases (described above), we’re still having trouble imagining how we would use our prototype as a daily driver ourselves.

In his talk, Jonathan Edwards suggests that we focus on the gap between spreadsheets and “stack programming”. This is the same gap that we identified early in our project. Perhaps that’s the ultimate killer app of substrates: to serve the long tail that’s not otherwise being served. HyperCard clearly served the long tail well, although I think that’s partially because people didn’t have real alternatives during that time. Sometimes we forget how life was like in that era. Hardware was slow, operating systems crashed, and compilers cost money. There was no Google, Stack Overflow, or GitHub Copilot; there were books and CD-ROMs of technical documentation, often well-written but expensive and rare. For a normal person in the late 1980s and early 1990s, HyperCard was like an oasis in a desert. Today, there are more, different solutions out there that cater to the long tail.

Finally, perhaps we’re still too attached to the model of desktop GUI object toolkits. Perhaps post-HyperCard systems shouldn’t look so much like HyperCard at all. Perhaps we should look to, say, Minecraft as a contemporary example of a commercially successful substrate. After all, Minecraft is widely approachable for non-programmers, doesn’t feel like a programming environment, is fully open-ended, privileges media over computation, supports adding interactivity and logic, persists data transparently, and runs locally while allowing online real-time multiplayer real-time sharing.

Future directions

With our current prototype, we think that we’ve been successful at proving the ideas and design principles that we started with. But there’s a lot more work to do. The number of Scrapps that can be built in a way that feel “native” is still low. Much of the time, existing knowledge of JavaScript is required. To improve this, we need to continue work in both “lowering the floor” and “raising the ceiling”.

Lowering the floor means making things more friendly and approachable for people with little or no programming experience. For example:

Raising the ceiling mades adding functionality and expressive power, letting users create more things with less effort. For example: