Cursor Composer in Practice — Multi-File Editing and Complete Workflows
Chapter 6: Cursor Composer in Practice — Multi-File Editing and Complete Workflows
Composer is the biggest difference between Cursor and other AI editors: you describe a feature, it simultaneously edits 5–10 files, shows you a complete diff, and you apply everything with one click after review. This is not completion — it's collaborative development. This chapter covers how Composer works internally, three techniques you must master, and a complete walkthrough of adding Stripe payments from scratch.
Chat vs Composer: When to Use Which
| Dimension | Cursor Chat | Cursor Composer |
|---|---|---|
| Primary use | Understanding, discussion, getting suggestions | Directly generating and modifying code |
| Code changes | Requires manual copy-paste | Automatic diff, one-click apply |
| File scope | Mainly single-file discussion | Coordinated changes across multiple files |
| Best for | Asking "why", learning principles | Doing "what", implementing features |
| Context control | Explicit @ references | Automatic project structure analysis |
Decision rule: if you need to modify code, use Composer; if you only need to understand code, use Chat.
Composer's Internal Workflow
When you submit a Prompt in Composer, these steps happen behind the scenes:
- Project analysis: Cursor scans the project structure and builds a file dependency graph
- Semantic search: Finds files in the vector index relevant to your description
- Context assembly: Combines relevant file contents, .cursorrules rules, and your Prompt into a long context
- Generate a plan: AI first plans which files to modify and what to add
- Generate code: Generates changes file-by-file according to the plan
- Display diff: Shows all changes in a git-diff-like interface for per-file review
Step 6 is critical: you see all changes before applying them. This is the most important difference between Composer and "just write it for me" — AI is the proposer, you are the decision-maker.
Three Techniques You Must Master
Technique 1: Use Checkpoints to Submit in Phases
Don't ask Composer to implement an entire feature in one Prompt. Break it into logical phases:
# Round 1 Prompt (data layer)
Add a Payment model to the Prisma schema with fields:
- id: UUID primary key
- userId: foreign key to User
- amount: Integer (store in cents, not dollars)
- currency: String (default "USD")
- status: Enum (pending / completed / failed)
- stripePaymentIntentId: String (nullable)
- createdAt: DateTime
Generate the corresponding migration file. Do not touch any other files.
After applying round 1, test that the database migration succeeds, then start round 2:
# Round 2 Prompt (service layer)
Based on the Payment model just created, create PaymentService:
- createPaymentIntent(userId, amount, currency): calls Stripe API to create a payment intent
- confirmPayment(paymentIntentId): confirms successful payment in Webhook, updates DB status
- getUserPayments(userId): queries a user's payment history
Use the existing Stripe client instance in @src/lib/stripe.ts. Do not reinitialize.
Why phases matter: each step can be tested and verified, problems are easy to isolate, and you avoid "changed 50 files but have no idea what broke."
Technique 2: Use Agent Mode for Large Uncertain Tasks
Click the "Agent" button in Composer to enter autonomous execution mode: Cursor reads files, runs commands, and decides the next step on its own. Best for tasks like "complete this large refactor."
Typical Agent mode Prompt:
Analyze the project's error handling approach, then standardize it to:
1. All API errors return { success: false, error: { code, message } } format
2. Service layer throws a custom AppError class (already defined in src/errors.ts)
3. Route layer uniformly catches and formats the response
List the plan before executing. After each module, describe what you changed.
Agent mode warning: It executes real commands, including potential file deletions. Make sure git is in a clean state before starting, so you can roll back if something goes wrong.
Technique 3: The Right Way to Review Diffs
When Composer shows you a diff, don't just click "Accept All." The correct review flow:
- Look at the file list first: Are any files being modified that shouldn't be?
- Focus on new external calls: What new libraries or APIs are being introduced? Do they comply with project standards?
- Check edge cases: Is error handling complete? How are null values, zeros, and overlong inputs handled?
- Verify constraints: Did the AI honor the constraints you specified in your Prompt (don't change the interface, keep tests passing)?
Real Case: Adding Stripe Payments in Two Rounds
Goal: Add Stripe one-time payment to an existing Next.js + Prisma project.
Round 1: Data model and service layer
Add Stripe payment to the project, Phase 1: infrastructure
1. Add Payment model to prisma/schema.prisma:
- id, userId(FK), amount(Int,cents), currency(String),
status(Enum:pending/completed/failed),
stripePaymentIntentId(String?), createdAt
2. Create src/lib/stripe.ts: initialize Stripe client (use STRIPE_SECRET_KEY env var)
3. Create src/services/PaymentService.ts:
- createPaymentIntent(userId: string, amount: number): Promise<{clientSecret: string, paymentId: string}>
- confirmPayment(stripePaymentIntentId: string): Promise<void>
Do only these three things. Do not touch routes or frontend.
Round 2: API routes and Webhook
Based on the PaymentService from the previous step, complete Phase 2:
1. POST /api/payments/create-intent:
- Verify user is logged in (use getCurrentUser from src/lib/auth.ts)
- Call PaymentService.createPaymentIntent
- Return clientSecret to frontend
2. POST /api/webhooks/stripe:
- Verify Stripe Webhook signature (use stripe.webhooks.constructEvent)
- Handle payment_intent.succeeded event, call PaymentService.confirmPayment
- IMPORTANT: this route must use req.text() not req.json()
(Stripe signature verification requires the raw body)
Do not create frontend components. Backend API only.
After two rounds the payment backend is complete, fully auditable, and easy to test.
5 Common Failure Patterns
| Failure pattern | Symptom | Fix |
|---|---|---|
| Prompt too vague | Generated code looks reasonable but doesn't match project style | Add specific constraints: which library, which existing file to follow |
| Too much in one request | Dozens of files changed; unknown what broke | Split into small phases; one concern per round |
| No .cursorrules | AI doesn't know project conventions; style drifts between rounds | Write .cursorrules first with tech stack and standards |
| Accept All without reviewing | Bugs hard to trace back to which round caused them | Review every diff carefully before accepting |
| Agent mode out of control | Modified files it shouldn't, deleted important content | Commit before starting; git checkout to recover |
Chapter Key Points
- Composer is for "building features," Chat is for "understanding code" — know the boundary and each tool delivers its full value
- Phased Checkpoints are essential: Break one large feature into 3–5 Prompts. Validate each round before starting the next. Far more reliable than a single large commit.
- Agent mode is a double-edged sword: Powerful but risky. Ensure clean git state before starting; monitor what it does in real time.
- Diff review is not ceremonial: Focus on new external calls, edge case handling, and whether constraints were honored.
- .cursorrules is an upfront investment for Composer: Without it, Composer's generated code style will drift. Write it once; it pays off on every subsequent use.