Live editing
When the agent (or you) saves a file in an app, the open app changes in place. Components keep their state, styles swap without a flash, and a chat you were halfway through stays on screen.
#The loop
- A file is saved. The agent's file tools write it into the workspace and commit it to history.
- The engine notices. A file watcher on your apps collects the changed paths for a moment, then tells the shell over the WebSocket which app's source changed.
- The builder rebuilds only what changed. The shell keeps a sandboxed builder frame for each app on screen. It rebuilds the changed source files and writes a small update file into the app's
dist/folder through the engine. - The open app swaps the code. The app's page loads the update and replaces just those modules. React Refresh re-renders the affected components with their state intact.
- The agent checks its work. It asks for the build result and any errors the page threw, and fixes them before telling you it is done.
#What changes where
| You change | What happens |
|---|---|
src/ components | Hot update. Components re-render and keep their state. |
| CSS, including Tailwind classes anywhere in the source | The stylesheet is swapped in place. |
| A module that is not only components (a store, a helper with side effects) | The update climbs to the nearest module that can take it: one that exports only components, or one that calls import.meta.hot.accept(). If none can, the page reloads. |
index.html, package.json, lockfiles, tsconfig.json, .env | Full rebuild and reload. New packages are installed first with app_deps, which the agent runs after editing package.json; builds wait until an install finishes. |
plugins/ | No build. The next request runs the new code (see below). |
data/ | No build. Open pages get a look_changed event and can re-read their data. |
#How the builder works
The builder is the part of a dev server an app needs, running in your browser:
- Every source file becomes its own module. TypeScript and JSX go through esbuild (compiled to WebAssembly), and components get Babel's React Refresh transform so they can be swapped live.
- Everything from
node_modulesis bundled once into a single dependencies file. It is only rebuilt when the packages change, which keeps updates small. - Tailwind v4 runs as the real compiler over the app's files, so any class the agent writes works right away.
- The output is plain files in
dist/: the page, the dependencies, a snapshot of every module and numbered update files. After 20 updates the builder writes a fresh snapshot.
Module resolution follows what apps expect from a normal bundler: relative paths, tsconfig paths, the @ alias for src, and packages by their exports, browser, module and main fields. The full list of supported imports is on Build an app.
#One tab builds at a time
With the same app open in several tabs or devices, one holds the build lease and the others receive its output. If that tab closes or hangs, another takes over. Every open copy loads the same update files, so they all stay in step.
#When a page reloads instead
Hot updates are always on, but some changes cannot be applied in place. The page reloads when:
- a changed module declines hot updates, or nothing above it can accept one;
- a tab was away long enough that the update files it missed were folded into a new snapshot;
- the app was fully rebuilt, for example with the Rebuild button.
Code that uses top-level await cannot be swapped as a module, so those apps are built as one production bundle and reload on each change.
#Errors
A build error covers the app with an overlay showing the file, line and message. Fix the file and it clears on the next build, or close it with Close or Esc. Runtime errors the page throws (uncaught exceptions, unhandled promise rejections and console.error) are collected too.
The agent reads both through its tools:
| Tool | What it does |
|---|---|
app_check | Waits for the current sources to build and returns the errors with file and line, plus runtime errors from the open page. |
app_console | Reads recent console output from the app's open pages, so the agent can add a console.log and see what happened. |
app_rebuild | Forces a full rebuild when an app looks stale. |
app_deps | Installs or removes packages. |
That is why the agent works best with the app open beside it: the open page is what builds the app and reports its errors.
#Plugins update on the next request
Plugins are not built. The engine reads each plugin's plugin.js and manifest.json fresh whenever either file changes, and every call evaluates the module from scratch in the sandbox, so the next request runs the new code. Changes to a plugin's permissions take effect the same way.
#Data changes reach open pages
When anything under an app's data/ folder changes, open copies of the app receive a look_changed event with the paths. The app decides what to do; Roleplay re-reads the affected characters, chats or lorebooks. This is how a character the agent edits shows up in an open chat without a refresh.
const ws = new WebSocket(`${location.protocol === "https:" ? "wss" : "ws"}://${location.host}/v1/ws`)
ws.onmessage = (ev) => {
const frame = JSON.parse(ev.data)
// payload.paths are relative to the app, like "data/notes/today.json"
if (frame.type === "look_changed") reloadNotes(frame.payload.paths)
}
#Every change is in history
Your workspace is a git repository. The agent's file tools commit each write with your name and (via agent), so a live edit you do not like is one request away from being undone: ask the agent to put the file back. App updates and imports are committed to the same history.