A portable, framework-agnostic discipline for the write path of any React or React Native app using a query/cache layer.
BY stareezy-1/frontend-architecture-skill contributors, via Agentic Awesome SkillsUPDATED Oct 26, 2026
INTENDED FOR✳Claude Code>_Codex↗CursorInstall in a listed agent.
instructions.md
---
name: frontend-optimistic-mutations
description: "A portable, framework-agnostic discipline for the write path of any React or React Native app using a query/cache layer."
risk: critical
source: https://github.com/stareezy-1/frontend-architecture-skill/tree/main/skills/frontend-optimistic-mutations
source_repo: stareezy-1/frontend-architecture-skill
source_type: community
date_added: 2026-07-01
license: MIT
license_source: https://github.com/stareezy-1/frontend-architecture-skill/blob/main/LICENSE
---
# Frontend Optimistic Mutations (the write path)
## When to Use
Use this skill when you need a portable, framework-agnostic discipline for the write path of any React or React Native app using a query/cache layer. Codifies the optimistic-update lifecycle (cancel in-flight queries → snapshot every affected cache → patch instantly → roll back verbatim on error → invalidate on...
> Portable skill — readable by Claude Code, OpenCode, Codex, Cursor, Windsurf, and others.
> This skill describes the **discipline of the write path** — optimistic updates, rollback,
> idempotency, cache coherence — not a UI library or a styling system. It builds directly on the
> **frontend-data-contracts** skill (writes go through the typed client) and the
> **frontend-architecture** skill (mutations live in `modules/{feature}/hooks/`, keyed by a factory).
The goal: a write **feels instant** (the UI reflects it before the server confirms), is **safe**
(a failure reconciles with authoritative state, and retries require server-enforced deduplication), and leaves the cache
**coherent** (the detail view and every list page agree). All three at once — that's the craft.
---
## 0. The five core ideas
1. **The optimistic lifecycle is fixed.** cancel → snapshot → patch → (error: roll back) → (settle: invalidate). Every optimistic mutation follows the same five beats.
2. **Roll back verbatim.** On failure, restore the exact snapshot taken before the patch — not a "best guess" re-derivation. Keep the snapshot in mutation context.
3. **Idempotency is generated once, not per attempt.** The key is created at form init (or first intent), so a network retry replays the original server response instead of performing the action twice.
4. **Caches move in lock-step.** A status change patches the detail cache **and** every list page that contains the entity, so badges never disagree across surfaces.
5. **Server state never enters the client store.** Optimistic state lives in the query cache, not Zustand/Redux. The cache is the single source of truth for server data (per frontend-architecture §4).
---
## 1. When to be optimistic (and when not)
| Situation | Strategy |
| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| High-confidence, low-conflict write (toggle nonfinancial status, like, reorder) | **Optimistic** — patch immediately, roll back on error. |
| Create that returns a server-generated id/number/total | **Pending state**, then `setQueryData` from the server response. A temporary optimistic row is optional; reconcile on success. |
| Destructive or hard-to-reverse write (delete with cascade, send money) | **Confirm first**, then optimistic _or_ pending — never silent-optimistic. |
| Write whose result the user can't see yet (background job) | **Pending + toast**, invalidate when done. No optimistic patch. |
Optimism is a UX tool for writes you're confident will succeed. If failure is common or expensive to
undo, prefer a pending state.
---
## 2. The optimistic lifecycle (TanStack Query)
The canonical shape. Each beat has a job; skipping one breaks correctness.
```ts
// modules/invoice/hooks/useInvoiceMutations.ts
interface MarkPaidContext {
previousInvoice: Invoice | undefined; // detail snapshot
previousLists: Array<[readonly unknown[], InvoiceListResponse]>; // every list page snapshot
}
export function useMarkInvoicePaid() {
const queryClient = useQueryClient();
const notifyError = useApiErrorToast();
return useMutation<Invoice, ApiError, { id: InvoiceId }, MarkPaidContext>({
mutationKey: invoiceKeys.all,
retry: false, // a network error alone does not make this POST safe to retry
mutationFn: ({ id }) => apiClient.post<Invoice>(INVOICE_API.markPaid(id)),
// 1 + 2 + 3: cancel in-flight reads, snapshot, patch
onMutate: async ({ id }) => {
await queryClient.cancelQueries({ queryKey: invoiceKeys.all }); // (1) no late refetch clobber
const detailKey = invoiceKeys.detail(id);
const previousInvoice = queryClient.getQueryData<Invoice>(detailKey); // (2) snapshot detail
if (previousInvoice) {
queryClient.setQueryData<Invoice>(detailKey, {
// (3) patch detail
...previousInvoice,
status: InvoiceStatus.Paid,
});
}
const previousLists: MarkPaidContext["previousLists"] = [];
for (const [key, list] of queryClient.getQueriesData<InvoiceListResponse>(
{
queryKey: invoiceKeys.lists(),
},
)) {
if (!list) continue;
previousLists.push([key, list]); // (2) snapshot each page
if (!list.invoices.some((i) => i.id === id)) continue;
queryClient.setQueryData<InvoiceListResponse>(key, {
// (3) patch matching row
...list,
invoices: list.invoices.map((i) =>
i.id === id ? { ...i, status: InvoiceStatus.Paid } : i,
),
});
}
return { previousInvoice, previousLists };
},
// 4: revalidate on failure; an old whole-list snapshot can erase newer writes.
onError: (error) => {
void queryClient.invalidateQueries({ queryKey: invoiceKeys.all });
notifyError(error);
},
// 5: invalidate so authoritative server state (paidAt, aggregates) refetches
onSettled: (_d, _e, { id }) => {
void queryClient.invalidateQueries({ queryKey: invoiceKeys.detail(id) });
void queryClient.invalidateQueries({ queryKey: invoiceKeys.lists() });
},
});
}
```
**Why each beat:**
- **cancel** — without it, a query that was already in flight can resolve _after_ your patch and overwrite the optimistic state.
- **snapshot** — the only safe rollback source; never reconstruct prior state by hand.
- **patch** — the instant UX; mutate detail **and** lists together (§4).
- **failure** — revalidate and surface the typed ApiError; this concurrent example deliberately does not restore whole-cache snapshots. Use pending UI while authoritative data reloads.
- **invalidate on settle** — success or failure, refetch so server-computed fields (timestamps, totals) are authoritative. Settle, not just success: a failed write may still have changed server state.
---
Whole-snapshot rollback is safe only when cache writes are serialized or no newer update has occurred. The reference therefore invalidates on failure. Restoring snapshots requires serialized writes or versioned per-entity patches that prove no newer update would be overwritten. Filtered lists may change membership/sort/totals when status changes; merely editing a row is insufficient. Use pending state when those rules cannot be reproduced safely. An optimistic Paid badge does not prove a payment settled.
## 3. Non-optimistic writes: create with server-owned fields
A create that returns an id/number/total can't be fully optimistic. Run it as a pending mutation and
seed the cache from the response.
```ts
export function useCreateInvoice() {
const queryClient = useQueryClient();
const notifyError = useApiErrorToast();
return useMutation<Invoice, ApiError, CreateInvoiceInput>({
mutationFn: ({ document, idempotencyKey }) =>
apiClient.post<Invoice>("/invoices", document, { idempotencyKey }),
onSuccess: (invoice) => {
queryClient.setQueryData(invoiceKeys.detail(invoice.id), invoice); // seed detail
void queryClient.invalidateQueries({ queryKey: invoiceKeys.lists() }); // refresh lists
},
onError: (error) => notifyError(error),
});
}
```
---
## 4. Cache coherence (lock-step detail + lists)
A single entity appears in many caches: its detail, and every filtered/paginated list page. An
optimistic patch must touch **all of them** or surfaces disagree. Use a **hierarchical key factory**
(from frontend-architecture §4.3) so you can target precisely.
```ts
export const invoiceKeys = {
all: ["invoices"] as const,
lists: () => [...invoiceKeys.all, "list"] as const,
list: (p: IListParams) => [...invoiceKeys.lists(), p] as const,
detail: (id: InvoiceId) => [...invoiceKeys.all, "detail", id] as const,
} as const;
```
- `getQueriesData({ queryKey: invoiceKeys.lists() })` enumerates **every** cached list page so you can patch each.
- `invalidateQueries({ queryKey: invoiceKeys.lists() })` refreshes them all on settle.
- `detail(id)` targets exactly one entity.
Snapshot **each** page you touch (keyed by its exact query key) so rollback restores every page
verbatim, not just the one currently on screen.
---
## 5. Idempotency (safe retries on money-moving writes)
A retried POST must not perform the action twice. Generate the key **once, at form init** (or first
user intent), carry it through retries, and let the client send it as a header. The server replays
the original response for a repeated key within its window.
```ts
// at form initialisation — stable for the lifetime of this attempt
const [idempotencyKey] = useState(() => crypto.randomUUID());
// Start a fresh key for a NEW logical intent/payload; persist the old key for retries.
// mutation forwards it; the typed client puts it on the header
apiClient.post<Invoice>("/invoices", document, { idempotencyKey });
```
**Hard rules:**
- Generate the key at **intent time**, not inside `mutationFn` (which re-runs per retry → defeats the purpose).
- The application-owned data client must explicitly forward the key and the server must enforce it, bind it to actor/operation/payload, reject changed payloads and define expiry. No library here auto-detects financial routes.
- Pair idempotency with **disabled-while-pending** UI so the user can't fire a second distinct write.
---
## 6. Retry policy
- **Reads:** retry a few times with backoff (default in most query libs) — safe and idempotent.
- **Writes:** do **not** auto-retry non-idempotent mutations. Retry only when an idempotency key guarantees replay, and retryable failures have been classified. A network error can occur after the server committed; it is never sufficient by itself.
- **Conflicts (409):** don't retry — surface the typed error, invalidate, and let the user re-decide on fresh data.
```ts
useMutation({
retry: false, // enable bounded retries only after verifying server deduplication
});
```
---
## 7. Library adapters
The five-beat lifecycle is the same; the hooks differ.
| Library | Optimistic mechanism |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **TanStack Query** | `onMutate` (cancel + snapshot + patch) → `onError` (rollback) → `onSettled` (invalidate). The reference shape above. |
| **RTK Query** | `onQueryStarted`: `updateQueryData` returns a `patchResult`; `await queryFulfilled` and call `patchResult.undo()` in `catch`. `invalidatesTags` on settle. |
| **SWR** | `mutate(key, optimisticData, { rollbackOnError: true, populateCache, revalidate: true })` — optimistic data + automatic rollback + revalidate. |
For **React Native**, all three libraries work unchanged; the cache is the source of truth on
native too. Keep mutation hooks DOM-free so they're shareable across web and native.
---
## 8. Conventions checklist (enforce in review)
- [ ] Optimistic mutations follow cancel → snapshot → patch → rollback → invalidate.
- [ ] `onMutate` cancels in-flight queries before patching.
- [ ] Rollback restores the **exact** snapshot from context, not a re-derivation.
- [ ] Detail **and** every affected list page are patched and snapshotted together.
- [ ] `onSettled` invalidates so server-computed fields are refetched (on success _and_ error).
- [ ] Idempotency key is generated at intent time and replayed across retries, not regenerated per attempt.
- [ ] Money-moving / destructive writes confirm first and disable the trigger while pending.
- [ ] Non-idempotent writes don't auto-retry; 409s surface rather than retry.
- [ ] Server state stays in the query cache — never copied into a client store.
- [ ] Query keys come from a hierarchical factory; invalidation is scoped, not global-blunt.
---
## 9. How to apply this skill
**Adding an optimistic mutation:** decide it's safe to be optimistic (§1). Write the five beats
(§2). Identify every cache the entity lives in and patch/snapshot all of them (§4).
**Making a write safe to retry:** generate an idempotency key at form init, thread it through the
mutation, confirm the client sends it (§5), and set a network-only bounded retry (§6).
**Debugging a flicker / wrong-state-after-write:** check that `onMutate` cancels queries (late
refetch clobber) and that `onSettled` invalidates (stale server-computed fields). Check that _all_
list pages were patched, not just the visible one.
**Reviewing the write path:** run the checklist in §8. The highest-value catches are missing
`cancelQueries` (race clobber), partial cache patches (detail/list disagreement), and idempotency
keys generated inside `mutationFn` (no longer protect retries).
---
## Publishing / installing this skill
This skill follows the Anthropic `SKILL.md` format and is portable across agents.
1. Keep it under `skills/frontend-optimistic-mutations/SKILL.md` in a public GitHub repo.
2. Keep the frontmatter `name` and high-signal `description` — discovery indexes match against it.
3. Install with: `npx skills add <org>/<repo> --skill "frontend-optimistic-mutations"`.
4. Non-`SKILL.md` agents can be pointed here from `AGENTS.md` / `CLAUDE.md`; Kiro can mirror it as a steering file.
## Limitations
- Use this skill only when the task clearly matches its upstream source and local project context.
- Verify commands, generated code, dependencies, credentials, and external service behavior before applying changes.
- Do not treat examples as a substitute for environment-specific tests, security review, or user approval for destructive or costly actions.
---
name: frontend-optimistic-mutations
description: "A portable, framework-agnostic discipline for the write path of any React or React Native app using a query/cache layer."
risk: critical
source: https://github.com/stareezy-1/frontend-architecture-skill/tree/main/skills/frontend-optimistic-mutations
source_repo: stareezy-1/frontend-architecture-skill
source_type: community
date_added: 2026-07-01
license: MIT
license_source: https://github.com/stareezy-1/frontend-architecture-skill/blob/main/LICENSE
---
# Frontend Optimistic Mutations (the write path)
## When to Use
Use this skill when you need a portable, framework-agnostic discipline for the write path of any React or React Native app using a query/cache layer. Codifies the optimistic-update lifecycle (cancel in-flight queries → snapshot every affected cache → patch instantly → roll back verbatim on error → invalidate on...
> Portable skill — readable by Claude Code, OpenCode, Codex, Cursor, Windsurf, and others.
> This skill describes the **discipline of the write path** — optimistic updates, rollback,
> idempotency, cache coherence — not a UI library or a styling system. It builds directly on the
> **frontend-data-contracts** skill (writes go through the typed client) and the
> **frontend-architecture** skill (mutations live in `modules/{feature}/hooks/`, keyed by a factory).
The goal: a write **feels instant** (the UI reflects it before the server confirms), is **safe**
(a failure reconciles with authoritative state, and retries require server-enforced deduplication), and leaves the cache
**coherent** (the detail view and every list page agree). All three at once — that's the craft.
---
## 0. The five core ideas
1. **The optimistic lifecycle is fixed.** cancel → snapshot → patch → (error: roll back) → (settle: invalidate). Every optimistic mutation follows the same five beats.
2. **Roll back verbatim.** On failure, restore the exact snapshot taken before the patch — not a "best guess" re-derivation. Keep the snapshot in mutation context.
3. **Idempotency is generated once, not per attempt.** The key is created at form init (or first intent), so a network retry replays the original server response instead of performing the action twice.
4. **Caches move in lock-step.** A status change patches the detail cache **and** every list page that contains the entity, so badges never disagree across surfaces.
5. **Server state never enters the client store.** Optimistic state lives in the query cache, not Zustand/Redux. The cache is the single source of truth for server data (per frontend-architecture §4).
---
## 1. When to be optimistic (and when not)
| Situation | Strategy |
| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| High-confidence, low-conflict write (toggle nonfinancial status, like, reorder) | **Optimistic** — patch immediately, roll back on error. |
| Create that returns a server-generated id/number/total | **Pending state**, then `setQueryData` from the server response. A temporary optimistic row is optional; reconcile on success. |
| Destructive or hard-to-reverse write (delete with cascade, send money) | **Confirm first**, then optimistic _or_ pending — never silent-optimistic. |
| Write whose result the user can't see yet (background job) | **Pending + toast**, invalidate when done. No optimistic patch. |
Optimism is a UX tool for writes you're confident will succeed. If failure is common or expensive to
undo, prefer a pending state.
---
## 2. The optimistic lifecycle (TanStack Query)
The canonical shape. Each beat has a job; skipping one breaks correctness.
```ts
// modules/invoice/hooks/useInvoiceMutations.ts
interface MarkPaidContext {
previousInvoice: Invoice | undefined; // detail snapshot
previousLists: Array<[readonly unknown[], InvoiceListResponse]>; // every list page snapshot
}
export function useMarkInvoicePaid() {
const queryClient = useQueryClient();
const notifyError = useApiErrorToast();
return useMutation<Invoice, ApiError, { id: InvoiceId }, MarkPaidContext>({
mutationKey: invoiceKeys.all,
retry: false, // a network error alone does not make this POST safe to retry
mutationFn: ({ id }) => apiClient.post<Invoice>(INVOICE_API.markPaid(id)),
// 1 + 2 + 3: cancel in-flight reads, snapshot, patch
onMutate: async ({ id }) => {
await queryClient.cancelQueries({ queryKey: invoiceKeys.all }); // (1) no late refetch clobber
const detailKey = invoiceKeys.detail(id);
const previousInvoice = queryClient.getQueryData<Invoice>(detailKey); // (2) snapshot detail
if (previousInvoice) {
queryClient.setQueryData<Invoice>(detailKey, {
// (3) patch detail
...previousInvoice,
status: InvoiceStatus.Paid,
});
}
const previousLists: MarkPaidContext["previousLists"] = [];
for (const [key, list] of queryClient.getQueriesData<InvoiceListResponse>(
{
queryKey: invoiceKeys.lists(),
},
)) {
if (!list) continue;
previousLists.push([key, list]); // (2) snapshot each page
if (!list.invoices.some((i) => i.id === id)) continue;
queryClient.setQueryData<InvoiceListResponse>(key, {
// (3) patch matching row
...list,
invoices: list.invoices.map((i) =>
i.id === id ? { ...i, status: InvoiceStatus.Paid } : i,
),
});
}
return { previousInvoice, previousLists };
},
// 4: revalidate on failure; an old whole-list snapshot can erase newer writes.
onError: (error) => {
void queryClient.invalidateQueries({ queryKey: invoiceKeys.all });
notifyError(error);
},
// 5: invalidate so authoritative server state (paidAt, aggregates) refetches
onSettled: (_d, _e, { id }) => {
void queryClient.invalidateQueries({ queryKey: invoiceKeys.detail(id) });
void queryClient.invalidateQueries({ queryKey: invoiceKeys.lists() });
},
});
}
```
**Why each beat:**
- **cancel** — without it, a query that was already in flight can resolve _after_ your patch and overwrite the optimistic state.
- **snapshot** — the only safe rollback source; never reconstruct prior state by hand.
- **patch** — the instant UX; mutate detail **and** lists together (§4).
- **failure** — revalidate and surface the typed ApiError; this concurrent example deliberately does not restore whole-cache snapshots. Use pending UI while authoritative data reloads.
- **invalidate on settle** — success or failure, refetch so server-computed fields (timestamps, totals) are authoritative. Settle, not just success: a failed write may still have changed server state.
---
Whole-snapshot rollback is safe only when cache writes are serialized or no newer update has occurred. The reference therefore invalidates on failure. Restoring snapshots requires serialized writes or versioned per-entity patches that prove no newer update would be overwritten. Filtered lists may change membership/sort/totals when status changes; merely editing a row is insufficient. Use pending state when those rules cannot be reproduced safely. An optimistic Paid badge does not prove a payment settled.
## 3. Non-optimistic writes: create with server-owned fields
A create that returns an id/number/total can't be fully optimistic. Run it as a pending mutation and
seed the cache from the response.
```ts
export function useCreateInvoice() {
const queryClient = useQueryClient();
const notifyError = useApiErrorToast();
return useMutation<Invoice, ApiError, CreateInvoiceInput>({
mutationFn: ({ document, idempotencyKey }) =>
apiClient.post<Invoice>("/invoices", document, { idempotencyKey }),
onSuccess: (invoice) => {
queryClient.setQueryData(invoiceKeys.detail(invoice.id), invoice); // seed detail
void queryClient.invalidateQueries({ queryKey: invoiceKeys.lists() }); // refresh lists
},
onError: (error) => notifyError(error),
});
}
```
---
## 4. Cache coherence (lock-step detail + lists)
A single entity appears in many caches: its detail, and every filtered/paginated list page. An
optimistic patch must touch **all of them** or surfaces disagree. Use a **hierarchical key factory**
(from frontend-architecture §4.3) so you can target precisely.
```ts
export const invoiceKeys = {
all: ["invoices"] as const,
lists: () => [...invoiceKeys.all, "list"] as const,
list: (p: IListParams) => [...invoiceKeys.lists(), p] as const,
detail: (id: InvoiceId) => [...invoiceKeys.all, "detail", id] as const,
} as const;
```
- `getQueriesData({ queryKey: invoiceKeys.lists() })` enumerates **every** cached list page so you can patch each.
- `invalidateQueries({ queryKey: invoiceKeys.lists() })` refreshes them all on settle.
- `detail(id)` targets exactly one entity.
Snapshot **each** page you touch (keyed by its exact query key) so rollback restores every page
verbatim, not just the one currently on screen.
---
## 5. Idempotency (safe retries on money-moving writes)
A retried POST must not perform the action twice. Generate the key **once, at form init** (or first
user intent), carry it through retries, and let the client send it as a header. The server replays
the original response for a repeated key within its window.
```ts
// at form initialisation — stable for the lifetime of this attempt
const [idempotencyKey] = useState(() => crypto.randomUUID());
// Start a fresh key for a NEW logical intent/payload; persist the old key for retries.
// mutation forwards it; the typed client puts it on the header
apiClient.post<Invoice>("/invoices", document, { idempotencyKey });
```
**Hard rules:**
- Generate the key at **intent time**, not inside `mutationFn` (which re-runs per retry → defeats the purpose).
- The application-owned data client must explicitly forward the key and the server must enforce it, bind it to actor/operation/payload, reject changed payloads and define expiry. No library here auto-detects financial routes.
- Pair idempotency with **disabled-while-pending** UI so the user can't fire a second distinct write.
---
## 6. Retry policy
- **Reads:** retry a few times with backoff (default in most query libs) — safe and idempotent.
- **Writes:** do **not** auto-retry non-idempotent mutations. Retry only when an idempotency key guarantees replay, and retryable failures have been classified. A network error can occur after the server committed; it is never sufficient by itself.
- **Conflicts (409):** don't retry — surface the typed error, invalidate, and let the user re-decide on fresh data.
```ts
useMutation({
retry: false, // enable bounded retries only after verifying server deduplication
});
```
---
## 7. Library adapters
The five-beat lifecycle is the same; the hooks differ.
| Library | Optimistic mechanism |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **TanStack Query** | `onMutate` (cancel + snapshot + patch) → `onError` (rollback) → `onSettled` (invalidate). The reference shape above. |
| **RTK Query** | `onQueryStarted`: `updateQueryData` returns a `patchResult`; `await queryFulfilled` and call `patchResult.undo()` in `catch`. `invalidatesTags` on settle. |
| **SWR** | `mutate(key, optimisticData, { rollbackOnError: true, populateCache, revalidate: true })` — optimistic data + automatic rollback + revalidate. |
For **React Native**, all three libraries work unchanged; the cache is the source of truth on
native too. Keep mutation hooks DOM-free so they're shareable across web and native.
---
## 8. Conventions checklist (enforce in review)
- [ ] Optimistic mutations follow cancel → snapshot → patch → rollback → invalidate.
- [ ] `onMutate` cancels in-flight queries before patching.
- [ ] Rollback restores the **exact** snapshot from context, not a re-derivation.
- [ ] Detail **and** every affected list page are patched and snapshotted together.
- [ ] `onSettled` invalidates so server-computed fields are refetched (on success _and_ error).
- [ ] Idempotency key is generated at intent time and replayed across retries, not regenerated per attempt.
- [ ] Money-moving / destructive writes confirm first and disable the trigger while pending.
- [ ] Non-idempotent writes don't auto-retry; 409s surface rather than retry.
- [ ] Server state stays in the query cache — never copied into a client store.
- [ ] Query keys come from a hierarchical factory; invalidation is scoped, not global-blunt.
---
## 9. How to apply this skill
**Adding an optimistic mutation:** decide it's safe to be optimistic (§1). Write the five beats
(§2). Identify every cache the entity lives in and patch/snapshot all of them (§4).
**Making a write safe to retry:** generate an idempotency key at form init, thread it through the
mutation, confirm the client sends it (§5), and set a network-only bounded retry (§6).
**Debugging a flicker / wrong-state-after-write:** check that `onMutate` cancels queries (late
refetch clobber) and that `onSettled` invalidates (stale server-computed fields). Check that _all_
list pages were patched, not just the visible one.
**Reviewing the write path:** run the checklist in §8. The highest-value catches are missing
`cancelQueries` (race clobber), partial cache patches (detail/list disagreement), and idempotency
keys generated inside `mutationFn` (no longer protect retries).
---
## Publishing / installing this skill
This skill follows the Anthropic `SKILL.md` format and is portable across agents.
1. Keep it under `skills/frontend-optimistic-mutations/SKILL.md` in a public GitHub repo.
2. Keep the frontmatter `name` and high-signal `description` — discovery indexes match against it.
3. Install with: `npx skills add <org>/<repo> --skill "frontend-optimistic-mutations"`.
4. Non-`SKILL.md` agents can be pointed here from `AGENTS.md` / `CLAUDE.md`; Kiro can mirror it as a steering file.
## Limitations
- Use this skill only when the task clearly matches its upstream source and local project context.
- Verify commands, generated code, dependencies, credentials, and external service behavior before applying changes.
- Do not treat examples as a substitute for environment-specific tests, security review, or user approval for destructive or costly actions.
COMPATIBILITY & REFERENCES
Platform guidance
Imported SKILL.md format for Claude Code, Codex and Cursor. Review the complete instructions and required tools before use. Package integrity checked; individual agent behavior has not been tested.
Creates comprehensive frontend UI/UX designs including user interfaces, design systems, component libraries, responsive layouts, and accessibility implementations. Produces wireframes, mockups, design specifications, and implementation guidelines. Use when designing user interfaces, creating desi...
Expert in building scalable design systems with Atomic Design, design tokens, and theming infrastructure. Use when creating component libraries, implementing dark mode, or establishing typography and color systems. Covers multi-brand support, Storybook-driven development, and accessibility-first ...
Use when implementing Liquid Glass effects, reviewing UI for Liquid Glass adoption, debugging visual artifacts, optimizing performance, or requesting expert review of Liquid Glass implementation - provides comprehensive design principles, API patterns, and troubleshooting guidance from WWDC 2025....