Ecosystems in the era of AI

0

Ask Google or ChatGPT what to build a web app with and the answer is Next.js, and one of the first reasons given is the size of its ecosystem. That was a good reason while people wrote most of the code. Now you and your agent write it together, and most of the typing is the agent's. The ecosystem question is still relevant, but the answer has changed.

Ecosystem size measured search cost

Ecosystem size stood for "someone has already solved my problem and I can find their solution". A large ecosystem meant a Stack Overflow answer for the error, a tutorial for the feature, a package for the widget, and a hiring pool that already knew the conventions. Each of those saved a developer time, and for twenty years the developer was the bottleneck.

Picking a stack by ecosystem size was picking by how fast a person could find an answer. Models learned from their training data that this definition of an ecosystem is what matters when choosing a framework, and they still recommend by it. The irony is that an agent does not search the way a person did, so the thing the model is selecting for matters little to the agent doing the work.

What an agent needs from an ecosystem

An agent still uses parts. It does not need a community of gatekeepers deciding which parts and conventions are allowed. It needs parts it can assemble quickly: they work, they are documented, and their abstractions do not get in the way of the agent making progress. Writing text is cheap for an agent. An abstraction earns its place only when it prevents token churn, the cycle of writing, failing and rewriting, or when it makes a result repeatable, secure or consistent in a way the agent would otherwise have to get right every time. An abstraction the agent gets wrong causes churn instead of preventing it. Give an agent parts that pass that test and it moves mountains.

The largest and most stable body of code in software is the standards. HTML has been HTML for thirty years. CSS, SQL and TypeScript have grown, but a select statement or a <form> written in 2015 is still correct. An agent writes these with almost no variance, because everyone wrote them the same way.

A framework's own layer is a much smaller body of code, and it changes from version to version, so the training data holds several conflicting versions of it. The agent mixes them. An abstraction saves a person from writing the standard by hand. An agent writes the standard by hand faster than it recalls the abstraction correctly, and when it recalls the abstraction wrong, the mistake is in the code the agent understands least.

Elements HTML is standard HTML with a few lightweight syntax extensions, type checking, and a runtime that takes care of the rest. A database migration is a SQL file. A stylesheet is CSS. An RPC function is a function with a JSDoc comment. Where Elements adds an abstraction, it has to earn its place: session, sql, LiveTable, and a few more. An agent learns those easily, because each is small and documented. The rest is the languages already in the agent's training corpus.

The rule for a package

For a person, reusing a package is cheap: install it, read the README, call it. For an agent, reusing a package means finding it, judging it, installing it, learning its API from little training data, and configuring it to fit the app. Sometimes that is less work than building the feature from the primitives. Sometimes it is more.

That is the rule for an Elements package. We build one when it saves you and your agent more in tokens and time than it would cost to build and maintain the same thing from scratch, or when it makes something repeatable, secure or consistent that an agent would otherwise have to get right every time. A package that passes is installed and instantly affects the app. A major benefit of the Elements architecture is that a package can span the database, the server and the page: it can carry a schema, server functions, authorization, templates and CSS together, written to fit each other, because the system that runs all of them is one system.

Example: the @elements (opens in a new tab)/ui package

We spent months building the UI building blocks any app can start from and extend. The package holds the common templates, charts included, the behaviors underneath them, and a complete style system with tokens, the type scale, layout and the classes for buttons, forms, cards and pills. We think it exceeds the React ecosystem. It is designed to work together, built from regular Elements templates, with an easily extensible style system written in standard CSS, and it comes with every new Elements app.

A template is imported from the package and used like any other HTML tag:

import { Dialog, DialogFooter, DialogClose } from "@elements/ui/dialog";
import { Button } from "@elements/ui/button";

<Dialog title="Edit profile" description="Make changes, then save.">
  <Button slot="trigger">Edit profile</Button>
  <input value={form.name}>
  <DialogFooter>
    <DialogClose><Button>Cancel</Button></DialogClose>
    <Button variant="primary" onclick={() => save(form)}>Save</Button>
  </DialogFooter>
</Dialog>

It renders on the server, so the page arrives finished. It imports its own CSS, so importing it styles it. Its attributes are typed, so a variant that does not exist is a build error, and the editor completes the ones that do. The set covers forms (Input, Field, Select, Combobox, DatePicker, FileUpload, ColorPicker), overlays (Dialog, Sheet, Drawer, Popover, DropdownMenu, Command, Toast), display (Card, Badge, Tabs, Accordion, Tree, Avatar), layout (Sidebar, Resizable, Carousel), data (DataTable, VirtualList, six kinds of chart, Kanban), chat (Message, MessageList, PromptInput), and two editors, RichText and CodeEditor. Twelve themes, and one import restyles all of them.

The templates are built on the browser's own elements where the browser has one. A Checkbox is a native checkbox, a Switch is a native checkbox with the switch role, a TimePicker is the browser's time field, so keyboard, touch and accessibility come from the browser vendors. Where the browser has no element, the templates are built from a small set of behaviors, focus scope, roving focus, dismiss layers, floating panels, and those behaviors are exported for templates of your own. Anything beyond the package, your agent builds from the same templates and behaviors, in the same language as the rest of the page.

Elements works with npm

Anything an Elements package does not provide, a third party npm package can, and the Elements package installer works with those. Elements does not support React. In its place is the UI system described above, with the templates an app could need, written in HTML syntax instead of a made up JSX syntax, on a far more performant UI runtime.

The build loop decides speed

An agent works in a loop. It edits, builds, reads the errors and edits again, hundreds of times in a session. The sooner its tools report the errors, and the more of them they report in one place, the sooner the agent finishes.

On the Next.js stack that loop is next dev, a type checker, a test runner and a migration tool, each a separate process with its own output. In Elements, a project server runs while you work and holds the build in memory. A save builds in 1 to 5 milliseconds. Tests run incrementally: a save reruns only the tests the change affects, and every other test keeps its stored result. When the agent asks for the build state, it gets the errors from type checking, tests, migrations and program analysis in one report, in about 20 microseconds, because the work is already done.

Objections

Models know Next.js better than a newer framework. They do, and a framework's own layer is where that matters. Most of an Elements app is HTML, CSS, SQL and TypeScript. For the parts specific to Elements, every one is documented, and the build tells the agent when it got something wrong.

Tested libraries handle edge cases an agent will miss. They do, and that is the case for a package that earns its place. The UI templates are built on the browser's own elements where the browser has one, and on behaviors written once and shared, so the keyboard, focus and accessibility work is done once and not regenerated in each app. Plumbing where a mistake is a security problem, such as sessions and sign-in, is written once in the framework and tested, and Elements comes with it.

People still have to review the code. Often they do not: the build has already checked the types, run the tests, applied the migrations and analyzed the program before anyone reads a line. When a person does read it, they are reading HTML, SQL and TypeScript. That is easier to read than JSX spaghetti.

A business app has to be handed to other developers. Every web developer knows HTML, CSS, SQL and TypeScript. A developer who joins an Elements project reads the app in languages they already know, and the documentation covers the rest. A team that hires only people who know one framework hires from a smaller pool.

Choosing a stack for you and your agent

  1. Abstractions that prevent token churn, or buy repeatability, security or consistency, and none that only rename the standard. Standard languages over framework dialects: HTML, CSS, SQL and TypeScript, written as themselves.
  2. One way to do each thing, so the agent does not have to choose between conventions.
  3. The plumbing an app needs, included: sign-in, payments, live data, jobs and email.
  4. The UI an app needs, included, and any npm package with one command.
  5. A build that reports its errors in one place, from one command, with the answer ready when the agent asks.
  6. Documentation for every part.
  7. Runs on a machine you control.

Conclusion

Ecosystem size measured how fast a person could find someone else's solution. You and your agent write the solution now, and the agent writes the standards most accurately. You need parts that work, are documented, and have abstractions that do not get in the way of the agent, with no gatekeepers between you and them. Elements is optimized for humans and their agents, not just framework insiders. It comes with the plumbing and the UI system an app needs, builds a package wherever one saves more than it costs, and works with npm for the rest. For anything new, Elements is the obvious choice.

Add a comment