From 175ac74c811daf97bebf6a44c0e069c962670a0a Mon Sep 17 00:00:00 2001 From: Fabian Hiller Date: Fri, 21 Aug 2026 17:05:51 -0400 Subject: [PATCH] Add blog post on libraries in the age of coding agents Two alternative drafts of the same post; one should be removed before merge: - form-libraries-in-the-age-of-coding-agents: focused on form libraries - do-you-still-need-libraries: general take with Formisch as the example --- .../do-you-still-need-libraries/index.mdx | 143 ++++++++++++++++++ .../index.mdx | 143 ++++++++++++++++++ 2 files changed, 286 insertions(+) create mode 100644 website/src/routes/blog/(posts)/do-you-still-need-libraries/index.mdx create mode 100644 website/src/routes/blog/(posts)/form-libraries-in-the-age-of-coding-agents/index.mdx diff --git a/website/src/routes/blog/(posts)/do-you-still-need-libraries/index.mdx b/website/src/routes/blog/(posts)/do-you-still-need-libraries/index.mdx new file mode 100644 index 00000000..837af3b1 --- /dev/null +++ b/website/src/routes/blog/(posts)/do-you-still-need-libraries/index.mdx @@ -0,0 +1,143 @@ +--- +cover: Libraries +title: 'Do you still need libraries when agents write the code?' +description: >- + Coding agents can generate working code on demand, so it's fair to ask why + you'd install a dependency at all. We think the answer depends on the + problem, and that the deep problems still belong in libraries. +published: 2026-08-21 +authors: + - fabian-hiller +--- + +import { Link } from '~/components'; + +Ask a coding agent for a login form and you get a working one in a couple of minutes. The same goes for a debounce helper, a modal, or a client for your API. So a question that used to be settled is open again: if a model can write all of this on demand, why install a library at all? + +We build a form library, so we have a stake in the answer. But the question is fair, and the honest answer is not "always use libraries". It depends on the problem. This post is our attempt to draw the line, and to explain why the deep problems still belong in libraries, maybe more than before. + +## The case against libraries + +The argument goes like this: a dependency is code you didn't write, running in your product, on someone else's release schedule. It's a supply chain to audit, breaking changes to migrate through, and an API that never fits your case exactly. If an agent can generate purpose-built code on demand, you can skip all of that. When requirements change, you regenerate instead of migrating. + +Some people take it further. [This post](https://maho.dev/2026/03/ai-is-making-libraries-obsolete/) argues that general-purpose libraries are becoming unnecessary overhead now that models can generate exactly what each project needs. And Drew Breunig went as far as [publishing a library with no code](https://www.dbreunig.com/2026/01/08/a-software-library-with-no-code.html): a spec and a test suite that your agent implements on demand, in whatever language your project is in. + +## What got cheap, and what didn't + +Writing code got cheap. Everything around it didn't. Maybe you review what the agent wrote, in which case the bottleneck just moved, because an agent produces two hundred lines faster than anyone can read them. Or maybe you don't, and as models get better, fewer people will. Then the question is what your trust rests on instead. + +A good library earns that trust outside your repo. The edge cases were found by other people's bug reports, the fixes are reviewed by maintainers who carry the context, and the test suite encodes years of production surprises from thousands of apps. Generated code has none of that behind it. It ran once while the agent watched, and it will run unread in your product until it breaks. There's no upstream either: no changelog, no security advisories, no fix that arrives because another project hit the bug first. When something does break, it breaks only in your codebase, in a state machine nobody has ever read. + +There's a longer-term version of this too. Developers who grow up with agents will understand less of what happens under the hood. That's fine on its own. It's what abstractions are for, and nobody reads the output of their compiler. But it only works when the thing under the hood has a name, documentation, a test suite and a maintainer. You can use a library without understanding its internals, because the understanding lives somewhere. With two hundred generated lines that only your repo has, it lives nowhere. + +## Where generating wins + +To be clear, the reverse is often true. Breunig's own conclusion is that spec-only libraries "likely work best for implement-and-forget utilities", and we'd draw the line in a similar place. A slug function, a date formatter, glue between two APIs, a one-off migration script: these are small, fully specified, and done when they're done. Pulling in a dependency for them was always questionable, and with an agent it's just unnecessary. If the whole problem fits in a prompt, let the agent write it. + +The mistake is applying that logic to problems that only look small. + +## The problems that stay deep + +Some problems accumulate edge cases for as long as software exists. Dates and time zones. Text editing. Drag and drop. Routing. Caching. Forms. They all look simple from the outside, and every one of them is a state machine with years of bug reports built into the good implementations. + +Breunig lists the conditions under which real libraries keep winning: when performance matters, when the edge cases are hard to test, when bugs need a place to be reported and fixed, when the code needs updates over time, and when a community adds value around it. That list is a decent definition of a deep problem. And a deep problem is exactly where a fresh, untested implementation costs you the most, because most of the complexity is not in your prompt, so it's not in the generated code either. It shows up one bug report at a time, in code that only your team has ever run. + +## A worked example: forms + +Forms are the deep problem we know best. They're also where frontend code stops being low-stakes, because the form's output becomes the request your backend receives, a double submit becomes a duplicate order, and input lost to a re-render is a user's work gone. Every field tracks its input, whether it was touched, whether it's dirty, and its current errors. Validation has timing (on submit, on blur, on change after the first error) and it can be async. Focus has to move to the first invalid field. Field arrays have to carry each entry's state through insert, move, swap and remove. None of that is in the prompt "build me a login form", so none of it is in the result. This is the shape of what an agent writes on its own, abbreviated: + +```tsx +import { type ChangeEvent, useState } from 'react'; + +export default function LoginPage() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [emailError, setEmailError] = useState(); + const [passwordError, setPasswordError] = useState(); + const [emailTouched, setEmailTouched] = useState(false); + const [passwordTouched, setPasswordTouched] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + + function validateEmail(value: string) { + if (!value) return 'Email is required.'; + if (!/^\S+@\S+\.\S+$/.test(value)) return 'Email is invalid.'; + } + + function handleEmailChange(event: ChangeEvent) { + setEmail(event.target.value); + if (emailTouched) setEmailError(validateEmail(event.target.value)); + } + + // ...the same for the password, two blur handlers, a submit + // handler that validates everything again, and the JSX that + // wires each piece to the right input +} +``` + +Seven `useState` calls for two fields, a whole-component re-render on every keystroke, and an email regex that exists nowhere else in your codebase. With a form library (this one is Formisch, ours), the same form looks like this: + +```tsx +import { Field, Form, useForm } from '@formisch/react'; +import * as v from 'valibot'; + +const LoginSchema = v.object({ + email: v.pipe(v.string(), v.email()), + password: v.pipe(v.string(), v.minLength(8)), +}); + +export default function LoginPage() { + const loginForm = useForm({ schema: LoginSchema }); + + return ( +
console.log(output)}> + + {(field) => ( +
+ + {field.errors &&
{field.errors[0]}
} +
+ )} +
+ + {(field) => ( +
+ + {field.errors &&
{field.errors[0]}
} +
+ )} +
+ +
+ ); +} +``` + +The difference is not the line count. It's what the second version doesn't contain: the state machine. Touched tracking, validation timing, error routing and the submit lifecycle are not in the diff, because they live in the library, where they're typed, tested with 100% coverage, and the same in every form you ship. Updates are fine-grained too: the form only re-renders the individual part where something actually changed instead of re-rendering the entire form. You review a schema and some markup. The hard part was written, and reviewed, once. + +There's also a quieter effect. Ten hand-rolled forms are ten slightly different state machines, and agents learn conventions from the code they read, so every inconsistent form teaches the next one to be inconsistent too. With a library there's one mental model, visible in every file the agent opens, and it's the consistency that compounds instead. + +## What to look for in a library now + +Agents don't just change whether you use libraries. They change which libraries are worth using. Integration work used to be an argument for the big batteries-included option, because wiring small tools together was tedious. Agents are good at exactly that wiring, so the calculus shifts toward libraries that are precise about what they own and easy to build on. A few properties matter more than they used to. Formisch is the example we know best, so that's the one we'll use. + +**Headless.** An agent is good at markup and glue, so a library that owns the hard state and none of the UI plays to its strength. Formisch manages form state and validation and owns no markup. The agent, or you, builds whatever UI the project needs on top. + +**Declarative.** The less code it takes to express the problem, the less there is to generate and the less there is to review. In Formisch a [Valibot](https://valibot.dev/) schema defines the form once, and both the types and the validation come from it, so they can't drift apart. + +**Modular.** Formisch ships 26 methods (`focus`, `insert`, `move`, `reset`, `validate`, `getDirtyInput` and the rest). Each is an independent function over the form store, lives in its own folder, and is tree-shaken away if you don't import it, which is why bundles start at 2.5 kB. It also means the library is extendable in a way an agent can use: a building block we don't ship can be written as a new method that follows the same pattern, without forking the state management underneath. + +**Readable by agents.** A library is only as useful as what the model knows about it, and training data goes stale. Formisch serves every docs page as Markdown, publishes per-framework `llms.txt` files, runs a free MCP server at `formisch.dev/mcp`, and ships an agent skill you can install with `npx skills add open-circle/agent-skills --skill formisch valibot`. The coding agents guide has the setup, and the agent documentation post has the full story. + +## The division of labor + +So our answer to the question in the title: yes, for the deep problems, and the shape of the library matters more than it did. Let the agent write the utilities, the glue and the UI, because those are specific to your product and cheap to regenerate. Let a library own the state machine underneath, because that part is the same in everyone's product and expensive to get wrong. The agent writes the parts that make your app yours, and the library carries the parts you shouldn't have to think about. + +## Try it + +If you want to see what that looks like in practice, the playground runs a working Formisch form in your browser with nothing installed, and getting it into a project is one command: + +```bash +npm install @formisch/react valibot +``` + +The other seven frameworks work the same way with `@formisch/angular`, `@formisch/preact`, `@formisch/qwik`, `@formisch/react-native`, `@formisch/solid`, `@formisch/svelte` or `@formisch/vue`. If you'd draw the line between agents and libraries somewhere else, we're happy to argue about it on [Discord](https://discord.gg/w5mRTETqzv) or in an [issue](https://github.com/open-circle/formisch/issues/new). And if Formisch is useful to you, a star on [GitHub](https://github.com/open-circle/formisch) helps other people find it. diff --git a/website/src/routes/blog/(posts)/form-libraries-in-the-age-of-coding-agents/index.mdx b/website/src/routes/blog/(posts)/form-libraries-in-the-age-of-coding-agents/index.mdx new file mode 100644 index 00000000..1d82da88 --- /dev/null +++ b/website/src/routes/blog/(posts)/form-libraries-in-the-age-of-coding-agents/index.mdx @@ -0,0 +1,143 @@ +--- +cover: Agents & Libraries +title: 'Do you still need a form library when agents write the code?' +description: >- + Coding agents can generate a working form in seconds, so it's fair to ask why + you'd install a form library at all. We think a good form library is worth + more with agents, not less. This post makes that case. +published: 2026-08-21 +authors: + - fabian-hiller +--- + +import { Link } from '~/components'; + +Ask a coding agent for a login form and you get a working one in a couple of minutes. It renders, it validates, it submits. So the question comes up more and more: if a model can write this on demand, why install a form library at all? + +We build a form library, so you know where this post lands. But it's a fair question, and the premise behind it is true. Writing code got cheap. Reading it, maintaining it and trusting it didn't. That's the case for a form library now, and this post makes it. + +## The case against libraries + +The argument goes like this: a dependency is code you didn't write, running in your product, on someone else's release schedule. It's a supply chain to audit, breaking changes to migrate through, and an API that never fits your case exactly. If an agent can generate purpose-built code on demand, you can skip all of that. When requirements change, you regenerate instead of migrating. + +Some people take it further. [This post](https://maho.dev/2026/03/ai-is-making-libraries-obsolete/) argues that general-purpose libraries are becoming unnecessary overhead now that models can generate exactly what each project needs. And Drew Breunig went as far as [publishing a library with no code](https://www.dbreunig.com/2026/01/08/a-software-library-with-no-code.html): a spec and a test suite that your agent implements on demand, in whatever language your project is in. + +The interesting part of that experiment is where its author lands. In his own words, spec-only libraries "likely work best for implement-and-forget utilities". Maintained libraries keep winning when performance matters, when the edge cases are hard to test, when bugs need a place to be reported and fixed, when the code needs updates over time, and when a community adds value around it. + +Forms check every point on that list. + +## Forms are a worst case for generated code + +A form looks like the easiest thing an agent could write: some inputs, a validation function, a submit handler. That's the happy path, and agents handle the happy path well. + +But a form is a state machine wearing a UI. Every field tracks its input, whether it was touched, whether it's dirty, and its current errors. Validation has timing (on submit, on blur, on change after the first error) and it can be async. Errors have to reach the right element, and focus has to move to the first invalid field. Field arrays are harder still, because insert, move, swap and remove have to carry each entry's state along with its value. On top of that there's reset, initial input from the server, the submit lifecycle, and keeping all of it type-safe. + +Most of this is not in your prompt, so most of it is not in the generated code either. It shows up one bug report at a time. And since every generated form is a fresh implementation, each one is a new, untested state machine. Fixing the third form doesn't fix the fourth. + +Forms also aren't the cosmetic kind of frontend code. The form's output becomes the request your backend receives, a double submit becomes a duplicate order, and input lost to a re-render is a user's work gone. + +## The same form, twice + +This is the shape of what an agent writes without a library, abbreviated, because the real file keeps going: + +```tsx +import { type ChangeEvent, useState } from 'react'; + +export default function LoginPage() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [emailError, setEmailError] = useState(); + const [passwordError, setPasswordError] = useState(); + const [emailTouched, setEmailTouched] = useState(false); + const [passwordTouched, setPasswordTouched] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + + function validateEmail(value: string) { + if (!value) return 'Email is required.'; + if (!/^\S+@\S+\.\S+$/.test(value)) return 'Email is invalid.'; + } + + function handleEmailChange(event: ChangeEvent) { + setEmail(event.target.value); + if (emailTouched) setEmailError(validateEmail(event.target.value)); + } + + // ...the same for the password, two blur handlers, a submit + // handler that validates everything again, and the JSX that + // wires each piece to the right input +} +``` + +Seven `useState` calls for two fields, and the count grows with every field. The whole component re-renders on every keystroke. The email regex is a little dialect of validation that exists nowhere else in your codebase. And none of this is tested beyond the fact that it ran once while the agent watched. + +With Formisch, the same form looks like this: + +```tsx +import { Field, Form, useForm } from '@formisch/react'; +import * as v from 'valibot'; + +const LoginSchema = v.object({ + email: v.pipe(v.string(), v.email()), + password: v.pipe(v.string(), v.minLength(8)), +}); + +export default function LoginPage() { + const loginForm = useForm({ schema: LoginSchema }); + + return ( +
console.log(output)}> + + {(field) => ( +
+ + {field.errors &&
{field.errors[0]}
} +
+ )} +
+ + {(field) => ( +
+ + {field.errors &&
{field.errors[0]}
} +
+ )} +
+ +
+ ); +} +``` + +The difference is not the line count. It's what the second version doesn't contain: the state machine. Touched tracking, validation timing, error routing and the submit lifecycle are not in the diff, because they live in the library, where they are typed, tested with 100% coverage, and the same in every form you ship. The schema is the single source of truth, so the types and the validation can't drift apart. And updates are fine-grained: the form only re-renders the individual part where something actually changed instead of re-rendering the entire form. + +This is worth more with agents, not less. If you review what the agent wrote, the hand-rolled version is the bottleneck, because an agent produces two hundred lines faster than you can read them, while the Formisch version cuts the review down to a schema and some markup. And if you don't review it, which will only get more common as models improve, it matters even more where the state machine lives. You can use a library without understanding its internals, because the understanding lives somewhere: with its docs, its tests and its maintainers. With two hundred generated lines that only your repo has ever run, it lives nowhere. + +## Ten forms later + +There's a second effect that shows up after the first few forms. Ten hand-rolled forms are ten slightly different state machines. One validates on blur, one on change, one tracks touched state and two don't, and each has its own opinion of what an email looks like. That's a maintenance problem, but it's also context: agents learn conventions from the code they read, so every inconsistent form teaches the next one to be inconsistent too. + +A library turns this around. One mental model, applied everywhere, visible in every file the agent opens. With a library it's the consistency that compounds instead. + +## Where Formisch fits + +We didn't design Formisch for coding agents. But a lot of what we decided, including what we left out, works in their favor. + +Formisch is headless. It manages form state and validation and owns no markup, so you, or your agent, build whatever UI the project needs on top. It's schema-based: a [Valibot](https://valibot.dev/) schema defines the form once, and both the types and the validation come from it. And it's one framework-agnostic core with a thin adapter per framework, with reactivity injected at build time. A model learns one set of primitives and applies them across Angular, Preact, Qwik, React, React Native, Solid, Svelte and Vue. The architecture post walks through how that works. + +The part that matters most here is the modular methods API. Formisch ships 26 methods (`focus`, `insert`, `move`, `reset`, `validate`, `getDirtyInput` and the rest). Each is an independent function over the form store, lives in its own folder, and is tree-shaken away if you don't import it. That's why bundles start at 2.5 kB. Modularity also cuts the other way: since every method is a small, standalone function over a documented core, an agent that needs a building block we don't ship can write one that follows the same pattern, without forking the state management underneath. In the v1 announcement a user described Formisch as "the right amount of abstraction", because what it lacks, you can build on top. That's exactly the property a coding agent can use. + +So the division of labor we're arguing for is this: the library owns the state machine, and the agent writes the glue and the custom blocks. + +## Point your agent at the docs + +If your agent writes the Formisch code, it should read the current API instead of guessing from training data. There's an agent skill you can install with `npx skills add open-circle/agent-skills --skill formisch valibot`, per-framework `llms.txt` files, a Markdown version of every docs page, and a free MCP server at `formisch.dev/mcp` that searches, reads and lists the docs scoped to your framework. The coding agents guide has the setup, and the agent documentation post has the full story. + +## Try it + +The playground runs a working form in your browser with nothing installed. When you want it in a project, install the package for your framework alongside Valibot: + +```bash +npm install @formisch/react valibot +``` + +For the other frameworks it's the same command with `@formisch/angular`, `@formisch/preact`, `@formisch/qwik`, `@formisch/react-native`, `@formisch/solid`, `@formisch/svelte` or `@formisch/vue`. Then hand your agent a schema and let it do the rest. If a form behaves in a way you don't expect, [open an issue](https://github.com/open-circle/formisch/issues/new) or tell us on [Discord](https://discord.gg/w5mRTETqzv). And if Formisch is useful to you, a star on [GitHub](https://github.com/open-circle/formisch) helps other people find it.