jettypod 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/PROTECT_SKILLS.md +28 -0
- package/.claude/settings.json +24 -0
- package/.claude/settings.local.json +16 -0
- package/.claude/skills/epic-discover/SKILL.md +262 -0
- package/.claude/skills/feature-discover/SKILL.md +393 -0
- package/.claude/skills/speed-mode/SKILL.md +364 -0
- package/.claude/skills/stable-mode/SKILL.md +591 -0
- package/.github/workflows/test-safety.yml +85 -0
- package/README.md +25 -0
- package/SPEED-STABLE-AUDIT.md +853 -0
- package/SYSTEM-BEHAVIOR.md +1241 -0
- package/TEST_SAFETY_AUDIT.md +314 -0
- package/TEST_SAFETY_IMPLEMENTATION.md +97 -0
- package/cucumber.js +8 -0
- package/docs/COMMAND_REFERENCE.md +903 -0
- package/docs/DECISIONS.md +68 -0
- package/docs/README.md +48 -0
- package/docs/STANDARDS-SYSTEM-DOCUMENTATION.md +374 -0
- package/docs/TEST-REWRITE-PLAN.md +261 -0
- package/docs/ai-test-writing-requirements.md +219 -0
- package/docs/claude-code-skills.md +607 -0
- package/docs/core-jettypod-methodology/comprehensive-jettypod-methodology.md +582 -0
- package/docs/core-jettypod-methodology/deprecated/jettypod-comprehensive-standards.md +1222 -0
- package/docs/core-jettypod-methodology/deprecated/jettypod-operating-guide.md +3399 -0
- package/docs/core-jettypod-methodology/deprecated/jettypod-technical-checklist.md +1325 -0
- package/docs/core-jettypod-methodology/deprecated/jettypod-vibe-coding-framework.md +1544 -0
- package/docs/core-jettypod-methodology/deprecated/prompt-engineering-guide.md +320 -0
- package/docs/core-jettypod-methodology/deprecated/vibe-coding-cheatsheet (1).md +516 -0
- package/docs/core-jettypod-methodology/deprecated/vibe-coding-framework.md +1544 -0
- package/docs/features/jettypod-standards-explained.md +543 -0
- package/docs/features/standards-inventory.md +257 -0
- package/docs/gap-analysis-current-vs-comprehensive-methodology.md +939 -0
- package/docs/jettypod-system-overview.md +409 -0
- package/features/auto-generate-production-chores.feature +14 -0
- package/features/claude-md-protection/steps.js +487 -0
- package/features/decisions/index.js +490 -0
- package/features/decisions/index.test.js +208 -0
- package/features/git-hooks/git-hooks.feature +30 -0
- package/features/git-hooks/index.js +93 -0
- package/features/git-hooks/index.test.js +137 -0
- package/features/git-hooks/post-commit +56 -0
- package/features/git-hooks/post-merge +47 -0
- package/features/git-hooks/pre-commit +28 -0
- package/features/git-hooks/simple-steps.js +53 -0
- package/features/git-hooks/simple-test.feature +10 -0
- package/features/git-hooks/steps.js +196 -0
- package/features/jettypod-update-command.feature +46 -0
- package/features/mode-prompts/index.js +95 -0
- package/features/mode-prompts/simple-steps.js +44 -0
- package/features/mode-prompts/simple-test.feature +9 -0
- package/features/mode-prompts/validation.test.js +120 -0
- package/features/refactor-mode/steps.js +217 -0
- package/features/refactor-mode.feature +49 -0
- package/features/skills-update/index.test.js +216 -0
- package/features/step_definitions/auto-generate-production-chores.steps.js +162 -0
- package/features/step_definitions/terminal-logo.steps.js +145 -0
- package/features/step_definitions/update-command.steps.js +183 -0
- package/features/terminal-logo/index.js +39 -0
- package/features/terminal-logo/terminal-logo.feature +30 -0
- package/features/update-command/index.js +181 -0
- package/features/update-command/index.test.js +225 -0
- package/features/work-commands/bug-workflow-display.feature +22 -0
- package/features/work-commands/index.js +311 -0
- package/features/work-commands/simple-steps.js +69 -0
- package/features/work-commands/stable-tests.feature +57 -0
- package/features/work-commands/steps.js +1120 -0
- package/features/work-commands/validation.test.js +88 -0
- package/features/work-commands/work-commands.feature +13 -0
- package/features/work-tracking/discovery-validation.test.js +228 -0
- package/features/work-tracking/index.js +1511 -0
- package/features/work-tracking/mode-required.feature +112 -0
- package/features/work-tracking/phase-tracking.test.js +482 -0
- package/features/work-tracking/prototype-tracking.test.js +485 -0
- package/features/work-tracking/tree-view.test.js +310 -0
- package/features/work-tracking/work-set-mode.feature +71 -0
- package/features/work-tracking/work-start-mode.feature +88 -0
- package/full-test.txt +0 -0
- package/install.sh +89 -0
- package/jettypod.js +1640 -0
- package/lib/bug-workflow.js +94 -0
- package/lib/bug-workflow.test.js +177 -0
- package/lib/claudemd.js +130 -0
- package/lib/claudemd.test.js +195 -0
- package/lib/comprehensive-standards-full.json +1778 -0
- package/lib/config.js +181 -0
- package/lib/config.test.js +511 -0
- package/lib/constants.js +107 -0
- package/lib/constants.test.js +164 -0
- package/lib/current-work.js +130 -0
- package/lib/current-work.test.js +146 -0
- package/lib/database-project-config.test.js +107 -0
- package/lib/database.js +256 -0
- package/lib/database.test.js +106 -0
- package/lib/decisions-generator.js +102 -0
- package/lib/decisions-generator.test.js +457 -0
- package/lib/decisions-helpers.js +119 -0
- package/lib/decisions-helpers.test.js +310 -0
- package/lib/discovery-checkpoint.js +83 -0
- package/lib/docs-generator.js +280 -0
- package/lib/external-checklist.js +177 -0
- package/lib/git.js +142 -0
- package/lib/git.test.js +145 -0
- package/lib/logo.js +3 -0
- package/lib/migrations/001-epic-to-parent.js +24 -0
- package/lib/migrations/002-default-work-item-modes.js +37 -0
- package/lib/migrations/002-default-work-item-modes.test.js +351 -0
- package/lib/migrations/003-epic-discovery-fields.js +52 -0
- package/lib/migrations/004-discovery-decisions-table.js +32 -0
- package/lib/migrations/005-migrate-decision-data.js +62 -0
- package/lib/migrations/006-feature-phase-field.js +61 -0
- package/lib/migrations/007-prototype-tracking.js +38 -0
- package/lib/migrations/008-scenario-file-field.js +24 -0
- package/lib/migrations/index.js +74 -0
- package/lib/production-helpers.js +69 -0
- package/lib/project-state.test.js +92 -0
- package/lib/test-helpers.js +184 -0
- package/lib/test-helpers.test.js +255 -0
- package/package.json +36 -0
- package/prototypes/test/index.html +1 -0
- package/setup-dist-repo.sh +68 -0
- package/test-safety-check.sh +80 -0
- package/work-item-tracking-plan.md +199 -0
|
@@ -0,0 +1,1325 @@
|
|
|
1
|
+
# The Vibe Coding Operating System: Complete Technical Cheat Sheet
|
|
2
|
+
|
|
3
|
+
## 🎯 QUICK MODE SELECTOR
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
CLARITY CHECK: RISK CHECK:
|
|
7
|
+
□ Know exactly what to build? □ Real users depend on this?
|
|
8
|
+
□ Have clear success criteria? □ Handling sensitive/financial data?
|
|
9
|
+
□ Built similar before? □ Hard to fix if wrong?
|
|
10
|
+
|
|
11
|
+
0-1 Risk + Low Clarity → Discovery Mode
|
|
12
|
+
0-1 Risk + High Clarity → Speed Mode
|
|
13
|
+
2-3 Risk + High Clarity → Production Mode
|
|
14
|
+
2-3 Risk + Low Clarity → STOP! Discovery first
|
|
15
|
+
Something broken → Recovery Mode (always)
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# 🚀 USE CASE 1: STARTING A NEW PROJECT
|
|
22
|
+
|
|
23
|
+
## 🔍 DISCOVERY MODE: "I don't know what I'm building"
|
|
24
|
+
|
|
25
|
+
**TECHNICAL MUST DO:**
|
|
26
|
+
|
|
27
|
+
- [ ] Create 3+ HTML/React prototypes
|
|
28
|
+
- [ ] Use fake data (`const mockData = [...]`)
|
|
29
|
+
- [ ] Try different UI libraries (Material-UI vs Tailwind)
|
|
30
|
+
- [ ] Test localStorage vs IndexedDB
|
|
31
|
+
- [ ] Compare REST vs GraphQL approaches
|
|
32
|
+
- [ ] Build kanban vs list vs table views
|
|
33
|
+
|
|
34
|
+
**EXAMPLE APPROACHES TO TEST:**
|
|
35
|
+
|
|
36
|
+
```jsx
|
|
37
|
+
// Approach 1: Client-side everything
|
|
38
|
+
const App = () => {
|
|
39
|
+
const [data, setData] = useState(mockData);
|
|
40
|
+
return <div>{/* All logic in React */}</div>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Approach 2: Backend-driven
|
|
44
|
+
const App = () => {
|
|
45
|
+
const data = await fetch('/api/data');
|
|
46
|
+
return <div>{/* Thin client */}</div>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Approach 3: Hybrid
|
|
50
|
+
const App = () => {
|
|
51
|
+
const cache = localStorage.getItem('data');
|
|
52
|
+
const fresh = await fetch('/api/sync');
|
|
53
|
+
return <div>{/* Smart sync */}</div>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**PROMPT TEMPLATE:**
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
"Show me 3 different ways to build [concept]:
|
|
62
|
+
1. Simplest possible
|
|
63
|
+
2. Most user-friendly
|
|
64
|
+
3. Most scalable
|
|
65
|
+
Create interactive mockups I can click through"
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 🏃 SPEED MODE: "I know what I want, just build it"
|
|
70
|
+
|
|
71
|
+
**TECHNICAL MUST DO:**
|
|
72
|
+
|
|
73
|
+
- [ ] Single HTML/React file to start
|
|
74
|
+
- [ ] Use localStorage instead of database
|
|
75
|
+
- [ ] Inline styles or Tailwind CDN
|
|
76
|
+
- [ ] `useState` for all state management
|
|
77
|
+
- [ ] Hardcoded data for testing
|
|
78
|
+
- [ ] `console.log` for debugging
|
|
79
|
+
- [ ] Working version in 1 hour max
|
|
80
|
+
|
|
81
|
+
**SPEED MODE SETUP:**
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<!-- Single file start -->
|
|
85
|
+
<!DOCTYPE html>
|
|
86
|
+
<html>
|
|
87
|
+
<head>
|
|
88
|
+
<script src="<https://unpkg.com/react@18/umd/react.development.js>"></script>
|
|
89
|
+
<script src="<https://unpkg.com/react-dom@18/umd/react-dom.development.js>"></script>
|
|
90
|
+
<script src="<https://cdn.tailwindcss.com>"></script>
|
|
91
|
+
</head>
|
|
92
|
+
<body>
|
|
93
|
+
<div id="root"></div>
|
|
94
|
+
<script type="text/babel">
|
|
95
|
+
const App = () => {
|
|
96
|
+
const [items, setItems] = useState(
|
|
97
|
+
JSON.parse(localStorage.getItem('items') || '[]')
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<div className="p-4">
|
|
102
|
+
{/* Build entire app here */}
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
</script>
|
|
107
|
+
</body>
|
|
108
|
+
</html>
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**TECHNICAL AVOID:**
|
|
113
|
+
|
|
114
|
+
- ❌ Microservices/message queues
|
|
115
|
+
- ❌ Database setup on day 1
|
|
116
|
+
- ❌ Redux/complex state management
|
|
117
|
+
- ❌ Docker/Kubernetes
|
|
118
|
+
- ❌ GraphQL/complex APIs
|
|
119
|
+
- ❌ OAuth/complex auth
|
|
120
|
+
|
|
121
|
+
## 🛡️ PRODUCTION MODE: "This needs to be bulletproof"
|
|
122
|
+
|
|
123
|
+
**TECHNICAL MUST DO:**
|
|
124
|
+
|
|
125
|
+
- [ ] Use Auth0/Clerk/Supabase Auth (never custom)
|
|
126
|
+
- [ ] Environment variables from start (.env, .env.local)
|
|
127
|
+
- [ ] Sentry error tracking setup
|
|
128
|
+
- [ ] Database migrations (Prisma/Knex)
|
|
129
|
+
- [ ] Input validation (Zod/Yup)
|
|
130
|
+
- [ ] HTTPS only
|
|
131
|
+
- [ ] CORS configuration
|
|
132
|
+
- [ ] Rate limiting
|
|
133
|
+
|
|
134
|
+
**PRODUCTION SETUP:**
|
|
135
|
+
|
|
136
|
+
```jsx
|
|
137
|
+
// Project structure
|
|
138
|
+
/app
|
|
139
|
+
/pages
|
|
140
|
+
/components
|
|
141
|
+
/lib
|
|
142
|
+
/auth
|
|
143
|
+
/db
|
|
144
|
+
/validation
|
|
145
|
+
/api
|
|
146
|
+
/routes
|
|
147
|
+
/middleware
|
|
148
|
+
/services
|
|
149
|
+
/tests
|
|
150
|
+
/unit
|
|
151
|
+
/integration
|
|
152
|
+
/e2e
|
|
153
|
+
|
|
154
|
+
// Environment setup
|
|
155
|
+
// .env.local
|
|
156
|
+
DATABASE_URL=postgresql://...
|
|
157
|
+
AUTH_SECRET=generated-secret-key
|
|
158
|
+
SENTRY_DSN=https://...
|
|
159
|
+
STRIPE_SECRET_KEY=sk_test_...
|
|
160
|
+
|
|
161
|
+
// Never hardcode secrets
|
|
162
|
+
const apiKey = process.env.API_KEY; // ✅
|
|
163
|
+
const apiKey = "sk_live_abc123"; // ❌ NEVER
|
|
164
|
+
|
|
165
|
+
// Input validation from day 1
|
|
166
|
+
import { z } from 'zod';
|
|
167
|
+
|
|
168
|
+
const userSchema = z.object({
|
|
169
|
+
email: z.string().email(),
|
|
170
|
+
password: z.string().min(8),
|
|
171
|
+
age: z.number().min(13).max(120),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Database with migrations
|
|
175
|
+
npx prisma init
|
|
176
|
+
npx prisma migrate dev
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 🚨 RECOVERY MODE: "Oh shit, I need to start over"
|
|
181
|
+
|
|
182
|
+
**WHEN THIS HAPPENS:**
|
|
183
|
+
|
|
184
|
+
- Can't figure out what you're building after weeks
|
|
185
|
+
- Technical debt is overwhelming
|
|
186
|
+
- Starting fresh would be faster than fixing
|
|
187
|
+
|
|
188
|
+
**RECOVERY ACTIONS:**
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Save what works
|
|
192
|
+
git stash
|
|
193
|
+
git branch backup-old-attempt
|
|
194
|
+
|
|
195
|
+
# Extract the good parts
|
|
196
|
+
mkdir new-project
|
|
197
|
+
cp old-project/components/WorkingFeature.js new-project/
|
|
198
|
+
cp old-project/utils/useful-helpers.js new-project/
|
|
199
|
+
|
|
200
|
+
# Start fresh with lessons learned
|
|
201
|
+
npx create-next-app@latest new-project
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
# 🔨 USE CASE 2: BUILDING NEW FEATURES
|
|
208
|
+
|
|
209
|
+
## 🔍 DISCOVERY MODE: "Not sure how this fits"
|
|
210
|
+
|
|
211
|
+
**TECHNICAL MUST DO:**
|
|
212
|
+
|
|
213
|
+
- [ ] `console.log` existing data structures
|
|
214
|
+
- [ ] Map component dependencies
|
|
215
|
+
- [ ] Try 2-3 implementation approaches
|
|
216
|
+
- [ ] Test with different data sizes (10 vs 10,000)
|
|
217
|
+
- [ ] Check render performance with React DevTools
|
|
218
|
+
|
|
219
|
+
**DISCOVERY PATTERNS:**
|
|
220
|
+
|
|
221
|
+
```jsx
|
|
222
|
+
// Understanding integration points
|
|
223
|
+
console.log('Current data structure:', data);
|
|
224
|
+
console.log('Props received:', props);
|
|
225
|
+
console.log('State before update:', state);
|
|
226
|
+
|
|
227
|
+
// Try different approaches
|
|
228
|
+
// Option 1: Optimistic updates
|
|
229
|
+
const handleSave = () => {
|
|
230
|
+
setData(newData); // Update immediately
|
|
231
|
+
api.save(newData).catch(() => setData(oldData)); // Rollback if fails
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Option 2: Pessimistic updates
|
|
235
|
+
const handleSave = async () => {
|
|
236
|
+
setLoading(true);
|
|
237
|
+
await api.save(newData);
|
|
238
|
+
setData(newData); // Update after success
|
|
239
|
+
setLoading(false);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Option 3: Hybrid with status
|
|
243
|
+
const handleSave = () => {
|
|
244
|
+
setData({...newData, status: 'pending'});
|
|
245
|
+
api.save(newData)
|
|
246
|
+
.then(() => setData({...newData, status: 'saved'}))
|
|
247
|
+
.catch(() => setData({...oldData, status: 'error'}));
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## 🏃 SPEED MODE: "Just add the button"
|
|
253
|
+
|
|
254
|
+
**TECHNICAL MUST DO:**
|
|
255
|
+
|
|
256
|
+
- [ ] Add feature as new component/function
|
|
257
|
+
- [ ] Use existing data structures
|
|
258
|
+
- [ ] Read-only features first
|
|
259
|
+
- [ ] Test with `console.log`
|
|
260
|
+
|
|
261
|
+
**ISOLATED FEATURE PATTERN:**
|
|
262
|
+
|
|
263
|
+
```jsx
|
|
264
|
+
// Good: Self-contained feature
|
|
265
|
+
const ExportButton = ({ data }) => {
|
|
266
|
+
const handleExport = () => {
|
|
267
|
+
const csv = data
|
|
268
|
+
.map(row => `${row.date},${row.amount},${row.description}`)
|
|
269
|
+
.join('\\n');
|
|
270
|
+
|
|
271
|
+
const blob = new Blob([csv], { type: 'text/csv' });
|
|
272
|
+
const url = URL.createObjectURL(blob);
|
|
273
|
+
const a = document.createElement('a');
|
|
274
|
+
a.href = url;
|
|
275
|
+
a.download = 'export.csv';
|
|
276
|
+
a.click();
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
return <button onClick={handleExport}>Export CSV</button>;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Add to existing component
|
|
283
|
+
<div>
|
|
284
|
+
{/* existing code */}
|
|
285
|
+
<ExportButton data={expenses} />
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## 🛡️ PRODUCTION MODE: "This touches payments"
|
|
291
|
+
|
|
292
|
+
**TECHNICAL MUST DO:**
|
|
293
|
+
|
|
294
|
+
- [ ] Write tests first → make tests pass with minimal code → refactor (TDD)
|
|
295
|
+
- [ ] Use transactions for database operations
|
|
296
|
+
- [ ] Add idempotency keys for payments
|
|
297
|
+
- [ ] Implement retry logic with exponential backoff
|
|
298
|
+
- [ ] Add audit logging
|
|
299
|
+
- [ ] Use feature flags
|
|
300
|
+
|
|
301
|
+
**PRODUCTION FEATURE IMPLEMENTATION:**
|
|
302
|
+
|
|
303
|
+
```jsx
|
|
304
|
+
// Test first
|
|
305
|
+
describe('Subscription Feature', () => {
|
|
306
|
+
test('handles successful payment', async () => {
|
|
307
|
+
const result = await processSubscription(validCard);
|
|
308
|
+
expect(result.status).toBe('active');
|
|
309
|
+
expect(auditLog).toHaveBeenCalledWith({
|
|
310
|
+
action: 'subscription_created',
|
|
311
|
+
userId: expect.any(String),
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test('handles declined card', async () => {
|
|
316
|
+
const result = await processSubscription(declinedCard);
|
|
317
|
+
expect(result.status).toBe('failed');
|
|
318
|
+
expect(customer.notified).toBe(true);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// Implementation with all safeguards
|
|
323
|
+
const processSubscription = async (paymentMethod) => {
|
|
324
|
+
const idempotencyKey = generateIdempotencyKey();
|
|
325
|
+
|
|
326
|
+
return await db.transaction(async (trx) => {
|
|
327
|
+
try {
|
|
328
|
+
// Create Stripe subscription
|
|
329
|
+
const subscription = await stripe.subscriptions.create({
|
|
330
|
+
customer: customerId,
|
|
331
|
+
items: [{ price: priceId }],
|
|
332
|
+
payment_method: paymentMethod,
|
|
333
|
+
idempotency_key: idempotencyKey,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// Update database
|
|
337
|
+
await trx('subscriptions').insert({
|
|
338
|
+
user_id: userId,
|
|
339
|
+
stripe_subscription_id: subscription.id,
|
|
340
|
+
status: subscription.status,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Audit log
|
|
344
|
+
await trx('audit_logs').insert({
|
|
345
|
+
action: 'subscription_created',
|
|
346
|
+
user_id: userId,
|
|
347
|
+
metadata: { subscription_id: subscription.id },
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
return { success: true, subscription };
|
|
351
|
+
} catch (error) {
|
|
352
|
+
logger.error('Subscription failed', {
|
|
353
|
+
error: error.message,
|
|
354
|
+
userId,
|
|
355
|
+
idempotencyKey,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
throw new Error('Payment processing failed');
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## 🚨 RECOVERY MODE: "The new feature broke everything"
|
|
366
|
+
|
|
367
|
+
**IMMEDIATE ACTIONS:**
|
|
368
|
+
|
|
369
|
+
```jsx
|
|
370
|
+
// 1. Feature flag to disable
|
|
371
|
+
if (process.env.ENABLE_NEW_FEATURE === 'true') {
|
|
372
|
+
return <NewFeature />;
|
|
373
|
+
} else {
|
|
374
|
+
return <OldFeature />;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// 2. Quick patch
|
|
378
|
+
try {
|
|
379
|
+
return riskyNewFeature();
|
|
380
|
+
} catch (error) {
|
|
381
|
+
console.error('New feature failed, falling back', error);
|
|
382
|
+
return oldReliableFeature();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// 3. Circuit breaker
|
|
386
|
+
let failures = 0;
|
|
387
|
+
const featureWithCircuitBreaker = () => {
|
|
388
|
+
if (failures > 3) {
|
|
389
|
+
return null; // Don't render if failing
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
return <NewFeature />;
|
|
393
|
+
} catch (error) {
|
|
394
|
+
failures++;
|
|
395
|
+
setTimeout(() => failures--, 60000); // Reset after 1 min
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
# 🔧 USE CASE 3: REFACTORING
|
|
405
|
+
|
|
406
|
+
## 🔍 DISCOVERY MODE: "What is this code even doing?"
|
|
407
|
+
|
|
408
|
+
**TECHNICAL MUST DO:**
|
|
409
|
+
|
|
410
|
+
- [ ] Add JSDoc comments first
|
|
411
|
+
- [ ] Log all function inputs/outputs
|
|
412
|
+
- [ ] Create characterization tests
|
|
413
|
+
- [ ] Use `git diff` to track changes
|
|
414
|
+
- [ ] Keep both implementations temporarily
|
|
415
|
+
|
|
416
|
+
**UNDERSTANDING MESSY CODE:**
|
|
417
|
+
|
|
418
|
+
```jsx
|
|
419
|
+
// Step 1: Add explanatory comments
|
|
420
|
+
function mysteryFunction(x, y, z) {
|
|
421
|
+
// Log to understand
|
|
422
|
+
console.log('Inputs:', { x, y, z });
|
|
423
|
+
|
|
424
|
+
const a = x * 2 + y; // TODO: Why multiply by 2?
|
|
425
|
+
console.log('Intermediate a:', a);
|
|
426
|
+
|
|
427
|
+
const b = z ? a / z : a; // Avoid division by zero
|
|
428
|
+
console.log('Result b:', b);
|
|
429
|
+
|
|
430
|
+
return b > 100 ? 100 : b; // Cap at 100
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Step 2: Write tests to document behavior
|
|
434
|
+
test('mystery function behavior', () => {
|
|
435
|
+
expect(mysteryFunction(10, 5, 2)).toBe(12.5);
|
|
436
|
+
expect(mysteryFunction(10, 5, 0)).toBe(25); // Doesn't divide
|
|
437
|
+
expect(mysteryFunction(100, 100, 1)).toBe(100); // Caps at 100
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// Step 3: Refactor with confidence
|
|
441
|
+
function calculateAdjustedScore(base, bonus, divisor) {
|
|
442
|
+
const rawScore = base * 2 + bonus;
|
|
443
|
+
const adjustedScore = divisor ? rawScore / divisor : rawScore;
|
|
444
|
+
return Math.min(adjustedScore, 100);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
## 🏃 SPEED MODE: "Quick cleanup"
|
|
450
|
+
|
|
451
|
+
**TECHNICAL MUST DO:**
|
|
452
|
+
|
|
453
|
+
- [ ] Extract components (500+ lines)
|
|
454
|
+
- [ ] Rename variables for clarity
|
|
455
|
+
- [ ] Create folders by feature
|
|
456
|
+
- [ ] Move utils to separate file
|
|
457
|
+
|
|
458
|
+
**QUICK REFACTOR PATTERNS:**
|
|
459
|
+
|
|
460
|
+
```jsx
|
|
461
|
+
// Before: 500 line component
|
|
462
|
+
const App = () => {
|
|
463
|
+
// ... 500 lines of mixed concerns
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// After: Organized by feature
|
|
467
|
+
/features
|
|
468
|
+
/expenses
|
|
469
|
+
ExpenseList.js
|
|
470
|
+
ExpenseForm.js
|
|
471
|
+
expenseUtils.js
|
|
472
|
+
/auth
|
|
473
|
+
LoginForm.js
|
|
474
|
+
useAuth.js
|
|
475
|
+
/shared
|
|
476
|
+
Button.js
|
|
477
|
+
Modal.js
|
|
478
|
+
|
|
479
|
+
// Simple extraction
|
|
480
|
+
// Before: Inline complex logic
|
|
481
|
+
const Component = () => {
|
|
482
|
+
const result = data
|
|
483
|
+
.filter(x => x.active)
|
|
484
|
+
.map(x => ({...x, tax: x.amount * 0.1}))
|
|
485
|
+
.reduce((sum, x) => sum + x.amount + x.tax, 0);
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
// After: Named function
|
|
489
|
+
const calculateTotalWithTax = (data) => {
|
|
490
|
+
const TAX_RATE = 0.1;
|
|
491
|
+
return data
|
|
492
|
+
.filter(item => item.active)
|
|
493
|
+
.map(item => ({...item, tax: item.amount * TAX_RATE}))
|
|
494
|
+
.reduce((sum, item) => sum + item.amount + item.tax, 0);
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const Component = () => {
|
|
498
|
+
const result = calculateTotalWithTax(data);
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
## 🛡️ PRODUCTION MODE: "Refactoring critical path"
|
|
504
|
+
|
|
505
|
+
**TECHNICAL MUST DO:**
|
|
506
|
+
|
|
507
|
+
- [ ] Comprehensive test coverage first
|
|
508
|
+
- [ ] Performance baseline measured
|
|
509
|
+
- [ ] Run old+new in parallel
|
|
510
|
+
- [ ] Gradual migration with feature flags
|
|
511
|
+
- [ ] Monitor during transition
|
|
512
|
+
|
|
513
|
+
**SAFE PRODUCTION REFACTOR:**
|
|
514
|
+
|
|
515
|
+
```jsx
|
|
516
|
+
// Step 1: Measure current performance
|
|
517
|
+
console.time('oldImplementation');
|
|
518
|
+
const oldResult = oldFunction(data);
|
|
519
|
+
console.timeEnd('oldImplementation'); // Baseline: 450ms
|
|
520
|
+
|
|
521
|
+
// Step 2: Comprehensive tests
|
|
522
|
+
describe('Payment Processing', () => {
|
|
523
|
+
const testCases = [
|
|
524
|
+
{ input: normalPayment, expected: 'success' },
|
|
525
|
+
{ input: declinedCard, expected: 'declined' },
|
|
526
|
+
{ input: expiredCard, expected: 'expired' },
|
|
527
|
+
// ... 20+ test cases
|
|
528
|
+
];
|
|
529
|
+
|
|
530
|
+
testCases.forEach(({ input, expected }) => {
|
|
531
|
+
test(`handles ${expected}`, () => {
|
|
532
|
+
expect(oldFunction(input).status).toBe(expected);
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// Step 3: Parallel implementation
|
|
538
|
+
const processPayment = async (data) => {
|
|
539
|
+
const useNewImplementation = await featureFlag.isEnabled('new-payment-flow');
|
|
540
|
+
|
|
541
|
+
if (useNewImplementation) {
|
|
542
|
+
// Monitor new implementation
|
|
543
|
+
const start = Date.now();
|
|
544
|
+
const result = await newPaymentFlow(data);
|
|
545
|
+
metrics.record('new_payment_duration', Date.now() - start);
|
|
546
|
+
|
|
547
|
+
// Shadow mode: run old one too for comparison
|
|
548
|
+
if (config.shadowMode) {
|
|
549
|
+
const oldResult = await oldPaymentFlow(data);
|
|
550
|
+
if (result.status !== oldResult.status) {
|
|
551
|
+
logger.warn('Payment flow mismatch', { old: oldResult, new: result });
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return result;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return oldPaymentFlow(data);
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
// Step 4: Gradual rollout
|
|
562
|
+
// Day 1: 1% of users
|
|
563
|
+
// Day 3: 10% of users
|
|
564
|
+
// Day 7: 50% of users
|
|
565
|
+
// Day 14: 100% of users
|
|
566
|
+
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
## 🚨 RECOVERY MODE: "Refactoring broke production"
|
|
570
|
+
|
|
571
|
+
**ROLLBACK PROCEDURES:**
|
|
572
|
+
|
|
573
|
+
```bash
|
|
574
|
+
# Immediate rollback
|
|
575
|
+
git revert HEAD
|
|
576
|
+
git push origin main --force-with-lease
|
|
577
|
+
|
|
578
|
+
# Or feature flag disable
|
|
579
|
+
curl -X POST <https://api.launchdarkly.com/flags/new-refactor/off>
|
|
580
|
+
|
|
581
|
+
# Database rollback if needed
|
|
582
|
+
psql $DATABASE_URL -c "
|
|
583
|
+
ALTER TABLE users RENAME COLUMN new_field TO new_field_backup;
|
|
584
|
+
ALTER TABLE users RENAME COLUMN old_field_backup TO old_field;
|
|
585
|
+
"
|
|
586
|
+
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
# 🧪 USE CASE 4: TESTING
|
|
592
|
+
|
|
593
|
+
## 🔍 DISCOVERY MODE: "Learning through tests"
|
|
594
|
+
|
|
595
|
+
**TECHNICAL MUST DO:**
|
|
596
|
+
|
|
597
|
+
- [ ] Test edge cases to understand
|
|
598
|
+
- [ ] Use `.toMatchSnapshot()` for complex outputs
|
|
599
|
+
- [ ] Test with boundary values
|
|
600
|
+
- [ ] Log intermediate values
|
|
601
|
+
|
|
602
|
+
**CHARACTERIZATION TESTING:**
|
|
603
|
+
|
|
604
|
+
```jsx
|
|
605
|
+
// Discovering what existing code does
|
|
606
|
+
describe('Legacy Calculator', () => {
|
|
607
|
+
// Test various inputs to understand behavior
|
|
608
|
+
test('exploring edge cases', () => {
|
|
609
|
+
console.log(calculate(0)); // => 0
|
|
610
|
+
console.log(calculate(-1)); // => throws error
|
|
611
|
+
console.log(calculate(0.1)); // => 0.11 (weird!)
|
|
612
|
+
console.log(calculate(999999)); // => 100 (capped?)
|
|
613
|
+
console.log(calculate(null)); // => 0 (coercion)
|
|
614
|
+
console.log(calculate('5')); // => 5.5 (string coercion)
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
// Document the discovered behavior
|
|
618
|
+
test('actual behavior', () => {
|
|
619
|
+
expect(calculate(0)).toBe(0);
|
|
620
|
+
expect(() => calculate(-1)).toThrow();
|
|
621
|
+
expect(calculate(0.1)).toBeCloseTo(0.11);
|
|
622
|
+
expect(calculate(999999)).toBe(100);
|
|
623
|
+
expect(calculate(null)).toBe(0);
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
## 🏃 SPEED MODE: "Just test the math"
|
|
630
|
+
|
|
631
|
+
**TECHNICAL MUST DO:**
|
|
632
|
+
|
|
633
|
+
- [ ] Test calculations only
|
|
634
|
+
- [ ] Simple Jest/Vitest tests
|
|
635
|
+
- [ ] Skip UI tests
|
|
636
|
+
- [ ] Manual testing is fine
|
|
637
|
+
|
|
638
|
+
**MINIMAL TEST SUITE:**
|
|
639
|
+
|
|
640
|
+
```jsx
|
|
641
|
+
// Only test critical business logic
|
|
642
|
+
describe('Invoice Calculator', () => {
|
|
643
|
+
test('calculates subtotal', () => {
|
|
644
|
+
const items = [
|
|
645
|
+
{ price: 10, quantity: 2 },
|
|
646
|
+
{ price: 5, quantity: 3 },
|
|
647
|
+
];
|
|
648
|
+
expect(calculateSubtotal(items)).toBe(35);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
test('applies discount', () => {
|
|
652
|
+
expect(applyDiscount(100, 0.1)).toBe(90);
|
|
653
|
+
expect(applyDiscount(100, 0)).toBe(100);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
test('calculates tax', () => {
|
|
657
|
+
expect(calculateTax(100, 0.08)).toBe(8);
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
// Skip these in Speed Mode:
|
|
662
|
+
// - Component rendering tests
|
|
663
|
+
// - API integration tests
|
|
664
|
+
// - E2E tests
|
|
665
|
+
// - Visual regression tests
|
|
666
|
+
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
## 🛡️ PRODUCTION MODE: "Test everything"
|
|
670
|
+
|
|
671
|
+
**TECHNICAL MUST DO:**
|
|
672
|
+
|
|
673
|
+
- [ ] Unit tests with Jest/Vitest
|
|
674
|
+
- [ ] Integration tests with Supertest
|
|
675
|
+
- [ ] E2E tests with Playwright
|
|
676
|
+
- [ ] Load testing with k6
|
|
677
|
+
- [ ] Property-based tests for calculations
|
|
678
|
+
|
|
679
|
+
**COMPREHENSIVE TEST PYRAMID:**
|
|
680
|
+
|
|
681
|
+
```jsx
|
|
682
|
+
// 1. Unit Tests (70%)
|
|
683
|
+
describe('User Service', () => {
|
|
684
|
+
test('creates user with valid data', async () => {
|
|
685
|
+
const user = await createUser(validData);
|
|
686
|
+
expect(user).toHaveProperty('id');
|
|
687
|
+
expect(user.email).toBe(validData.email);
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
test('rejects invalid email', async () => {
|
|
691
|
+
await expect(createUser({ email: 'invalid' }))
|
|
692
|
+
.rejects.toThrow('Invalid email');
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
// 2. Integration Tests (20%)
|
|
697
|
+
describe('API Endpoints', () => {
|
|
698
|
+
test('POST /users creates user', async () => {
|
|
699
|
+
const response = await request(app)
|
|
700
|
+
.post('/api/users')
|
|
701
|
+
.send({ email: 'test@test.com', password: 'Test123!' })
|
|
702
|
+
.expect(201);
|
|
703
|
+
|
|
704
|
+
expect(response.body).toHaveProperty('id');
|
|
705
|
+
expect(mockDatabase.users).toHaveLength(1);
|
|
706
|
+
expect(mockEmailService).toHaveBeenCalled();
|
|
707
|
+
});
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
// 3. E2E Tests (10%)
|
|
711
|
+
test('Complete user journey', async ({ page }) => {
|
|
712
|
+
await page.goto('/signup');
|
|
713
|
+
await page.fill('[name=email]', 'test@test.com');
|
|
714
|
+
await page.fill('[name=password]', 'Test123!');
|
|
715
|
+
await page.click('button[type=submit]');
|
|
716
|
+
|
|
717
|
+
await expect(page).toHaveURL('/dashboard');
|
|
718
|
+
await expect(page.locator('h1')).toContainText('Welcome');
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
// 4. Property-Based Tests
|
|
722
|
+
import fc from 'fast-check';
|
|
723
|
+
|
|
724
|
+
test('discount never makes price negative', () => {
|
|
725
|
+
fc.assert(
|
|
726
|
+
fc.property(
|
|
727
|
+
fc.float({ min: 0, max: 1000000 }),
|
|
728
|
+
fc.float({ min: 0, max: 1 }),
|
|
729
|
+
(price, discountRate) => {
|
|
730
|
+
const final = applyDiscount(price, discountRate);
|
|
731
|
+
expect(final).toBeGreaterThanOrEqual(0);
|
|
732
|
+
expect(final).toBeLessThanOrEqual(price);
|
|
733
|
+
}
|
|
734
|
+
)
|
|
735
|
+
);
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
// 5. Load Tests (k6)
|
|
739
|
+
export default function() {
|
|
740
|
+
const response = http.get('<https://api.example.com/users>');
|
|
741
|
+
check(response, {
|
|
742
|
+
'status is 200': (r) => r.status === 200,
|
|
743
|
+
'response time < 500ms': (r) => r.timings.duration < 500,
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
export const options = {
|
|
748
|
+
stages: [
|
|
749
|
+
{ duration: '2m', target: 100 }, // Ramp up
|
|
750
|
+
{ duration: '5m', target: 100 }, // Stay at 100 users
|
|
751
|
+
{ duration: '2m', target: 0 }, // Ramp down
|
|
752
|
+
],
|
|
753
|
+
thresholds: {
|
|
754
|
+
http_req_duration: ['p(95)<500'], // 95% of requests under 500ms
|
|
755
|
+
},
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
## 🚨 RECOVERY MODE: "Tests are failing in CI"
|
|
761
|
+
|
|
762
|
+
**DEBUGGING CI FAILURES:**
|
|
763
|
+
|
|
764
|
+
```jsx
|
|
765
|
+
// Add debugging for CI-only failures
|
|
766
|
+
test('flaky test', async () => {
|
|
767
|
+
// Add verbose logging
|
|
768
|
+
console.log('Environment:', process.env.NODE_ENV);
|
|
769
|
+
console.log('Database URL:', process.env.DATABASE_URL?.substring(0, 20));
|
|
770
|
+
|
|
771
|
+
// Increase timeouts for CI
|
|
772
|
+
jest.setTimeout(10000);
|
|
773
|
+
|
|
774
|
+
// Add retries for flaky network
|
|
775
|
+
let attempts = 0;
|
|
776
|
+
while (attempts < 3) {
|
|
777
|
+
try {
|
|
778
|
+
const result = await apiCall();
|
|
779
|
+
expect(result).toBeDefined();
|
|
780
|
+
break;
|
|
781
|
+
} catch (error) {
|
|
782
|
+
attempts++;
|
|
783
|
+
if (attempts === 3) throw error;
|
|
784
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Screenshot on failure (Playwright)
|
|
789
|
+
try {
|
|
790
|
+
await expect(page.locator('.success')).toBeVisible();
|
|
791
|
+
} catch (error) {
|
|
792
|
+
await page.screenshot({ path: 'test-failure.png' });
|
|
793
|
+
throw error;
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
---
|
|
800
|
+
|
|
801
|
+
# 🐛 USE CASE 5: DEBUGGING
|
|
802
|
+
|
|
803
|
+
## 🔍 DISCOVERY MODE: "Why is this slow sometimes?"
|
|
804
|
+
|
|
805
|
+
**TECHNICAL MUST DO:**
|
|
806
|
+
|
|
807
|
+
- [ ] Add performance.now() timers
|
|
808
|
+
- [ ] Use Chrome Performance profiler
|
|
809
|
+
- [ ] Check Network waterfall
|
|
810
|
+
- [ ] Add data size logging
|
|
811
|
+
- [ ] Test on slow 3G throttling
|
|
812
|
+
|
|
813
|
+
**PERFORMANCE INVESTIGATION:**
|
|
814
|
+
|
|
815
|
+
```jsx
|
|
816
|
+
// Add timing to understand bottlenecks
|
|
817
|
+
const debug = async (operation, data) => {
|
|
818
|
+
console.group(`Debug: ${operation}`);
|
|
819
|
+
console.log('Data size:', JSON.stringify(data).length);
|
|
820
|
+
console.log('Item count:', Array.isArray(data) ? data.length : 'N/A');
|
|
821
|
+
|
|
822
|
+
console.time('Operation duration');
|
|
823
|
+
const start = performance.now();
|
|
824
|
+
|
|
825
|
+
const result = await performOperation(data);
|
|
826
|
+
|
|
827
|
+
const duration = performance.now() - start;
|
|
828
|
+
console.timeEnd('Operation duration');
|
|
829
|
+
|
|
830
|
+
if (duration > 1000) {
|
|
831
|
+
console.warn(`Slow operation detected: ${duration}ms`);
|
|
832
|
+
console.trace(); // Show call stack
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
console.log('Memory:', performance.memory?.usedJSHeapSize);
|
|
836
|
+
console.groupEnd();
|
|
837
|
+
|
|
838
|
+
return result;
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
// Profile specific users
|
|
842
|
+
if (user.hasPerformanceIssues) {
|
|
843
|
+
window.enableProfiling = true;
|
|
844
|
+
console.profile('UserInteraction');
|
|
845
|
+
// ... user interactions
|
|
846
|
+
console.profileEnd();
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
## 🏃 SPEED MODE: "Quick fix"
|
|
852
|
+
|
|
853
|
+
**TECHNICAL MUST DO:**
|
|
854
|
+
|
|
855
|
+
- [ ] `console.log` before errors
|
|
856
|
+
- [ ] Add `|| []` for undefined arrays
|
|
857
|
+
- [ ] Use `?.` optional chaining
|
|
858
|
+
- [ ] Quick try/catch wrapping
|
|
859
|
+
|
|
860
|
+
**COMMON QUICK FIXES:**
|
|
861
|
+
|
|
862
|
+
```jsx
|
|
863
|
+
// Fix: Cannot read property of undefined
|
|
864
|
+
// Before
|
|
865
|
+
const name = user.profile.name; // Crashes if profile undefined
|
|
866
|
+
|
|
867
|
+
// After
|
|
868
|
+
const name = user?.profile?.name || 'Anonymous';
|
|
869
|
+
|
|
870
|
+
// Fix: map is not a function
|
|
871
|
+
// Before
|
|
872
|
+
items.map(item => ...) // Crashes if items not array
|
|
873
|
+
|
|
874
|
+
// After
|
|
875
|
+
(items || []).map(item => ...)
|
|
876
|
+
// or
|
|
877
|
+
Array.isArray(items) && items.map(item => ...)
|
|
878
|
+
|
|
879
|
+
// Fix: Async errors not caught
|
|
880
|
+
// Before
|
|
881
|
+
async function risky() {
|
|
882
|
+
const data = await fetch('/api');
|
|
883
|
+
return data.json();
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// After
|
|
887
|
+
async function safe() {
|
|
888
|
+
try {
|
|
889
|
+
const data = await fetch('/api');
|
|
890
|
+
return await data.json();
|
|
891
|
+
} catch (error) {
|
|
892
|
+
console.error('API failed:', error);
|
|
893
|
+
return { error: 'Failed to load data' };
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// Fix: State update on unmounted component
|
|
898
|
+
useEffect(() => {
|
|
899
|
+
let mounted = true;
|
|
900
|
+
|
|
901
|
+
fetchData().then(data => {
|
|
902
|
+
if (mounted) {
|
|
903
|
+
setData(data);
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
return () => { mounted = false; };
|
|
908
|
+
}, []);
|
|
909
|
+
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
## 🛡️ PRODUCTION MODE: "Systematic debugging"
|
|
913
|
+
|
|
914
|
+
**TECHNICAL MUST DO:**
|
|
915
|
+
|
|
916
|
+
- [ ] Structured logging (winston/pino)
|
|
917
|
+
- [ ] Correlation IDs for request tracking
|
|
918
|
+
- [ ] APM tools (DataDog/New Relic)
|
|
919
|
+
- [ ] Database query analysis
|
|
920
|
+
- [ ] Distributed tracing
|
|
921
|
+
|
|
922
|
+
**PRODUCTION DEBUGGING SETUP:**
|
|
923
|
+
|
|
924
|
+
```jsx
|
|
925
|
+
// Structured logging with context
|
|
926
|
+
import pino from 'pino';
|
|
927
|
+
|
|
928
|
+
const logger = pino({
|
|
929
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
930
|
+
formatters: {
|
|
931
|
+
bindings: (bindings) => ({
|
|
932
|
+
pid: bindings.pid,
|
|
933
|
+
hostname: bindings.hostname,
|
|
934
|
+
node_version: process.version,
|
|
935
|
+
}),
|
|
936
|
+
},
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
// Request middleware with correlation ID
|
|
940
|
+
app.use((req, res, next) => {
|
|
941
|
+
req.id = crypto.randomUUID();
|
|
942
|
+
req.logger = logger.child({ requestId: req.id });
|
|
943
|
+
|
|
944
|
+
req.logger.info({
|
|
945
|
+
method: req.method,
|
|
946
|
+
url: req.url,
|
|
947
|
+
ip: req.ip,
|
|
948
|
+
userAgent: req.get('user-agent'),
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
next();
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
// Trace database queries
|
|
955
|
+
const db = knex({
|
|
956
|
+
client: 'postgresql',
|
|
957
|
+
connection: DATABASE_URL,
|
|
958
|
+
pool: { min: 2, max: 10 },
|
|
959
|
+
debug: process.env.NODE_ENV === 'development',
|
|
960
|
+
postProcessResponse: (result, queryContext) => {
|
|
961
|
+
logger.debug({
|
|
962
|
+
query: queryContext.sql,
|
|
963
|
+
bindings: queryContext.bindings,
|
|
964
|
+
duration: queryContext.duration,
|
|
965
|
+
rows: result?.length,
|
|
966
|
+
});
|
|
967
|
+
return result;
|
|
968
|
+
},
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
// Error tracking with context
|
|
972
|
+
app.use((err, req, res, next) => {
|
|
973
|
+
const errorId = crypto.randomUUID();
|
|
974
|
+
|
|
975
|
+
req.logger.error({
|
|
976
|
+
errorId,
|
|
977
|
+
error: {
|
|
978
|
+
message: err.message,
|
|
979
|
+
stack: err.stack,
|
|
980
|
+
code: err.code,
|
|
981
|
+
},
|
|
982
|
+
request: {
|
|
983
|
+
method: req.method,
|
|
984
|
+
url: req.url,
|
|
985
|
+
headers: req.headers,
|
|
986
|
+
body: req.body, // Be careful with sensitive data
|
|
987
|
+
},
|
|
988
|
+
user: req.user?.id,
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
// Send to Sentry with context
|
|
992
|
+
Sentry.captureException(err, {
|
|
993
|
+
tags: {
|
|
994
|
+
errorId,
|
|
995
|
+
requestId: req.id,
|
|
996
|
+
},
|
|
997
|
+
user: {
|
|
998
|
+
id: req.user?.id,
|
|
999
|
+
},
|
|
1000
|
+
extra: {
|
|
1001
|
+
url: req.url,
|
|
1002
|
+
method: req.method,
|
|
1003
|
+
},
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
res.status(500).json({
|
|
1007
|
+
error: 'Internal server error',
|
|
1008
|
+
errorId, // User can reference this for support
|
|
1009
|
+
});
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
## 🚨 RECOVERY MODE: "Production is on fire"
|
|
1015
|
+
|
|
1016
|
+
**EMERGENCY DEBUGGING:**
|
|
1017
|
+
|
|
1018
|
+
```jsx
|
|
1019
|
+
// 1. Emergency circuit breaker
|
|
1020
|
+
const CircuitBreaker = require('opossum');
|
|
1021
|
+
|
|
1022
|
+
const options = {
|
|
1023
|
+
timeout: 3000,
|
|
1024
|
+
errorThresholdPercentage: 50,
|
|
1025
|
+
resetTimeout: 30000,
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
const breaker = new CircuitBreaker(dangerousFunction, options);
|
|
1029
|
+
|
|
1030
|
+
breaker.on('open', () => {
|
|
1031
|
+
logger.error('Circuit breaker opened - using fallback');
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
breaker.fallback(() => {
|
|
1035
|
+
return { cached: true, data: lastKnownGood };
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
// 2. Database query killer
|
|
1039
|
+
-- Find slow queries
|
|
1040
|
+
SELECT pid, now() - pg_stat_activity.query_start AS duration, query
|
|
1041
|
+
FROM pg_stat_activity
|
|
1042
|
+
WHERE (now() - pg_stat_activity.query_start) > interval '5 minutes';
|
|
1043
|
+
|
|
1044
|
+
-- Kill specific query
|
|
1045
|
+
SELECT pg_terminate_backend(PID);
|
|
1046
|
+
|
|
1047
|
+
-- Kill all queries from specific user
|
|
1048
|
+
SELECT pg_terminate_backend(pid)
|
|
1049
|
+
FROM pg_stat_activity
|
|
1050
|
+
WHERE usename = 'app_user' AND pid != pg_backend_pid();
|
|
1051
|
+
|
|
1052
|
+
// 3. Memory leak detection
|
|
1053
|
+
const v8 = require('v8');
|
|
1054
|
+
const fs = require('fs');
|
|
1055
|
+
|
|
1056
|
+
// Take heap snapshot
|
|
1057
|
+
const writeHeapSnapshot = () => {
|
|
1058
|
+
const filename = `heap-${Date.now()}.heapsnapshot`;
|
|
1059
|
+
const stream = v8.writeHeapSnapshot();
|
|
1060
|
+
stream.pipe(fs.createWriteStream(filename));
|
|
1061
|
+
logger.info(`Heap snapshot written to ${filename}`);
|
|
1062
|
+
};
|
|
1063
|
+
|
|
1064
|
+
// Monitor memory growth
|
|
1065
|
+
setInterval(() => {
|
|
1066
|
+
const usage = process.memoryUsage();
|
|
1067
|
+
logger.info({
|
|
1068
|
+
memory: {
|
|
1069
|
+
rss: Math.round(usage.rss / 1024 / 1024) + 'MB',
|
|
1070
|
+
heapTotal: Math.round(usage.heapTotal / 1024 / 1024) + 'MB',
|
|
1071
|
+
heapUsed: Math.round(usage.heapUsed / 1024 / 1024) + 'MB',
|
|
1072
|
+
external: Math.round(usage.external / 1024 / 1024) + 'MB',
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
// Emergency dump if memory too high
|
|
1077
|
+
if (usage.heapUsed > 500 * 1024 * 1024) {
|
|
1078
|
+
writeHeapSnapshot();
|
|
1079
|
+
// Optionally restart
|
|
1080
|
+
// process.exit(1);
|
|
1081
|
+
}
|
|
1082
|
+
}, 60000);
|
|
1083
|
+
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
---
|
|
1087
|
+
|
|
1088
|
+
# 🚢 USE CASE 6: DEPLOYMENT
|
|
1089
|
+
|
|
1090
|
+
## 🔍 DISCOVERY MODE: "How do I put this online?"
|
|
1091
|
+
|
|
1092
|
+
**EXPLORE OPTIONS:**
|
|
1093
|
+
|
|
1094
|
+
```jsx
|
|
1095
|
+
// Option 1: Static hosting (frontend only)
|
|
1096
|
+
// Netlify, Vercel, GitHub Pages
|
|
1097
|
+
// Cost: $0
|
|
1098
|
+
// Good for: Portfolio, docs, SPAs
|
|
1099
|
+
npm run build
|
|
1100
|
+
# Drag 'build' folder to Netlify
|
|
1101
|
+
|
|
1102
|
+
// Option 2: Serverless
|
|
1103
|
+
// Vercel, Netlify Functions, AWS Lambda
|
|
1104
|
+
// Cost: $0-20/month
|
|
1105
|
+
// Good for: APIs, simple backends
|
|
1106
|
+
export default function handler(req, res) {
|
|
1107
|
+
res.status(200).json({ data });
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// Option 3: Platform-as-a-Service
|
|
1111
|
+
// Railway, Render, Heroku
|
|
1112
|
+
// Cost: $5-50/month
|
|
1113
|
+
// Good for: Full-stack apps
|
|
1114
|
+
{
|
|
1115
|
+
"scripts": {
|
|
1116
|
+
"start": "node server.js"
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// Option 4: VPS
|
|
1121
|
+
// DigitalOcean, Linode
|
|
1122
|
+
// Cost: $5-40/month
|
|
1123
|
+
// Good for: Multiple apps, full control
|
|
1124
|
+
ssh root@server
|
|
1125
|
+
apt update && apt install nodejs
|
|
1126
|
+
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
## 🏃 SPEED MODE: "Just ship it"
|
|
1130
|
+
|
|
1131
|
+
**QUICKEST DEPLOYMENT:**
|
|
1132
|
+
|
|
1133
|
+
```bash
|
|
1134
|
+
# Frontend only - Netlify Drop
|
|
1135
|
+
1. npm run build
|
|
1136
|
+
2. Go to app.netlify.com/drop
|
|
1137
|
+
3. Drag 'build' folder
|
|
1138
|
+
4. Done! Live URL in seconds
|
|
1139
|
+
|
|
1140
|
+
# Full-stack - Railway
|
|
1141
|
+
1. Push to GitHub
|
|
1142
|
+
2. Connect Railway to repo
|
|
1143
|
+
3. Add environment variables
|
|
1144
|
+
4. Deploy! Auto-deploys on push
|
|
1145
|
+
|
|
1146
|
+
# Quick environment setup
|
|
1147
|
+
echo "DATABASE_URL=postgres://..." > .env
|
|
1148
|
+
echo ".env" >> .gitignore
|
|
1149
|
+
git push
|
|
1150
|
+
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
## 🛡️ PRODUCTION MODE: "Zero-downtime deployment"
|
|
1154
|
+
|
|
1155
|
+
**PRODUCTION DEPLOYMENT SETUP:**
|
|
1156
|
+
|
|
1157
|
+
```jsx
|
|
1158
|
+
// 1. Multiple environments
|
|
1159
|
+
// .github/workflows/deploy.yml
|
|
1160
|
+
name: Deploy
|
|
1161
|
+
on:
|
|
1162
|
+
push:
|
|
1163
|
+
branches: [main, staging]
|
|
1164
|
+
|
|
1165
|
+
jobs:
|
|
1166
|
+
test:
|
|
1167
|
+
runs-on: ubuntu-latest
|
|
1168
|
+
steps:
|
|
1169
|
+
- uses: actions/checkout@v2
|
|
1170
|
+
- run: npm test
|
|
1171
|
+
|
|
1172
|
+
deploy-staging:
|
|
1173
|
+
if: github.ref == 'refs/heads/staging'
|
|
1174
|
+
needs: test
|
|
1175
|
+
runs-on: ubuntu-latest
|
|
1176
|
+
steps:
|
|
1177
|
+
- run: npm run deploy:staging
|
|
1178
|
+
|
|
1179
|
+
deploy-production:
|
|
1180
|
+
if: github.ref == 'refs/heads/main'
|
|
1181
|
+
needs: test
|
|
1182
|
+
runs-on: ubuntu-latest
|
|
1183
|
+
steps:
|
|
1184
|
+
- run: npm run deploy:production
|
|
1185
|
+
|
|
1186
|
+
// 2. Blue-green deployment
|
|
1187
|
+
const blueGreenDeploy = async () => {
|
|
1188
|
+
// Deploy to green environment
|
|
1189
|
+
await deployToGreen();
|
|
1190
|
+
|
|
1191
|
+
// Health check
|
|
1192
|
+
const healthy = await healthCheck('green');
|
|
1193
|
+
if (!healthy) {
|
|
1194
|
+
throw new Error('Green deployment unhealthy');
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// Switch traffic
|
|
1198
|
+
await updateLoadBalancer('green');
|
|
1199
|
+
|
|
1200
|
+
// Monitor
|
|
1201
|
+
await sleep(5 * 60 * 1000);
|
|
1202
|
+
|
|
1203
|
+
// Keep blue as backup
|
|
1204
|
+
console.log('Deployment successful');
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1207
|
+
// 3. Database migrations (safe)
|
|
1208
|
+
-- Step 1: Add column (backward compatible)
|
|
1209
|
+
ALTER TABLE users ADD COLUMN email_verified BOOLEAN DEFAULT false;
|
|
1210
|
+
|
|
1211
|
+
-- Step 2: Deploy code that writes both old and new
|
|
1212
|
+
await db.users.update({
|
|
1213
|
+
verified: true, // old column
|
|
1214
|
+
email_verified: true, // new column
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
-- Step 3: Backfill data
|
|
1218
|
+
UPDATE users SET email_verified = verified WHERE email_verified IS NULL;
|
|
1219
|
+
|
|
1220
|
+
-- Step 4: Deploy code that reads from new column
|
|
1221
|
+
const isVerified = user.email_verified;
|
|
1222
|
+
|
|
1223
|
+
-- Step 5: Drop old column (after all deploys)
|
|
1224
|
+
ALTER TABLE users DROP COLUMN verified;
|
|
1225
|
+
|
|
1226
|
+
// 4. Feature flags for gradual rollout
|
|
1227
|
+
if (await featureFlag.isEnabled('new-checkout', userId)) {
|
|
1228
|
+
return <NewCheckout />;
|
|
1229
|
+
} else {
|
|
1230
|
+
return <OldCheckout />;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
```
|
|
1234
|
+
|
|
1235
|
+
## 🚨 RECOVERY MODE: "Bad deploy!"
|
|
1236
|
+
|
|
1237
|
+
**EMERGENCY ROLLBACK:**
|
|
1238
|
+
|
|
1239
|
+
```bash
|
|
1240
|
+
# Immediate rollback options
|
|
1241
|
+
|
|
1242
|
+
# 1. Git revert
|
|
1243
|
+
git revert HEAD --no-edit
|
|
1244
|
+
git push origin main
|
|
1245
|
+
|
|
1246
|
+
# 2. Platform rollback (Vercel)
|
|
1247
|
+
vercel rollback
|
|
1248
|
+
|
|
1249
|
+
# 3. Container rollback (Docker)
|
|
1250
|
+
docker stop new_container
|
|
1251
|
+
docker start old_container
|
|
1252
|
+
|
|
1253
|
+
# 4. Database rollback
|
|
1254
|
+
pg_restore -d database_name backup_file.dump
|
|
1255
|
+
|
|
1256
|
+
# 5. DNS switch (if using blue-green)
|
|
1257
|
+
# Point DNS back to old environment
|
|
1258
|
+
|
|
1259
|
+
# 6. Feature flag disable
|
|
1260
|
+
curl -X POST <https://api.launchdarkly.com/flags/broken-feature/off>
|
|
1261
|
+
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1264
|
+
---
|
|
1265
|
+
|
|
1266
|
+
# 🏆 QUICK REFERENCE TABLES
|
|
1267
|
+
|
|
1268
|
+
## Success Metrics by Mode
|
|
1269
|
+
|
|
1270
|
+
| Mode | Metric | Target |
|
|
1271
|
+
| --- | --- | --- |
|
|
1272
|
+
| **Speed** | Time to ship | < 4 hours |
|
|
1273
|
+
| **Discovery** | Clarity achieved | 3 prototypes tested |
|
|
1274
|
+
| **Production** | Uptime | 99.9% |
|
|
1275
|
+
| **Recovery** | Time to fix | < 30 minutes |
|
|
1276
|
+
|
|
1277
|
+
## Technology Stack by Mode
|
|
1278
|
+
|
|
1279
|
+
| Component | Speed Mode | Production Mode |
|
|
1280
|
+
| --- | --- | --- |
|
|
1281
|
+
| **Frontend** | React (single file) | Next.js/Remix |
|
|
1282
|
+
| **State** | useState | Zustand/Redux |
|
|
1283
|
+
| **Database** | localStorage | PostgreSQL |
|
|
1284
|
+
| **Auth** | None/hardcoded | Auth0/Clerk |
|
|
1285
|
+
| **Hosting** | Netlify/Vercel | AWS/Railway |
|
|
1286
|
+
| **Monitoring** | console.log | Sentry/DataDog |
|
|
1287
|
+
|
|
1288
|
+
## When to Switch Modes
|
|
1289
|
+
|
|
1290
|
+
| From → To | Trigger |
|
|
1291
|
+
| --- | --- |
|
|
1292
|
+
| **Speed → Discovery** | "I don't know what I'm building anymore" |
|
|
1293
|
+
| **Discovery → Speed** | "Now I know exactly what I want" |
|
|
1294
|
+
| **Speed → Production** | "Real users are depending on this" |
|
|
1295
|
+
| **Any → Recovery** | "Something is broken in production" |
|
|
1296
|
+
|
|
1297
|
+
## Emergency Commands
|
|
1298
|
+
|
|
1299
|
+
```bash
|
|
1300
|
+
# Git
|
|
1301
|
+
git log --oneline -10 # Find last working
|
|
1302
|
+
git checkout [hash] -- [file] # Restore file
|
|
1303
|
+
git reset --hard [hash] # Nuclear option
|
|
1304
|
+
|
|
1305
|
+
# Database
|
|
1306
|
+
SELECT pg_terminate_backend(pid); # Kill query
|
|
1307
|
+
VACUUM FULL table_name; # Clean table
|
|
1308
|
+
REINDEX TABLE table_name; # Fix index
|
|
1309
|
+
|
|
1310
|
+
# Process
|
|
1311
|
+
pm2 restart all # Restart apps
|
|
1312
|
+
kill -9 [PID] # Force kill
|
|
1313
|
+
systemctl restart nginx # Restart nginx
|
|
1314
|
+
|
|
1315
|
+
# Debug
|
|
1316
|
+
tail -f app.log # Watch logs
|
|
1317
|
+
netstat -tulpn | grep :3000 # Check port
|
|
1318
|
+
df -h # Disk space
|
|
1319
|
+
top # CPU/Memory
|
|
1320
|
+
|
|
1321
|
+
```
|
|
1322
|
+
|
|
1323
|
+
---
|
|
1324
|
+
|
|
1325
|
+
**Remember:** Choose your mode based on clarity and risk. Follow the technical patterns for your use case. Switch modes when the signals appear. Ship working code that helps users.
|