> ## Documentation Index
> Fetch the complete documentation index at: https://docs.swiftaiboilerplate.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Feature Chat Module

> Complete chat UI with dual styles, streaming, and pagination

Production **chat system** with **2 UI styles**, **streaming AI responses**, **cursor pagination**, and **comprehensive error handling**.

<Info>
  **Full technical details** in your project at `/docs/modules/Feature.Chat.md`
</Info>

## What You Get

* ✅ **2 professional UIs** - Bubble (WhatsApp) and Centered (ChatGPT)
* ✅ **Streaming responses** - Real-time token-by-token rendering
* ✅ **Cursor pagination** - Infinite scroll with smart prefetch
* ✅ **Conversation management** - Create, rename, delete, filter by title
* ✅ **Message limits** - Free-tier cap (`kFreeMessageLimit = 10`) gates non-subscribers
* ✅ **Error handling** - Retry, cancellation, localized error messages

**Time saved: 40-60 hours** of UI implementation, streaming logic, pagination, state management, and testing.

## Liquid Glass layout

* `ChatView` and `ChatGPTStyleView` host the input bar via `.safeAreaInset(edge: .bottom)`. The scroll view is the full body, not a `VStack` stacking input below messages. This is what lets Liquid Glass sample the content behind the input bar on iOS 26.
* The scroll-edge effect is wired through `saiScrollEdgeGlass(.bottom)` so the bottom of the chat transcript blurs into the input chrome.
* `ChatViewModel` has a sibling extension file, `ChatViewModel+Memory.swift`, which keeps memory-extraction concerns out of the main view model.

## UI Styles

<Tabs>
  <Tab title="Bubble Style">
    WhatsApp/iMessage-like interface:

    * Messages in colored bubbles
    * User on right (blue), AI on left (gray)
    * Timestamps and avatars
    * Smooth animations

    **Best for:** Casual, conversational apps
  </Tab>

  <Tab title="Centered Style">
    ChatGPT-like interface:

    * Full-width messages
    * Clean, minimal design
    * Role badges (You/AI)
    * Professional appearance

    **Best for:** Professional, productivity apps
  </Tab>
</Tabs>

**Style is chosen per conversation** - the history screen's launcher cards ("Bubble Chat" / "Prompt Chat") pick which UI opens via `makeChatView(conversationID:style:)`.

## Key Components

### Production Factory

The boilerplate uses a **factory pattern** in `CompositionRoot`:

```swift theme={null}
// Factory method with dependency injection
public func makeChatViewModel(conversationID: UUID) -> ChatViewModel {
    ChatViewModel(
        conversationID: conversationID,
        messageRepository: messageRepository,  // SwiftData repository
        llmClient: llmClient,                  // Proxy or Echo client
        paymentsStatusProvider: PaymentsStatusAdapter(paymentsClient: paymentsClient)
    )
}

// Create a dual-style chat container (renders the selected ChatUIStyle)
public func makeDualStyleChatView(
    conversationID: UUID,
    onRequireSubscription: (() -> Void)? = nil
) -> DualStyleChatView {
    let viewModel = makeChatViewModel(conversationID: conversationID)

    return DualStyleChatView(
        viewModel: viewModel,
        onRequireSubscription: onRequireSubscription
    )
}
```

### ChatViewModel

**What it handles:**

* Message persistence via `messageRepository`
* LLM streaming with cancellation
* Cursor-based pagination (`InfinitePaginator`)
* Error handling and retry (`retryLast()`)
* Loading states
* Free-tier message cap via the injected `paymentsStatusProvider`

### ChatHistoryViewModel

Manages conversation list:

```swift theme={null}
@MainActor
@Observable
public final class ChatHistoryViewModel {
    var conversations: [ConversationDTO]
    var isLoading: Bool

    public func loadConversations() async
    public func createNewConversation() async -> ConversationDTO?
    public func renameConversation(id: UUID, newTitle: String) async
    public func deleteConversation(id: UUID) async
}
```

## Streaming Pattern

Real-time AI response rendering:

```swift theme={null}
// In ChatViewModel.send(): accumulate streamed chunks into the message
var accumulatedText = ""
for try await chunk in llmClient.streamResponse(messages: messages) {
    accumulatedText += chunk
    messages[index].text = accumulatedText  // UI updates via @Observable
}
```

**Benefits:**

* ✅ Low latency (first token fast)
* ✅ Better UX (gradual appearance)
* ✅ Cancellable generation
* ✅ Memory efficient

## Pagination

Efficient infinite scroll for chat history:

```swift theme={null}
// InfinitePaginator handles:
// - Cursor-based pagination (MessageCursor)
// - Reverse chronological order
// - Prefetch before the user hits the end
// - State tracking (idle / loadingMore / endReached / error)

@MainActor
public final class InfinitePaginator {
    public init(pageSize: Int = 50, prefetchThreshold: Int = 5)

    public private(set) var state: State

    public func shouldLoadMore(visibleIndex: Int, totalCount: Int) -> Bool
    public func loadNext(
        using loader: (MessageCursor?) async throws -> (items: [MessageDTO], next: MessageCursor?)
    ) async -> Result<[ChatMessage], AppError>
    public func reset()
}
```

## Customization Examples

### Change Chat Colors

```swift theme={null}
// Bubble colors resolve through DSColors (Packages/DesignSystem):
//   DSColors.bubbleUserOrFallback      -> AccentPrimary colorset
//   DSColors.bubbleAssistantOrFallback -> Surface colorset
//
// Edit those colorsets in
// Packages/DesignSystem/Sources/DesignSystem/Resources/Assets.xcassets
// and both chat styles update automatically.
```

### Add Message Actions

```swift theme={null}
// Add a context menu to MessageRow in ChatView.
// Copy works against the existing ChatMessage.text.
// Regenerate/Delete are methods you add to ChatViewModel yourself —
// they are not shipped on the view model.
.contextMenu {
    Button("Copy", systemImage: "doc.on.doc") {
        UIPasteboard.general.string = message.text
    }
    // Example extension points to implement on ChatViewModel:
    // Button("Regenerate", systemImage: "arrow.clockwise") { ... }
    // Button("Delete", systemImage: "trash", role: .destructive) { ... }
}
```

### Add Custom AI Persona

```swift theme={null}
// In ChatViewModel. LLMMessage.role is a String: "user" | "assistant" | "system".
let systemPrompt = LLMMessage(
    role: "system",
    content: """
    You are a Swift expert.
    Provide clear answers with code examples.
    """
)

let messages = [systemPrompt] + history + [userMessage]
```

### Conversation search

Search ships as client-side filtering in `ChatHistoryView` (no full-text message search).
It matches conversation title and persona name:

```swift theme={null}
// ChatHistoryView.filteredConversations
viewModel.conversations.filter { conversation in
    conversation.title.localizedCaseInsensitiveContains(searchText) ||
    (conversation.personaName?.localizedCaseInsensitiveContains(searchText) ?? false)
}
```

## UI Components

| Component           | Package      | Purpose                                           |
| ------------------- | ------------ | ------------------------------------------------- |
| `ChatView`          | FeatureChat  | Bubble-style chat interface                       |
| `ChatGPTStyleView`  | FeatureChat  | Centered-style interface                          |
| `DualStyleChatView` | FeatureChat  | Container that renders the selected `ChatUIStyle` |
| `MessageRow`        | FeatureChat  | Bubble-style message row                          |
| `ChatHistoryView`   | FeatureChat  | Conversation list                                 |
| `ChatRowCard`       | FeatureChat  | Conversation row                                  |
| `ChatSearchBar`     | FeatureChat  | Search input                                      |
| `EmptyStateView`    | FeatureChat  | No conversations state                            |
| `SAIInputBar`       | DesignSystem | Message composition bar                           |
| `ChatBubble`        | DesignSystem | Reusable message bubble                           |

## Key Files

| Component  | Location                                                     |
| ---------- | ------------------------------------------------------------ |
| ViewModels | `Packages/FeatureChat/Sources/FeatureChat/ViewModels/`       |
| Views      | `Packages/FeatureChat/Sources/FeatureChat/Views/`            |
| Components | `Packages/FeatureChat/Sources/FeatureChat/Views/Components/` |
| Paginator  | `Packages/FeatureChat/Sources/FeatureChat/Paginator/`        |

## Dependencies

* **Core** - Error handling, logging
* **Storage** - Conversation and message persistence
* **DesignSystem** - UI components and tokens
* **Localization** - Localized error messages (`AppError.localizedUserMessage`)

<Warning>
  FeatureChat does **not** depend on AI. It's the other way around: `LLMClient` and
  `LLMMessage` live in FeatureChat, and the **AI** package re-exports FeatureChat
  (`@_exported import FeatureChat`). Removing chat without first relocating those types
  breaks `Packages/AI`. See the per-module removal notes in
  `docs/checklists/APP_STORE_4_3_HARDENING.md`.
</Warning>

## Used By

* **AI** - Re-exports FeatureChat; `ProxyLLMClient` (in `Packages/AI`) conforms to `LLMClient`. The dev-fallback `EchoLLMClient` (app target, `Composition/LLMClientFactory.swift`) also conforms to `LLMClient`
* **Main App** - Primary feature
* **CompositionRoot** - Factory methods for ViewModels

## Best Practices

<AccordionGroup>
  <Accordion title="ViewModel Design">
    * @MainActor for UI updates
    * @Observable for state
    * Async/await for operations
    * Handle all error cases
  </Accordion>

  <Accordion title="Streaming">
    * Update UI incrementally
    * Show typing indicator
    * Support cancellation
    * Handle interruptions
  </Accordion>

  <Accordion title="Performance">
    * Paginate messages
    * Lazy load conversations
    * Compress images
    * Cancel tasks on dismiss
  </Accordion>
</AccordionGroup>

## Learn More

<CardGroup cols={2}>
  <Card title="Full Documentation" icon="book" href="https://github.com/SwiftAIBoilerplatePro/SwiftAIBoilerplatePro-Distribution/blob/main/docs/modules/Feature.Chat.md">
    Find complete FeatureChat guide in your project
  </Card>

  <Card title="UI Styles Guide" icon="palette" href="https://github.com/SwiftAIBoilerplatePro/SwiftAIBoilerplatePro-Distribution/blob/main/Packages/FeatureChat/CHAT_UI_STYLES.md">
    Find complete Chat UI patterns guide in your project
  </Card>

  <Card title="AI Module" icon="brain" href="/pages/modules/ai">
    LLM integration
  </Card>

  <Card title="Building Guide" icon="hammer" href="/pages/guides/building-your-app">
    Customize chat
  </Card>
</CardGroup>

## Build with AI (fast)

You can customize this module in minutes using our ready-to-paste LLM prompts.

### Example Prompt

Context: `Packages/FeatureChat/**`
Prompt:
"Refactor chat UI to centered layout using DS spacing/tokens. Add toggle in Settings. Update tests."

### Example Output

```swift theme={null}
struct TypingIndicatorView: View {
  @State private var isVisible = false
  var body: some View { /* minimal dots animation */ }
}
// TODO: drive from ChatMessage.isStreaming / ChatViewModel.isSending (throttle: 600ms)
```

See all prompts → /docs/prompts/Feature.Chat.prompts.md\
See in project: `docs/modules/Feature.Chat.md`

## Test Coverage

The `FeatureChatTests` target runs as part of the workspace suite — **\~598 tests across 12
package test targets + the app test suites**, all in one `Boilerplate.xctestplan` run.

FeatureChat tests cover:

* Message sending
* Streaming responses
* Pagination
* Conversation management
* UI rendering
* Error scenarios
