Modules/How I Work with AI
02

How I Work with AI

My actual workflow. Real prompts, real sessions. How I think about problems before writing the first message.

PromptsWorkflowThinking
After 20+ projects with Claude, I've figured out what works and what doesn't. This isn't theory — these are patterns I use every single day, extracted from real sessions building real software. If there's one module you should really internalize, it's this one. Everything else in this course builds on the workflow you'll learn here.
The Architect Mindset — The Most Important Shift

Before we get into prompts and techniques, you need to understand the fundamental mindset. You are not learning to be a programmer. You are learning to be an architect. An architect doesn't lay bricks — an architect decides where the building goes, how many floors it has, what each room is for, and what the experience should feel like for the people inside.

You decide WHAT to build. AI decides HOW to build it.

This means your job is: define the problem, describe the solution, review the output, test the result, and iterate. Claude's job is: write the code, pick the right libraries, handle the syntax, and implement the technical details. When you fully embrace this division of labor, everything clicks.
Bad Prompt vs. Good Prompt — Real Examples

Let me show you the difference between prompts that waste your time and prompts that produce great results.

Bad: "Build me a dashboard."
Good: "I need a dashboard page at /dashboard that shows email campaign stats. It should display 4 cards at the top: total sent, total delivered, total bounced, and open rate percentage. Below that, a line chart showing sends per day for the last 7 days. I'm using Next.js with TypeScript and Tailwind CSS. The data comes from a PostgreSQL database via an API at /api/stats."

Bad: "Add authentication."
Good: "I need login and registration pages. Users sign up with email and password. After login, they get a JWT token stored in an httpOnly cookie. There should be two roles: admin and client. Admins see all data, clients only see their own campaigns. The backend is Express with TypeScript. The users table already exists in PostgreSQL with columns: id, email, password_hash, role, created_at."

Bad: "Make the page look better."
Good: "The campaigns list page works but looks rough. Add proper spacing between elements — at least 16px gap between the cards. Make the table headers bold with a light gray background. Add hover effects on table rows. The action buttons should be smaller and use outline style instead of filled. Keep the layout but make it look more professional."
The Conversation Flow — It's a Dialogue, Not a Monologue

People think working with AI is: write one perfect prompt, get perfect code. That's completely wrong. It's a conversation. Here's what a real session looks like when I'm building a feature:

Me: I need a page where users can upload a CSV file of email recipients. The CSV has columns: email, first_name, last_name, company. After upload, show a preview table with the first 10 rows so the user can verify the data looks right. Then a "Confirm Upload" button that saves everything to the database.

Claude: *[Creates the upload page with file input, CSV parsing, preview table, and confirm button]*

Me: Good, but the CSV parser chokes when there are empty fields. Some rows have no company name. It should handle empty fields gracefully — just show an empty cell in the preview and save null to the database.

Claude: *[Fixes the parser to handle empty fields]*

Me: Nice. Now I also need a progress bar during upload because some files have 50,000+ rows and it takes a few seconds. And after upload completes, redirect to the recipients list page.

Claude: *[Adds progress bar and redirect]*

Me: The progress bar doesn't show actual progress, it just shows a spinner. Can you make it show real percentage based on rows processed?

Claude: *[Updates to real progress tracking]*

That's four messages to build one feature. Each message refines the result. This is normal. This is the workflow.
The Iteration Loop: Describe, Generate, Test, Fix, Repeat

Every single feature I've ever built follows this loop:

1. Describe — Tell Claude what you need in plain language
2. Generate — Claude writes the code
3. Test — You run it and see what happens
4. Fix — If something's wrong, describe what happened vs. what you expected
5. Repeat — Keep going until it works perfectly

The key insight: step 3 is non-negotiable. You must test every change. Don't assume it works because the code looks reasonable. Run it. Click every button. Try edge cases. Submit empty forms. Upload weird files. The testing is YOUR job, and it's where you catch 90% of the issues. Claude can write great code, but it can't click your buttons and see your screen.
How to Handle Errors — The Debugging Formula

You will see errors. Guaranteed. When you do, there's a formula that works every single time:

1. Copy the entire error message — don't summarize, don't paraphrase, copy the whole thing including the stack trace
2. Tell Claude what you were doing when it happened — "I clicked the submit button" or "I tried to load the dashboard page" or "I ran npm run dev"
3. Tell Claude what you expected to happen — "The form should have saved and redirected me to the list"

Real example:

Me: I get this error when I try to save a new campaign:
Error: relation "campaigns" does not exist at /app/api/campaigns/route.ts:15:5
I expected the campaign to be saved to the database and redirect me to /campaigns.

Claude: The error means the campaigns table doesn't exist in your database yet. Let me create a migration to set it up... *[writes the SQL and migration]*

That's it. Error message + what you did + what you expected. Claude fixes it. Don't be afraid of errors — they're information. The red text in your terminal is your friend.
CLAUDE.md Files — The Game Changer Nobody Talks About

Almost every project I have has a file called CLAUDE.md in the root directory. This is hands down the most underrated feature in the entire Claude Code workflow.

When you start Claude Code in a project folder, it automatically reads CLAUDE.md and loads that context before you even type anything. Think of it as a briefing document for your AI teammate. Instead of re-explaining your project every time you open a new session, Claude already knows.

Here's a real example of what goes in a CLAUDE.md:

`
# CampaignPulse

## What This Is
A QA tool for email campaigns. Users paste campaign HTML or a URL,
and the tool automatically checks links, UTM parameters, DNS records,
HTML rendering, and content quality.

## Tech Stack
- Vanilla JavaScript (no frameworks)
- HTML/CSS frontend
- n8n webhooks for backend processing
- Deployed on AWS EC2 at campaignpulse.infuse.com

## File Structure
- index.html — main page
- app.js — core logic, all checks run from here
- styles.css — styling
- config.js — API endpoints and webhook URLs

## Important Notes
- The n8n webhook URL for link checking is in config.js — don't hardcode it elsewhere
- DNS checks use Google DNS API, rate limited to 10 req/sec
- All check results must follow the scoring format: { score: 0-100, issues: [], passed: [] }

## Conventions
- No build tools, no npm, no bundlers — keep it simple
- All new checks go in the checks/ folder as separate files
- CSS uses BEM naming convention
`

With this file, Claude knows your stack, your structure, your conventions, and your gotchas before you say a single word. It's the difference between working with a new contractor every day vs. working with a teammate who knows the project inside out.
When to Start a New Conversation vs. Continue

This is a practical question everyone hits. Here's my rule:

Continue the same conversation when:
- You're still working on the same feature
- Claude just built something and you need to iterate on it
- You're debugging a problem Claude already has context about
- The conversation is under ~20 messages

Start a new conversation when:
- You're switching to a completely different feature
- The conversation is getting long (30+ messages) and Claude starts giving worse answers
- You realize you went down a wrong path and want a fresh start
- You're starting work the next day on the same project

With Claude Code, this matters less because CLAUDE.md gives persistent context. But for regular Claude chat sessions, starting fresh with good context in your first message is often better than dragging along a 50-message conversation where Claude is confused about what you've changed.
How the Context Window Works — Why Long Conversations Get Worse

Claude has a "context window" — basically, how much text it can hold in its memory at once. Think of it like a desk: there's only so much paper you can spread out before things start falling off the edge.

When your conversation is short, everything fits on the desk. Claude remembers your file structure, your database schema, the code it wrote 5 messages ago, and the error you got. Great.

When your conversation gets really long — 40, 50, 60 messages — the early messages start getting pushed out. Claude might "forget" that you changed the database schema in message 3, or that you renamed a file in message 12. You'll notice this when Claude suggests something you already tried, or writes code that contradicts what it wrote earlier.

The fix: When conversations get long, start a new one. Summarize the current state in your first message: "I'm working on CampaignPulse. So far I've built X, Y, and Z. The current issue is..." A fresh conversation with good context beats a stale long conversation every time.
The Real Power Move: Give Examples of What You Want

One of the strongest prompting techniques is showing Claude an example of the output you expect.

Without example: "I need a function that formats campaign stats."
With example: "I need a function that takes campaign stats and formats them. Input: { sent: 15420, opened: 8731, bounced: 201 }. Expected output: { sent: '15,420', opened: '8,731 (56.6%)', bounced: '201 (1.3%)' }. Percentages should be relative to sent count, formatted to one decimal place."

The second version leaves zero ambiguity. Claude doesn't have to guess what "format" means to you — you showed it. I use this technique constantly, especially for data transformations, API response structures, and UI text formatting.
Describing Expected Behavior — Be Specific About What the User Sees

Don't just describe what the code should do. Describe what the user experiences.

Instead of: "Add form validation."
Say: "When the user clicks Submit with empty fields, show a red error message below each empty field saying 'This field is required.' Don't submit the form. Don't clear the fields they already filled in. When they fix a field and click away, the error for that field should disappear immediately."

Instead of: "Add loading state."
Say: "When the user clicks 'Generate Report,' the button should show a spinner icon and change text to 'Generating...' It should be disabled so they can't click it twice. A progress bar should appear below the button. If it takes more than 10 seconds, show a message saying 'This is taking longer than usual, please wait.'"

The more you describe the user experience, the better the result.
Common Mistakes Beginners Make with AI Coding

I've seen the same mistakes over and over — and I made most of them myself at the start:

1. Being too vague: "Build me a web app" gives garbage results. Always include: what it does, who it's for, what tech stack, what data it handles.

2. Trying to build everything at once: Your first message should NOT be "Build me a complete SaaS with auth, dashboard, billing, admin panel, and API." Start with one feature. ONE.

3. Not giving context about what exists: Claude doesn't know you already have a users table, or that you're using Tailwind, or that your API is at /api/v2/. Tell it.

4. Not testing: "Claude said the code works, so it must work." No. Test it. Every time.

5. Changing requirements mid-message: "Build a login page. Actually also add a dashboard. And make sure the sidebar has navigation. Oh and add dark mode." One thing at a time.

6. Not pasting error messages: "It doesn't work" is useless. Paste the actual error. The full thing.

7. Assuming Claude remembers everything: It doesn't remember between sessions. Use CLAUDE.md. Re-state context when needed.
How to Review AI Code Even If You Don't Fully Understand It

You don't need to understand every line Claude writes. But you do need to review it. Here's how I do it, even now:

1. Does the file structure make sense? If Claude created 15 files for a simple form, something's wrong. If it put everything in one massive file, that's also a red flag.

2. Look for patterns you recognize. After a few projects, you'll recognize things like: this is a React component, this is an API route, this is a database query. If something looks wildly different from what you've seen before, ask Claude to explain it.

3. Test it. This is the ultimate review. Does the button work? Does the page load? Does the data save? If it works, the code is probably fine. If it doesn't, you found a bug.

4. Ask Claude to explain. Literally say: "Explain what this code does step by step, in simple terms." Claude will walk you through it. This is also how you learn — not from textbooks, but from understanding code you're actually using.

5. Check for obvious issues. Hardcoded passwords? API keys in the frontend code? Console.log statements everywhere? These are things even a beginner can spot.

6. Does it match what you asked for? If you asked for a table with 5 columns and Claude built one with 3, that's a problem regardless of how clean the code is.
When AI Is Wrong — And What to Do About It

Claude is not perfect. It makes mistakes. Here are the common ones and how to handle them:

Outdated approaches: Claude sometimes suggests libraries or patterns that are outdated. If it recommends something and your code throws deprecation warnings, say: "This seems to be using an old API. What's the current recommended approach for [X]?"

Hallucinated APIs: Occasionally Claude will use a function or method that doesn't exist. You'll know because you get an error like TypeError: x.someMethod is not a function. Just paste the error and Claude will correct itself.

Wrong assumptions about your code: Claude might assume you have a file or function that you don't. It'll write import { authMiddleware } from './middleware/auth' when that file doesn't exist. Tell it: "That file doesn't exist. I don't have auth middleware yet. Create it from scratch."

Overengineering: Sometimes Claude builds something way more complex than you need. If you asked for a simple form and got a 200-line component with custom hooks, context providers, and error boundaries — say: "This is too complex. I need a simple form with 3 fields that POSTs to /api/submit. Keep it minimal."

The fix is always the same: describe what went wrong, what you expected, and ask Claude to try again. Don't be afraid to push back.
Start with a Clear Problem, Not a Vague Idea

The number one mistake: going to Claude and saying "build me an app." That's like walking into a restaurant and saying "make me food." You'll get something, but probably not what you wanted.

Instead, start with the problem. What are you actually trying to solve? Who's it for? What should it do?

Here are three real prompts from my projects, showing the difference:

Vague (bad): "I need a QA tool."
Specific (good): "I need a web-based tool where I paste the HTML of an email campaign and it automatically checks: are all links working, do all links have UTM parameters, are DNS records (SPF, DKIM, DMARC) configured for the sending domain, and is the HTML valid. Show results as a checklist with pass/fail for each check and an overall score out of 100."

Vague (bad): "Help me process Excel files."
Specific (good): "I have Excel files with columns: Lead Name, Email, Company, Status, Date Added. I need a script that reads the file, filters rows where Status is 'Active' and Date Added is within the last 30 days, removes duplicates by email address, and exports the result as a new Excel file with the same column format. The input files are usually 5,000-50,000 rows."
Break Big Things into Small Steps

You don't build a house by saying "make me a house." You lay the foundation, then the walls, then the roof. Software is the same.

When I built SMTPCloud Dashboard (44,000+ lines of code), I didn't ask Claude to build it all at once. The conversation went something like this over weeks:

- Session 1: "Set up a Next.js project with TypeScript and Tailwind"
- Session 2: "Create the PostgreSQL database schema for users and campaigns"
- Session 3: "Build the login page with email/password"
- Session 4: "Add the API endpoint for fetching campaign stats"
- Session 5: "Build the dashboard page that shows stats from the API"
- Session 6: "Add campaign creation — a form with name, subject, recipients"
- ... and so on for months

Each step was something I could test and verify before moving to the next one. If something broke, I knew exactly where it broke. This is how you build something big without getting overwhelmed.
Give Context: "I Have X, I Want Y"

Claude doesn't know what you've already built unless you tell it. The magic formula is simple: here's what I have, here's what I want.

"I have a Next.js app with a PostgreSQL database. I have a users table and a campaigns table. I want to add a page at /campaigns that shows all campaigns for the logged-in user, sorted by date, with pagination — 20 items per page."

That one prompt gives Claude: your tech stack, your existing data model, what you want, where it goes, and how it should behave. That's a prompt that produces good code on the first try.

Another real example: "I have an Express API with JWT auth. The auth middleware is in src/middleware/auth.ts and it attaches req.user with { id, email, role }. I want to add a new endpoint GET /api/campaigns/:id/recipients that returns all recipients for a campaign, but only if the logged-in user owns that campaign. Return 403 if they don't."

Notice how I specified: the framework, the auth setup, where the middleware lives, what data it provides, the exact endpoint path, the behavior, AND the error case. That's a prompt that works.
Use CLAUDE.md for Every Project — Here's Exactly What Goes In One

I already mentioned CLAUDE.md above, but let me be even more specific. Here's another real example, from a different project:

`
# Quality Monitoring

## Overview
Enterprise QA tool for INFUSE. Tracks quality issues across campaigns.
Syncs data from 7 Google Sheets every 10 minutes via cron jobs.

## Stack
- Backend: Fastify + TypeScript
- Frontend: React 18 + Vite + Tailwind
- Database: PostgreSQL with 8 tables, 15+ indexes
- Deployment: Docker Compose on AWS EC2
- Testing: Vitest, 111 tests

## Architecture
- Backend: src/routes/ -> src/services/ -> src/db/
- Frontend: feature-based — src/features/dashboard/, src/features/issues/, etc.
- Each feature has: components/, hooks/, utils/

## Auth
- JWT with 3 roles: admin, team_lead, agent
- Role checks in route-level middleware
- Token in httpOnly cookie, refresh via /api/auth/refresh

## Cron Jobs
- Google Sheets sync: every 10 min (src/cron/sheets-sync.ts)
- Daily report: midnight UTC (src/cron/daily-report.ts)
- Cleanup: weekly Sunday 3am (src/cron/cleanup.ts)

## Gotchas
- Google Sheets API rate limit: 60 requests per minute per user
- The sheets-sync can take up to 45 seconds for large sheets — don't reduce the interval below 5 min
- Frontend .env.local has VITE_API_URL that must match the backend host
`

Every new Claude Code session on this project starts with full context. No wasted messages re-explaining things. This alone probably saves me 30% of my time across all projects.
Prompting Patterns That Consistently Work

After hundreds of sessions, these patterns reliably produce good results:

- Be specific about file paths: "In app/api/users/route.ts, add a GET endpoint that..."
- Mention your tech stack: "Using Next.js 14 with App Router, TypeScript, and Prisma"
- Reference existing patterns: "Follow the same pattern we used in the campaigns API"
- Ask for explanations: "Explain what this useEffect does and why we need it"
- Paste error messages in full — never summarize them, copy-paste the whole thing
- Be clear about what to keep and what to change: "Keep the header and sidebar as they are. Only change the main content area."
- Specify edge cases: "What should happen if the user uploads an empty file? What if the CSV has wrong column names?"
- Give numbers: "The table will have up to 50,000 rows" or "Users will upload files up to 100MB" — this affects the technical approach
Prompting Patterns That Don't Work

Things that consistently produce bad results:

- "Make it better" — better how? Faster? Prettier? More features?
- "Fix all the bugs" — which bugs? What symptoms? What error messages?
- "Build me a complete SaaS" — too big, break it down into features
- Changing requirements mid-message — finish one thing, then start the next
- Not providing error messages — Claude cannot read your screen
- Assuming Claude remembers previous sessions — it doesn't, unless you use CLAUDE.md
- "Just make it work" — this tells Claude nothing about what "work" means to you
- Asking Claude to guess your business logic — it doesn't know your domain, spell it out
The Real Workflow: From Idea to Production

Here's what my actual end-to-end process looks like:

1. Problem — I identify something that wastes time or is error-prone
2. Think — I spend 10-15 minutes thinking about what the solution should look like. Not the code — the user experience. What does the person see? What do they click? What data do they get?
3. First prompt — I describe the problem and the solution to Claude. I include tech stack, data structure, and expected behavior.
4. Build iteratively — One feature at a time. Test each one before moving on.
5. Debug — When things break (they will), I use the error formula: error message + what I did + what I expected.
6. Refine — "This works but I also need..." or "Can you improve how this looks?"
7. Deploy — Put it on a server. Usually this is another conversation with Claude: "I need to deploy this Express app on my Hetzner server at [IP]. I'm using PM2 and Nginx."
8. Iterate in production — Real users find real problems. Fix them. Add features they ask for.

A typical feature takes 5-20 messages. A typical project takes 20-50 sessions over days or weeks. This is a marathon, not a sprint. But every session produces something tangible.
Tips That Took Me 20 Projects to Learn

- Start your message with the goal, not the background. "I need to add pagination to the campaigns table" is better than a three-paragraph explanation of your project followed by the request.
- When something looks wrong but you can't explain why, take a screenshot or describe exactly what you see: "The table shows 3 columns but I expected 5. The data is there in the API response — I checked in the browser dev tools."
- If Claude suggests installing a new library, ask why. Sometimes there's a simpler way to do it with what you already have.
- Save prompts that worked well. When you get a great result from a specific prompt structure, remember that structure for next time.
- Don't be afraid to say "start over." If Claude went down the wrong path after 10 messages, starting fresh is faster than trying to untangle the mess.
- Always tell Claude about constraints: "This needs to work offline," or "The server only has 1GB of RAM," or "My users are not technical — the UI must be dead simple."

The bottom line: working with AI is a skill, and it's different from programming. It's closer to being a great project manager than being a great coder. You need to communicate clearly, break problems down, provide context, and verify results. Master that, and you can build anything.