ansible-automation-eng…
Ansible automation: playbooks, roles, collections, Molecule testing, Vault security.
<!-- Loaded by nextjs-ecommerce-engineer when task involves cart state, add-to-cart, quantity updates, cart persistence, or abandoned cart -->
$ npx -y skills add notque/vexjoy-agent --agent claude-codeHow it fires
How this agent gets triggered: by you, by Claude, or both.
Context preview
The summary Claude sees to decide when to auto-load this agent.
<!-- Loaded by nextjs-ecommerce-engineer when task involves cart state, add-to-cart, quantity updates, cart persistence, or abandoned cart -->
<!-- Loaded by nextjs-ecommerce-engineer when task involves cart state, add-to-cart, quantity updates, cart persistence, or abandoned cart -->
The shopping cart is the most stateful part of any e-commerce site. The fundamental tension: guests have no server identity, authenticated users want their cart everywhere.
**When to use:** Deciding where cart state lives. The answer depends on whether the user is authenticated.
Guest user: localStorage -> sync to DB on login Authenticated: DB is source of truth, cached in React state Both: Cart ID in cookie survives browser refresh without login
// lib/cart.ts — server-side cart resolution
import { cookies } from 'next/headers'
import { db } from '@/lib/db'
export async function getCart() {
const cookieStore = cookies()
const cartId = cookieStore.get('cart-id')?.value
if (!cartId) return null
return db.cart.findUnique({
where: { id: cartId },
include: {
items: {
include: { product: true },
orderBy: { createdAt: 'asc' },
},
},
})
}
export async function getOrCreateCart() {
const existing = await getCart()
if (existing) return existing
const cart = await db.cart.create({ data: {} })
// Set cart cookie — persists across page navigations
cookies().set('cart-id', cart.id, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 30, // 30 days
})
return cart
}---
**When to use:** Context for UI-only cart state (drawer open/closed, item count badge). Server state (React Query, SWR, or Server Actions with revalidation) for actual cart data.
// context/CartContext.tsx — UI state only, not cart data
'use client'
import { createContext, useContext, useState } from 'react'
interface CartUIState {
isOpen: boolean
openCart: () => void
closeCart: () => void
}
const CartUIContext = createContext<CartUIState | null>(null)
export function CartUIProvider({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(false)
return (
<CartUIContext.Provider value={{
isOpen,
openCart: () => setIsOpen(true),
closeCart: () => setIsOpen(false),
}}>
{children}
</CartUIContext.Provider>
)
}
export function useCartUI(): CartUIState {
const ctx = useContext(CartUIContext)
if (!ctx) throw new Error('useCartUI must be used within CartUIProvider')
return ctx
}Actual cart data comes from a Server Component via `getCart()`, not from context.
---
**When to use:** Adding/removing items. The UI should respond instantly — don't wait for the server roundtrip to update the count or remove the item.
// components/AddToCartButton.tsx
'use client'
import { useOptimistic, useTransition } from 'react'
import { addToCartAction } from '@/actions/cart'
interface CartItem {
productId: string
quantity: number
}
interface AddToCartButtonProps {
productId: string
initialCount: number
}
export function AddToCartButton({ productId, initialCount }: AddToCartButtonProps) {
const [isPending, startTransition] = useTransition()
const [optimisticCount, addOptimistic] = useOptimistic(
initialCount,
(state: number, delta: number) => state + delta
)
function handleClick(): void {
addOptimistic(1) // Update UI immediately
startTransition(async () => {
await addToCartAction(productId) // Server mutation
})
}
return (
<button
onClick={handleClick}
disabled={isPending}
aria-busy={isPending}
>
Add to cart {optimisticCount > 0 && `(${optimisticCount})`}
</button>
)
}---
**When to use:** Cart page where users can increase/decrease quantities.
// actions/cart.ts
'use server'
import { revalidatePath } from 'next/cache'
import { db } from '@/lib/db'
import { getOrCreateCart } from '@/lib/cart'
import { z } from 'zod'
const UpdateQuantitySchema = z.object({
productId: z.string().cuid(),
quantity: z.number().int().min(0).max(99),
})
export async function updateCartQuantity(
productId: string,
quantity: number
): Promise<{ success: boolean; error?: string }> {
const parsed = UpdateQuantitySchema.safeParse({ productId, quantity })
if (!parsed.success) {
return { success: false, error: 'Invalid quantity' }
}
const cart = await getOrCreateCart()
if (quantity === 0) {
// Remove the item
await db.cartItem.deleteMany({
where: { cartId: cart.id, productId },
})
} else {
await db.cartItem.upsert({
where: { cartId_productId: { cartId: cart.id, productId } },
update: { quantity },
create: { cartId: cart.id, productId, quantity },
})
}
revalidatePath('/cart')
return { success: true }
}---
**When to use:** Hybrid approach for maximum resilience — cookie for server-side reads, localStorage for offline/fast client-side access.
// lib/cart-client.ts — client-side cart operations
const CART_STORAGE_KEY = 'cart-items-preview'
interface CartPreview {
count: number
updatedAt: number
}
export function saveCartPreview(count: number): void {
try {
const preview: CartPreview = { count, updatedAt: Date.now() }
localStorage.setItem(CART_STORAGE_KEY, JSON.stringify(preview))
} catch {
// localStorage unavailable (private browsing, storage full) — non-fatal
}
}
export function getCartPreview(): number {
try {
const raw = localStorage.getItem(CART_STORAGE_KEY)
if (!raw) return 0
const preview = JSON.parse(raw) as CartPreview
// Treat as stale after 1 hour
if (Date.now() - preview.updatedAt > 3600_000) return 0
return preview.count
} catch {
return 0
}
}---
**When to use
Essays and writing behind this toolkit live at vexjoy.com. VexJoy Agent connects plain-English requests to specialist agents, skills, and workflows. /do selects the knowledge and tools needed for your task.
Repo: notque/vexjoy-agent
Ansible automation: playbooks, roles, collections, Molecule testing, Vault security.
**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes **Version range**:…
**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**: Molecule 6.0+ / ansible-lint 6.0+ /…
Universal rules injected by /do at dispatch. Each agent's .md file supplies domain rules.
**Scope**: Failure modes in agent output style — over-reporting, self-congratulation, verbose narration, and hedging. Covers what to detect and how to fix…
Zero-dependency combat visual upgrades: CSS particle replacement, Framer Motion combat juice, CSS 3D card transforms.