# Install AI Autocomplete (Swift / iOS) — agent instructions

You are an AI coding agent installing **AI Autocomplete** into the user's iOS app. Follow these steps in order. Do not skip the "Hand off to the user" section — those steps are not yours to perform.

Reference docs: https://ai-autocomplete.com/docs/swift/getting-started

Requirements: **iOS 17+**, **Swift 5.10+**. The SDK has zero third-party dependencies.

AI Autocomplete uses two key types:

- **Public keys** (`pk_v1_...`) — safe to ship in app binaries. Sent as a Bearer token directly from the app. Use this for the basic install path below.
- **Secret keys** (`sk_v1_...`) — server-only. The user's backend exchanges a secret key for a short-lived access token (`at_v1_...`) that the SDK consumes. Recommended for production. See the "Production: secret key + access token" section at the end.

---

## Step 0 — Confirm the project shape

Sanity-check the project: there should be an `.xcodeproj` / `.xcworkspace`, or a `Package.swift` for an SPM-based app. Confirm the deployment target is iOS 17 or later — if it's lower, **stop and tell the user**; the SDK won't build for earlier targets. Note whether the app is SwiftUI or UIKit (look at the app entry point: `@main struct ... : App` vs `AppDelegate`).

## Step 1 — Ask the user which integration tier

The SDK ships three tiers. **Stop, ask the user, and wait for their answer.** Do not pick on their behalf.

> "AI Autocomplete ships three integration tiers — which do you want?
>
> **Tier 1 — Full view (`AIAutocomplete` / `AIAutocompleteView`)**
> Drop-in. The SDK owns the text field, pills, dropdown, and all state. You pass a configuration (auth, callbacks) and you're done. This is the right choice for most apps.
>
> **Tier 2 — Your input + SDK dropdown (`AIAutocompleteDropdown`)**
> You own the text field; the SDK renders its dropdown next to it, driven by a shared `AIAutocompleteController`. Pick this if you have a custom input (rich editor, search bar, existing form field).
>
> **Tier 3 — Headless (`AIAutocompleteCore` only)**
> The `@Observable` controller exposes all state and actions; you render everything yourself.
>
> Which one — full view, your input + SDK dropdown, or headless?"

If the user says "you decide" or seems unsure, recommend Tier 1.

## Step 2 — Add the package and wire up the SDK

Add the Swift package:

- **Xcode project:** File → Add Package Dependencies… → paste `https://github.com/magicx-ai/ai-autocomplete-ios.git` → add the library product matching the tier (below) to the app target. If you cannot drive Xcode, give the user these exact clicks and wait.
- **Package.swift-based app:** add the dependency and product directly:

```swift
dependencies: [
    .package(url: "https://github.com/magicx-ai/ai-autocomplete-ios.git", from: "1.0.0")
],
// in the app target:
dependencies: [
    .product(name: "AIAutocompleteSwiftUI", package: "ai-autocomplete-ios")
]
```

Library product per tier: Tier 1 or 2 in SwiftUI → `AIAutocompleteSwiftUI`; Tier 1 or 2 in UIKit → `AIAutocompleteUIKit`; Tier 3 → `AIAutocompleteCore`.

Add **exactly one** product — the one matching your UI layer. Each product re-exports the layers beneath it, so `AIAutocompleteCore` comes in transitively: the `import AIAutocompleteCore` line in the examples below compiles without adding Core as a separate dependency. Do not add multiple products.

> **Expected:** the package resolves and displays in Xcode as `AIAutocompleteKit` even though the repo is `ai-autocomplete-ios`. That's correct — you did not add the wrong package.

Then add the integration code for the tier the user picked. **Hardcode the public key as the literal string `"PASTE_PUBLIC_KEY_HERE"`** — leave that placeholder in the code. The user will replace it after Step 3. Do **not** invent a key. Once the install is verified end-to-end it's the user's call how to manage the key long-term.

If you don't know where to mount the view, ask the user before guessing.

**Tier 2/3 rule: always reset after submitting.** Tier 1 does this automatically. In Tiers 2 and 3 the consumer owns the submit flow — call `controller.submitAndReset()` (or `submit()` then `reset()`) at the end of your submit handler. It clears the input and rotates the per-session ID so the server's history stays accurate; skipping it degrades suggestion quality on subsequent submissions.

### Tier 1 — Full view (SwiftUI)

```swift
import AIAutocompleteCore
import AIAutocompleteSwiftUI
import SwiftUI

struct MyAutocompleteView: View {
    var body: some View {
        AIAutocomplete(configuration: .init(
            apiConfig: .apiKey(.init(apiKey: "PASTE_PUBLIC_KEY_HERE")),
            onSubmit: { result in
                print(result.query)
                print(result.completedParams)
            }
        ))
        .padding()
    }
}
```

### Tier 1 — Full view (UIKit)

```swift
import AIAutocompleteCore
import AIAutocompleteUIKit
import UIKit

let autocomplete = AIAutocompleteView(
    configuration: .init(
        apiConfig: .apiKey(.init(apiKey: "PASTE_PUBLIC_KEY_HERE")),
        onSubmit: { result in print(result.query) }
    )
)
// Pin leading/trailing/top; the view sizes its own height via intrinsicContentSize.
```

### Tier 2 — Your input + SDK dropdown (SwiftUI)

```swift
import AIAutocompleteCore
import AIAutocompleteSwiftUI
import SwiftUI

struct MyAutocompleteView: View {
    @State private var controller = AIAutocompleteController(
        configuration: .init(apiConfig: .apiKey(.init(apiKey: "PASTE_PUBLIC_KEY_HERE")))
    )
    @FocusState private var isFocused: Bool

    var body: some View {
        VStack {
            TextField("Create a", text: controller.textBinding)
                .focused($isFocused)
                .onChange(of: isFocused) { _, focused in controller.setFocused(focused) }
                .padding(12)
                .background(.background, in: .rect(cornerRadius: 12))
                .overlay(RoundedRectangle(cornerRadius: 12).stroke(.secondary.opacity(0.3)))
                .aiAutocompleteDropdown(controller) // apply AFTER the chrome so it anchors to the full visual boundary
                .zIndex(1)                           // float above any sibling below

            Button("Submit") { controller.submitAndReset() }
        }
    }
}
```

**Positioning the dropdown (read this — it's the most common Tier 2 mistake).** `.aiAutocompleteDropdown(_:)` is a floating overlay, not a laid-out element. It anchors to the edge of whatever view you attach it to (bottom edge by default; flip with `optionsPosition`). Three rules:

- **Attach it to your field's outermost container, not the bare `TextField`.** Apply it *after* the padding / background / border modifiers (as above). Attaching it to the inner text anchors the dropdown inside your padded area instead of the field's visual edge, so it appears misaligned.
- **Raise its `zIndex`.** Because it overlays rather than reserving layout space, sibling views rendered after it (anything below in the stack) can paint on top. Give the field container a higher `.zIndex` than its siblings. The value is relative: `.zIndex(1)` wins when siblings use the default `.zIndex(0)`, but if any sibling already sets an explicit zIndex, use one higher than the highest of them.
- **Watch for clipping in `ScrollView` / `Form` / `List`.** Those clip their content, which can crop the floating dropdown. Attach to the field container and raise its zIndex; if an ancestor still clips it, place the field outside the scroll clip or use Tier 1, which manages its own overlay window.

### Tier 3 — Headless

Use `AIAutocompleteController` from `AIAutocompleteCore` directly: bind text via `controller.textBinding` (SwiftUI) or call `updateText(_:)`, render from `segments` / `filteredOptions` / `isDropdownOpen`, select with `selectOption(_:)`, and call `start()` when your UI appears. See https://ai-autocomplete.com/docs/swift/api-reference for the full state/action surface.

## Step 3 — Hand off to the user (you cannot do this)

After your code changes are complete, **stop** and print the following to the user verbatim — including the bullets and URLs. These are browser tasks; the agent cannot perform them.

> **Two browser steps you need to do before this works:**
>
> 1. Go to **https://ai-autocomplete.com/edit** and create your product. This configures what your autocomplete will suggest (the prompts, parameter types, and option lists).
> 2. Go to **https://ai-autocomplete.com/account/keys**, create a **public key**, copy it, and paste it in place of `PASTE_PUBLIC_KEY_HERE` in the code. The public key starts with `pk_v1_`.
>
> Once both are done, build and run on a simulator or device. Reply when you've confirmed it works (or share what's failing).

Do not pretend you can open URLs, create the product, or generate the key on the user's behalf. Wait for their reply before moving to Step 4.

## Step 4 — Verify it works

Once the user replies that they've pasted the key, walk them through verification:

1. Build and run (`⌘R`) on an iOS 17+ simulator or device.
2. Open the screen where you mounted the autocomplete and confirm it renders and the dropdown shows suggestions on focus.
3. If a **401 Unauthorized** appears (set `onError` and log it if needed), the key is wrong or it isn't a public key — ask the user to re-copy it from `/account/keys` and confirm it starts with `pk_v1_`.
4. If you see **404** or "no suggestions returned", the product likely wasn't created yet — ask the user to confirm they finished Step 3.1.

Wait for an explicit "yes, it works" before moving on.

## Step 5 — Offer to customize the styling

Once the user confirms the autocomplete works, ask:

> "Want to customize the look? Everything visual lives in one value type — `AIAutocompleteAppearance` — fonts, colors, corner radii, spacing, columns, glass vs. solid dropdown. I can match your app's palette — tell me what you want different (e.g. 'match my brand primary', 'tighter corners', 'two columns'), or say 'looks fine' to wrap up."

If the user wants changes, build an `AIAutocompleteAppearance` (import `AIAutocompleteUIKit` — the type lives there) and apply it via `.aiAutocompleteAppearance(_:)` in SwiftUI or the `appearance` property in UIKit. Defaults adapt to light/dark automatically; keep custom colors dynamic (`UIColor { traits in ... }`) so they do too. Full token list: https://ai-autocomplete.com/docs/swift/styling

If the user says "looks fine" or doesn't want changes, you're done. Mention briefly that for production they may want to switch to the access-token flow below, but don't build it unless asked.

---

## Production: secret key + access token (recommended)

Public keys work, but for production apps we recommend the access-token flow:

- The **secret key** (`sk_v1_...`) lives **only on the user's backend server** — never in the app binary, never in source.
- The user's **backend** exchanges the secret key for a short-lived **access token** (`at_v1_...`) by calling AI Autocomplete.
- The **app** fetches that access token from a route on the user's *own* backend and hands it to the SDK. The app never touches the secret key.

Only build this once the basic install above is verified, and confirm with the user before adding server code. **If the project has no backend, STOP and tell the user — you cannot do the secret-key flow without a server. Stay on public keys.**

### A. Backend token-exchange route

The backend hits `POST https://api.ai-autocomplete.com/api/auth/token` with the secret key as a Bearer token and the `product_id` in the body. The endpoint returns `{ access_token, expires_at, token_type }` (`expires_at` is **unix seconds**). Forward that response to the app unchanged. The product UUID is visible in the URL of the product editor at `/edit` — ask the user to copy it.

```
POST https://api.ai-autocomplete.com/api/auth/token
Authorization: Bearer <sk_v1_...>          # server-side env var, never in the app
Content-Type: application/json

{ "product_id": "<uuid>" }
```

### B. Switch the SDK to access-token mode (app-side)

> **This code runs in the app.** It contains no secret key — only the URL of the user's own backend route from Step A.

```swift
struct TokenResponse: Decodable {
    let accessToken: String
    let expiresAt: Double // unix seconds from your backend
}

AIAutocomplete(configuration: .init(
    apiConfig: .accessToken(.init(
        getAccessToken: {
            let url = URL(string: "https://your-backend.example.com/api/ac-token")!
            let (data, _) = try await URLSession.shared.data(from: url)

            let decoder = JSONDecoder()
            decoder.keyDecodingStrategy = .convertFromSnakeCase
            let payload = try decoder.decode(TokenResponse.self, from: data)

            // IMPORTANT: the Swift SDK expects expiresAt in unix MILLISECONDS.
            return AccessToken(
                accessToken: payload.accessToken,
                expiresAt: payload.expiresAt * 1000
            )
        }
    )),
    onSubmit: { result in print(result.query) }
))
```

The SDK refreshes 30 seconds before expiry and retries once with a forced refresh on a 401 — no refresh code needed on your side.

### C. Remove the public key

Once the access-token flow is verified, drop the hardcoded public key from the code — you don't need it anymore.

Full docs: https://ai-autocomplete.com/docs/swift/authentication
