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

# Offline queue

> Keep edits made without a connection and send them once there is one.

An outbox for edits, the way a mail app holds messages until it can send them. Your screen changes at once; the queue makes the change stick, now or later. It survives a reload, so an edit made offline isn't lost if the user closes the app.

```ts outbox.ts theme={null}
import { api, createOfflineQueue } from '@cskn/sdk';

type Op =
  | { kind: 'delete'; id: string }
  | { kind: 'rename'; id: string; name: string };

export const outbox = createOfflineQueue<Op>(
  'lists',
  (op) =>
    op.kind === 'delete'
      ? api.delete(`/apps/my-app/lists/${op.id}`)
      : api.patch(`/apps/my-app/lists/${op.id}`, { name: op.name }),
  { onDrop: (op) => queryClient.invalidateQueries({ queryKey: ['lists'] }) },
);
```

```tsx theme={null}
// The screen has already changed; this makes it stick.
outbox.add({ kind: 'delete', id });
```

## createOfflineQueue

**name** `string` *required*

The queue's name. Ops are stored in `localStorage` under `cskn.outbox.<name>`, so keep it stable and unique within your app.

**send** `(op: Op) =\> Promise\<unknown\>` *required*

Sends one op. Ops are plain data, not functions, so they can be stored and sent after a reload.

**options.onDrop** `(op: Op, error: unknown) =\> void`

Called when the server refuses an op. Use it to put the screen back the way the server has it.

**options.isOffline** `(error: unknown) =\> boolean`

Decides whether a failure means "no connection". By default, a request that got no response at all, or `navigator.onLine === false`.

## How ops are sent

* In order, one at a time.
* A failure with no response keeps the op and retries: when the device comes back online, when the page returns to the foreground, and on a backoff timer up to one minute.
* A response with any HTTP error status is final. The op is dropped and `onDrop` is called.
* Ops left over from a previous visit are sent when the page loads.

If `localStorage` is full or blocked, the queue still works for the current page; it just doesn't survive a reload.

## The queue object

| Member | Description |
| - | - |
| `add(op)` | Queue an op and try to send it right away. |
| `pending()` | Ops not yet sent, oldest first. |
| `flush()` | Try to send what's queued now. Resolves when the queue is empty or stuck offline. |
| `subscribe(listener)` | Called whenever the queue changes. Returns an unsubscribe. |

## Showing what's waiting

`useOfflineQueue(queue)` returns the unsent ops and re-renders as they change:

```tsx theme={null}
import { useOfflineQueue } from '@cskn/sdk';

function OutboxNote() {
  const pending = useOfflineQueue(outbox);
  if (!pending.length) return null;
  return <p>{pending.length} changes waiting for a connection</p>;
}
```

> [!TIP]
>
> Combined with an undo toast, the queue becomes the commit step: show the change, offer undo, and call `outbox.add(op)` only when the toast closes without an undo. See [`toast.undoable`](https://docs.cskn.dev/ui/components/feedback).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.