KOVO

The web framework that hands your agent the fix — database to DOM.

Kovo is built from the ground up so AI coding agents get a precise error and know exactly what to fix. And it's delightful for your users: pages are real HTML, interactive at first paint.

✓ No JS required on load
$ pnpm create kovo my-app
Start the tutorial
one rename, every layer caughtkovo check
$ git diff db/schema.ts
-  price: integer('price'),
+  priceCents: integer('price_cents'),

$ kovo check

 server/queries/product.ts:14 QUERY
  projection reads dropped column price
  → select priceCents, or alias: price: products.priceCents

 src/product-card.tsx:13 BINDING
  data-bind "product.price" has no source in the query
  → bind product.priceCents — format in a derive

 src/cart/checkout.tsx:31 FORM
  field price is not in the cart/add mutation schema

 src/routes/sale.ts:8 ROUTE
  redirect builds /sale?max=price against a dropped param

4 errors · 4 files · each with its fix0 guesses

How it works

Build-time checks from backend to frontend

Every layer below is checked against the next at build time. Don't take our word for it — break something:

Database

products = table({
  details: nullable(json),
  price: integer()
})

Server query

query('product', {
  reads: [product],
  load: …  → shape
})

Client data

<script fw-query="product">
{"price": 1299}

Rendered UI

<h2 data-bind=
  "product.price">
kovo check — caught at the database → query junction
✗ FW402query 'product' reads a column that no longer exists

  server/queries/product.ts:14 — select(products.price)
  → the column is now priceCents — select it, or alias: price: products.priceCents
  every query is compiled against the live schema, so a rename can't reach production
kovo check — caught at the query → client junction
✗ FW223the page depends on data the query no longer ships

  src/product-card.tsx:13 — data-bind="product.price"
  → the projection now ships priceCents — update the binding, or restore the field
  bindings are typed against the query's emitted shape, not against hope
kovo check — caught at the client → UI junction
✗ FW227binding path 'product.pricee' does not exist

  src/product-card.tsx:13 — data-bind="product.pricee"
  → did you mean product.price?
  the DOM is part of the type system: a typo in an attribute is a build error

this demo is plain HTML and CSS — radio buttons and :has(). that's the point.L0 on the interaction ladder

For agents

Errors worth reading

Every diagnostic teaches: the line, the reason, the fixes — so the loop is edit → check → fixed, not edit → deploy → bug report. The behavior graph is queryable too: kovo explain mutation cart/add answers "what refreshes?" with diffable output for CI.

$ kovo check
13 │  render: () => <h2>{product.details.name}</h2>

✗ FW227product.details can be null here
  fix 1  {product.details?.name}
  fix 2  make the projection non-null in the query

✓ caught in 0.4s — before anything ran

For users

No uncanny valley

No hydration means no window where the page looks ready but isn't. A button works the moment it paints.

Typical SPAinteractive at 3.2s

0ms paint⚠ looks ready, ignores clicks3.2s

Kovointeractive at first paint

0ms paint✓ every click works — tiny loader, handlers on demand

With JavaScript off, every page still renders and every form still posts. This site runs on Kovo — try it.

all build gates green loader 1,913 B gzip — measured this build TTI = first paint JS-off: every page fixpoint compile see how it's verified →