agent-browser
Browser automation CLI for AI agents. Use when the user needs to interact with websites,…
Complete fullstack workflow combining GET API routes, server actions, SWR data fetching, and form handling. Use when building features that need both data fetching and mutations from API to UI.
$ npx -y skills add elie222/inbox-zero --skill fullstack-workflow --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/fullstack-workflowContext preview
The summary Claude sees to decide when to auto-load this skill.
Complete fullstack workflow combining GET API routes, server actions, SWR data fetching, and form handling. Use when building features that need both data fetching and mutations from API to UI.
name: fullstack-workflow description: Complete fullstack workflow combining GET API routes, server actions, SWR data fetching, and form handling. Use when building features that need both data fetching and mutations from API to UI.
Complete guide for building features from API to UI, combining GET API routes, data fetching, form handling, and server actions.
When building a new feature, follow this pattern: 1. **GET API Route** - For fetching data 2. **Server Action** - For mutations (create/update/delete) 3. **Data Fetching** - Using SWR on the client 4. **Form Handling** - Using React Hook Form with Zod validation
For fetching data. Always wrap with `withAuth` or `withEmailAccount`:
// apps/web/app/api/user/example/route.ts
import { NextResponse } from "next/server";
import prisma from "@/utils/prisma";
import { withEmailAccount } from "@/utils/middleware";
// Auto-generate response type for client use
export type GetExampleResponse = Awaited<ReturnType<typeof getData>>;
export const GET = withEmailAccount(async (request) => {
const { emailAccountId } = request.auth;
const result = await getData({ emailAccountId });
return NextResponse.json(result);
});
// We make this its own function so we can infer the return type for a type-safe response on the client
async function getData({ emailAccountId }: { emailAccountId: string }) {
const items = await prisma.example.findMany({
where: { emailAccountId },
});
return { items };
}For mutations. Use `next-safe-action` with proper validation.
**Action clients** (defined in `apps/web/utils/actions/safe-action.ts`):
| Client | Context | Use when | |--------|---------|----------| | `actionClientUser` | `ctx.userId` | Only need authenticated user | | `actionClient` | `ctx.emailAccountId`, `ctx.userId` | Need user + email account (most mutations) | | `adminActionClient` | `ctx.logger` | Admin-only actions (no userId in ctx) |
Always use `.metadata({ name: "actionName" })` for Sentry instrumentation. Use `SafeError` for expected errors.
**Validation Schema** (`apps/web/utils/actions/example.validation.ts`):
import { z } from "zod";
export const createExampleBody = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email"),
description: z.string().optional(),
});
export type CreateExampleBody = z.infer<typeof createExampleBody>;
export const updateExampleBody = z.object({
id: z.string(),
name: z.string().optional(),
email: z.string().email().optional(),
description: z.string().optional(),
});
export type UpdateExampleBody = z.infer<typeof updateExampleBody>;**Server Action** (`apps/web/utils/actions/example.ts`):
"use server";
import { actionClient } from "@/utils/actions/safe-action";
import { createExampleBody, updateExampleBody } from "@/utils/actions/example.validation";
import prisma from "@/utils/prisma";
export const createExampleAction = actionClient
.metadata({ name: "createExample" })
.inputSchema(createExampleBody)
.action(async ({
ctx: { emailAccountId },
parsedInput: { name, email, description }
}) => {
const example = await prisma.example.create({
data: {
name,
email,
description,
emailAccountId,
},
});
return example;
});
export const updateExampleAction = actionClient
.metadata({ name: "updateExample" })
.inputSchema(updateExampleBody)
.action(async ({
ctx: { emailAccountId },
parsedInput: { id, name, email, description }
}) => {
const example = await prisma.example.update({
where: { id, emailAccountId },
data: { name, email, description },
});
return example;
});Use SWR for client-side data fetching:
import useSWR from "swr";
import { GetExampleResponse } from "@/app/api/user/example/route";
export function useExamples() {
return useSWR<GetExampleResponse>("/api/user/example");
}Use React Hook Form with `useAction` from `next-safe-action/hooks`:
import { useCallback } from "react";
import { useForm, type SubmitHandler } from "react-hook-form";
import { useAction } from "next-safe-action/hooks";
import { zodResolver } from "@hookform/resolvers/zod";
import { Input } from "@/components/Input";
import { Button } from "@/components/ui/button";
import { toastSuccess, toastError } from "@/components/Toast";
import { getActionErrorMessage } from "@/utils/error";
import { createExampleAction } from "@/utils/actions/example";
import { createExampleBody, type CreateExampleBody } from "@/utils/actions/example.validation";
export function ExampleForm({ onSuccess }: { onSuccess?: () => void }) {
const {
register,
handleSubmit,
formState: { errors },
reset,
} = useForm<CreateExampleBody>({
resolver: zodResolver(createExampleBody),
});
const { execute, isExecuting } = useAction(createExampleAction, {
onSuccess: () => {
toastSuccess({ description: "Example created!" });
reset();
onSuccess?.();
},
onError: (error) => {
toastError({
description: getActionErrorMessage(error.error),
});
},
});
return (
<form className="space-y-4" onSubmit={handleSubmit(execute)}>
<Input
type="text"
name="name"
label="Name"
registerProps={register("name")}
error={errors.name}
/>
<Input
type="email"
name="email"
label="Email"
registerProps={register("email")}
error={errors.email}
/>
<Input
type="text"
name="description"
label="Description"
registerProps={register("description")}
error={errors.description}
/>
<Button type="submit" loading={isExecuting}>
Create Example
</ButtoThe world's best AI personal assistant for email. Open source app to help you reach inbox zero fast.
Repo: elie222/inbox-zero
Browser automation CLI for AI agents. Use when the user needs to interact with websites,…
Cursor Cloud VM setup and service startup instructions for local development
Simplify and refine recently modified code for clarity, consistency, and maintainability…
Review the working tree, commit it safely, and open a GitHub pull request. Use when the user…