Agents have to prove their work
An agent building your app can no longer call a screen finished on a hunch. It has to show a real query returning real data, and evidence that its branch actually merged, and a readiness pass reconciles the app that exists against the app that was ordered before any of it deploys. That was the week’s theme. Around it landed a public API for SootSim, an app build where every screen is worked on in parallel from the first minute, a redesigned gallery, and a sync layer that stopped sending a complete snapshot on every pull.
Platform
Apps that come out of a build actually finished
Apps built by agents come out closer to done on the first pass. Before, an agent could mark a screen complete on a hunch, and the whole app could be declared ready the moment the last card was checked off, even with a route missing, a button wired to nothing, or starter scaffolding still sitting in the app. Now an agent has to show its work, meaning a real query returning real data from the app’s own database, plus evidence that its branch actually merged rather than a self-report that it did. Nothing deploys until a readiness pass compares the app that actually exists against the app that was supposed to be built and names each specific gap it finds.
Quality coverage comes from durable records of what was really tested, so a build can no longer reach the end with the feature you asked for never exercised. Merged work with no test record gets surfaced and chased once the board drains. Verdicts resolve across every completed pass instead of only the newest, so a late narrow pass cannot erase a defect it never examined, while a genuine retest still clears a bug that was fixed. Animation claims need proof behind them, but only for the state changes the quality check itself called out, which keeps the requirement honest instead of turning it into busywork. Preview inspection also stopped reporting invisible overlays as real elements, which had been sending agents chasing things no user could ever see.
Generated apps ship their real feature, and a real landing screen
Ask for an app built around an interactive letter-grid puzzle and you used to get the puzzle as a hardcoded picture of a puzzle. The rule requiring the real thing only recognised centerpieces that render something, like charts, maps and geometry, so anything drawn or played slipped through as decoration. Planning now recognises drawn and interactive mechanics too, has to name the distinctive behaviors in the task that owns them, and is flatly forbidden from reducing a signature mechanic to a visual mock. A batch of guidance corrections came straight out of real failures. Vector shapes read live theme colors instead of silently ignoring them. Map layers wait for the map style to finish loading rather than rendering blank. Screens that save data get the real data guidance instead of guessing. A data field sharing its name with a reserved database word is off-limits, after one such field took an entire app down on launch. And sample images ship with the app rather than being pulled from a third-party host that was blocking them.
Landing screens got the same treatment. The design guidance described only a card pattern, so generated marketing pages put their hero photo in a small inset box instead of filling the screen. Agents now know a full-bleed hero recipe: a controlled-opacity photo, a theme-aware blend, a scrim, an edge fade, and padding only on the copy. They also have to prove the imagery actually rendered rather than only confirming the files exist. Each starting point declares its own launch surfaces, since a web landing and a native sign-in are two compositions of one brand. An app splits the two, a game uses one shared title screen, a travel app sends both straight to login.
Builds lose less work, and simple tasks merge themselves
A broad correctness pass went through the multi-agent build flow. Agents sync their branch before they start, so nobody builds on top of code that has already moved. Task branches take their snapshot after the merge into main lands, so a finished task’s branch reflects what really shipped. Shared data contracts require an explicit owner, which stops two agents both deciding to rewrite the schema. An agent stops rather than starts when its project knowledge fails to load. Starting a support agent is all-or-nothing, so a canceled start no longer leaves an empty thread behind. Running agents survive a change of ownership of the runtime, and they release their claim on work when their driver stops instead of sitting on it. Asking for a change to an app that had already been built used to park everything, with the agents replying that there was no branch to work on and simply stopping; a follow-up request now picks up work the same way a first one does.
The other half is speed. Tasks graded easiest merge the moment they are submitted and their checks pass, with no separate approval round trip, and an independent reviewer is brought in only for the hardest tier of task, where it earns its cost. Reviewer preview environments stay warm between tasks instead of being torn down and rebuilt each time.
Following a build reads like a team at work
The lead agent is called Manager everywhere you see it, including the review notes it posts in chat, which used to show up attributed to your own account. The floor shows only agents that are actually working, so finished reviewers and idle agents stop piling up, and every agent stands on the floor instead of floating against the back wall. Opening a task gives you a wide two-column card, metadata on one side and a genuinely readable description on the other, collapsing to a single column on a narrow window. It no longer grabs your cursor into the title, where a stray keystroke would overwrite it. Reassigning a task by hand is gone now that scheduling owns it; unassigning, which is the safe direction, stays.
Build activity runs inline with the conversation instead of in a separate rail beside it: your message, the agents picking it up, a card for each finished piece, the reply, one continuous thread. The transcript got a repair pass at the same time. Message rows were being measured too short, so blocks of text painted on top of each other, and tool status chips were sized off the wrong scale and came out about four times too large. Author identity moved to its own row above the content, the redundant preamble on code cards is gone, and consecutive file edits fold into a single horizontal card.
Drop images into chat and the agent actually looks at them
Attach an image to a chat message and it becomes real input rather than something you have to describe in words. The agent writes a description of each attachment up front so it always knows what it is holding, and when the description is not enough it can go back and ask about a specific region of the actual pixels. Screenshots, design references, a photo of a whiteboard: all of them work as a starting point for what gets built. Image rendering is kept out of the way of whatever else the chat is doing, so a large attachment does not stall the rest of the conversation.
The mobile app: an app grid, glass chrome, and a chat that stops jumping
The projects list is a two-column app grid now, with generated icons and colorful fallbacks, sitting evenly instead of packing to the left. The floating header traded its flat divider line for a curved glass glint, and every floating surface shares one glass treatment, with the frosted header pills back on side pages where they had been mounting invisible and permanently disabling the material. Chat stays above the keyboard through focus and send, the assistant avatar is gone from message rows for a cleaner thread, and the split seam on the build screen no longer shows a stark black band in light mode. On the phone, the thinking dots used to live inside the streaming reply, so the row grew and shrank and shoved the whole conversation around every time a message settled. There is now one fixed line at the bottom of the thread that pulses while the agent works, then collapses to a single resting dot with how long the last turn took.
The Plan tab leads with a Documents lane: browse the project’s plan documents, drill into folders, open any of them in a floating card lifted over the chat. Agents can hand you a particular document or a particular task in chat instead of only the whole spec, and the card they send shows that task’s real title and where it currently sits, so tapping it lands you on the exact row or page.
Dialogs turn into bottom sheets on a phone
Every shared dialog adapts to a bottom sheet at phone widths on the web and stays a centered dialog on a desktop. Docs, keyboard shortcuts, browsing shared apps, branch review and the comparison lightbox all sit on that one surface, so they drag, dismiss, and keep their close button in the same place no matter which one you opened. Sheets keep proper focus handling, so the keyboard and the escape key behave the way they do on a desktop, and comparison tiles stack correctly instead of squashing side by side. The old parallel dialog implementation was deleted rather than left sitting next to the new one.
Beta invites became a real guided setup
Redeeming a beta invite used to be a single button that either worked or did not. It is a proper flow now: the code is checked for validity before you are asked to sign in, you choose which account the beta access lands on, your GitHub identity gets linked to that account, and you are walked through what is left to set up. Plan and seat handling was reworked to match, so an invite puts you on the right plan with the right number of team seats instead of leaving you to sort it out afterwards. The invite screen itself was redesigned along the way. It leads with the choice that matters, drops the boxy card borders, uses the same sign-in form as everywhere else, and its account picker stays quick even for accounts with a lot of history behind them.
Drive SootSim from your own scripts
SootSim now takes instructions from your own scripts and CI, instead of only from the command line on your machine. Create an API key in Billing, give it an expiration date, and grant it only the scopes an integration actually needs. With that key you can spin up hosted previews, publish a bundle you built yourself, and record test-run results, and each one comes back as a page you can share with anyone. The command-line tool can also just reuse the login you already have, so simple local use needs no key at all.
Key management shows what each key is for and when it was last used, so a stale credential is easy to spot and revoke. Full reference docs cover authentication, previews, builds, test runs, error shapes, and rate limits, which run at 120 requests a minute per credential. Spending caps stay tied to your account rather than to a key, so handing out more keys never quietly raises what you can be charged.
The simulated iPhone got smoother
Swiping, switching apps, and scrolling a photo gallery in the simulated iPhone all got noticeably smoother. Scroll input mirrors correctly through the phone shell instead of drifting. Animated screens retire cleanly when they are done rather than piling up behind the ones you can see, and images are released when they scroll off screen, so a long session stops slowly growing in memory. The phone’s rounded corners also stay proportional at any zoom level in Safari, where they used to go visibly wrong at anything other than 100%.
Previews stay on their own branch, and start faster
When several agents are working on different branches at once, a preview could latch onto the wrong branch’s environment and show you work from a different task. Every preview, web and native, is bound to its exact branch now, and so is the live data-refresh connection behind it. Two previews open side by side also used to clobber each other’s signed-in user and each other’s data, since they shared one browser storage scope and the last writer won. Each gets its own isolated storage now, so you can compare two versions of an app without one quietly overwriting the other’s state.
Three things were quietly breaking previews outright. Every authentication call inside a project preview returned an error, because part of the auth code was being served in the wrong module format. Seeded starter data containing true/false or structured values threw and rolled back the entire seed, which is why freshly generated apps could come up completely empty. And stylesheets that come from installed packages now load before the app runs, so a preview no longer flashes unstyled. Startup is quicker on top of that: preview shells warm up ahead of the runtime, mobile previews prerender, building your project overlaps with the runtime booting instead of queueing behind it, and while it works you see real staged progress instead of a bare spinner.
A redesigned gallery and home overview
Browsing templates and shared apps used to mean scrolling past a wall of per-listing color tints, blue and green and orange heroes competing with the screenshots inside them. The tints are gone. Cards are neutral, lightly raised surfaces, and the phone floats above its card and overhangs the right edge instead of sitting boxed inside a colored block. Titles moved off an extra-wide display face onto a narrower bold cut so long app names fit on one line, and the detail pages got the same treatment.
The overview part of the home page was rebuilt alongside it. The comparison cards got a tighter showcase treatment, and the decorative pillar illustrations were replaced with small stat graphics that show the actual numbers. Typography was corrected across the header, nav, hero, sections, and footer; the deployment section shows real platform logos instead of generic marks; sections keep their natural height on phones instead of being stretched by the desktop layout; and the copy through the overview was rewritten to match.
Tech
Sync pulls became incremental
Zero’s HTTP sync path used to answer every pull with a complete authorized snapshot of everything the client could see. Now it sends an incremental rowsPatch instead: each changed row is point-read through the current visibility predicate and becomes a put when it is visible to you or a del when it drops out of view. Visibility follows the real access model rather than a parallel approximation of it. A project belongs to an account and is shared through accountResourceGrant, with accountMember controlling account-wide scope, so a grant, a revoke, or a membership change resets only the users actually affected. Their next pull returns a single clear followed by a fresh snapshot, then resumes incrementally from there.
Pull, push, global invalidation, pruning, replay and mount init all serialize through one queued application database and one authoritative last-mutation-id ledger, which removes the interleaving hazards a second writer would introduce. A client that already has data adopts the old snapshot cookie’s high-water mark, so it takes exactly one forward reset instead of looping forever against a stale or future cookie. Follow-ups closed two gaps the first pass left: bounded usage windows were being missed by invalidation, and usage history needed to stay incremental rather than falling back to a full send. Roughly 1,600 lines changed, and the route files were consolidated at the end so pull, push and visibility each have exactly one owner.
Handing the sync layer back to its library
Last week’s entry headlined the move of the application database onto native SQLite. This week continued that work by unwinding the hand-written machinery that had accumulated around sync. Server-side Zero integration across the app, all four examples and all six templates moved onto a single consolidated Orez executor for both push and pull, with the on-zero runtime injected through exactly one module boundary in the project worker. Generation fencing, background queueing and coalescing, awaitable settle, ack-timeout desync detection and log classification all became the library’s job; the app keeps one observation bit plus auth-identity and logging helpers. Roughly 1,300 lines of hand-written push handling and push diagnostics were deleted outright, and the pre-Rust sync branches went with them.
Hand-maintained partition and schema membership lists were replaced by generated manifests, and every namespace moved to a namespace-first folder layout with control and project data instances in separate folders. Cloudflare deploy prep learned to recognize executor-based SQLite apps, which had previously matched neither the old SQLite guard nor the Postgres pool pattern and threw for every newly seeded project. Mutation helpers shed about 280 lines of compensating reads that existed to work around an upstream Zero insert bug, which was root-caused and reported with runtime proof, so seeding a fresh project graph, the orchestrator graph and the main session graph all insert directly now. Orez also dropped its queryForward option, so clients always ship their desired queries and every server acknowledges gotQueries. The Contrast-specific storage scoping, reset handling and fatal sync events moved upstream into on-zero, cutting each template’s Zero client from around 175 lines of bespoke wiring to a plain app file plus a toast when sync is lost for good. The Orez family landed pinned at 0.5.38, and the one deliberate divergence from upstream Zero, the snapshot pull, is written down with its cost rather than left for someone to rediscover as a bug.
Three ways sync worked locally and not on a worker
Sync pulls and pushes can fail on a deployed Cloudflare worker while the identical code passes locally, and this week turned up three separate reasons why. First, the control sync server was being constructed at module scope. Its schema init starts async I/O the moment the object is built, and Cloudflare Workers forbid async I/O in global scope, so the worker died before it could answer anything; moving construction inside the request handler fixed it while still memoizing one instance per worker. Second, the control namespace was writing project-owned tables inside seeding transactions and metering usage outside any mutator, so those rows landed in the change log with no table config attached, and every subsequent pull threw “unknown change-log table” and never recovered on its own.
Declaring those tables in the new on-zero.config.ts puts them in schema membership without syncing a single row to clients, with a one-time invalidate route per namespace to retire the rows already written under the old payload. The third was in the Rust sync host: its query resolver was handing sync-core the app’s response wrapper instead of the bare query AST, so every authenticated pull returned 400 on an unregistered query hash. Browser smoke checks now capture the response body when one fails, so the alert names the actual rejecting error rather than a bare status code.
A migration ledger that trusted its own bookkeeping
Two separate failures traced back to the same thing: the native migration runner skips any statement whose id is already in the ledger, without checking that its effect is actually present. So a table a migration had declared could be absent while the ledger insisted the migration had run, and the schema assertion downstream threw. Because the original migration could never re-run, the repair had to ship as a new migration with a new id. The schema assertion reports every missing table in one pass now instead of stopping at the first, which answered the rest in one deploy rather than one deploy each. The ledger key also includes a hash of the SQL itself, so a changed statement re-runs, with IF NOT EXISTS keeping re-applies harmless. Untangling the first one also turned up a foreign key conflicting with rows that already existed, per-namespace transaction journals left in a bad state, and contention on the storage turns the auth path needed, fixed by moving auth session reads off the schema-migration turn.
The second came from how Drizzle expresses a table recreate: four statements, each ledgered separately. A run that stopped after the rename left the create recorded as applied with nothing left to rename, so every later provision died on no such table: __new_tokenUsage with the namespace stuck for good. An early repair tried to infer rebuild boundaries by matching generated SQL text, and live tailing showed it did not stop the retry, because the corrective rebuild had itself vanished from the current migration list. Inference is gone entirely. Each statement id is now an immutable execution identity, so editing historical SQL can never resurrect a destructive statement, supersessions.json explicitly names the four invalid old rebuild statements the corrective rebuild replaces, and the temp-table identity is carried onto the whole statement block so a completed recreate skips itself while an interrupted one replays. A permanent workerd regression test reads the checked-in migrations and the supersession sidecar through the production parser, and a new check replays every migration on Bun SQLite, closing the gap where three migration runtimes existed and only two were covered.
The timestamp type split that broke ordered queries
Zero compares raw SQLite cells for columns it treats as numbers, so a single row storing a timestamp as text next to integer rows breaks every ordered query on the client. All three shapes were in play at once: integers, ISO strings written by the login bootstrap, and f64-serialized decimal text like 1784788681197.0 produced because Cloudflare’s Durable Object SQL API binds every JavaScript number as a double into columns the earlier pg-bridge era had declared as text. The end state is a column swap to declared INTEGER on every Zero-synced timestamp column across the public and private schemas, done as add, convert, drop and rename rather than DROP TABLE, because enforced foreign keys would have cascade-deleted the children.
Getting there surfaced three Durable Object behaviors worth writing down. The migration ledger’s own writes survive a rolled-back transaction, so an aborted repair pass leaves statements recorded as applied with their effects undone; the runner now deletes its own ledger inserts on failure and appends the failing table’s live table_info to the error message. Change capture stores four JSON copies of an updated row inside one pending-changes row, so a 730KB stale workspace layout cache blew past the 2MB row cap with SQLITE_TOOBIG and aborted the entire transaction. And the Durable Object caps LIKE pattern length, so prefix matching had to move to instr. One downstream bug fell out of the same split: the anonymous-account prune cron compared an ISO-text cutoff against now-integer activity timestamps, and SQLite sorts text after all integers, so its activity protection had silently never matched anything. A related bridge bug fell out too, where PostgreSQL writes timestamptz text with a +HH offset while SQLite only accepts +HH:MM, so strftime() returned NULL for every row the old bridge wrote and NULL propagated into NOT NULL columns.
Merges and deploys survive a restart
Branch merges, project seeds and forks, and deployment builds used to be in-flight work living inside a request. If the request died, the work was silently gone. They are persisted durable operations now, with a typed payload, a lease with a three minute TTL, and a reconciler plus sweeper, so an operation that gets interrupted is picked back up rather than lost. The one-off deploy runner kick and the Apple status sweep endpoints were deleted in favor of the shared durable work poller, so there is one mechanism instead of three: a 313-line operation engine, a 250-line reconciler, four operation kinds.
That also fixed a fork in the Zero effect scheduler that had been costing whole merges. On node, runBackground returned its promise, so the executor awaited it and every non-barrier deferred effect blocked its push response; on Cloudflare the same effect rode waitUntil and did not. That is why approve-and-merge sat past the 30 second client budget on a merge it had explicitly deferred, measured against 13 to 43ms p50 for board mutations that enqueue nothing. Effects that must land first already pass barrier: true, and each deferred action now persists a durable operation whose sweep owns recovery.
Ownership fenced end to end, at both levels
Many agents work one project at the same time, and until this week who owned what was optimistic shared state that a stack of recovery loops tried to reconcile after the fact. Those loops guessed. They derived recovery candidates from materialized identity, so a fabricated id could make a healthy agent look missing and get it killed, two agents could claim the same ready task, and a client could believe an agent held an assignment the server never recorded. The entire layer, roughly 500 lines plus an API route, was deleted rather than patched. A task being ready now only means it is available: one explicit server assignment creates a real, exact lease, and a runner resumes only after the server confirms that exact lease, backed by three new schema migrations. Submitting work binds to the exact task the agent worked instead of whatever task happened to be current, reviewers are pinned to an exact branch revision so they read the code that was actually submitted, interrupting an agent takes a two-step confirmation naming the exact task, and file writes are verified against the authoritative owner and fail closed. The shared root layout, which no lane owned and five lanes were editing simultaneously with only the first merge winning, is single-owned now.
The same shape went into execution ownership, where more than one tab or process can drive a project’s agents and the stale ones have to be shut out precisely. Ownership is keyed on a per-acquisition identity rather than a tab-stable id, because a callback left over from a previous acquisition could otherwise act on its successor, and releases name the exact acquisition so a late release for an expired lease cannot delete the live driver’s. Zero mutations compare the manager generation inside their own write transaction, while external file, branch and provider effects use concurrent server-owned reservations. Runners resolve the current confirmed claim on every project write instead of capturing one at tool-creation time, which is what wedged whole waves: a single stale assertion set a permanent rejection latch and every write afterwards failed as stale. Losing the lease no longer tears down each agent’s local data runtime either, since a 2.6 second lease gap once cost a build wave another 12.4 seconds of cold pool boots serialized five agents deep against roughly 0.3 seconds warm. Renewal was proven against the real route with real auth under six concurrent HTTP/2 model streams.
Every screen of an app builds in parallel
App builds were serializing in two places at once. Screen tasks were only created after the data layer merged, so on a 22-minute run screens started around 14 minutes in and never finished; on one run four screens started at the 4-minute mark and none of them merged in the following 18. And review funnelled back through a single orchestrator merge queue, so four screens finishing in parallel merged one at a time. Now every screen’s layout and UI task is created in the very first batch, only the final data wiring waits on the data layer, and non-trivial tasks get their own independent reviewer that merges concurrently instead of sitting in the orchestrator FIFO. Several adjacent stalls went with it: a wave no longer declares itself merge-stalled while workers are actively writing or still in review, a transient burst of unregistered-named-query 400s no longer false-aborts a run, runs stop early when the infrastructure is broken or two waves overlap, and a single missing optional field on task creation no longer rejects every task and leaves the board empty for an entire run.
The scheduling around it was tightened too. Quality passes abort when protected files change underneath them rather than certifying a build that no longer exists, and that invalidation survives a failure in the reporting step itself. The orchestrator returns repeated refusals back to the model instead of swallowing them, prioritizes the screens actually being graded before secondary work and reserves time to rework them, replans stalled screens onto role-matched workers, and resolves ready screens even when idle worker capacity does not line up cleanly. Screen tasks derive their dependencies from which route scaffolds are unfinished and who owns each route, so parallel work unblocks as soon as it genuinely can. A reviewer’s preview is reset before a worker is reassigned onto it, so review evidence can never come from someone else’s session, and on hosted runs pages are closed before the runtime lease is released so a finished run frees its slot instead of leaking it.
Build agents coordinate instead of colliding
Tasks honor declared dependencies now, and each agent’s merge scope is limited to what it owns, with an uncontended ownership request granted immediately instead of waiting out a round of negotiation. Generated output written by the project’s own tooling is exempt from that ownership gate, so a data or theme task stops bouncing on files its own generate step had to produce. Task completion is driven by a successful branch merge, so the board can no longer show finished work that never reached the project. Workers stop re-running the same type check at submission and reviewers trust the submission’s stamped result, so one piece of work is not validated three times, and a worker that keeps failing the same operation is parked rather than looping. Merge-approval mutations got a 120 second acknowledgement budget, because in a real run they are a single uncancelled POST serving as both phases and the 30 second optimistic-write default fired while the merge was still landing.
Stall recovery also stopped restarting itself. Recovery used to re-run setup from scratch, producing 4 start_project calls and 11 branches for 5 lanes. The board nudge now carries completed setup state, branch and agent creation are idempotent by name, escalations on a broken main collapse into one owned unblock task, and the nudge reports remaining budget with merge-only guidance in the final minutes. Passive monitoring of an in-progress build was mounting agent preview instances of its own and crashing the renderer; that path is read-only now.
A build run that ran out of memory, not time
Late in long runs, workers started failing a 30-second startup handshake. The obvious read was a timeout that needed raising. The actual cause: the tab’s heap had climbed to roughly 2.4GB and garbage collection was blocking the main thread for 27 to 35 seconds at a stretch, so the worker’s ready message physically could not be processed inside the window. Extending a timeout cannot help a blocked main thread.
The fix caps both the agent pool and the preview-iframe pool against live heap usage rather than the static heap limit, so heavyweight in-tab runtimes stop being added to an already saturated tab and the heap never reaches storm size. Two things landed with it. The project server emits a boot-phase trace, so a stall names the step it died on instead of reporting an opaque worker timeout, and a second consecutive start failure on one lane pauses for a decision instead of resetting into a churn loop. The run’s hard time cap is derived from model class now, at 30 minutes for uniform fast models, 40 mixed and 60 for larger ones, rather than a flat 90 that let one run burn its last 8 minutes resetting a dead lane.
Agents can now see what they actually built
An agent without vision had to infer what a screen looked like from a flat list of boxes, which is how an illustration painted straight across a headline once shipped. The layout inspection tool reports overlapping boxes directly now, and excludes media nodes from being treated as layout containers, since otherwise the artwork gets blamed as the headline’s parent. Agents can also read the app in dark theme. The element query an agent uses to confirm its work separates “this element exists in the tree” from “this element is in the viewport”, so an offscreen node can no longer be presented as a finished screen. Terminal closeout was judging a hidden native pane because the driver’s own synthetic input read as user control; it presents the stage with an explicit request now, while ambient behavior still yields to real control.
When an agent inspects a project’s data or checks its own work, it goes through a real authenticated session against the running project rather than an unauthenticated side channel, so named queries return what a logged-in user would actually see and an agent cannot be fooled by an empty result that only looks empty because nobody was signed in. A large sample-state live-data browser proof landed alongside the five runtime bugs it exposed: synthetic input no longer claims user control behind an isTrusted gate, required visible-merge demo beats cannot be reclassified as churn, storage access caches only positive lookups so a pre-commit race cannot harden into a terminal 403, the AST bundler keeps external named and star re-exports on module.exports, and web text queries find text split across sibling nodes. A live six-plus-six negative control disproved a suspected worker-pool capacity ceiling, with all slots ready in under 300ms, so slot boot, ready, fail and dispose tracing landed instead of a speculative allocation change.
Live preview stopped serving stale app content while agents edit
The agent preview pool was rebuilt around ordering and lifetime guarantees. Bundle applies inside the web preview iframe run strictly serialized in the order they were received, and a newer bundle aborts the one ahead of it, with the abort raced against the unabortable CDN preflight import so a hung fetch cannot wedge the queue. Previously the mount serial was assigned after the async preflight, so an older bundle with a slow preflight could mount after a newer fast one and leave stale content live on screen. Iframe realms are retired only after a committed about:blank navigation instead of on a timer, because a realm torn down with async work still in flight stays pinned from Blink C++ with no JS retainer to release it. Changes that need a remount cold-apply in place and keep preview readiness suppressed until the new tree is genuinely mounted, with a generation id on each completion so a stale done cannot clear a newer apply’s suppression.
Hot reloads were exhausting the heap on a separate path. Live web preview applies each hot bundle through a fresh blob module URL; revoking that URL frees the bytes, but the browser keeps the module graph alive for the lifetime of the iframe realm, so a long agent build accumulated graph after graph until the host page ran out of heap. The hidden web realm recycles after 8 hot bundles or 24 MB of module code now, deferring the recycle if something is holding a lease on it.
Local development moved onto trusted HTTP/2
The local stack is HTTPS and HTTP/2 only now, with a certificate authority provisioned by mkcert, so browser-side model streaming and the agent bridge behave like production instead of running into per-origin connection limits over HTTP/1. Getting there took several fixes. The certificate is minted by the dev launcher rather than during Vite config load, because One’s loadConfig sets IS_VXRN_CLI before evaluating the config, making one patch during install indistinguishable from a dev serve so every install tried to write the host trust store. Trust goes through mkcert instead of a hand-rolled security add-trusted-cert that needed a GUI authorization prompt and hard-failed on CI runners, and TLS setup was pulled out of browser-side env imports.
The HTTP/2 move also left Zero’s wake websocket upgrade with no bridge, so control and project wake sockets returned 404 and clients fell back to 15 to 30 second safety polling. Vite proxies the control and per-project wake paths to the native Zero namespaces now, and restoring that bridge cut local IDE boot from about 195 seconds to about 16.
Server-only code and secrets kept out of worker and native bundles
Two bundles were dragging in code that cannot exist where they run. The Rust sync host worker and the Metro native bundle both imported values from the app env module, which runs createEnv at module load and asserts every production secret; inside the worker none exist, so Cloudflare rejected the upload with a 10021 startup validation error after the data worker had already flipped, leaving the two planes skewed, and Metro died the same way on the over-the-air bundle. The app id and port bases moved into side-effect-free constant modules that the env module re-exports, so app-side importers are unchanged. Bundling both ways showed the old import producing 2 occurrences of a runner token and the new one producing 0, with a 15KB smaller worker.
Separately, every over-the-air and TestFlight bundle was failing because One’s Metro resolver stubs .server files but never saw them: babel’s module-resolver rewrote the aliased path to an absolute one and the config’s absolute-path branch answered first with a real file, so an HMAC server module dragged node:crypto into the native graph. The absolute-path branch resolves only the extension now and hands the result back upstream, so One’s .css, middleware and .server rules all apply again. The resulting iOS bundle builds, contains no node:crypto and none of that module’s implementation, and is byte-comparable to the previous build.
Lockstep recording captures every animation frame by construction
Realtime pull-capture had a failure mode nothing could see: whenever the worker round trip outran the frame budget, the recorder silently stored a duplicate frame. A rejected demo clip set had perfect 60fps container pacing carrying only 1.3fps of actual content motion, and every existing signal was blind to it, since presentation-timestamp spacing, jank counters, and pixel hashing all look correct when the duplicate arrives on schedule.
The fix is a lockstep mode on record start --format mp4 --fps N --lockstep. It parks the tenant, shell, and compositor frame clocks and steps them in order by exactly 1000/fps of virtual time per output frame, stamping each with its exact virtual timestamp, so a dropped or duplicated frame is not merely unlikely but unrepresentable. The realtime path was fixed separately: the bitmap pump used to schedule each tick after the previous paint finished, so per-frame cost added onto the interval and a 60fps request landed near 15fps. Ticks follow an absolute drift-compensated timeline now, capture scale rides the captureSurfaces protocol so workers downscale through createImageBitmap before transfer, and a larger encoder queue absorbs keyframe spikes instead of dropping frames mid-animation. Under lockstep, spring, timing, and worklet motion are exact, while wall-clock timers and live gesture pacing compress relative to the output timeline, which is the honest tradeoff.
SootSim
- Apps that use Mapbox maps now render real satellite imagery on a globe in the simulated iPhone, with no Mapbox key required, and public route styles and camera flights line up with what the real thing does.
- The scroll position a Reanimated shared value reads is driven by real scroll input, and the reduced-motion setting is delivered before the app boots, so an app that checks it while loading sees the right answer from its first render. With that verified end to end, react-native-reanimated’s compatibility rating moved to 95%.
- Apps using Reanimated could hard-crash on unmount, because host-node resolution walked refs and accepted a wrapper object where a real native view was expected; it resolves instances through the renderer the way React Native does now, a net deletion of about 300 lines, and the Uniswap demo app no longer crashes 12.6 seconds in.
- Text set to shrink so it fits now follows React Native’s iOS auto-sizing behavior exactly, after a 390-line rework of layout, the renderer and font scaling, with a new conformance case covering it.
- An app that uses the camera in the simulator gets the host webcam feed instead of a denied permission prompt, so camera flows can be exercised without a physical device.
- Pressable wrappers that lay out as pure containers had no bounds of their own, so hit testing missed them and long-press menus never opened; hit testing falls through to the children’s bounds now.
- Inspection can report whether an event handler has a peer, plus a breakdown by handler name, separating “never registered” from “registered on the wrong side” for keyboard and scroll handlers the shell runtime owns.
sootsim inspectandsootsim perftrace scroll offsets end to end and report compositor frame jank, so a stuttering list is diagnosed from real per-frame offsets rather than screenshots.- Blending visual results into a package’s compatibility score was discarding the API-based estimate rather than combining with it, which understated coverage; both are blended now.
- Native screen transition tags were registering before the shell owned the screen and being dropped; they retry once ownership lands.
- App previews can be rendered natively through react-native-skia, behind a tree-sync port and shell protocol that lets the same real app code drive either host, with iOS continuous-corner squircles, backdrop blur, packed colors and asymmetric borders painted the way iOS draws them rather than approximated by a browser.
Mobile app
- A project you create anywhere else now reaches your phone. The phone’s connection to your account fetched once when it connected and then went quiet, making zero requests over 100 seconds, so a parked phone sat there indefinitely while the project already existed on the server.
- Being denied access to one project no longer wipes your whole session and drops you back at login; only an actually expired session does that.
- Default projects were recording their creation time as text while every other project recorded a number, and since the project list sorts on that value, any account holding both kinds hit a type error and the app hard-crashed right after login.
- Opening the app used to sit spinning on the launch brandmark indefinitely if the session request hung; it now decides from what it already knows, and returning users land directly in their last workspace.
- The spinning logo would stall partway through a rotation and freeze; it rides the device’s live frame loop now.
- Opening a link that pointed at a specific tab used to bounce you back to chat, because mounting the project reset which surface was showing.
- The chat was pinned to the very first stable release of its list library, fifteen releases behind, which is why short conversations sat behind the input box and the newest message was not visible right after opening a thread or sending.
- The preview card in chat flattened from three nested boxes to one, tapping it dismisses the keyboard first so the preview animates in over a closing keyboard, and transformed preview stacks render at full Retina scale.
- The app’s cloud environment was guessing at when its backend had woken up; it now requests an authorized wake token and follows actual authenticated readiness, so reconnecting after the phone has been asleep lands on a live session.
- Groundwork for per-app custom native iOS builds landed behind an environment flag, with a build flavor resolved from the app’s own dependency graph and a camera fixture app proving the path end to end.
- Filler subtitles were removed, and icon-only header buttons are now reachable and correctly labeled by VoiceOver.
Previews and templates
- A burst of merges used to reboot the preview pane once per merge and could knock it over; a burst now collapses into a single reboot. The failure was captured in a 676-line runtime reproduction before the fix.
- A routine merge from an agent used to blow away your preview and reload from scratch; routine merges stay on the hot reload path now, and only changes that genuinely need a fresh start, like a schema regeneration, trigger a hard reload.
- The stand-in phone drawn while the simulator loads used to guess its own dimensions and linger over an already-drawn simulator, so you saw a wrong-sized phone covering a working preview; it now disappears the moment the real frame paints.
- The
contrast ioscommands and the native preview pane bind to the simulator that is actually on your screen rather than a detached instance. - Navigating within a branch preview dropped the parameters identifying which branch it was, so its data requests fell back to the main worker and answered with a storm of unknown-named-query errors; scope is now cached per preview frame.
- Preview videos capture in an offscreen headed Chrome so rendering is genuinely GPU-backed, upload control responses are excluded from edge compression rather than worked around in the CI browser, and uploaded recordings carry their pull request all the way through.
- The native preview relays simulator identity and the shell profiler into the guest and passes a runtime storage scope, so data a previewed app stores belongs to that runtime instead of leaking across previews.
- Every generated app’s back chevron used to land instantly with no animation, because the root native stack disabled animation outright and the auth group had no navigator to animate at all; both are fixed across all starting points.
- Scrolling in generated apps respects the safe area everywhere by default through shared page and list components, empty states always come with an action that does something, avatar editing and shared images work on native again, and controls carry proper labels for screen readers on web.
- Templates gained reliable data icons, a currency amount component and a sparkline chart, with metric cards rebuilt on top of them, and a new lint rule requires official text size tokens so generated apps stop accumulating one-off font sizes.
- The flights example runs the full push notification loop now: register the device, remember it, and deliver a real native notification when a tracked fare drops.
- An
@vxrn/nativepreview dependency registered for One 1.21.8 was removed once 1.21.9 kept the native toolbar opt-in, and combined native deps now register as real registry modules instead of evaluator-local state that hot reuse could leave unset.
Editor and workspace
- The warm type-checking program could be evicted while it was still producing results, so diagnostics came back empty or partial; active programs are held alive until they finish reporting.
- A project’s type check was assembled from partial file scopes with different path handling, so agents could get contradictory verdicts about whether the same project compiled; it is now a single authoritative project-wide check.
- When the language server bridge or schema generation failed underneath the project, the failure was swallowed and the runtime signals panel still looked healthy; those failures now appear as real signals and through the command line.
- Editing a task no longer depends on an implicit save: there is a real confirm control, and the close button floats above the dialog so it never fights with the content, which every dialog in the app picked up.
- The file tree’s right-click menu uses the same menu building blocks as the rest of the app, menu triggers are fully operable from the keyboard, tooltips get out of the way when a menu opens, and escape reaches the thing you expect because hidden inspector overlays no longer swallow it.
- Dragging a pane into the top bar to dock it is gone, roughly 680 lines removed; it was a rarely used mode that made layout state hard to predict, and layout presets handle arranging the workspace.
- Every input and text area uses reading typography instead of monospace, and focusing one gives you a subtle themed ring rather than the saturated blue halo that made fields look unstyled.
- Lux boots ahead of the heavier build environment, so you can start typing while everything else is still coming up.
- The two halves of the loading brandmark chase each other in the same direction with the inner disc lapping at double speed, instead of counter-rotating and reading as a strobe.
- Loading the browser editor on a phone was never going to go well, so phones now get a clean prompt to grab the native app and the editor never boots.
AI agents
- Asking for a tweak used to send the agents off to amend the product spec; they classify the request first now, so a small change is just a task, a genuinely new area gets its own document, and only a change to the product’s premise touches the spec.
- The rule that stops a run on runtime errors counted lifetime totals, so six repeatedly-blocked placeholder images could fail an app that rendered perfectly; it counts a rolling 90-second cascade now, and two non-errors stopped being counted as errors.
- Model routing is confirmed once per orchestrator session and cached rather than re-resolved on every agent spawn, orchestration tool durations are persisted into wave reports, and a session refuses to start a turn against an incomplete tool registry. The measurement showed turn structure, not model latency, dominates startup, with round trips at 5 to 8 seconds.
- Automation tiers can route through Cloudflare-hosted models, with DeepSeek and Kimi K2.7 Code selectable for build runs and an access preflight so a run fails fast with a clear reason. The default orchestrator was pinned back to deepseek-v4-flash after a tier change quietly moved it onto a slower route; the restored default merged its first task in 55 seconds.
- Every agent role’s tool list carried both image tools at once while the attachment registry provides exactly one of the pair, so agent creation threw for every model and in the orchestrator’s case died silently behind a fire-and-forget relay; one shared capability filter picks the right one.
- Worker kickoff moved from full skill bodies to roughly 250-character routes, retiring a heavy-skill cap that had been dropping applicable guidance to save a few hundred characters and could silently discard a skill the manager had explicitly named.
- When an agent’s edit targets text that no longer matches, the tool quotes the closest matching region instead of failing blankly. A sliding distinct-line scan replaced per-window Levenshtein, which measured around 2 seconds on a 5,000-line file.
- The Drizzle schema reader moved from a 783-line regex front-end to a 617-line acorn-typescript AST reader, so comments, export style, aliases and formatting can no longer change what a schema is understood to mean, and unparseable source throws its real syntax error.
- All three schema generators picked their inputs from a hardcoded path list, so a schema or relations module one import away was invisible and generation emitted a schema missing those tables; inputs now come from the import graph.
- Agents working on hidden branches were reading a file view that did not match the preview client scoped to that branch; the manifest, the fetch path and the preview pool now share one scoping model.
- Runs were starting on a red type baseline, with template push routes carrying a removed on-zero claims API and server files hidden from the agents that had to fix them; templates match the current API and the agent-facing check covers exactly the files agents can see.
- Guidance corrections drawn from real runs: one agent believed Card lacks an
onPressand wrapped a Card in a Pressable over it, another read its own open sheet’s scrim as a stray framework overlay and rewrote two working sheets to mount conditionally, losing their exit animation. The underlying warning behind the second was fixed upstream in Tamagui. - Guidance was also tightened on routing and typography, accessible shared primitives, button versus pressable semantics, bounded and virtualized list containers, containing absolutely positioned children on web, query tuple shapes, and native modal patterns, while the layout analysis tool stopped flagging undersized tap targets on plain text links.
- When a type error lands on a whole JSX object, the diagnostic explains what it actually means rather than handing over raw compiler text, and the styling guidance now teaches only valid spacing and color values.
- Build runs are graded on parallel utilization, so a run where most workers sat idle scores accordingly instead of looking fine because the end result was acceptable.
- Build runs persist continuous host CPU and memory evidence through closeout, and you can attach to and watch a build in progress; connecting at the browser level was blocked because the driver already held it, so attach targets the page instead.
- File tool payloads and the transcript cards that render them are capped before they are persisted, so a single large read or write no longer inflates a conversation’s stored history.
- Merges bind to the exact revision of the branch an agent owns, so a task approved while another merge is in flight cannot land the wrong tree, and rolling quality passes stay terminal after a merge race instead of re-opening.
- Captures that hit the deadline unfinished are labeled incomplete builds rather than graded as if they were real, and the slim dev stack now serves the screenshot capture endpoint, after one graded run spent 150 seconds collecting connection-refused errors.
Sync and data
- Merging a schema change cleared only the bulk read cache, so the runtime restart came up mixing cache generations and threw “no such table” in a loop until the run aborted; both caches clear together now, with a guard so a read already in flight cannot repopulate the stale generation.
- Recovery is stamped with a single generation number that every runner, nudge loop, and queued mutation reads, so one sync reset invalidates everything at once instead of piecemeal.
- Canceled transactions and throttled reloads recover cleanly, and the recovery throttle survives a full local storage quota, where previously the throttle record itself failed to write and recovery could spin.
- The sync auth callback is now a POST that carries the namespace and returns normalized claims directly, and sync identity was separated from app auth, which closes a class of bug where a permission check could read “has a sync identity” as “is signed in”.
- The native sync host bundle kept only CREATE statements, so an index over a column added by a later ALTER failed every fresh namespace observe; it dumps a scratch-migrated schema now, and adopting a namespace that is already in its final shape stays idempotent because an ALTER ADD is skipped when the column exists.
- Pull was running
CREATE TABLE IF NOT EXISTSon the client ledger without auserIDcolumn, so on a namespace that pulled before its first push it won the race against the executor’s own DDL and every later push failed; pull only reads that table now. - Three places that quietly degraded on failure now refuse instead: a mutation push is rejected when the rate-limit decision is unavailable, the preview worker rejects tenant API requests until its route table is confirmed, and a failed iframe dependency transform surfaces as an error rather than silently altering the module graph.
- PostgreSQL server code was reachable from the in-browser project worker’s module graph; node aliases and a query-server shim cut it at the bundle boundary, with a config test guarding the boundary so it does not drift back.
- The attach command dispatcher checks server-confirmed ownership before executing anything, so a client cannot drive a session it does not own, and write operations across the finance, travel, pet and RPG examples gained schema validation on their arguments.
- Branch tracking used to lose a branch’s ancestry once main moved past it, and a reviewer could be handed the wrong diff after a branch had synced with others in between; ancestry is preserved and review context resolves to the changes their author actually wrote.
Deploy and infrastructure
- A deploy could finish live and then be overwritten as failed, because the step that finalizes hosting always called out to the hosting provider even when it did not apply, and its “this worker does not exist” error clobbered the good result.
- The superseded-deploy skip assumed a newer main tip always has its own workflow run behind it, but GitHub never starts a workflow from a token-authenticated push, so an in-flight run stood down for a successor that could not exist and main reached two consecutive green runs that validated and deployed nothing.
- App deploys were sending private runtime values as ordinary Wrangler vars, which are readable from worker configuration; they now go through the encrypted secrets-file payload, and production secret sync rejects the shared development placeholders outright.
- Every external GitHub Action across all 21 workflow files is pinned and fork-triggered workflows are restricted, since that pipeline builds and deploys everything.
- Cold Durable Object backend initialization is serialized so two simultaneous first requests cannot both try to initialize it, scheduled requests get a resolvable host, and SQLite backup restore goes through a single batch RPC rather than per-statement calls.
- An Orez upgrade scopes SQLite clients in both split workers and generated user-app workers to the active request signal, so a canceled request releases its transaction instead of leaving it open, and template exports publish self-contained artifacts rather than depending on runtime assembly.
- The namespace export streams tables in storage order while each import batch commits its own transaction, so under enforced foreign keys a restore could insert child rows before their parents; the import now buffers and inserts per table in topological order, verified acyclic against a live export.
- Pushing example databases to production wrote everything in one shot and failed on the large ones, and the admin view of iOS publish attempts scanned every namespace in a single pass; both chunk and page now.
- Headless browser sessions used for rendering could leak and hold capacity indefinitely; a dedicated Worker inventories live sessions and closes stale ones, waiting for each close to propagate before reporting success.
- The exception-spike monitor wrote its state after attempting alert delivery, so a delivery failure crashed it before the write and the same spike announced itself as brand new on every subsequent schedule.
- A repair migration re-declares the durable operation and factory write operation tables that a schema snapshot restore dropped while their ledger rows survived, healing a drifted project and doing nothing on a healthy one, and native migration failures now name the failing statement, instance and ledger entry.
- A first working send path for a hosted Apple push service was built and proven, so an app never has to hold the shared APNs key itself: a project-authenticated send endpoint, a server-owned registry, a Durable Object that mints and caches the ES256 team JWT, and delivery straight over Worker fetch, which reaches Apple over HTTP/2 with no sidecar needed. It is explicitly not deployed, not published, and not wired to any app; rate limiting, a queue with a dead-letter path, and audit logging are written down as launch requirements.
Website, CLI and accounts
contrast chat send "..."messages the assistant as the project’s user straight from the command line, with--attachfor files,--waitto block until it replies, andchat waitandchat logto come back later. It replaces the older nestedide chatverb.- The dev screenshot command signs in the same way automated capture does, so it can shoot pages behind a login instead of returning a picture of the login screen.
- The Contrast preview GitHub Action can run in parallel with the rest of your pipeline instead of only as a serial step, with phases you can split across separate jobs, and the runner waits for a complete runtime before opening the simulator.
- Loading your projects fetched each project’s icon from that project’s own storage in parallel, and a single wedged or half-provisioned one failed the entire request; a project that cannot produce an icon now just shows without one.
- Creating a project could briefly hand the editor, chat, and agents an empty file tree; nothing activates now until the seeded files are genuinely there, a failed seed stops the project rather than continuing half set up, and a template name that does not exist is rejected before any of it starts.
- Projects that began on the old starter and were migrated could not preview at all, because the migration added code importing a package the project’s manifest never declared; the migration merges the missing declarations in without touching anything you edited.
- Some older projects carried a partially applied table from an earlier migration, and the next migration then tried to index a column that had never been created, wedging sync and previews entirely; that migration repairs itself now, preserving and backfilling existing rows.
- Setting up a team plan for an organization that already had accounts replaced them instead of keeping them.
- Loading a team’s landing page fired several separate lookups one after another, at a median 3.35 seconds per request in production; all three come out of a single read now, which also removes the chance of the seat count and membership permissions disagreeing.
- Background polling from running agents was competing with the dashboards people had open, making them sluggish at exactly the moment they wanted to watch progress; that polling stays out of the way now, with a latency check running against production regularly.
- Display type fits its line by measurement rather than being eyeballed, and a light leak that had been referencing two video files present on no checkout is drawn procedurally instead.
- Loop and manifesto type had word gaps set at twice their line gaps, which tore a hole in short lines; all gaps resolve to one value.
- Contrast and SootSim split into separate font configurations over a shared foundation, with semantic typography applied across the marketing site, pricing, invites, billing, chat markdown and integrations pages.
- App-tile turntable sprite strips were re-encoded lossily at full alpha quality, taking them from about 2.5 MB to about 1.3 MB total, visually identical at their on-screen size.
- A layout and copy pass landed on the pricing pages for both products, plus a trimmed drop area on the compatibility scan.
- A project server module with no exports is rewritten before evaluation instead of throwing, so an empty or stubbed-out server file does not take the whole project’s evaluation down with it.
Fixes & polish
Beyond the headline work, 575 fixes and 16 performance improvements landed this week. Three are worth calling out. Browsing shared community apps returned exactly one app instead of the whole catalog, because a missing page-size value read as zero and clamped the list to a single row. Freshly generated apps could come up completely empty, because seeded starter data containing true/false or structured values threw and rolled back the entire seed, with every authentication call inside a project preview returning an error on top of that; both are fixed. And loading your projects failed outright when a single wedged or half-provisioned project could not produce its icon, taking the whole list down with it, where a project that cannot produce one now simply shows without it.