DEV Community

darren
darren

Posted on

Why agent-built apps need a local-first state layer

Vibe coding is fast.

You describe a product idea, ask an agent to build it, and a few minutes later you have a working UI.

But I kept running into the same problem:

the first version looks good, but the state architecture breaks quickly.

Every button starts calling the server directly.
Every component owns a slightly different version of the same data.
Optimistic updates become one-off hacks.
Retries are inconsistent.
Reload recovery is unreliable.
And eventually, every click becomes a server round trip.

That is why I built VibeLayer.

VibeLayer is an early open-source local-first state layer for TypeScript apps built by coding agents.

GitHub:
https://github.com/ahamoment-101/VibeLayer

The problem with agent-generated app state

When a coding agent builds a UI, the fastest path is usually this:

async function renameTodo(id: string, title: string) {
await fetch(/api/todos/${id}, {
method: "PATCH",
body: JSON.stringify({ title }),
});

refetchTodos();
}

This works in a demo.

But as the app grows, the pattern spreads everywhere:

Component -> fetch
Component -> fetch
Component -> fetch
Component -> fetch

Soon the app has no clear state boundary.

The UI is tightly coupled to the backend.
Network failures become UI failures.
Local edits are fragile.
The agent has too many places where it can make the wrong architectural decision.

The issue is not that coding agents are bad.

The issue is that we often give them no state architecture to follow.

The rule I wanted

I wanted a simple rule that both humans and agents could understand:

UI reads from local state.
UI writes through named mutations.
Mutations are persisted locally first.
Sync happens later through a backend adapter.

That is the core idea behind VibeLayer.

Instead of letting components call the backend directly, the app follows a local-first flow:

User action
-> named mutation
-> local state update
-> durable mutation queue
-> backend sync

The user gets an instant UI response.

The app keeps a durable record of what needs to sync.

The backend integration is isolated behind a transport adapter.

And most importantly, the coding agent has fewer choices.

What VibeLayer does

VibeLayer gives a TypeScript app three core pieces:

  1. Local source of truth

The UI reads from local state first.

const todos = client.store.query("todo");

The app does not need to wait for the server before the interface can respond.

  1. Named mutations

The UI writes business intent instead of calling random APIs.

await client.mutate("todo.rename", {
id: "todo_1",
title: "Ship the demo",
});

The important difference is that the mutation has a name and a contract.

That makes it easier for both humans and agents to reason about what is allowed to happen.

  1. Durable mutation queue

VibeLayer persists the local state and the mutation queue before network sync.

That means the app can survive reloads, temporary network failures, and backend delays.

The backend catches up later through an adapter.

Local first.
Persist first.
Sync later.
What VibeLayer is not

VibeLayer is not trying to replace a full sync platform.

It is not a database.
It is not a backend service.
It is not a realtime server.
It is not a CRDT editor.
It is not a replacement for every local-first tool.

The goal is narrower:

give agent-built apps a safer default state architecture.

Tools like Replicache, Electric, and PowerSync are serious sync infrastructure.

VibeLayer is smaller and more opinionated.

It is a state layer for the messy middle: AI-generated TypeScript apps that need better local state, better mutation boundaries, and fewer direct server calls from UI components.

Why this matters for coding agents

Humans can sometimes keep architecture in their heads.

Agents are different.

If every component is allowed to call fetch(), the agent will usually choose the fastest local solution.

That works for one screen.

It does not work for a real app.

So the goal is not to make the agent smarter.

The goal is to give it better boundaries.

Do not write to the backend from components.
Do not duplicate server state everywhere.
Do not invent a new optimistic update strategy every time.
Use local state.
Use named mutations.
Use the sync adapter.

This is the kind of rule an agent can follow.

Example use cases

I think this pattern is especially useful for:

  • AI-generated SaaS prototypes
  • dashboards
  • CRUD tools
  • internal tools
  • admin panels
  • offline-tolerant user edits
  • apps built with Claude Code, Cursor, Codex, or similar coding agents

These apps often do not need full collaborative editing.

But they do need a cleaner state boundary than scattered fetch() calls.

Current status

VibeLayer is early.

The current repo includes:

  • core runtime
  • local store
  • named mutations
  • durable mutation queue
  • IndexedDB storage
  • CLI package
  • basic todo example

You can try it locally:

git clone https://github.com/ahamoment-101/VibeLayer.git
cd VibeLayer
npm install
npm run example:todo
What I am looking for

I am not claiming this is production-ready yet.

I am trying to validate whether this problem feels real to other people building with coding agents.

The question I care about most is:

Do agent-built apps need a default local-first state architecture?

If you have built apps with Claude Code, Cursor, Codex, or other coding agents, I would love blunt feedback.

GitHub:
https://github.com/ahamoment-101/VibeLayer

Top comments (0)