In edit mode the tray gets a "Describe the change" field. The user types a request in their own language. The hosted backend asks the model for a change, validates the answer against your catalog and locks, and returns a proposal. The SDK draws it as a preview with a list of what changes; the user taps Apply or Discard. Apply is validated again on the device, saved, synced and recorded as one undo step.
| Route | Edits |
|---|---|
POST /ai/patch |
one screen: layout, theme, options, style; on a user screen it may place portable parts and generic pieces |
POST /ai/navigation |
the navigation document: entries, menus, start screen |
POST /ai/designs |
named designs: create, switch, rename, duplicate, delete, restore, undo |
The model is Claude by Anthropic.
Turn it on
AI needs sync. It is off for an app until you switch it on in the dashboard, and off in the app until you configure it in the SDK:
let engine = TweakLaneEngine(
catalog: makeCatalog(), userId: account.id, sync: syncConfiguration,
ai: AITweakConfiguration(providerName: "Anthropic",
privacyPolicyURL: URL(string: "https://example.com/privacy")))
engine.ai?.isEnabled = false // host switch at any time; nothing is sent while off
A kill switch also turns AI off.
Consent
Before the first request leaves the device, a sheet names the provider, says what is sent and
asks for permission. App Review guideline 5.1.2(i) requires disclosing that personal data is
shared with a third-party AI and obtaining explicit permission. Consent is stored per user on the
device and on the server, and the server refuses prompts without it (HTTP 403
consent_required). It is tied to the provider name and a consent version: change either and
users are asked again. Users can turn AI off at any time; consent is revoked on the device at once
and on the server when reachable.
| Route | Purpose |
|---|---|
GET /ai/consent |
{enabled, provider, consent} |
PUT /ai/consent |
body {provider, consentVersion}; records consent |
DELETE /ai/consent |
revokes consent |
What is sent to the model
The server, never the app, calls the model. It sends an instruction text, your catalog's component ids, display names, lock levels and alternatives, the locked node ids of the screen, the screen's current layout (node ids, component ids, order, visibility, size, alignment, props and labels) and the user's request. No user id, token, device data or other screens. Props are part of the layout, so keep personal data out of props.
How answers are checked
The model's output is treated as untrusted. It passes, in order:
- a size cap (32 KB by default)
- a JSON object with operations (at most 50) or a full document; unknown fields are dropped
- no URL-like strings that are not already in the layout or catalog
- operations applied with the same rules as manual edits
- AI policy: no new components on host screens, none removed, swaps only within alternatives,
props only to values the app already uses, labels and
lockfields reset to your own - the full server validator: catalog, locks, structure, limits
- it must change something
Any failure returns {status: "rejected", reasons} and nothing is applied. Locks can't be
bypassed by a prompt: a request to hide the prices or drop Restore Purchases comes back rejected
or as a safe substitute with a note.
Limits and retention
| Setting | Default |
|---|---|
| Requests per user | 5 per minute, 30 per hour (HTTP 429 rate_limited) |
| Timeout | 20 s |
| Prompt length | 500 characters |
| Answer size | 32 KB |
Store prompts (ai_store_prompts) |
on; off stores only the prompt length |
| Prompt log retention | 30 days |
Each AI request writes one log row per app: user id, screen, provider, model, catalog hash, the prompt (unless switched off), outcome, reason codes, output size and latency. Rows older than the retention period are deleted daily. Consent records are kept until the app is deleted, as the record of consent.