Enterprise / ISV SPA-delivery surface — artifacts, health, handoff zip, and the Studio wizard / LLM-authored extension generators.
Advanced App Builder
Enterprise / ISV tier tenants see an Advanced tab on every app at
/admin/apps/:id/advanced. It's a thin shell around the app's
advanced_config_json envelope and a handful of delivery actions.
This page walks through the surface. For the raw spec see
Advanced-App-Builder-Enterprise-Spec.md at the repo root; this
doc is the admin-facing rewrite.
What lives here
| Section | Purpose |
|---|---|
| Experience strategy | Pick workspace pages (Path 1) vs extension SPA (Path 2). Drives AI Designer proposals. |
| Artifacts | Two markdown documents — engineering_spec_md and feature_phases_md — that describe the app's SPA shape. Authored by the admin (or the AI Designer). |
| Nav + page health | Every nav entry classified as ok / missing_page / orphaned_extension, with per-row deep links. |
| Handoff zip | One-click download with both markdowns + a manifest stub + a README for an external SDK developer. |
| Generate extension | Produce a working extension bundle — via the Studio wizard or LLM-authored. |
Authoring artifacts with AI
The Advanced page's two markdown editors (engineering_spec_md
feature_phases_md) feed the bundle generator. Drafting them from a blank page is tedious, so an Author artifacts with AI button above the editors calls a frontier Claude model with a pinned spec-writing prompt and populates both fields for you to review and edit before Save.
- Requires the same Anthropic key as the bundle generator
(
llm.anthropic_api_key). The button is disabled with a tooltip when no key is stored — wire one up at/admin/settings/llm. - Each call ≈ $0.05. Metered as a distinct resource for spec authoring (separate from bundle generation) so spend reports can distinguish spec authoring from bundle generation.
- Overwrite protection: if either editor already has content, clicking the button toggles an inline confirm strip. A second click (or Cancel) proceeds / aborts. No blocking modal — the text just warns.
- Nothing persists on the LLM call itself. The response
goes into the textareas as draft; the admin clicks Save
to write them to
apps.advanced_config_json. - If you have a half-authored draft, the button passes your
existing content to the prompt as an
<existing_*>XML block — the model refines rather than blank-slates.
Failure modes mirror the bundle generator: 422 for missing key or invalid LLM JSON, 502 for upstream transport errors, every call is metered regardless.
Generate extension
The generator turns your annotated entities + chosen layout + brand into a scan-clean ES-module extension bundle the host knows how to load. Two synthesis paths:
Synth paths
| Path | Output | When to use |
|---|---|---|
| Studio wizard | Deterministic — same picks → identical bytes. A 4-step wizard (Layout → Branding → Entities → Review) emits a themed, multi-page React/Tailwind app: per-entity list / detail / new / edit pages, plus a textual workflow viewer + advance-state buttons on detail pages of FSM-bearing entities. | The standard path. Free, no LLM, predictable. |
| LLM-authored | A frontier Claude model authors a bespoke React functional component from your engineering_spec + feature_phases markdown. Every call is non-deterministic. | Bespoke UI shaped by prose — when "list / detail / form" doesn't fit the requirement. ≈ $0.05 per call. |
Pick the path with the segmented control above the action buttons. LLM mode is disabled until an Anthropic key is stored — see Setup below.
For the full Studio walkthrough — what each step does, how the preset palettes work, what the generated bundle contains — see the Studio wizard page.
Studio install flow
Clicking Open Studio… launches the wizard modal. The wizard collects your selections without hitting the server. Only on Install at the Review step does the client:
POST /api/v1/tenant/themesto materialize the chosen palette as a tenant theme (named<App> themeby default — editable in the Review step).POST .../generate-extensionwithmode: "install", synth: "studio", the newtheme_id, the chosenlayout, and the tickedentity_names.
Both calls are scoped to the authenticated tenant. If theme
creation fails, the wizard surfaces the error inline and never
calls generate. If generate fails after theme creation, the
wizard names the orphaned theme so you can delete it from
/admin/themes (or retry the install — same
theme, no orphan).
LLM install flow (preview-then-confirm)
Clicking Install while synth = llm runs a two-step flow:
POST .../generate-extensionwithmode: "preview", synth: "llm".- Modal opens with the authored source in a scroll-locked
<pre>. - Cancel discards and closes the modal. Confirm install
fires a second LLM call with
mode: "install", synth: "llm"that actually writes thetenant_extensionsrow.
The double-LLM-call (≈ $0.10 per install flow) is deliberate — each confirm re-rolls the bundle, so admins who don't like the first preview can Cancel → Install → preview again for a fresh attempt. If you want the same source twice, use Download after Preview-then-Confirm and re-install locally via the Extensions page.
Delivery modes
| Mode | Behavior |
|---|---|
| Open Studio… (Studio only) | Opens the wizard modal. Install + Download buttons live inside the wizard's Review step. |
| Download (LLM only) | Streams a three-file zip: manifest.json, index.mjs, README.md. Nothing persists server-side beyond a delivery.last_generated_at timestamp. |
| Install (LLM only) | Runs the preview-then-confirm flow above. |
| Preview (LLM only) | Returns the authored source as JSON and opens a modal. No persistence. Used internally by the Install button — you don't call it directly from the UI. |
What you get out
Regardless of path, the manifest advertises exactly one page at
/ext/<id>/<app-slug>. Required scopes are pinned per path:
Studio bundles ship ["data:read", "data:write"] (CRUD always
writes); LLM bundles get scopes inferred from the authored
source (read-only by default; widened to data:write if the
bundle calls useCreateEntity, etc. — see Real-data
extensions below).
Installed rows are always is_active = 0. The admin must
explicitly activate from /admin/extensions. Deliberate for
LLM output (untrusted input — the scanner + heuristic validator
are defenses-in-depth). Same gate applies to Studio bundles
even though they're deterministic — no exceptions.
Setup for LLM mode
LLM-authored generation needs a dedicated Anthropic API key, stored separately from the AI Designer's primary LLM config so OpenAI / Gemini tenants can opt in without reconfiguring their chat.
Configure at /admin/settings/llm:
- Scroll to Anthropic API Key — code generator.
- Paste an
sk-ant-…key. - Save Settings. The LLM-authored toggle on the Advanced App Builder becomes enabled immediately.
The primary API Key field above is independent. You can run OpenAI / Gemini / DeepSeek / custom for AI Designer chat while still having an Anthropic key on file for the code generator.
What's pinned
The LLM synthesis path is not tenant-configurable beyond the api_key. Pinned to:
- Provider:
anthropic - Base URL:
https://api.anthropic.com - Model: a pinned frontier Claude model
- Required scopes on the emitted manifest:
["data:read"] - Bundle size cap: 128 KiB
Code-generation quality is load-bearing, and cheap models produce unparseable JS. Changing any of these is a source-level edit.
Failure modes
The generator surfaces four distinct HTTP responses — all admin-readable in the red error banner below the buttons:
| Status | Cause | Fix |
|---|---|---|
400 Bad Request | LLM mode: blank artifacts. Studio mode: missing layout, missing theme_id, theme not owned by tenant, or empty entity_names. Either mode: invalid synth or mode value. | Fill in the markdown editors (LLM); finish the wizard's required fields (Studio). |
422 Unprocessable | LLM mode: missing Anthropic key → error points at /admin/settings/llm. Or: validator rejected the authored bundle (forbidden token, missing import, size cap). | Add the key, or re-roll via Install (cheaper to retry than to debug). |
502 Bad Gateway | Anthropic API returned an error or timed out. | Transient — try again in a minute. Check your Anthropic account is in good standing. |
500 Internal | Our bug. | File an issue with the request timestamp + tenant id. |
Every LLM-mode call is metered against the LLM-generation meter
in your usage ledger regardless of
outcome — a 422 validator reject or 502 transport failure
still costs. Log-aggregation carries the full detail (actor,
model, bundle bytes, violation count, outcome) via
tracing::info! structured fields.
Cost expectations
Rough estimate at ship: $0.05 per LLM call, scaled by your
organization's wholesale_discount_multiplier. The Install
flow uses two calls (preview + confirm), so ≈ $0.10 per
installed extension. Studio mode is free (no upstream).
Tunable — the LLM-generation meter's base cost lives in the platform's metering configuration; adjust once you have real spend data.
Real-data extensions (SDK 0.2.0+)
Generated bundles read and write live tenant data. The Studio wizard and the LLM-authored path both have access to the full SDK hook surface, so an extension can list entity records, render FSM workflow state, and mutate tenant data through the exact same backend API the admin shell uses.
What this means for generated bundles
- Studio wizard — list, detail, new, and edit pages compose
<SmartField>from each entity's annotations, so labels, required flags, enum options, and field types all flow from/admin/entities/:name/annotationsautomatically. FSM detail pages additionally mount<WorkflowSection>with the current state badge, advance buttons (one per legal outgoing transition), and the merged event-log history. - LLM-authored — the system prompt enumerates every shipped
hook with signatures + three worked examples (paginated
useEntities,useJob+<WorkflowHistory>, transition button). The model is forbidden from emittingconst SEED_*/const FAKE_*stubs — prompt-level enforcement; slips get caught in human review at the Preview modal. - Scope inference — Studio bundles always ship
["data:read", "data:write"](CRUD always writes). LLM bundles go through a pre-install analyzer that greps the generated source for hook names and widens the manifest'srequired_scopes. A bundle that callsuseCreateEntitylands with["data:read", "data:write"];useTransitionJobadds"workflow:execute";useCancelJobadds"workflow:admin". The admin sees the inferred set in the amber scope-disclosure strip on the preview modal before confirming.
Realtime auto-refresh
Every read hook subscribes to the provider's shared WebSocket by
default. useEntities('widget') refreshes whenever another
session creates, updates, or deletes a widget; useJob(id) +
<WorkflowHistory jobId={id} /> redraw on every transition — no
polling, no backend changes on your end.
Opt out per hook when a view is deliberately snapshot-style:
useEntities('widget', undefined, { realtime: false });
See the CRUD UI recipe for a full list + create + update + delete page using these hooks.
Related
- Apps overview — the non-advanced surface.
- Extensions — where installed bundles land and are activated.
- Settings → LLM — where the Anthropic key is stored.
- SDK reference — the hooks + components the LLM has in its prompt context.
- CRUD UI recipe — full CRUD page using the 0.2.0 hook surface.