Without Generating Text, How Does Jev Operate a Browser and Build Interfaces? A Breakdown of Browser Use and json-render
Through the Browser Use and json-render open-source examples, this article explains how Jev makes structured decisions inside a limited action or component space, and why DOM inspection, text generation, JSON assembly, validation, rendering, and final execution must still be handled by surrounding code.
Contents

When people build an AI system that operates a browser, the most intuitive approach is usually to give a screenshot or the DOM to a general-purpose large model, ask it to analyze the page and plan the next step, and then have it generate a click position, selector, or tool call.
Building an interface often follows the same pattern. After the user describes what they want, the model directly generates JSON, JSX, or front-end code, which the system then tries to parse, validate, and render.
Browser Use’s Jev Ultrafast and json-render’s Jev experiment take a different route:
Code first limits what the model is allowed to do to a finite set, and Jev only chooses from that set.
In Browser Use, that set consists of the executable actions and interactable elements on the current web page. In json-render, it consists of components, property configurations, data bindings, and layout positions prepared in advance by the application.
Jev does not need to write a complete operation plan or generate an entire UI JSON tree. It only answers questions such as:
- Should the next step be a click, text input, scroll, or wait?
- Which element on the current page should be operated on?
- Which components should appear in the interface?
- In which parent container, slot, and order should a component be placed?
These two examples do not show that “a model that cannot generate text can still do everything.” They show a different software architecture: turning an open-ended generation task into a sequence of constrained, verifiable decisions.
What Jev actually does here
Jev is a System One model released by TypeSafe AI. It accepts a state together with a set of typed questions defined by the developer, and returns structured results such as Choice, Score, or Noul, rather than a long piece of text intended for a human reader.
You can think of it as a decision function with probabilistic outputs:
Current state
↓
Developer constructs a finite candidate set
↓
Jev chooses, scores, or evaluates
↓
Regular code validates the result
↓
Execute an action or render the interface
Here:
Choice: selects one option from a provided set;Score: assigns a position on the ordered levels supplied by the developer;Noul: returns the probability that a particular proposition is true.
The key point is not the response format but the division of responsibility. Jev does not freely generate page copy, browser selectors, JavaScript, or complete JSON. Code still owns state, control flow, permissions, and execution. TypeSafe describes this pattern as “unstructured state in, typed probabilistic decisions out.” (typesafe.ai)
The following sections look at how Browser Use and json-render apply this pattern in real systems.
Browser Use: first turn the page into a finite action space
Browser Use’s jev-ultrafast project demonstrates a browser agent: the user provides a natural-language goal, the program reads the current page, Jev chooses the next action, and browser code executes it.
The task in the public demo is to search Google Flights for a one-way trip from Zürich to London. The project reports a recorded completion time of about 7.1 seconds, including model calls, text generation, browser execution, page loading, and retries caused by stale decisions. This is still only one task under one browser configuration, not a general reliability benchmark across arbitrary websites. (github.com)
Step 1: code reads the page; Jev does not simply “look at a screenshot”
In each decision cycle, browser code first reads the controls and text currently visible on the page and builds a numbered element table.
A simplified version might look like this:
[1] button Change ticket type · Round trip
[2] combobox Where from? · San Francisco
[3] combobox Where to? · empty
[4] textbox Departure · empty
[5] button Search
The table contains the element type, name, current value, and index. The program also retains the real DOM node associated with each index so it can resolve the target again before execution.
In this example, Jev is not directly looking at a screenshot and guessing that a button is located at coordinates (482, 316). Screenshots are mainly used for demonstrations and human inspection. The actual decisions are driven by structured state extracted from the DOM. The project also states that page labels are added during screenshot rendering and do not drive the browser. (github.com)
That distinction matters.
If a model freely generates coordinates or a CSS selector, it may return:
- a selector that does not exist on the page;
- an element position that is already stale;
- a control that is covered or cannot be clicked;
- a piece of JavaScript capable of arbitrary behavior.
Jev Ultrafast instead restricts the model to selecting from element indexes that the program has just observed.
Step 2: Jev selects the action and the target element
The project exposes the following action set:
CLICK
TYPE_TEXT
SELECT
SCROLL_UP
SCROLL_DOWN
WAIT
DONE
BLOCKED
Based on the current page state, the program only offers actions and compatible targets that are actually available at that moment.
For example:
- if the current page has no dropdown, no
SELECTtarget is offered; - if there are three text fields, text-entry targets are limited to those three elements;
- if there are ten clickable elements, click targets are limited to those ten elements.
A single decision can be simplified as:
Question 1: What should the next action be?
Candidates: CLICK / TYPE_TEXT / SELECT / WAIT / DONE
Question 2: If the action is CLICK, which element should be clicked?
Candidates: [1] / [5] / [8] / [11]
Question 3: If the action is TYPE_TEXT, which element should receive the text?
Candidates: [2] / [3] / [4]
These questions can be evaluated in parallel within one request. Only the target compatible with the selected action is executed. If Jev chooses CLICK, the program reads only click_target; it does not execute a target precomputed for TYPE_TEXT.
The project calls this a dynamic, indexed action space. It reduces the number of serial model calls required at each step and avoids asking the model to freely generate operation parameters. (github.com)
Step 3: call a generative model only when text must be written
Jev can decide that the system should now type into the origin field, but it does not generate the actual text to enter.
When the action is TYPE_TEXT, the system calls a small text-generation model to produce the input value from the current task and target field. For example:
{
"text": "Zürich"
}
The result still has to be parsed as a very small JSON object before the browser is allowed to type it.
The browser agent therefore combines two different capabilities:
| Task | Responsible component |
|---|---|
| Decide whether the next step is clicking, typing, selecting, or waiting | Jev |
| Choose which element on the page to operate on | Jev |
| Generate the natural-language text that should be entered | Small generative model |
| Read the DOM and page state | Browser code |
| Click, type, and select | Browser code |
| Verify that the target has actually been completed | Independent validation code |
This is also why “Jev operates the browser” does not mean that Jev independently completes the entire browser task.
A more precise description is: Jev is the action selector inside the browser loop.
Step 4: code checks the page again before execution
After the model makes a choice, the program does not immediately click blindly.
Before executing, Jev Ultrafast also checks:
- whether the current page is still the same page the model observed;
- whether the corresponding DOM node still exists;
- whether the element is covered by other content;
- whether the current geometry is still valid;
- whether the form value and nearby context still match the snapshot;
- whether the input to the text-generation request has changed.
If the page changes before the model response returns, the previous decision may already be invalid. The program treats it as a stale decision instead of continuing to interact with an old element.
The project explicitly limits what model output can become: it is not directly converted into a CSS selector, screen coordinate, shell command, or executable JavaScript. Every target that is executed must be resolved again to a real DOM node that was previously observed. (github.com)
This code is not “intelligent,” but it determines whether the system is reliable.
The seven-second result cannot be credited to Jev alone
Across six alternating runs, the project reports that both implementations completed the task three times. Median task time fell from about 9.450 seconds to 7.092 seconds, a reduction of roughly 25%; browser protocol calls fell from 1,092 to 101. The author also stresses that these were only three runs per implementation on the same task and browser configuration, not a general reliability test. (github.com)
The performance gain therefore comes not only from model speed, but from the browser implementation as a whole:
- reading visible controls in one pass;
- reducing browser protocol round trips;
- placing action and target questions in the same decision request;
- calling a generative model only when text input is required;
- waiting only for necessary page changes after execution;
- avoiding the inclusion of unrelated page text in model context.
It would therefore be incorrect to conclude that simply replacing a model with Jev makes every browser agent finish in seven seconds.
The current MVP also lacks complete support for shadow DOM, iframes, canvas, file uploads, pop-up tabs, nested scrolling, and arbitrary keyboard widgets. Even when the model chooses DONE, the system still requires an independent check that the task was actually completed. (github.com)
json-render: let Jev choose components instead of generating the entire JSON page
json-render addresses a different problem: how to assemble a directly renderable interface from a natural-language request.
Traditional generative UI systems often ask a model to directly output:
- React or Vue code;
- a complete JSON UI tree;
- CSS and layout properties;
- event-handling logic;
- data-binding configuration.
This is flexible, but it also gives the model an enormous output space. It may misspell a component name, generate a nonexistent property, reference an unregistered action, or produce JSON that cannot be parsed.
The json-render Jev experiment reformulates the task:
The application first prepares a collection of valid component instances, and Jev only decides which ones to use and how to combine them.
This capability is still marked experimental. experimental_composeSpec and experimental_createEvaluator have not been released as stable APIs; their names and behavior may change between versions. The documentation recommends pinning an exact version and checking the changelog. (json-render.dev)
The application provides the component catalog and candidates first
Suppose the user asks:
Create a sales dashboard with an orders table at the top, a row of revenue, order-count, and new-customer metrics below it, and a weekly revenue chart at the bottom.
The application does not give this sentence directly to Jev and ask it to freely write UI JSON.
Instead, the application first provides candidates:
Dashboard
OrdersTable
MetricRow
RevenueMetric
OrdersMetric
NewCustomersMetric
RevenueBarGraph
Each candidate is not merely a name. It is a component instance configured by the application and can include:
- component type;
- fixed properties;
- available layout configuration;
- state bindings;
- data bindings;
- actions it is allowed to call;
- a candidate description intended for the model.
For example, a button candidate can be predefined as:
Component: Button
Text: Save
Action: savePreferences
Arguments: read the current /name state
Jev may decide whether to include this button, but it cannot invent an unregistered deleteAllUsers action.
The json-render documentation emphasizes that the platform controls the available capabilities and design system. Jev can only choose from components, configurations, and action bindings supplied by the application; missing copy, data, or components are not created by Jev automatically. (json-render.dev)
Phase 1: choose which components the interface needs
When creating a new interface, the first batch of decisions handles:
- which candidate is the root node;
- which components should be selected;
- how many instances of a reusable component are needed;
- which variant should be selected when multiple variants of the same resource exist.
For example:
Root component: Dashboard
Include:
- OrdersTable
- MetricRow
- RevenueMetric
- OrdersMetric
- NewCustomersMetric
- RevenueBarGraph
Once these choices are made, regular code immediately assembles an initial Spec, checks component properties and action arguments against the catalog schema, and streams an already renderable preview.
At this point, layout may still follow catalog order, but the user can already see a structurally valid intermediate result.
This differs from having a model emit the complete JSON token by token: Jev does not write serialized JSON. Code assembles the JSON from constrained choices. (json-render.dev)
Phase 2: decide parent-child relationships and ordering
After the components are selected, a second batch of decisions handles layout:
- which parent each component belongs to;
- which named slot inside the parent it should occupy;
- the order among sibling components.
The final structure may look like this:
Dashboard
├── OrdersTable
├── MetricRow
│ ├── RevenueMetric
│ ├── OrdersMetric
│ └── NewCustomersMetric
└── RevenueBarGraph
Code then checks:
- whether there is exactly one valid root;
- whether a parent-child cycle has been introduced;
- whether the depth exceeds the limit;
- whether a component has been placed in a valid slot;
- whether each candidate is used the permitted number of times;
- whether all properties, bindings, and action arguments pass schema validation.
If the composed layout is inconsistent, the system keeps the previously validated preview instead of emitting a broken UI tree.
For simple structures containing only one root, or only one child in a single slot, the second layout evaluation may not even be necessary. (json-render.dev)
Editing an interface is also a selection task, not a rewrite
json-render can also edit an existing Spec, for example:
- remove the Save button;
- move the orders table above the chart;
- replace one chart type with another provided candidate;
- change field order;
- replace a component configuration.
These edits usually use a sequential protocol:
- Select the element that needs to change.
- Select the new component recipe or destination.
- Apply the change in code.
- Validate the entire tree again.
Unchanged component IDs, state bindings, data, and compatible children are preserved whenever possible. The input Spec is not mutated directly. (json-render.dev)
Selecting a button does not mean the button executes automatically
json-render clearly separates “composing the interface” from “executing a business action.”
Jev may select a button bound to savePreferences, but the composer itself does not invoke that action. Execution happens only after the user clicks, and it is handled by the host application’s action handler.
The application still has to provide:
- user permission checks;
- argument validation;
- server-side authorization;
- data validity checks;
- idempotency and audit logging;
- secondary confirmation for dangerous operations.
The documentation specifically warns that registering an action in the catalog does not make it safe to accept arbitrary arguments. The composer cannot validate future runtime state, nor does it perform authorization on behalf of the application. (json-render.dev)
Structurally valid does not mean the interface is necessarily correct
json-render can ensure that the output conforms to its supported structure and schema, but it cannot guarantee that the interface Jev selects is complete, sensible, or visually good.
The documentation gives an example:
Generate a dashboard with the table at the top
This request may select only a table because it does not explicitly ask for metrics and a chart.
A more specific request:
Create a sales dashboard:
Place the orders table at the top;
place a row of revenue, order, and new-customer metrics below it;
place the weekly revenue chart last.
is more likely to select all required candidates and arrange them in the expected order.
This exposes an important boundary: Jev can only decide within the candidate space, while developers remain responsible for making that space complete and expressing the request clearly.
The reusable API described in the public documentation defaults to at most 32 evaluations, at most 32 elements created in a batch, and a maximum depth of 8. The public Playground tightens those limits to at most 14 elements per batch, 14 evaluations, and depth 4. When call, element, or depth limits are reached, the system may return a partial Spec, but “the process completed” still does not mean that the result is semantically correct. (json-render.dev)
The two examples are actually using the same architecture
Placed side by side, Browser Use and json-render solve different problems but share almost the same structure.
| Stage | Browser Use | json-render |
|---|---|---|
| User goal | Search for a flight, fill out a form, open a page | Create or modify an interface |
| State read by code | Visible DOM, controls, text, and values | Current Spec, component candidates, catalog, and tree structure |
| Finite candidate space | Click, type, select, scroll, and interactable elements | Component instances, parents, slots, and ordering |
| Jev is responsible for | Selecting the action and target | Selecting components, parent-child relationships, and order |
| Generative model is responsible for | Generating text only when input is required | In the Jev path, it does not freely generate UI; new copy and data must be supplied in advance or generated separately |
| Regular code is responsible for | DOM snapshots, freshness checks, execution, waiting, and result verification | Spec assembly, schema validation, tree validation, rendering, and action authorization |
| Main failure modes | Stale page state, missing targets, unsupported controls | Missing candidates, ambiguous requests, incomplete layout, poor selections |
| Final verification | Check whether the task goal was truly completed | Check whether the Spec is complete, usable, and consistent with product requirements |
Both follow the same formula:
Turn the environment into structured state
↓
Turn available actions into a finite candidate set
↓
Let Jev choose
↓
Let code validate and execute
↓
Observe the result again
Compared with asking a model to freely generate the next step, this approach gives up some flexibility in exchange for clearer control boundaries.
Why a model that does not generate text can still appear “intelligent”
Intelligence does not have to be expressed as an article, program, or conversation.
In many software workflows, the system only needs a decision:
- Which button should be clicked now?
- In which region should this element be placed?
- Should execution continue?
- Which component configuration best matches the user’s request?
- Has the current result completed the goal?
A general-purpose LLM can first generate an explanation and then wrap its answer in JSON. But if the code ultimately needs only one option, much of that intermediate text generation may not add value.
The Browser Use and json-render experiments move more of the “thinking process” into system design:
- developers define the state;
- developers define the candidate space;
- developers define the execution rules;
- the model fills only the semantic decision gaps that ordinary rules cannot easily cover.
This architecture does not eliminate errors; it changes their form.
Jev will not return a component or action outside the candidate set, but it can still:
- choose the wrong button;
- choose the wrong component;
- declare completion too early;
- select a weaker layout among several reasonable ones;
- omit necessary content because the request is ambiguous.
Type safety guarantees the interface, not the truth. The uploaded research report also emphasizes that constrained structure does not guarantee correct business judgment; question design, state modeling, thresholds, and independent validation remain central to production systems.
Which tasks fit this pattern
Browser Use and json-render suggest a practical test:
If a task can be decomposed into “choose from a finite candidate set,” it may be worth assigning that decision node to Jev.
Tasks that fit relatively well include:
- action selection in browsers and desktop applications;
- agent tool and skill routing;
- composing an interface from a controlled component catalog;
- choosing among candidate layouts;
- classifying email, support tickets, and documents;
- selecting relevant content from candidate evidence;
- deciding whether a result should be retried or escalated to a human.
Tasks that should not be handed directly to Jev include:
- writing long articles or customer-service replies;
- generating new copy that is absent from the candidate set;
- freely designing an entirely new visual system;
- writing complex programs;
- performing multi-step arithmetic or date calculations;
- inventing a solution when no candidate action exists;
- tasks requiring long-chain reasoning and open-ended planning.
Real products usually need a combination of models:
General-purpose model: generate goals, text, code, or candidate plans
Jev: judge, filter, and route among candidates
Regular code: validate, execute, fall back, and log
The small text model in Browser Use is a direct example of this division: Jev decides that text should be entered; the generative model decides what text to enter.
The real lesson is responsibility separation, not the two demos
The most reusable lesson from Browser Use and json-render is not that “Jev can browse the web” or “Jev can generate UI.”
A more accurate conclusion is:
- Browser Use changes browser operation from free-form generation into action selection over real DOM elements;
- json-render changes UI generation from free-form JSON writing into selection and ordering over the application’s own component catalog;
- Jev supplies semantic decisions;
- code limits permissions, maintains state, validates structure, and executes results;
- when open-ended text is needed, a generative model still handles it.
This architecture turns AI from the system’s only driver into one decision node inside a code-controlled workflow.
For teams that actually want to place AI inside production software, this may matter more than whether a model can generate a complete answer in one pass. System reliability ultimately depends not only on what the model chooses, but also on:
- what state the model was shown;
- which candidates the developer supplied;
- which actions the system permits;
- whether incorrect results can be intercepted;
- whether the system can reevaluate after a page or interface changes;
- whether completion is independently verified.
Not generating text does not mean Jev can do nothing.
It means that the model’s intelligence is expressed not primarily as a string, but as a set of choices that software can consume directly—and must still validate carefully.