Build Studio has two kinds of AI: a chat (the Build Studio AI bar, the same conversation as the AI Agent panel) that edits the open page through the engine's tools, and one-shot buttons on the element toolbar that ask the AI for suggestions. This page says what each one calls, what it writes, what it costs, and what doesn't work. It ends with how an agent that has no canvas (an AI employee, an outside agent on the API) builds pages with the same tools.
For what a page record looks like, see Build a website page; for screens, A page with several screens.
No AI action was run for this page. Each one spends the business's money and calls an outside model. Everything about the AI is read from the code, with the file and lines named, and marked code. What the site does with the resulting page was tested (the screens, the API writes); those results are in A page with several screens.
Every AI action in Build Studio
| Action, and where | What it does | Engine call | What it writes | Billed |
|---|---|---|---|---|
| Build Studio AI bar (bottom right of the canvas; reopened from the toolbar's AI button). Takes a prompt, up to 3 reference images, ↑ for history. | A chat turn. The prompt goes with a snapshot of the canvas: the selected element, a list of top-level sections, the body HTML (left out when an element is selected), body class and style, activeScreenId, the screen names, and the result of the AI's last edit (build-studio-ai-bar.tsx 46–145). | POST /ai/agent/stream; on /build-studio the engine loads the build-studio and appmint-pages skills (base-agent.ts 3082–3130) | through update_data: the canvas only, unsaved (below). Through page_write: the record directly. | yes, by tokens |
| Element toolbar → Rephrase, Improve, Summarize, Expand (select an element; the first button repeats the last one used) | Sends the element's text with a prompt for 3 versions, shows them one at a time; Apply puts the chosen one in (AIContextMenu.tsx 87–216, 281–341; ai-actions/text-tool.ts). | POST /ai/agent/stream as an ordinary agent request; the engine ignores the quickAction flags it sends (no reader in appengine) | the canvas, unsaved. On an element with tags inside (<p>Hi <b>there</b></p>), every piece of text becomes the whole suggestion, so it shows several times (AIContextMenu.tsx 299–316). Use it on a single heading or paragraph. | yes, by tokens |
| Element toolbar → Replace Image (only on an image, or an element holding one) | Asks the AI for a search keyword from the HTML around the image, then searches stock photos (Pexels) and shows 5; Apply sets src and alt. The tooltip says "AI generated image"; it is not generated (ai-actions/change-image.ts 145–263). | POST /ai/agent/stream for the keyword, then POST /repository/media/photos | the image's src/alt in the canvas, unsaved, pointing at the stock photo's URL | the keyword step, by tokens; the photo search is not AI |
| Element toolbar → Improve Layout | You describe the change; it generates new HTML and CSS for the element; Apply Update copies the new root element's attributes and, when the inside changed, puts the new inside in as text, so the markup shows on the page as literal <div …> (AIContextMenu.tsx 913–970; selective-virtual-dom.ts 710–714) | POST /ai/agent/stream | broken; undo with Ctrl-Z | yes, by tokens |
| Element toolbar → Add Animation | Generates anime.js code from a template. Apply Animation does nothing: it logs and closes (AIContextMenu.tsx 720–745). Animations that work are made in the animation panel. | POST /ai/agent/stream | nothing | yes, by tokens |
| Element toolbar → Add Interaction | Generates plain JavaScript; Apply Interaction keeps only attributes starting x-, @ or : from it, which plain JavaScript doesn't have and the site doesn't run (AIContextMenu.tsx 807–845, 823; ai-actions/interaction-action.ts 48) | POST /ai/agent/stream | nothing useful | yes, by tokens |
| Element toolbar → Saved | Suggestions and images you saved for later | none | the browser's local storage (ai-assist/ai-store.ts 127, 585) | no |
| Describe it, let AI build it (Create your website) | Makes an empty page from the brief's site, opens it in Build Studio, and sends the person's brief to the AI Agent once the page is open (setup-wizard/create-website-step.tsx 306–356, send at 352) | as the AI bar | as the AI bar: the canvas, unsaved | yes, by tokens |
| "Make me an image" in the chat | The agent's generate_image tool. Text-to-image goes to the business's AI provider and returns data[0].url (base-agent.ts 106–131). The current OpenAI and Gemini image models answer in base64 with no URL (integrations/open-ai/openai.api.ts 56–90), so the AI gets "Image generated successfully" and no address to put on the page. | the provider's image API | nothing on the page | yes, per image (upstream/upstream.service.ts 111–113) |
Not wired up: the "Choose a section to add" dialog's generator (section-selector-dialog.tsx 45–58 is an empty stub and nothing opens the dialog) and the "Generate Content with AI" dialog (content-generation-store.ts imports generateSiteSectionAction, which ai-actions/index.ts doesn't export). To add a section with AI, ask in the AI bar.
The engine tools behind them
| Tool | Defined in | Available when | What it writes |
|---|---|---|---|
update_data with path: "/build-studio" | ai/agents/base-agent.ts 2641–2668 (definition 3570) | every agent. It only works while the page is open in Build Studio: the engine sends it to the browser and waits up to 15 s for the browser to confirm; without it the result says confirmed: false, "NOT applied on screen" | the canvas of the open page, through the Studio's updateAIData (websitemint build-studio-store.tsx 560–830). Never the record. |
navigate /build-studio | base-agent.ts 3419–3460 | every agent | opens a page in Studio: { appType: "web", id: "<sk>", pageName }, or { appType: "web" } for a new one |
page_write | ai/skills/appmint-pages.skill.ts 28–147 | the appmint-pages skill: loaded on /build-studio, /pages, /site and their sub-routes, or in the build-studio / studio apps (24–25); anywhere else after load_skill appmint-pages | the record: html into the active screen (or data.html on a page without screens); title, description; the top-level style (javascript, css appended unless replaceStyle; scriptLinks, styleLinks merged) |
save_page_asset | ai/skills/build-studio.skill.ts 751–855 (definition 773) | the build-studio skill: on /build-studio (43) | a CSS/JS (or any text) file at path in the org's file storage; returns { url, path, contentType, size }, the URL signed and with ?ts= to break caches |
fetch_asset | build-studio.skill.ts 751–855 (definition 755) | same | nothing; reads an http(s) URL of up to 200 KB, text types only |
generate_image | base-agent.ts 106–131, 1725–1800 (definition 3210) | every agent | nothing on the page (see above) |
repo_find, repo_find_one, repo_create, repo_update, repo_delete | ai/skills/repository.skill.ts 30–112 | the repository skill | the record; repo_update shallow-merges into data only and can't reach style |
call_appengine | ai/skills/api-passthrough.skill.ts 218 | the API passthrough skill | any endpoint, e.g. PUT /repository/create with style |
load_skill | ai/skills/skill-manager.skill.ts 57 | the skill manager | loads appmint-pages (or build-studio) off its routes |
update_data on /build-studio, field by field
What the Studio does with each field the AI sends (build-studio-store.tsx 560–830, code):
| The AI sends | The canvas | At Save |
|---|---|---|
html as a full document, with no nodeId or selector | the active screen is replaced whole: its head goes into the canvas head, its body into the canvas | stored as the active screen |
html as a fragment, no target | replaces the canvas body; the screen's head stays | stored in the active screen's body |
nodeId (or selector) + html, replaceMode outer (default) or inner | swaps that element only, then checks the result: wrong nesting, a lost data-node-id or a missing target come back in the next turn's lastUpdate (targetFound, validated, validationError, resolvedNodeId, warnings) | stored |
bodyStyle, bodyClass | set on the canvas <body> (the editor's own classes kept) | written into the active screen's <body> tag (selective-virtual-dom.ts 1446–1459) |
screens, activeScreenId, or any other key | copied into the Studio's in-memory page only; the canvas's own screen list is not reloaded | lost. Save rebuilds screens, activeScreenId and html from the canvas's list (build-studio-store.tsx 410–433) |
So the AI can change the screen that is open, but can't add a screen, change another screen, switch the live screen or add animations through update_data, although the skill tells it to (build-studio.skill.ts 498–506, 611–619). Do those in Studio by hand, or through the record (below) with the page closed in Studio.
Every AI change goes onto the undo stack first (Ctrl-Z undoes it), marks the page unsaved, and stays unsaved until the person presses Save. Save also makes the screen open on the canvas the live one (A page with several screens).
page_write and the open canvas don't know about each other. In Build Studio the AI has both tools. page_write saves to the record at once; the open canvas doesn't reload it, and the person's next Save sends the canvas's own copy of the page. If the AI wrote with page_write, close and reopen the page in Studio before anyone saves.
page_write in detail
appmint-pages.skill.ts 57–147, code:
- Screens page:
htmlreplaces the screen named byactiveScreenId(or the first). Other screens anddata.htmlare left alone, sodata.htmlis out of date afterwards; the site doesn't read it. The result'snotessay "wrote into screen N of M". - No screens:
htmlgoes intodata.html. - Old
data.sections/data.contentpage: refused unlessconvertLegacyContent: true; then the old content is copied todata.legacyContentBackupand cleared. - Style:
javascriptandcssare appended to what is there, joined by a blank line;scriptLinksandstyleLinksare merged without repeats.replaceStyle: truereplaces all four. Running the same call twice adds the code twice. - It saves the whole record and returns
{ success, id, notes, styleAppended, version }. A save is not proof; check the site.
What it costs
- Every AI call above is billed to the business when it runs on Appmint's shared AI: by the tokens it used, per model, with Appmint's markup; images per image (Service rates). Before each call the engine checks the business's agreement and balance (
upstream/upstream.service.ts820–835); after it, it meters the use (361;usage/service-pricing.service.ts1494–1555). - A business on its own AI key pays its provider directly; Appmint bills nothing for those calls.
- There is no flat fee per AI request (
usage/usage.constants.ts13–17), so a long chat turn that reads the page and edits several times costs more than one line. Every toolbar button is a full agent request, not a cheaper path. - Free: the stock-photo search, Saved suggestions, undo, Save, and anything done by hand on the canvas.
Stop: before running any AI action for the person (a chat turn that will rebuild a page, a text tool, an image), say that it is billed to the business, and wait for yes. Don't generate several versions or retry without asking.
Where Appmint's own AI instructions are wrong
The build-studio and appmint-pages skills are the instructions the in-app AI gets. These parts don't match the code; follow the right-hand column.
| The instructions say | What is true | Proof |
|---|---|---|
wm-drawer: "Attrs: position="left|right|top|bottom", trigger-text, overlay. Content as direct children." wm-dialog: "trigger-text, overlay. Content as children." (build-studio.skill.ts 341–344) | The side is direction (default right); there is no trigger-text; both need a slot="trigger" child (it may be hidden) and a slot="content" child, or they never build. | custom-elements/drawer.ts 110–121, dialog.ts 165–175; tested in Appmint components on a page |
wm-navigation — "top nav bar" (350) | Not in the bundle the site loads; it stays an inert tag. Use plain links, wm-mega-menu or a wm-drawer. | custom-elements/bundle.ts 16–23 |
wm-storefront-product-control — "product listing/filter widget" (352–353) | One product's options, quantity and buttons. On the site it reads the product from a data-product attribute and only fires events; your code adds to the cart. | storefront-product-control.ts 389–408; components page |
"wm-drawer and wm-dialog default to overlay="false"" (appmint-pages.skill.ts 375–376) | The backdrop shows unless overlay="false" is written. | drawer.ts 113, dialog.ts 165 |
page_write html: "Writing this clears screens/sections/content." (appmint-pages.skill.ts 41) | It never clears screens: it writes the active screen. Old sections/content are cleared only with convertLegacyContent: true, after a backup. | appmint-pages.skill.ts 67–112 |
"Short / page-specific scripts → <script> block in <head>" (build-studio.skill.ts 184), and "NO separate styleLinks/scriptLinks/page.style.* fields" (30–31) | Page code belongs in Page settings → Style (style.javascript, or a file in scriptLinks). A <script> in the page runs on a full load only, not after the site moves the visitor without a reload, and never inside root's screens. style is also the only CSS the sign-in, account and checkout pages load. | Build a website page step 4; JS engine; Built-in pages |
"An inline <script> inside data.html NEVER runs" (appmint-pages.skill.ts 210) | It ran on a full load (tested, in a screen's head and body); it doesn't after an in-app move. Still don't use it. | A page with several screens |
Effects notify:…, close-dialog, open-dialog:#id, and refresh (400–402, 450) | notify shows nothing on a site page; open-dialog / close-dialog don't open or close; a bare refresh re-runs nothing. Use set-state + data-show, ui.dialog, refresh:#id. | Build a website page |
data-show="customer.signedIn" and a sign-in link to /signin (471–472) | customer.signedIn is true for every visitor; use customer.current.data.email. Sign-in is at /login. | Build a website page; site page routing |
Change another screen, add a screen or add animations by sending screens through update_data (498–506, 611–619) | Dropped at Save (above). | build-studio-store.tsx 410–433, 795–797 |
The AI bar's own header comment names a bs_set_page_html tool (build-studio-ai-bar.tsx 10–15) | There is no such tool; edits go through update_data. | appengine has no bs_set_page_html |
Ask first
- Which page, on which site? Open it in Build Studio for the chat to edit it.
- What should change, in their words? One element, one section, or the whole page. Scope decides whether the AI swaps one element or rewrites the page.
- Is it all right to spend AI credit? Every action is billed (see above), unless the business uses its own AI key.
- Which screen? The chat edits the screen open on the canvas, and Save makes that screen live.
- Images: their own photo (upload it to Files first), a stock photo (Replace Image), or a generated one (billed, and it doesn't reach the page today).
Steps
1. Build Studio: open the page on the screen to change
Open /app/build-studio/<page name> (or navigate to /build-studio with { appType: "web", id: "<sk>", pageName: "<name>" }). Click the chip of the screen to change in the screen bar. Select an element on the canvas if the change is to one element.
Hands on: the page's id and the screen open on the canvas. Check: the canvas shows the page, on the right screen; the Build Studio AI bar is at the bottom right (if not, the toolbar's AI button brings it back).
2. Build Studio AI: make the change
Stop: say the change will be billed as AI use, and wait for yes.
Type the request in the AI bar (or, as the in-app AI, call update_data with path: "/build-studio"):
| Request | What the AI should send |
|---|---|
| One element selected, any change to it | { "nodeId": "<selectedElement.nodeId>", "html": "<that element, changed>", "replaceMode": "outer" } |
| A named section ("the pricing section") | the same, with the nodeId from sections[] |
| Only the words inside an element | replaceMode: "inner" and the new inside |
| The whole page's background | { "bodyStyle": "…" } with nothing selected |
| A new page from scratch | html as a full document, CSS in its <head>, no <script> |
For text in one element, the toolbar's Rephrase / Improve / Summarize / Expand are the cheap way: choose, read the three versions, Apply one. For a photo, Replace Image → Apply. Don't use Improve Layout, Add Animation or Add Interaction (see the table at the top).
Hands on: the canvas, changed and unsaved.
Check: the canvas shows the change and nothing else changed. When the in-app AI made it, the next turn's context.currentPage.lastUpdate shows success: true; on a partial swap, targetFound and validated are true.
3. Build Studio: Save
The person presses Save. Make sure the screen visitors should see is the one open.
Stop: on a site that serves drafts, Save puts the change live. Say what will change at which address, and wait for yes. On a site with publishedPagesOnly, publishing is its own step (Publish, unpublish and restore a page).
Hands on: the saved page.
Check: repo_find_one on the page: the active screen's html has the change, activeScreenId is the screen visitors should see, data.html equals it.
4. Check it on the site
Open https://<site host>/<slug> in a browser, not a grep of the source (the source carries every screen's HTML in a script payload). The change shows; an image from Replace Image loads; text applied with a text tool appears once.
Building pages without the canvas
An AI employee, the AI Agent on another screen, or an agent outside Appmint has no canvas, so update_data does nothing for it. It writes the record, the way Build Studio stores it:
| Step | In-app agent | Outside agent (REST, header orgid, a signed-in user's token) |
|---|---|---|
| Tools | load_skill appmint-pages unless on /build-studio, /pages or /site | none |
| Find the page | repo_find on page, then repo_find_one | POST /repository/find/page, then GET /repository/get/page/<id> (a find result is partial) |
| A new page | repo_create on page with screens, activeScreenId, html (shape in Build a website page) | PUT /repository/create with { "datatype": "page", "isNew": true, "data": { … }, "style": { … } } (tested) |
| Change the live screen's body | page_write { id, html }, then repo_update patch: { html } to keep the mirror | read the record, change data.screens[<active>].html and data.html, then POST /repository/update/<id> with the whole record (tested) |
| Change another screen, add or reorder screens | repo_update patch: { screens: [ …all of them… ], activeScreenId, html } | POST /repository/update-partial/page/<id> with { "data.screens": [ … ], "version": <current> } (tested). Never { "data": { … } }. |
| Page CSS / JavaScript | page_write css / javascript (appended), replaceStyle: true to replace | the top-level style in the whole-record update (tested) |
| A CSS or JS file | save_page_asset, then its URL in styleLinks / scriptLinks | POST /repository/file/upload (location, file), then the URL in style |
| Check | open the site address in a browser | same |
Every rule on what renders is in Build a website page and A page with several screens.
Stop: a page saved on a site that serves drafts is live at once. Show the URL and what will be on it, and wait for yes.
When it's done
Tell the person: what changed and on which screen; which AI actions ran and that they were billed; that the change is saved (or still waiting for their Save); and the address to see it. If they used an action that doesn't work (Improve Layout, Add Animation, Add Interaction, an image generated in chat), say so and what to do instead.
What it can't do yet
- The chat can only change the screen open on the canvas. Screens, the live screen and animations sent through
update_dataare dropped at Save. - Nothing the AI does in Studio is saved until a person presses Save, and Save makes the open screen live.
- Improve Layout breaks the element (its new inside shows as text), Add Animation and Add Interaction apply nothing, but all three are billed.
- Text tools repeat the suggestion in every piece of text of an element that has tags inside.
- Replace Image is a stock-photo search, not AI image generation.
- An image generated in the chat doesn't reach the page with today's image models (no URL comes back), but is billed.
- No AI section generator outside the chat; the section dialogs are not wired up.
page_writewhile the page is open in Studio can be overwritten by the next Save from the canvas.- Appmint's AI instructions are wrong in places (table above); the in-app AI may still follow them.
Don'ts
- Don't run an AI action without telling the person it is billed.
- Don't tell the person a change is done before they have pressed Save and you have seen it on the site.
- Don't send
screensoractiveScreenIdthroughupdate_data; change screens in Studio or through the record. - Don't use Improve Layout, Add Animation or Add Interaction.
- Don't apply a text tool to a whole section; select one heading or paragraph.
- Don't use
page_writeon a page someone has open in Build Studio. - Don't follow the in-app instructions for
wm-drawer,wm-dialog,wm-navigation,<script>in the head,notify, orcustomer.signedIn. - Don't put an operator token in a page; page code calls the engine through the site's
/api.