<!-- AI agents: this is the Markdown version of an AppNorth documentation page. Index: https://www.appnorth.app/llms.txt -->

# Offline writes and cache

The client does not silently persist failed requests. Add `AppNorthOfflineQueue` for selected writes and `AppNorthReadCache` for boards and idea lists. You provide storage and decide its retention and protection.

## Provide storage {#storage}

Both storage protocols expose `load() async throws -> Data?` and `save(_:) async throws`. The following actor is a minimal memory-only implementation. Use **separate instances** for the queue and cache; their serialized formats are different.

```swift title="MemorySDKStorage.swift"
import Foundation
import AppNorth

actor MemorySDKStorage: AppNorthOfflineQueueStorage, AppNorthReadCacheStorage {
    private var data: Data?

    func load() async throws -> Data? { data }
    func save(_ data: Data?) async throws { self.data = data }
}
```

For persistence across launches, implement these protocols with your app's protected storage. Queue contents can include report text, diagnostics, screenshots, and an identity scope. Do not store the signing secret or credential alongside them.

## Queue a write {#queue}

Retain a queue for the account session. `submitIdea`, `submitComment`, and `submitFeedback` attempt the request immediately. They return `true` when submitted or `false` when a network failure was queued.

```swift title="QueuedComment.swift"
import AppNorth

func sendComment(
    queue: AppNorthOfflineQueue,
    client: AppNorthClient,
    ideaID: String,
    body: String,
    retryKey: String
) async throws -> Bool {
    try await queue.submitComment(
        using: client,
        handle: ideaID,
        body: body,
        idempotencyKey: retryKey
    )
}

func retryPending(queue: AppNorthOfflineQueue, client: AppNorthClient) async throws -> Int {
    try await queue.flush(using: client)
    return try await queue.pendingCount()
}
```

Only `AppNorthError.network` queues a write. Validation, authentication, and HTTP server errors propagate. The default queue limit is 50 entries; a full queue throws `AppNorthOfflineQueueError.full`. Votes and chat messages are not supported queue operations.

Call `flush(using:)` after reconnecting or returning to the foreground. The queue does not monitor connectivity itself. A network failure stops the flush and retains the remaining entries; another error throws and leaves the failing entry pending. A completed flush call does not prove the queue is empty: inspect `pendingCount()`, which counts all stored scopes.

## Cache community reads {#cache}

The cache defaults to a 300-second TTL. Pass it to `AppNorthView` to show fresh cached boards and ideas before the initial network load. It caches arrays, not pagination cursors, chat messages, or private report threads.

```swift title="CachedCommunity.swift"
import SwiftUI
import AppNorth

struct CachedCommunity: View {
    let client: AppNorthClient
    let cache: AppNorthReadCache

    var body: some View {
        AppNorthView(client: client, cache: cache)
    }
}
```

Construct the cache once with `AppNorthReadCache(storage: MemorySDKStorage())`, using the storage example above. Expired entries return `nil`; there is no stale-forever fallback. For your own reads, use `storeBoards`, `storeIdeas`, `boards(scope:)`, and `ideas(scope:query:)`. Include server, project, account, and all relevant filters in your cache keys. `clear(scope:)` removes that scope's boards and ideas.

## Separate accounts {#account-changes}

Queue replay matches the API URL, App ID, and lowercased credential email (or `anonymous`). This prevents replaying an old account's queued write with a different account. It does not erase old entries automatically.

On logout, stop submitting/flushing, discard the queue and view models, and erase account-owned storage if your retention policy requires it. Create new instances after changing identity. Clearing storage alone does not clear an already-loaded queue's in-memory entries. The built-in community model can retain its loaded arrays; recreate its screen on account changes as well.

## Verify {#verify}

1. Disable connectivity and submit one queued comment: expect `false` and one pending entry.
2. Reconnect, flush, and confirm one server comment and zero pending entries for this test.
3. Switch accounts before flushing and confirm the old entry is not sent as the new user.
4. Test cache reads before and after TTL expiry, and verify logout removes any data your app promises to forget.
