This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Development
npm run dev # Start Next.js dev server
# Build & Production
npm run build # Build for production
npm run start # Start production server
# Code Quality
npm run lint # Run ESLint
# Type checking (no tests exist)
npx tsc --noEmit
# Docker
npm run docker:compose:up # Start with Docker Compose (detached)
npm run docker:compose:down # Stop Docker Compose
npm run docker:compose:logs # Tail Docker Compose logs
npm run docker:build # Build Docker image
npm run docker:run # Run Docker container on :3000Retouchly is a Next.js 16 App Router SaaS for AI-powered image editing. Everything is in a single Next.js monorepo — no separate backend service.
src/app/— Pages and server actions (App Router)(tools)/— Route group for AI tools:background-remover,colorization,face-restoration,image-generation,image-overlay,text-to-speechactions/— Server Actions for all AI calls and Supabase writesapi/replicate-status/[id]/— Polling endpoint for async Replicate predictionsauth/— Auth callback and email verification routesdashboard/,history/,explore/,pricing/,profile/[id]/,settings/— User-facing pages
src/components/— React components grouped by feature;ui/contains shadcn/ui primitivessrc/store/useGeneratedStore.ts— Single Zustand store for all AI tool statesrc/lib/— Supabase client, OpenAI client, and client-side helpers for community/follow mutationssrc/hooks/— React hooks (useNotifications.tsfor Supabase Realtime)src/types/— Shared TypeScript types (e.g.notifications.ts)
Every AI tool follows the same two-function pattern per action file:
create*Prediction(input)— Server Action that callsreplicate.predictions.create(...)and returns{ predictionId, success, error }. Replicate client must be instantiated withuseFileOutput: false.*WithAI(input)— Legacy blocking version usingreplicate.run(...)kept for reference but not used by the store.
In the Zustand store (useGeneratedStore.ts):
- Components call store methods (never Server Actions directly)
- Store calls
create*Prediction()→ getspredictionId→ callsget().startPolling(predictionId, onComplete, onError) startPollingpolls/api/replicate-status/[id]every 1500ms; clears any prior timer before starting a new one (the timer is closure-scoped insidecreate()to avoid cross-tool cancellation)- For tool functions returning
Promise<string | null>, the polling callbacks are wrapped innew Promise<string | null>
Each AI operation deducts 2 credits via deductCredit(userId) in src/app/actions/credits-actions.ts. This calls the deduct_credits Supabase RPC (defined as SECURITY DEFINER to bypass RLS). Pro plan users get Infinity credits. Always call deductCredit before starting a prediction and bail out with toast.error if credit.ok is false.
The explore/ page has a deliberate client/server split:
- Client helpers (
src/lib/community-client.ts,src/lib/follow-client.ts) — run in the browser against Supabase RLS; used for like/follow mutations - Server Actions (
src/app/actions/community-actions.ts,follow-actions.ts) — used for reads needing server-side filtering or aggregation
Like counts are maintained by the increment_like_count Supabase RPC; fallback to manual fetch+update if the RPC is absent.
DB triggers (trg_notify_follow, trg_notify_like, trg_notify_comment) write to the notifications table. The useNotifications(userId) hook in src/hooks/useNotifications.ts subscribes to Supabase Realtime (postgres_changes INSERT on notifications filtered by recipient_id) and re-fetches on each event. NotificationBell in the Navbar consumes this hook.
OnboardingTour (injected in src/app/layout.tsx) uses localStorage key retouchly_show_onboarding as its trigger. Set this key to "true" (e.g. after signup in SignUpDialog) to show the tour on next render. Target elements are identified by data-tour attributes on Navbar elements: ai-tools, explore, credits, referral. The Settings page has a "Restart Tour" button that sets the key and reloads.
There is a single anon-key client at src/lib/supabase.ts used everywhere — both in Server Actions and client components. There is no separate service-role client. Operations that require elevated access use SECURITY DEFINER RPCs.
Tailwind CSS v4 with CSS variables in oklch color format. Dark mode via .dark class. shadcn/ui uses the new-york variant. Path alias @/* maps to src/*.
| Service | Purpose | Notes |
|---|---|---|
| Supabase | Auth, DB, Storage, Realtime | Single anon client; elevated ops via SECURITY DEFINER RPCs |
| Replicate | All image/voice AI models | Always useFileOutput: false; always use predictions.create() not run() for new features |
| OpenAI GPT-4 | AI assistant suggestions | src/lib/openai.ts |
| iyzipay | Payments (Turkish provider) | Listed as serverExternalPackages in next.config.ts |
Copy .env.example to .env.local:
OPENAI_API_KEY=
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
REPLICATE_API_TOKEN=
IYZICO_API_KEY=
IYZICO_API_SECRET=
IYZICO_BASE_URL=
There is no test infrastructure in this project. Use npx tsc --noEmit to catch type errors.