Chapter 13

Cursor + Product Teams

Product teams live in the gap between vision and execution. Ideas are plentiful; validating them is expensive. Prototypes take weeks. Specs drift from implementation. Cursor collapses this gap.

ℹ️
Bridging Vision and Execution

This chapter explores how product managers use Cursor to transform abstract requirements into executable specifications, validate ideas through rapid prototyping, and ensure what gets built matches what was envisioned—all without writing production code themselves.

13.1 Writing User Stories Directly into Prompts

The Traditional Handoff Problem

The typical workflow creates multiple translation layers:

  1. PM writes user story in Jira
  2. Engineering interprets story
  3. Implementation doesn't match intent
  4. Back-and-forth clarification
  5. Rework and delay

⚠️
The Disconnect

Stories describe what but not how. Engineers guess wrong, and the iteration tax compounds.

Cursor-Native User Stories

With Cursor, stories become executable specifications that generate implementation directly.

❌ Traditional story:

As a user, I want to filter products by price range
So that I can find items within my budget

✅ Cursor-enhanced story:

# User Story: Product Price Filter

## Acceptance Criteria
1. Display price range slider ($0-$10,000)
2. Filter updates in real-time (debounced 300ms)
3. Show count of matching products
4. Persist filter in URL query params
5. Clear filter button resets range

## Technical Requirements
- Component: @components/ProductFilters.jsx
- State: Use existing useProducts hook
- API: GET /api/products?minPrice={min}&maxPrice={max}
- UI: Follow @design/components/Slider.tsx

## Edge Cases
- Handle empty results gracefully
- Validate min <= max
- Round to 2 decimals

## Testing
- Unit: Component state management
- Integration: API filtering
- E2E: User can filter and share URL

Prompt to engineering:

Implement this user story:
@specs/price-filter-story.md

Generate:
1. React component with price slider
2. Integration with useProducts hook
3. URL state management
4. Unit and integration tests

Zero Interpretation Gap

Cursor generates complete implementation matching the spec. No interpretation gap. No ambiguity.

Best Practices for Executable Stories

1. Include Technical Context

## Technical Context
- Existing components: @components/
- API documentation: @docs/api.md
- State management: Redux (@store/products.js)
- Design system: @design/components/

This ensures generated code integrates seamlessly with existing architecture.

2. Define Success Explicitly

❌ Vague:

"Should be fast"

"Handle errors"

✅ Specific:

"API response < 200ms p95, UI updates < 16ms (60fps)"

"Display user-friendly error message, log to Sentry with context, allow retry"

3. Specify Edge Cases

## Edge Cases
- No products match → Display "No results found" with filter summary
- User types negative price → Validate and show inline error
- Slider at maximum → Include products at exact max price
- Network failure → Show cached results + retry button

Explicit edge cases prevent AI from making incorrect assumptions.

13.2 Rapid Prototyping with Cursor

From Sketch to Working Demo in Minutes

Traditional approach: Designers create static mockups. Stakeholders see pictures, not interactions. Feedback comes late, after engineering starts.

With Cursor: PMs create interactive prototypes during discussions—validating ideas in real-time before committing engineering resources.

Example: Dashboard Prototype in 60 Seconds

Scenario: Your CEO wants a real-time analytics dashboard. You need to demo tomorrow morning.

Prompt:

Create an interactive dashboard prototype:

Layout:
- Header with logo and user menu
- 4 metric cards (Revenue, Users, Conversion, Churn)
- 2 charts (Revenue trend line, User growth bars)

Data:
- Mock data (last 30 days)
- Update every 5 seconds (simulate real-time)
- Animate transitions

Tech: React + Recharts, Tailwind CSS, responsive
Style: Modern, purple/blue theme, dark mode support

Cursor generates (abbreviated):

export function AnalyticsDashboard() {
  const [metrics, setMetrics] = useState({
    revenue: '$142,500',
    users: '8,234',
    conversion: '3.2%',
    churn: '1.8%'
  });
  
  const [chartData] = useState(generateMockData());
  const [darkMode, setDarkMode] = useState(false);
  
  // Simulate real-time updates
  useEffect(() => {
    const interval = setInterval(() => {
      setMetrics(prev => ({
        ...prev,
        revenue: formatCurrency(
          parseCurrency(prev.revenue) + Math.random() * 1000
        ),
        users: (
          parseInt(prev.users.replace(/,/g, '')) + 
          Math.floor(Math.random() * 10)
        ).toLocaleString()
      }));
    }, 5000);
    
    return () => clearInterval(interval);
  }, []);
  
  return (
    <div className={darkMode ? 'dark' : ''}>
      <header className="bg-white dark:bg-gray-800 px-6 py-4 
                         flex justify-between items-center">
        <h1 className="text-2xl font-bold">Analytics Pro</h1>
        <button 
          onClick={() => setDarkMode(!darkMode)}
          className="text-2xl"
        >
          {darkMode ? '🌞' : '🌙'}
        </button>
      </header>
      
      <div className="grid grid-cols-4 gap-6 p-6">
        <MetricCard 
          title="Revenue" 
          value={metrics.revenue} 
          change={12.5} 
        />
        <MetricCard 
          title="Active Users" 
          value={metrics.users} 
          change={8.3} 
        />
        <MetricCard 
          title="Conversion" 
          value={metrics.conversion} 
          change={-2.1} 
        />
        <MetricCard 
          title="Churn Rate" 
          value={metrics.churn} 
          change={-5.4} 
        />
      </div>
      
      <div className="grid grid-cols-2 gap-6 p-6">
        <RevenueLineChart data={chartData} darkMode={darkMode} />
        <UserGrowthBarChart data={chartData} darkMode={darkMode} />
      </div>
    </div>
  );
}

Ready in Minutes

Fully interactive prototype with animations, dark mode, and real-time updates—ready in minutes. Stakeholders can click, interact, and provide feedback before any engineering sprint begins.

Prototyping from Design Files

Modern workflow:

  1. Export Figma design as PNG
  2. Upload to Cursor
  3. Prompt: "Convert this design to React component, match layout exactly, use Tailwind CSS"

Cursor analyzes the image and generates matching code—80-90% accurate, enough to validate interactions and gather feedback immediately.

When to Prototype vs. Build

✅ Use prototypes for:

  • Early-stage concept validation
  • Stakeholder alignment meetings
  • User research sessions
  • A/B test design exploration
  • Technical feasibility assessment

❌ Don't use prototypes for:

  • Production deployments
  • Security-sensitive features
  • Complex business logic
  • Performance-critical paths

ℹ️
The Golden Rule

Prototypes inform decisions. Production code implements decisions.

13.3 Aligning Product Specs with AI-Assisted Dev

The Spec-Implementation Drift

Traditional gap:

  1. Product writes spec (intent)
  2. Engineering interprets (understanding)
  3. Code gets written (implementation)
  4. QA discovers gap between intent and implementation
  5. Rework cycle begins

Each iteration wastes days or weeks.

Spec-Driven Development

When specs are structured for AI, they become living contracts that generate and validate code.

Spec document (specs/email-notifications.md):

# Email Notifications System

## Notification Types

### Security (Always Sent)
- Password changed
- New login from unknown device
- Two-factor auth enabled/disabled

### Activity (User Configurable)
- New follower
- Comment on post
- Weekly digest

### Marketing (Opt-in Only)
- Product updates
- Promotional offers

## Business Rules

1. Security notifications are mandatory (cannot disable)
2. Activity notifications: max 10 emails/day (rate limiting)
3. Marketing requires explicit consent
4. All emails include unsubscribe link
5. Failed sends retry 3 times (exponential backoff)

## API Endpoints

### GET /api/notifications/preferences
Returns user's current notification preferences

### PUT /api/notifications/preferences
Updates preferences

Validation:
- Cannot disable security notifications (return 400)
- Marketing opt-in requires explicit consent field

## Implementation Requirements
- Use existing EmailService (@services/email.js)
- Store preferences in users.notification_settings (JSONB)
- Log all preference changes for audit
- Rate limiting tracked in Redis

Prompt to engineering:

Implement Email Notifications System:
@specs/email-notifications.md

Generate:
1. API endpoints with validation
2. Service layer enforcing business rules
3. Tests verifying acceptance criteria

Cursor generates implementation enforcing all rules:

// src/api/notifications.js
router.put('/preferences', async (req, res) => {
  const updates = req.body;
  
  // Rule: Cannot disable security notifications
  if (updates.security?.enabled === false) {
    return res.status(400).json({
      error: 'Security notifications cannot be disabled'
    });
  }
  
  // Rule: Marketing requires explicit consent
  if (updates.marketing?.enabled && !req.body.marketingConsent) {
    return res.status(400).json({
      error: 'Marketing emails require explicit consent'
    });
  }
  
  await NotificationService.updatePreferences(req.user.id, updates);
  res.json({ success: true });
});

// src/services/NotificationService.js
async sendNotification(userId, type, data) {
  // Rule: Activity rate limiting (10/day)
  if (type.startsWith('activity.')) {
    const todayCount = await this.getActivityEmailCount(userId);
    if (todayCount >= 10) {
      logger.warn('Activity email rate limit hit', { userId, type });
      return { queued: false, reason: 'rate_limit' };
    }
  }
  
  // Rule: Retry 3 times with exponential backoff
  await EmailQueue.add({
    userId,
    type,
    data,
    retries: 3,
    backoff: { type: 'exponential', delay: 1000 }
  });
  
  return { queued: true };
}

Tests verify spec compliance:

test('rejects disabling security notifications', async () => {
  const response = await request(app)
    .put('/api/notifications/preferences')
    .send({ security: { enabled: false } })
    .expect(400);
  
  expect(response.body.error).toContain('cannot be disabled');
});

test('enforces activity rate limiting', async () => {
  // Send 10 activity emails
  for (let i = 0; i < 10; i++) {
    await NotificationService.sendNotification(
      userId, 
      'activity.comment', 
      {}
    );
  }
  
  // 11th should be rate limited
  const result = await NotificationService.sendNotification(
    userId, 
    'activity.comment', 
    {}
  );
  
  expect(result.queued).toBe(false);
  expect(result.reason).toBe('rate_limit');
});

test('requires explicit consent for marketing emails', async () => {
  const response = await request(app)
    .put('/api/notifications/preferences')
    .send({ 
      marketing: { enabled: true }
      // Missing marketingConsent
    })
    .expect(400);
  
  expect(response.body.error).toContain('explicit consent');
});

The Magic

Spec drives implementation. When specs update, regenerate code. Alignment is guaranteed.

Spec Templates for Common Features

Build reusable templates:

specs/templates/
├── api-endpoint.md        # RESTful API specification
├── user-workflow.md       # Multi-step user journey
├── data-model.md          # Database schema and rules
├── integration.md         # Third-party integration
└── background-job.md      # Async processing task

Example template:

# API Endpoint Specification Template

## Endpoint
[METHOD] /api/[resource]/[action]

## Purpose
[One sentence describing what this endpoint does]

## Authentication
- [ ] Public (no auth required)
- [ ] Authenticated users only
- [ ] Specific roles: [list roles]

## Request

### Headers
- Content-Type: application/json
- Authorization: Bearer {token}

### Body Schema
```json
{
  "field1": "string (required, max 100 chars)",
  "field2": "number (optional, min 0)"
}
```

### Validation Rules
1. [Field-specific validation]
2. [Cross-field validation]
3. [Business rule validation]

## Response

### Success (200)
```json
{
  "data": { ... },
  "meta": { ... }
}
```

### Error Cases
* 400: Validation error
* 401: Unauthorized
* 403: Forbidden
* 404: Resource not found
* 429: Rate limit exceeded

## Business Rules
1. [Rule with enforcement point]
2. [Rule with edge case handling]

## Testing Requirements
* [ ] Happy path test
* [ ] All error cases covered
* [ ] Rate limiting verified
* [ ] Authorization checked

## Performance Requirements
* Response time: < [X]ms p95
* Throughput: > [Y] req/sec

13.4 Measuring Product-Engineering Alignment

Key Alignment Metrics

Track these to ensure specs and implementation stay synchronized:

MetricTargetMeasurement Method
Spec-to-Code Match> 95%Automated validation against spec
Rework Rate< 10%Stories requiring reimplementation
Clarification Requests< 2 per storyQuestions during implementation
Acceptance on First Review> 80%Stories accepted without changes
Time from Spec to Done< 3 daysCycle time tracking

Automated Spec Validation

Create tests that verify implementation matches specification:

// tests/spec-validation/email-notifications.test.js
describe('Email Notifications Spec Compliance', () => {
  test('security notifications cannot be disabled per spec', async () => {
    // Reference: specs/email-notifications.md, Rule #1
    const response = await updatePreferences({ 
      security: { enabled: false } 
    });
    
    expect(response.status).toBe(400);
    expect(response.body.error).toMatch(/cannot be disabled/i);
  });
  
  test('activity emails rate limited to 10/day per spec', async () => {
    // Reference: specs/email-notifications.md, Rule #2
    const emailsSent = await sendActivityEmails(11);
    
    expect(emailsSent).toBe(10);
    expect(lastEmailResult.reason).toBe('rate_limit');
  });
  
  test('marketing requires explicit consent per spec', async () => {
    // Reference: specs/email-notifications.md, Rule #3
    const response = await updatePreferences({
      marketing: { enabled: true }
      // No marketingConsent field
    });
    
    expect(response.status).toBe(400);
    expect(response.body.error).toMatch(/explicit consent/i);
  });
});

ℹ️
Living Contract

These tests serve as a contract between product and engineering. When specs change, tests update, and implementation follows.

13.5 Common Pitfalls for Product Teams

Pitfall 1: Over-Specifying Implementation

Problem: Specs become too technical, constraining engineering flexibility.

❌ Bad:

Use React useState hook with debounced callback wrapped in useCallback and memoized with useMemo. Store filter state in Redux using filterReducer action UPDATE_PRICE_FILTER.

✅ Good:

Filter updates should feel instant but not hammer the API. Debounce appropriately (300ms recommended). Persist in URL for shareability. Use existing state management patterns.

ℹ️
The Rule

Specify what and why, not how. Let engineering determine implementation details.

Pitfall 2: Vague Acceptance Criteria

Problem: AI generates plausible code that doesn't solve the actual problem.

❌ Bad:

Users should be able to search products easily. The search should be good.

✅ Good:

Search requirements:

  • Text input with autocomplete (min 2 chars)
  • Search across: name, description, SKU, category
  • Results update as user types (debounced 300ms)
  • Show top 5 matches with highlighting
  • Display "No results" after 1 second of no matches
  • Pressing Enter navigates to full results page

Pitfall 3: Ignoring Technical Constraints

Problem: Specs ignore system limitations, leading to impossible implementations.

Example: "Display 10,000 products on one page with real-time updates"

Solution: Involve engineering early. Use Cursor to validate feasibility:

Analyze technical feasibility:
@specs/product-display.md

Consider:
- Browser rendering limits
- Memory constraints
- Network bandwidth
- Real-time update scalability

Suggest alternative approaches if infeasible.

Cursor might respond:

Current spec proposes rendering 10,000 DOM elements with WebSocket updates. This will cause:

  1. Browser memory: ~500MB for DOM alone
  2. Render blocking: 2-3 second initial paint
  3. WebSocket overhead: ~100 messages/sec = unsustainable

Recommended alternatives:

Option 1: Virtual scrolling

  • Render only visible rows (~20-30)
  • Memory: ~10MB, instant rendering
  • Updates: Only refresh visible items

Option 2: Pagination + filters

  • 50 items per page with smart filtering
  • Pre-load next page for smooth UX
  • Real-time updates only for current page

Option 3: Progressive loading

  • Show first 50 instantly
  • Infinite scroll loads more
  • Updates via periodic polling (every 5 sec)

Recommendation: Option 1 (virtual scrolling) provides best UX without technical limitations.

Prevent Costly Rework

This early feasibility check prevents costly rework.

Key Takeaways for Product Teams

Executable Specifications

Structure user stories with enough detail that they become direct inputs to implementation, eliminating interpretation gaps.

Prototype Relentlessly

Validate ideas with interactive prototypes before committing engineering resources. Minutes of prototyping save weeks of rework.

Spec-Driven Development

Treat specifications as living contracts. When specs are AI-readable, implementation automatically aligns with intent.

Measure Alignment

Track spec-to-code match rate, rework rate, and cycle time. Poor metrics indicate specification quality issues.

Balance Clarity and Flexibility

Specify what and why clearly, but let engineering determine how. Over-specifying implementation details constrains solutions.

The Future of Product Management

The future of product management isn't about writing more detailed specs in Jira. It's about creating executable specifications that generate implementation, enable rapid validation through prototyping, and maintain alignment throughout development. Product teams that master this approach ship faster, iterate smarter, and waste less engineering time on misaligned features.